act-act about projects rss

Serf Source Code Reading

このところ、Serfのコードを読んでいました。一旦、読んで理解した内容をまとめてみたいと思います。

Packages

Serfは大きく次の2つのパッケージに分かれており、各々の役割は以下のようになっています。

memberlist

  • クラスタのノードの状態管理
  • イベントの発行

serf

  • コマンドの提供
  • イベントをフックして任意のスクリプトを実行する仕組み
  • クラスタの状態のスナップショットの作成とリストア

そして、serfはパッケージはmemberlistパッケージに依存しています。 serf1

今回はmemberlistについて分かったことを書いていきます。

Memberlist Summary

memberlistはSerfクラスタの各ノード内に1つずつ存在しており、Serfクラスタ内の全ノード情報を保持しています。この保持しているノード情報が、Serfクラスタのノード間でやり取り(full state sync)されることで、ノード情報が伝播していきます。 serf2

また、各ノードは定期的に他のノードにpingを送信して生存確認を行います(probe)。この結果がNGだった場合、自身のノード情報を更新し、NGノードの情報を他のノードに送信して行きます(gossip)。 serf6

また、新しいノードがSerfクラスタに参加した場合も、自身のノード情報を更新し、新たに参加したノードの情報を他のノードに送信します(gossip)。 serf4

また、ノード間の情報のやり取りとは別に、各ノード内でSerfクラスタのノードの状態等が変更されると、その変更内容によってイベントが発火する仕組みがあります(event delegate)。 serf5

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)。 serf3

それでも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の部分を読んでないので、時間を作って読んでみたいと思います。