このところ、Serfのコードを読んでいました。一旦、読んで理解した内容をまとめてみたいと思います。
Packages
Serfは大きく次の2つのパッケージに分かれており、各々の役割は以下のようになっています。
memberlist
- クラスタのノードの状態管理
- イベントの発行
serf
- コマンドの提供
- イベントをフックして任意のスクリプトを実行する仕組み
- クラスタの状態のスナップショットの作成とリストア
そして、serfはパッケージはmemberlistパッケージに依存しています。
今回はmemberlistについて分かったことを書いていきます。
Memberlist Summary
memberlistはSerfクラスタの各ノード内に1つずつ存在しており、Serfクラスタ内の全ノード情報を保持しています。この保持しているノード情報が、Serfクラスタのノード間でやり取り(full state sync)されることで、ノード情報が伝播していきます。
また、各ノードは定期的に他のノードにpingを送信して生存確認を行います(probe)。この結果がNGだった場合、自身のノード情報を更新し、NGノードの情報を他のノードに送信して行きます(gossip)。
また、新しいノードがSerfクラスタに参加した場合も、自身のノード情報を更新し、新たに参加したノードの情報を他のノードに送信します(gossip)。
また、ノード間の情報のやり取りとは別に、各ノード内でSerfクラスタのノードの状態等が変更されると、その変更内容によってイベントが発火する仕組みがあります(event delegate)。
Reading README.md
まず、memberlistのREADME.mdに記載されている内容を確認しています。訳は適当ですので、各自で確認してください。
Protocol Description
- 新しいメンバが追加されると、既存のメンバからTCPでfull state syncされる
- gossipにはUDPが使われる
- ネットワーク利用率は、サーバのノード数に対して、heartbeatのような指数関数的な増加とは対照的に、一定である
- ランダムノードのstate exchangeは定期的にTCPで行われるが、gossipメッセージと比較するとかなり少ない頻度である
- 全てのstate情報が交換・マージされる為、memberlistは適切に収斂していく可能性が高い
- あるノードが応答しない場合、いくつかのノードがindirect probeを行う
- indirect probeが成功したかによって、ネットワークの問題であるかどうかを切り分ける
- indirect probeも失敗した場合、そのノードは”suspicious”としてマークlされる。但し”suspicious”であってもクラスタのメンバと見なす
- 一定時間”suspicious”であれば、そのノードは”dead”と見なされ、それが各ノードに伝播する
Changes from SWIM
- 定期的にTCPでfull state syncを実施する。SWIMはgossipで状態の変更を伝播するだけ。帯域を使えるのであれば、full state syncの方がより速くノードの状態が全体に行き渡る可能性が上がる
- failure detection protocolとgossipを明確に分離している。SWIMはprobe/ackメッセージの上にgossipメッセージを乗せているだけである。memberlistはそれだけでなく、定期的にgossipメッセージを送信している。この仕組みによって、gossipメッセージの方がfailure detectionよりも多くなるので、結果としてより速く全体にノードの情報やデータが行き渡る
- dead nodeの情報を一定期間保持する。”full syncs”が要求された場合、dead nodeの情報も含めて返る。SWIMはfull syncをしない為、nodeが死んだと認識したら即dead nodeの情報を消す。この変更も収斂の速度を上げる
Node state management
memberlistでは、ノード間で以下のメッセージをやり取りすることで、各ノードの状態を管理しています。メッセージを送信する間隔はconfigによって変更できます。以下はDefaultLANConfigに設定されている間隔です。
- probe(1秒)
- full state sync(pushPull)(ノード数が32までであれば30秒)
- gossip(200ミリ秒)
Probe
probeはUDPでノードの生存をチェックします。
まず、自身が保持するノード情報から生存チェック対象とするノードを選択します。このノードはprobeが実行される度に順に1つ選ばれ、最後までチェックしたらまた最初からチェックします。
対象ノードにpingメッセージを送信し、ackが返ってくればチェック完了です。ackが返ってこない場合は、自身が保持するノード情報からランダムに複数のノードを選択(DefaultLANConfigの場合は3)し、その選択したノードから対象ノードにpingメッセージを送信します(indirectPing)。
それでもackが返ってこない場合は、対象ノードをsuspect(疑わしい)とみなし、その情報を他のノードに伝播します。
Full state sync(pushPull)
full state sync(pushPull)はTCPを使い、2つのノード間でお互いが保持するノード情報を同期します。
まず、自身が保持するノード情報をランダムに1つ選び、それを対向側のノードとします。そしてその対向側のノードに対し、自身が保持するノード情報を送信します。これには、ホスト名、IPアドレス、通信ポート、そのノードの状態などが含まれます。
次に、対向側のノードが保持するノード情報を受信します。そして各ノード情報について自身のノード情報と比較し、異なる場合は更新して他のノードに伝播します。
対向側から見ると、full state syncを受信時に送信元からノード情報を受け取り、自身のノード情報を送信します。これはTCPリスナが担当します。
Gossip
gossipはprobeやpushPull以外の以下のメッセージを各ノードに送信します。
- compoundMsg
- 複数のメッセージをまとめたメッセージ
- pingMsg
- pingメッセージ
- indirectPingMsg
- 他ノードを介したpingメッセージ
- ackRespMsg
- pingに対するackのメッセージ
- suspectMsg
- ノードのstateがsuspect(疑わしい)であることを表すメッセージ
- aliveMsg
- ノードのstateがaliveであることを表すメッセージ
- deadMsg
- ノードのstateがdeadであることを表すメッセージ
- userMsg
- ユーザ定義のメッセージ
- compressMsg
- 圧縮されたメッセージ
これらのメッセージは各ノードのbroadcastキューに登録されています。あるノードのstateを他のノードに伝播したい場合は、一旦broadcastキューに登録します。gossipではbroadcastキューからメッセージを取得し、ランダムに選択した複数(DefaultLANConfigの場合は3)のノードに対してそのメッセージを送信します。
gossipにより送信されたメッセージを受信する側は、受信したメッセージによって適切な処理を実行します。これはUDPリスナが担当します。
Node status
ノードの状態は以下の3つが定義されています。
- stateAlive
- 正常な状態
- stateSuspect
- 疑わしい状態。この状態のまま一定期間経過すると、deadと見なされる
- stateDead
- 応答が確認できない状態。deadと見なされると、ノード情報から削除される
Delegate
config.DelegateにDelegateを設定しておくと、以下が発生した契機に各ハンドラが呼び出されます。
- NodeMeta
- 自身のノードがaliveになった際(現時点では起動時のみ)に呼び出されます。この時に自身のノードのmeta情報を返すと、そのmeta情報がノード情報に設定され、そのノード情報はaliveメッセージに含められて各ノードに送信されます
- NotifyMsg
- UDPリスナでユーザ定義のメッセージを受信した際に呼び出されます。
- GetBroadcasts
- broadcastキューからメッセージを取り出した後に呼び出されます。この時にuserMsgを返すと、broadcastキューから取り出したメッセージと共にそのuserMsgが各ノードに送信されます。
- LocalState
- full state syncの際に自身のノード情報を対向側のノードに送信する際に呼び出されます。この時に自身のノード情報に追加して送る情報を返します。この仕組みによって、full state sync時にやり取りするノード情報を拡張することができます。
- MergeRemoteState
- これはLocalStateの逆で、対向側のノードからノード情報を受信した際に呼び出されます。第一引数のbyteは、LocalStateが返したbyteになります。
Event propergation
conig.EventsにEventDelegateを設定しておくと、ノードの状態が変化した際に以下のイベントが発火し、EventDelegateのイベントハンドラが呼び出されます。
- NodeJoin
- stateAlive遷移時に発火
- NodeLeave
- stateDead遷移時に発火
Conclusion
今回はmemberlistのコードを読んで分かったことを記載しました。近いうちにserfについても書いてみようと思います。serfの方はまだcommandとagentの部分を読んでないので、時間を作って読んでみたいと思います。