書籍では、ZooKeeper を使う典型的な例として 「複数のマシンの中から一台をマスターとして選ぶ」 というユースケース、いわゆる 「リーダー選出」を挙げ、3 章以降でコーディング例を示しています。しかし、コールバックやら Watcher やらの組み合わせ方がややこしいため、コードのフローは追いにくいものになっています。
8 章では 「ZooKeeper の高レベル API」 として Curator フレームワークを紹介し、Curator を使った場合にリーダー選出のコーディングがどうなるかを示しているものの、それでも分かりにくいです。というか、分かりやすいかどうかよりも、リーダー選出のために、素の ZooKeeper API とはだいぶ異なる Curator API を学習しなければならないという点で既に、「なんか違う。そうじゃない」 感があります (あくまで個人的に)。
「リーダー選出、もっとすっきり書けるはず」、という信念のもと、もんもんと設計を考え、最終的に LeaderElection という一つのクラスにリーダー選出アルゴリズムをまとめるに至りました。このクラスを使うと、リーダー選出のコーディングは次のように直感的で簡潔になります。
// ZooKeeper インスタンスを用意します。
ZooKeeper zooKeeper = ...;
// リスナーの実装を用意します。
LeaderElection.Listener listener = new LeaderElection.Leader() {
@Override
public void onWin(LeaderElection election) {
System.out.println("私がリーダーです。");
}
@Override
public void onLose(LeaderElection election) {
System.out.println("他の誰かがリーダーです。");
}
@Override
public void onVacant(LeaderElection election) {
System.out.println("リーダーが辞めました。選出を再実行します。");
}
@Override
public void onFinish(LeaderElection election) {
System.out.println("コールバックチェーン終了。もう選出には参加しません。");
}
@Override
public void onStateChanged(LeaderElection election, State oldState, State newState) {
System.out.format("状態が %s から %s に変わりました。\n", oldState, newState);
}
};
// リーダー選出を実行します。直感的で簡潔でしょ?
new LeaderElection()
.setZooKeeper(zooKeeper)
.setListener(listener)
.start();
// 上記と同じですが、各変数を明示的に設定すると次のようになります。
new LeaderElection()
.setZooKeeper(zooKeeper)
.setListener(listener)
.setPath("/leader")
.setId(
String.valueOf(Math.abs(new Random().nextLong()))
)
.setAclList(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.start();
LeaderElection の実装は、次のいずれかを検出するまでは、リーダー選出に参加し続けます ─すなわち、コールバック (および必要に応じて Watcher) をスケジュールし続けます。
- 与えられた ZooKeeper インスタンスの状態が AUTH_FAILED もしくは CLOSED である。
- LeaderElection.finish() メソッドにより 「終了すべき」 とマークされている。
LeaderElection.Adapter クラスは LeaderElection.Listener インターフェースの空実装です。 コールバックメソッドの幾つかにしか興味がない場合は、このクラスが便利かもしれません。 例えば、onStateChanged() にしか興味がない場合は、次のようにコードを短くできます。
// リーダー選出を実行します。
new LeaderElection()
.setZooKeeper(zooKeeper)
.setListener(new Adapter() {
@Override
public void onStateChanged(LeaderElection election, State oldState, State newState) {
System.out.format("状態が %s から %s に変わりました。\n", oldState, newState);
}
})
.start();
LeaderElection クラスは nv-zookeeper という Maven artifact に入れてあるので、pom.xml に次のように書けば、すぐに使えます。
nv-zookeeper は作成したばかりなのでテストも不十分ですが、よかったら試しに使ってみてください。不具合指摘、改善要望、プルリクエストは歓迎します。JavaDoc はこちらです。