GoでKVSを書けるのかMoriyoshi Koizumi
Goとは• Plan9の開発者であるKen Thompson, Rob Pike, Russ Coxらが開発した新しいコンパイル型プログラミング言語
• コンパイル言語でありながら動的な型の扱いが可能
• Erlang や Ada のような CSP (並行プログラミング)を言語組み込みでサポート
• Dと同じようにネイティブコードに変換して実行する形式を取りつつ、オブジェクトのGCを行う
Goで適当なKVSを作る
•サーバソケットの作成、接続の待受および受容•セッションの維持•プロトコルのハンドリング• Key-Value を格納するバックエンド
ソケットの扱い
•net パッケージを利用•待受: net.Listen("tcp", "127.0.0.1:11211")
•受容: net.Accept()
•煩雑なsockaddr周りのこととかは全部パッケージが面倒見てくれます!!
セッションの維持
•標準パッケージを使って、pollerを使ったイベント駆動なサーバは今のところ書けない (syscall直接呼ばない限り)
•普通に Goroutine を使うgo handleConn(sock);
func main() { if len(os.Args) < 2 { fmt.Fprintf(os.Stderr, "usage: %s addr:port\n", os.Args[0]); os.Exit(255); } l, e := net.Listen("tcp", os.Args[1]); if e != nil { error("An error occurred (%s)", e.String()); os.Exit(1); } // blocking channel backend_ch := make(chan *kvsBackendConn); go backend(backend_ch); for { ch, e := l.Accept(); if e != nil { error("An error occurred (%s)", e.String()); os.Exit(1); } go handleConn(ch, backend_ch, false); } os.Exit(0)}
プロトコルのハンドリング
opcode := int(header[1]);key_length := (int(header[2]) << 8) | int(header[3]);extra_length := int(header[4]);data_type := int(header[5]);body_length := (int(header[8]) << 24) | (int(header[9]) << 16) | (int(header[10]) << 8) | int(header[11]);
まあこうなっちゃいますよね
KVバックエンド
•とりあえずビルトインのmapを使ってみる• 1個のGoroutineとして実装 (アクター指向的に)•バックエンドとセッションを担うGoroutine間のチャネルを同期的なものにすることでロックを行う (mutexとか明示的に書かなくても済む)
func backend(ch <-chan *kvsBackendConn) { kvs := make(map[string] []byte); for req := range ch { switch (req.command) { case 0: // get value, e := kvs[string(req.key)]; if e { req.resp_ch<- (&kvsBackendConn{command:0, value:value}) } else { req.resp_ch<- (&kvsBackendConn{command:0, value:nil}) } break; case 1: // set kvs[string(req.key)] = req.value; break; } }}
backend_ch<- (&kvsBackendConn{ command: 0, key: key, value: nil, resp_ch: recv_ch });result := <-recv_ch;resp_extra := make([]byte, 4);if result != nil { resp = buildResponse(opcode, 0, ...)} else { resp = buildResponse(opcode, 1, ...)}
できたー
で、パフォーマンスは?
実装 / パラメータ 所要時間 QPSGo / GOMAXPROCS=1 1.563 6397.95Go / GOMAXPROCS=2 1.564 6393.86Go / GOMAXPROCS=3 1.579 6345.18Go / GOMAXPROCS=4 1.696 5896.22
memcached (1.4.4) 0.429 23310.02
GOMAXPROCSの値が性能に影響してないのはローカルでやってるからかと。意外と善戦
memslap --test=get --concurrency=4 --binary --verbose--servers=127.0.0.1 --execute-number=100 --initial-load=10000--tcp-nodelay
on Xeon W3520 @2.66GHz
netパッケージの実装•結構、てか相当適当•これで性能出すのは割と大変では?
Poll serverWrite()FD FD FD
FD FD FD FD
FD
Read() FD
Write()
Read()
FD
epoll(2)/kqueue(2)
channelcommunication
once.Do(startServer)
syscallでblockしているGoroutineを起こすのにpipeを利用 (!)
まとめ•LLじゃないのにイベント駆動型じゃないネットワークコードが楽勝で書けるぜ!!!!と思いきや…
•つまるところchannelとfdを混ぜて使うにはpipe(2)やeventfd(2)を使うしかなく、アレな感じになってしまうので
•結局直接触らせろ!! (pollerに!!) という話になってしまう
提 供
moriyoshi