Golang: 操作共享数据

在反复琢磨之后,最后还是决定用通过channel来模拟dispatcher的效果,网上的说法很多,不管了,可能我C#写多了,暂时就这样。

package main

import (
  "fmt"
  "encoding/json"
  "net/http"
  "container/list"
  "github.com/gorilla/mux"
  "github.com/codegangsta/negroni"
)

var dispatcher chan func() = make(chan func())
var users *list.List = list.New()

func main() {
  go runDispatcher()

  router := mux.NewRouter()
  router.Methods("GET").Path("/user").HandlerFunc(get)
  router.Methods("POST").Path("/user/{id}").HandlerFunc(set)

  n := negroni.New()
  n.Use(negroni.NewRecovery())
  n.Use(negroni.NewLogger())
  n.UseHandler(router)

  n.Run(":3000")
}

func runDispatcher() {
  for {
    (<-dispatcher)()
  }
}

func set(resp http.ResponseWriter, req *http.Request) {
  vars := mux.Vars(req)
  done := make(chan bool)
  dispatcher <- func() {
    users.PushBack(vars["id"])
    done <- true
  }
  <-done
  fmt.Fprintln(resp, "ok")
}

func get(resp http.ResponseWriter, req *http.Request) {
  var result []string
  done := make(chan bool)
  dispatcher <- func() {
    result = []string { }
    for e := users.Front(); e != nil; e = e.Next() {
      result = append(result, e.Value.(string))
    }
    done <- true
  }
  <-done

  resp.Header().Set("Content-Type", "application/json")
  fmt.Println(result, cap(result), len(result))
  j, _ := json.Marshal(result)
  resp.Write(j)
}

Go学习问题汇总

  • 代码如何组织? package main不能拆分成多个文件,必须分包?
  • T和&T有什么区别? 会产生什么影响?
  • 如何理解"Do not communicate by sharing memory; instead, share memory by communicating."? channel是否可以取代mutex实现共享数据的读写?
  • new和make的区别? 设计意图是什么?
  • 如何建立异常体系? 实践panic, recover?

Go在线学习资料汇总

The Go Programming Language Specification - The Go Programming Language

CHANNEL

Go! (Part 4) - channel

package main

import "fmt"
import "time"

func main() {
  fmt.Println("running...")
  p := make(chan int)
  go execute(p)

  fmt.Println("exit with chan(p) = ", <-p)
}

func execute(c chan int) {
  fmt.Println("begin execute...")
  time.Sleep(3 * time.Second)
  fmt.Println("end execute...")
  c <- 1
}

go起头就是请求方法并行执行,然后通过channel进行通信同步。channel是同步对象,所以可以写入读取都是安全的,<-p是阻塞的,此处等同于join。

其实在go里面还是需要仔细甄别mutex和channel的选择问题,当然你可以用channel一定程度上代替mutex,比如,

package main

var global int = 0
var c = make(chan int, 1)

func thread1(){
    <-c // Grab the ticket
    global = 1
    c <- 1 // Give it back
}

func thread2(){
    <-c
    global = 2
    c <- 1
}

func main() {
   c <- 1 // Put the initial value into the channel
   go thread1()
   go thread2()
}

取自: goroutine - How can we use channels in Google Go in place of mutex? - Stack Overflow

问题是channel和mutex还是略有区别,当然channel本身是机遇mutex来实现的,其实可以理解为mutex还是针对global data, cache, state等状态同步,而channel更是一种同步机制,在Use a sync.Mutex or a channel?一文中作了一个区分,

Channel Mutex 
passing ownership of data,
distributing units of work,
communicating async results
caches,
state

只能说术业有专攻,各取所需罢了。

Go! (Part 3) - Object

package main

import "fmt"

type User struct {
	name string
	age  int
}

func (self *User) hello() {
	fmt.Println(self.name)
}

func main() {
	p := User{"nonocast", 18}
	p.hello()
}

this的概念通过显示方式传入,传值和传引用和C是一致的。

最为关键的是interface体系,

package main

import "fmt"

type User struct {
	name string
	age  int
}

func (self *User) hello() {
	fmt.Println(self.name)
}

type IHello interface {
	hello()
}

func main() {
	var p IHello
	p = &User{"nonocast", 18}
	p.hello()
}

虽说可以通过不同的方式创建对象,看上去也没什么区别,看代码,

package main

import (
  "fmt"
  "reflect"
)

type User struct {
  name string
}

func main() {
  fmt.Println("hello world")

  var x User
  y := new(User)

  fmt.Println(reflect.TypeOf(x))  // main.User
  fmt.Println(reflect.TypeOf(y))  // *main.User
}

Go! (Part 2) - Syntax

变量

  • 大小写敏感
  • var x int = 1或x := 1,:=表示声明同时初始化变量,根据初始化对象反推x类型。
  • 支持多重赋值,i, j = j, i
  • const表示常量,和C一样,但多了一个itoa,遇到const清零,每次itoa自增1
  • 基础类型包括:bool, byte, int8, int16, int, uint, uintptr, float32, float64, complex64, complex128, string ,rune(字符), error
  • 复合类型包括:pointer, array, slice, map, chan(通道), struct, interface
  • string对中文支持很好,内部很明显采用utf8编码
    func main() {
      x := "中国"
      fmt.Println(x)
      fmt.Println(len(x)) // 6
    }
    
  • 数组:[32]byte, [3][5]int, [2][2][2]float64
    package main
    
    import "fmt"
    
    func main() {
    	var x [3]int
    	x[0] = 7
    	x[1] = 8
    	x[2] = 9
    
    	p := [3]int{7, 8, 9}
    
    	for each := range p {
    		fmt.Println(each)
    	}
    }
    
  • Go语言中和C一样区分值类型(value type)和引用类型
  • 内置的map
    package main
    
    import "fmt"
    
    func main() {
    	var x map[int]string = make(map[int]string)
    	x[0] = "aaa"
    	x[1] = "bbb"
    	x[2] = "ccc"
    
    	// another ways
    	p := map[int]string{0: "aaa", 1: "bbb", 2: "ccc"}
    
    	for k, v := range p {
    		fmt.Println(k, v)
    	}
    
    }
    

流程控制

无非就是if, switch, for goto, 对,没有while,直接跳转Golang-控制语句 - 奇语闲谭

for {
  fmt.Println("forever")
}

说实话,蛮妖怪的。

函数

  • first class, 多返回值,支持匿名和闭包
  • 最简单的函数: func x() { }
  • 最完整的函数: func x(a int, b string) (ret int, err error) { }
  • 简化: func x(a, b int) int { }, 当a,b同为int,且只有一个返回值时
  • 不定参数:func x(args ...int)
  • 匿名
    func main() {
    	x := func(p int) { fmt.Println(p) }
    	x(99)
    }
    

Go! (Part 1)

开始学习Go,首先需要明确的是Go是静态语言,就是说需要编译链接后生成可执行文件才能运行。

从hello world开始,
vi hello.go

package main

import "fmt"

func main() {
	fmt.Println("hello world")
}

直接运行,go run hello.go
编译,go build hello.go

由于Go天生跨平台所以在*nix/mac下会生成hello,./hello运行,而windows下则直接生成hello.exe,如果你跨平台用gcc就知道有多麻烦了,而且go在平台层面直接支持x86和ia64,所以你也不需要考虑这些事情。

我个人感觉Go就是一个全新的C升级版,跨平台,直接支持utf8,支持异常处理,匿名函数和lambda,简化过于灵活的指针,抽象32/64bit架构,直接支持并行,直接生成二进制,混C,你还等什么呢?

Mongodb 笔记 (Part 2)

熟悉MongoDB

  • 每个document都由一个"_id"特殊的键,这个键在此document所属的collection中是唯一的, _id不是guid,而是一个特定的ObjectId, 12字节长。
    > new ObjectId()
    ObjectId("542e39a90a9e3f862cf511cc")

    ObjectId=时间戳(4)+机器名散列(3)+PID(3)+计数器(4),所以1秒内同一进程插入的数据只有最后3位不同,如果插入的document没有_id,MongoDB会自动创建。

  • 区分类型,区分大小写。{"foo":3}和{"foo":"3"}是不同的,foo, Foo当然也是不同的。
  • document中不能出现重复key
  • collection不能以"system."开头,不能包含"$"字符
  • database-collection-document,database最终会变成文件系统里的文件,文件名就是数据库名(/data/db下并没有以数据库为名的文件,这是为什么呢?)
  • 通过mongod --rest启动内置web interface,允许rest的数据操作,见官方说明

    运行mongod --rest首先要确保关闭先前的mongod instance,进入mongo admin,然后db.shutdownServer()
  • shell(mongo)是一个javascript解释器加上mongo语法糖(db, use)等
  • Mac下开机启动通过homebrew的script参考brew info mongo, 手动启动服务,launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist

使用MongoDB shell

连接远程mongodb: mongo 192.168.0.105:27017/mydb,如果需要远程连接需要关闭默认配置中的bind_ip,修改mongod.conf,注释掉bind_ip:

# Store data in /usr/local/var/mongodb instead of the default /data/db
dbpath = /usr/local/var/mongodb

# Append logs to /usr/local/var/log/mongodb/mongo.log
logpath = /usr/local/var/log/mongodb/mongo.log
logappend = true

# Only accept local connections
# bind_ip = 127.0.0.1

仅启动shell: mongo --nodb
启动后再连接,即shell内连接:

conn = new Mongo("192.168.0.105:27017")
db = conn.getDB("mydb")

可以传入javavscript给shell,

mongo script1.js script2.js script3.js

或者

mongo --quiet mydb script1.js script2.js script3.js

--quiet即重定向stdout到null
如果在shell内可以通过load("script.js")调用script, load只能处理绝对路径无法处理~。

shell启动时会默认加载.mongorc.js,通常在rc.js中放入常用函数省去每次加载的麻烦。

继续,

  • upsert等同于insert+update,如果有则update,没有则insert
  • find支持正则表达式,db.users.find({"name": /joe/i}), MongoDB的正则采用Perl的PCRE库来实现
  • 可以通过script.js来扩展mongo服务端功能,这个后期着重研究,先提一下

使用MongoDB进行开发

索引

通过db.users.ensureIndex({"name":1});建立索引,通常一个特定的collection上不应该拥有2个以上的索引,挑选合适的字段建立索引非常重要。因为索引涉及的开销包括内存以及CUD时的更新。

通过explain()来检查效率,db.users.find({"name":"user101"});

聚合

MapReduce

复制

分片

应用程序管理

服务器管理

参考内容:

Mongodb 笔记 (Part 1)

  1. Mac OSX下通过brew安装mongodb(brew install mongodb),如果需要升到最新版本则如下步骤,

    # 升级brew的版本库
    brew update
    
    # 查看outdated的库和应用
    brew outdated
    
    # 升级outdated的库和应用
    brew upgrade
    
    # 清理调过期的库和应用
    brew cleanup
    
  2. 安装完成后如需开机启动,则跟着brew info mongodb配置即可,
    ==> Caveats
    To reload mongodb after an upgrade:
        launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
        launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
    Or, if you don't want/need launchctl, you can just run:
        mongod --config /usr/local/etc/mongod.conf
    ➜  ~  
    

    mongod默认的数据库路径为/data/db,所以sudo mkdir -p /data/db,然后需要将权限调回启动mongod的用户,sudo chown nonocast:wheel /data/db。

  3. mongod是服务程序,而mongo是mongod的shell,是client,这点需要明确。
  4. 热身,shell下打开mongo,默认use test数据库,切换到你自己use mydb,然后db.test.insert({name: "nonocast"})即完成了新建collection以及创建document 2个动作,然后通过db.test.drop()删除测试数据。
  5. 通过show collections可以查看当前的collection,通过db.createCollection(name)创建,通过具体collection drop删除,use会自动切换到新的数据库,而通过db.dropDatabase()删除当前所选中的数据库。
  6. 具体的database api可以见Database Methods — MongoDB Manual 2.6.4
  7. mongodb的2个核心概念,collection和document
  8. collection.find中可加入搜索条件,比如db.users.find( { age: { $gt: 18 } }, { name: 1, address: 1 } ).limit(5)
  9. CRUD,对应collection.insert(document), collection.update(criteria, document), collection.remove(criteria)
  10. mongodb admin UIs提供了很多通过web/app来管理mongodb的工具,眼花缭乱。最终在smog和mongo-express中选了mongo-express。

    npm install mongo-express -g后cd /usr/local/lib/node_modules/mongo-express,通过npm get config prefix获取全局安装路径,cp config.default.js config.js,基本不用修改,然后node app打开浏览器localhost:8081即可。

RFC 6455笔记

RFC 6455: http://tools.ietf.org/html/rfc6455

  • The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code. The security model used for this is the origin-based security model(同源策略) commonly used by web browsers. The protocol consists of an opening handshake followed by basic message framing,layered over TCP. The goal of this technology is to provide a mechanism for browser-based applications that need two-way communication with servers.
  • WebSocket的版本历程,
       +--------+-----------------------------------------+----------+
       |Version |                Reference                |  Status  |
       | Number |                                         |          |
       +--------+-----------------------------------------+----------+
       | 0      + draft-ietf-hybi-thewebsocketprotocol-00 | Interim  |
       +--------+-----------------------------------------+----------+
       | 1      + draft-ietf-hybi-thewebsocketprotocol-01 | Interim  |
       +--------+-----------------------------------------+----------+
       | 2      + draft-ietf-hybi-thewebsocketprotocol-02 | Interim  |
       +--------+-----------------------------------------+----------+
       | 3      + draft-ietf-hybi-thewebsocketprotocol-03 | Interim  |
       +--------+-----------------------------------------+----------+
       | 4      + draft-ietf-hybi-thewebsocketprotocol-04 | Interim  |
       +--------+-----------------------------------------+----------+
       | 5      + draft-ietf-hybi-thewebsocketprotocol-05 | Interim  |
       +--------+-----------------------------------------+----------+
       | 6      + draft-ietf-hybi-thewebsocketprotocol-06 | Interim  |
       +--------+-----------------------------------------+----------+
       | 7      + draft-ietf-hybi-thewebsocketprotocol-07 | Interim  |
       +--------+-----------------------------------------+----------+
       | 8      + draft-ietf-hybi-thewebsocketprotocol-08 | Interim  |
       +--------+-----------------------------------------+----------+
       | 9      +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 10     +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 11     +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 12     +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 13     +                RFC 6455                 | Standard |
       +--------+-----------------------------------------+----------+
    

    如上表所见,RFC 6455已经是正式版本,也是整个草案的第十个版本,网上也有用人hybi09,hybi10来表示RFC 6455,知道一下就OK了。

  • 整个WebSocket协议分为2个阶段,先握手,握手成功后升级为WebSocket开始通信,原文描述: After a successful handshake, clients and servers transfer data back and forth in conceptual units referred to in ths specification as "message". On the wire, a message is composed of one or more frames.
  • A frame has an associated type. Each frame belonging to the same message contains the same type of data. Broadly speaking, there are types for texture data(UTF-8), binary data and control frames. This version of the protocol defines six frame types and leaves ten reserved for future use.
  • The handshake from the client looks as follows:
    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    

    其中Leading Line + Request Line符合HTTP[RFC2612],首先由client发起请求升级为WebSocket协议连接,Host区分网站(domain)这个不用解释了,而/chat则区分同站点中不同的websocket域,即同一站点内允许多个websocket server。Upgrade+Connection+Sec-WebSocket-Key则是向server申请提升。
    其中Sec-WebSocket-Key为base64编码,此key需要验证后续response中的Sec-WebSocket-Accept是否匹配。
    而Sec-WebSocket-Protocol则是应用协议,这个看程序了,协议本身不关心。

  • The handshake from the server looks as follows:
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-WebSocket-Protocol: chat
    

    server回复就简单很多,如果提交上来又Upgrade+Connection+Sec-WebSocket-Key则将此Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"然后计算此字符串的SHA-1通过base64返回给client。
    回复的Leading line如果同意提升则status code为101,其余都认为失败。一旦101后双方就可以通信了。

  • WebSocket是基于TCP的,仅依赖HTTP进行握手,升级后脱离HTTP协议,WebSocket协议允许和HTTP共用80端口,或者同时共用443。
  • WebSocket URI定义了2种scheme,如下,
    • ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
    • wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]

    ws端口默认80,wss默认443。

Continue reading