読者です 読者をやめる 読者になる 読者になる

mrasu’s blog

読んだ物の内容など

CockroachDBの中 - gossip

分散関係のコードを読んでみようと思い、CockroachDBを見つけたのでgossipのところを読んだので、メモ

CockroachDBとは

CockroachDBは分散環境下で動くDBの1つです。
不完全ですが、SQLを使えたりACIDを備えていたりと、かなりRDBに近い動きをします。
名前の由来はゴキブリのような耐久性を持つところから
言語はGo

公式は、 www.cockroachlabs.com Githubは、 github.com

CockroachDBはノード管理のためにgossipプロトコルを使用しています。これの実装を覗いてみました。
gossipは、他ノードが持っている情報と自分の持つ情報を合わせることで、各ノードが持つ情報を伝播させる方法です。
僕もよくわかっていないので、Wikipedia など見てください。

読んでいるコードは、2016年08月16日(54986344933bff1bed0a0991d03bbc0ee19c45a0)付近のmasterブランチです。

gossip関係のファイル

gossipに関わるコードは /gossip下にあります。

重要なファイルは、

  • gossip.go
  • server.go
  • client.go

gossip.goが起点となって、gossip用のサーバーと、他ノードへアクセスするためのクライアントが起動します。
送受信するデータ内容は gossip.proto で定義されています。 (protocol buffer)

gossip.go

Start()

スタート地点は、Start()関数

func (g *Gossip) Start(addr net.Addr) {
    g.server.start(addr) // serve gossip protocol
    g.bootstrap()        // bootstrap gossip client
    g.manage()           // manage gossip clients
}

これによって、サーバーとクライアントが起動します。

Gossip#bootstrap()

クライアントの最初の接続先はresolverに登録された接続先の中でアクセスできるものを採用しています。
コードではresolverの配列を順にチェックして、最初に接続できたものを採用しますが、接続ができればランダムでも問題ないと思います。

クライアントが起動したら、現在の接続先が死んだ場合に別の接続先へ切り替えるための無限ループするgoroutineを作っています。

ここで見つけた、time.Timer が面白い動きをしていました。
これは、一定時間ごとにchannelに渡すようになっているので、定期的な動作をするのに便利ですね。
bootstrapの中では下のような使われ方をしています。

var bootstrapTimer timeutil.Timer
defer bootstrapTimer.Stop()
select {
case <-bootstrapTimer.C:
    bootstrapTimer.Read = true
case <-g.server.stopper.ShouldStop():
    return
}

Gossip#manage()

クライアントとサーバーが起動した後には、
サーバーの終了シグナルや、定期的なノード数の上限管理などが下のコードで実行されます。

for {
    select {
    case <-g.server.stopper.ShouldStop():
        return
    case c := <-g.disconnected:
        g.doDisconnected(c)
    case nodeID := <-g.tighten:
        g.tightenNetwork(nodeID)
    case <-cullTicker.C:
        func() {
            g.mu.Lock()
            if !g.outgoing.hasSpace() {
                leastUsefulID := g.is.leastUseful(g.outgoing)

                if c := g.findClient(func(c *client) bool {
                    return c.peerID == leastUsefulID
                }); c != nil {
                    ...
                    c.close()
                    ...
                } else {
                    ...
                }
            }
            g.mu.Unlock()
        }()
    case <-stallTicker.C:
        g.mu.Lock()
        g.maybeSignalStalledLocked()
        g.mu.Unlock()
    }
}