前回のmemberlistに続き、今回はserfのコードを読んでみました。
Serf Summery
serfは、memberlistを利用してクラスタ内のノードのJoin/Leaveやユーザ定義のイベントをフックし、各イベントにマッピングされたユーザスクリプトを実行します。
Packages
serfは以下の3つのpackageで構成されています。
- serf
- memberlistを作成
- memberlistのメッセージングの仕組みの拡張
- UserEvent
- ノードの状態をスナップショットとして保存し、リストアする仕組み
- agent
- 各ノード上で起動されるプロセス
- コマンドからのリクエストを受け付けて処理し、結果を返す
- command
- CLI
- 任意のノードのエージェントにリクエストを投げ、レスポンスを表示する
Full State Sync Message Expansion
serfはmemberlistのDelegateを定義しています。このDelegateを使って、memberlistがノード間でstate syncする際にやり取りする情報を拡張しています。具体的には以下の情報を追加で渡すようになっています。
- クラスタ内の各ノードのjoin time(statusLTimes)
- leaveしたノード名(leftMembers)
Join/Leave Event
serfはmemberlistのEventDelegateを定義しています。このEventDelegateを使って、ノードのJoin/Leave時に以下の事を行っています。
NodeJoin
- 現在保持しているノード情報(Serf.members)に、今回joinしたノードの情報があるかチェック
- ノード情報が無ければ、ノード情報(memberState)をStatusAliveで作成してmembersに追加
- recentJoinにjoinしたノードのintentが登録されていれば、そのintentが発行された時のLTimeをstatusLTimeとする
- resentLeaveにjoinしたノードのintentが登録されていれば、StatusLeavingに変更し、そのintentが発行された時のLTimeをstatusLTimeとする
- ノード情報が有れば、join時のノード情報で上書きし、StatusAliveにする
- Eventチャネルにjoinメッセージを送信
NodeLeave
- 現在保持しているノード情報から、今回leaveしたノードに該当する情報を取り出す – 取り出したノードのStatusがStatusLeavingであれば、StatusLeftに遷移させてleftMembersに追加 – StatusAliveであれば、StatusFailedに遷移させてfailedMembersに追加
- Eventチャネルにleaveメッセージを送信
UserMessage And SerfEvent
memberlistでは、gossipメッセージ送信時に、送信バッファの空き容量に応じてユーザ定義のメッセージを送信することができます(memberlist/broadcast.goのGetBroadcasts)。serfはこの機構を利用し、gossipメッセージ送信時に以下のメッセージを追加しています。
- messageJoinType
- あるノードがクラスタに参加した
- messageLeaveType
- あるノードがクラスタから外れた
- messageUserEventType
- ユーザ定義のイベント
このうち、messageJoinTypeとmessageLeaveTypeは、後述のコマンドを使って意図的にクラスタにjoin/leaveした場合に送信されます。恐らく、コマンド等で意図的に実行したjoin/leaveと、何らかの障害によって起こったjoin/leaveと区別されて扱われているのだと思います。これらのメッセージが送信されると、recentJoinやrecentLeaveにノード情報が登録されます。
MemberStatus
memberlistでも各ノードの状態を管理していましたが、それとは別にserfでも各ノードの状態を管理しています。serfで管理しているノードの状態と遷移の契機は以下のとおりです。
- StatusNone
- StatusAlive
- ノードが意図的にjoinし、且つNodeJoinイベントが発火した場合
- StatusLeaving
- ノードが意図的にleaveし、且つStatusAliveの場合
- joinイベント発火時に、recentLeaveに登録されていた場合
- タイミングによっては有りえる?
- StatusLeft
- StatusLeavingの状態でNodeLeaveイベントが発火した場合
- StatusFailedの状態で、state syncもしくはgossipで当該ノードがleaveしたことを通知された場合
- StatusFailed
- StatusAliveの状態でNodeLeaveイベントが発火した場合
Coalesce
Coalesceは、個々のイベントを集約して1つに纏めて、メッセージの送信を効率化する為の仕組みです。現時点でdefaultではdisableとなっています。
Coalesceについては、config.goのCoalescePeriodのところに記載されているコメントが分かりやすいので、簡単に訳してみました。
Serfは、EventChに送信されたノイズ量を削減する為に、複数のイベントを1つに纏めることができる。例えば、5つのノードが一気にクラスタにjoinすると、EventChには5つのEventMemberJoinが送信されるのではなく、5ノードの情報を含んだ1つのEventMemberJoinが送信される。これは障害や復帰の通知の氾濫を軽減する。
Snapshot
Snapshotは、現在保持しているノード情報をファイルに出力しておく機能です。これもconfig.goのSnapshotPathのところに記載されているコメントを簡単に訳してみました。
SnapshotPathを設定すると、生存しているノードとLamport clock値を保存したスナップショットが使えるようになる。Serf起動時にスナップショットを用いると、前回認識していたノードをjoinしようとしなくなり、過去のユーザイベントの再発を回避する
Agent
agentはノード上に、これまで見てきたmemberlistやserfのserfパッケージの機能を含むプロセスを立ち上げます。つまり、agentが起動して初めてこれらの機能がノード上で有効になります。
また、agentはRPCサーバでもあります。このRPCの受け口(TCP)は、コマンド引数”rpc-addr”で指定します。デフォルトは”127.0.0.1:7373”となっています。
agent起動時に、serfのEventChに対してイベントハンドラを設定します。現時点では固定でScriptEventHandlerが設定されるようになっています。このScriptEventHandlerによって、各イベントにマッピングされたスクリプトが実行されます。
Command
commandはjoinやleave、ユーザイベントを発行するCLIを提供します。これらのcommandを実行するとagentに対してRPCでリクエストを発行し、agentが処理を実行する仕組みになっています。
Conclusion
今回はserfのコードを読んでみました。正直あまり細かいところまでは理解できていませんが、概要は掴めたと思います。
記載している内容について間違っているところがあれば教えてもらえると嬉しいです(golangは一行も書いたことがないので)。
Update
- 2014/01/21: 最初の絵に間違いがあったので修正しました。user-data messageの中でUserEventが発火するのは、userevent messageだけです。join/leave messageはイベントを発火しません。member messageの方も、suspectは直接イベントを発火しないので、そのような記述に修正しました。