Zookeeper chap03
-
Upload
ohtsuchi -
Category
Technology
-
view
581 -
download
0
Transcript of Zookeeper chap03
ZooKeeper 第3章
Getting Started with the ZooKeeper API
社内勉強会資料
大槌 剛彦 (@ohtsuchi)
第3章の内容
•Setting the ZooKeeper CLASSPATH •Bin/zkEnv.sh 実行 で CLASSPATH 環境変数の設定
•Creating a ZooKeeper Session
•Implementing a Watcher
•Running the Watcher Example
•Getting Mastership
•Getting Mastership Asynchronously
•Setting Up Metadata
•Registering Workers
•Queuing Tasks
•The Admin Client
•zkCli の代わりに システムのstateを調べるシンプルなadminクライアントの例
•Takeaway Messages
•API はzkCliで使用するコマンドと一致するアプリを書ける
• errorをhandleする処理が必要
• 特に ConnectionLossException
• 非同期APIはパフォーマンスの利益をもたらし、errorリカバリをsimpleにできる
第2章の
Implementation of
a Master-Worker Example
のjava APIによる基本的な実装例
3つの役割: ・Master ・Worker ・Client
Creating a ZooKeeper Session
• ZooKeeper client library
– ZooKeeper handleを構築する • ZooKeeper handle = ZooKeeper サーバとのsession を表す
– serverとのconnectionが切れた場合は別のserverにmigrateする
– sessionが有効な状態を保てるように、継続的にアクティブな接続を維持しようとする。
– handleをclose → serverにsessionをkillするように伝える
Creating a ZooKeeper Session
• ZooKeeper handleを作るためのコンストラクタ
– connectString
• ZooKeeper server のホスト名・ポート
– sessionTimeout • ミリ秒で指定
– watcher
• eventの発生をclient applicationに通知する
• sessionの状態や、ZooKeeper dataの変更をmonitor
ZooKeeper( String connectString, int sessionTimeout, Watcher watcher)
Implementing a Watcher
• Master.javaの実装例
public interface Watcher { void process(WatchedEvent event); }
public class Master implements Watcher { … Master(String hostPort) { this.hostPort = hostPort; } void startZK() { zk = new ZooKeeper(hostPort, 15000, this); }
public void process(WatchedEvent e) { System.out.println(e); }
public static void main(String args[]) … { Master m = new Master(args[0]); m.startZK(); // wait for a bit Thread.sleep(60000);
1
2
3
4
Implementing a Watcher • Master.javaの実行ログ
– ①~③はlog4jでの出力
– ④はWatcher#process の出力
... - INFO [...] - Client environment:zookeeper.version=3.4.5-1392090, ...
...
... - INFO [...] - Initiating client connection, connectString=127.0.0.1:2181 ...
... - INFO [...] - Opening socket connection to server localhost/127.0.0.1:2181. ...
... - INFO [...] - Socket connection established to localhost/127.0.0.1:2181, initiating session ... - INFO [...] - Session establishment complete on server localhost/127.0.0.1:2181, ... WatchedEvent state:SyncConnected type:None path:null
1
2
3
4
Running the Watcher Example
• Disconnected event
– 再接続のために新しくZooKeeper handleを作ってはいけない!
– client libraryが対応してくれる • ネットワーク障害 や
• サーバ障害 にも対応
• 3台中1台のserverが死んでもサービスは停止しない
• ZooKeeper Manages Connections
– 自分でconnectionを制御しようとしてはいけない
– client library はconnectionをモニタし、障害を通知するだけでなく、再接続も行い、中断を最小限に抑える
– 不必要に新しいsessionを作ろうとすると、loadが増えて、より長い停止をもたらす
Running the Watcher Example
• two main management interfaces ( 詳細は第10章で)
– JMX
– four-letter words • stat
• dump
• Statの出力例(telnetで接続してから実行)
– この例では2つのクライアントが接続 (1) Master.java (2)このtelnet接続自身
stat ZooKeeper version: 3.4.5-1392090, built on 09/30/2012 17:52 GMT Clients: /127.0.0.1:39470[1](queued=0,recved=3,sent=3) /127.0.0.1:39471[0](queued=0,recved=1,sent=0) Latency min/avg/max: 0/5/48 Received: 34 Sent: 33 Connections: 2 Outstanding: 0 Zxid: 0x17
Mode: standalone …
Running the Watcher Example • dumpの出力例(telnetで接続してから実行)
– 1つの active session (Master.javaのsession)
– expiration time は ZooKeeperのコンストラクタで指定したsession timeout が元
– Master.javaをkill して dumpで繰り返しactive sessionを観察
» → sessionが終了するまではしばらく時間がかかることがわかる
» → 指定したsession timeout が過ぎるまで server側はsessionをkillしないため
– 処理が終了した時に直ちにsessionが消えるのは良い
» Master.java の終了処理に、ZooKeeper#close 呼び出しを追加
dump SessionTracker dump: Session Sets (3): 0 expire at Wed Nov 28 20:34:00 PST 2012: 0 expire at Wed Nov 28 20:34:02 PST 2012: 1 expire at Wed Nov 28 20:34:04 PST 2012: 0x13b4a4d22070006 ephemeral nodes dump: Sessions with Ephemerals (0):
The Master Role (補足:2章の復習)
• 1つのプロセスのみがmaster
– /master znodeを作成できたクライアントがactive masterとなる
– /master znodeを作成できなかったノードは backup masterとなり /masterをwatch.
– active masterのsessionがclose または expired
• /master znodeがなくなる(ephemeral znodeなので)
• → backup master が/master znodeを作成してactive masterとなる
– →
– stat /master true
create -e /master "master1…" master1
stat /master true master2
create -e /master “master2…“ (失敗)
watch
ephemeral
Getting Mastership (znodeのcreate)
• "/master" znodeを作成するのに2つ必要
– initial data • 例では、 random server ID を使用
– アクセス制御リスト (ACL) • 信頼できる環境内では、よくopen ACLが使用される
• 例では、ZooDefs.Ids.OPEN_ACL_UNSAFE を使用
– 名前が示すとおり、安全ではない
– ① “/master” znodeが既に存在していれば失敗する
– ② znodeに storeするdata (型=byte[])
– ③ open ACL
– ④ ephemeral znodeで作成
void runForMaster() { zk.create("/master", serverId.getBytes(), OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); }
1 2 3
4
Getting Mastership (例外処理)
• ZooKeeper#create は2つの例外をthrowする
– KeeperException
– InterruptedException
• 特に以下の例外は対処する必要あり (createが成功している可能性もある)
– ConnectionLossException(KeeperExceptionのサブクラス)
• ネットワークエラー時など
• 以下の事がクライアント側で不明
– request がserver に届いたかどうか
– request がserver で処理されたけれど responseを受け取れていないかどうか
• → Requestが処理されたかどうか、再requestする必要があるかどうか、要調査
– InterruptedException • client threadでThread.interruptが呼ばれた場合
• アプリのshutdown時など
Getting Mastership (例外処理: getDataで確認)
• 例外発生時に、システムの状態を確認する必要がある – もし自分のcreateが成功していたら、他のノードはmasterになれていない
– どのノードもmasterとして活動しないことになる
• どのprocessが“/master” znodeを作成したのかを調べる
– ConnectionLossException 対応時
– ZooKeeper#getData を使用
• path
– dataを取得するznodeのpath
• watch
– true: ZooKeeper handle作成時(ZooKeeperのコンストラクタ)に、引数で渡したWatcher objectを介して eventを取得する
– ※次の例では、現在の data を知りたいだけなので falseをセット
• stat
– getData メソッド内で、znode のmetadataがセットされる
• 戻り値
– znode のdata
byte[] getData(String path, bool watch, Stat stat)
Getting Mastership (例外処理: getDataで確認)
– ① /master znodeのdataをget
– ② /master znodeのdataは masterになったノードのサーバidが入っている(createの第2引数)
• 自分自身のserverIdと一致していれば自分がmaster(isLeader = true)
// “/master”を作成したノードが存在すれば true
boolean checkMaster() { while (true) { try {
Stat stat = new Stat(); byte data[] = zk.getData("/master", false, stat); isLeader = new String(data).equals(serverId)); return true; } catch (NoNodeException e) { // no master, so try create again return false; } catch (ConnectionLossException e) { } } }
1
2
Getting Mastership (例外処理: getDataで確認)
void runForMaster() throws InterruptedException { while (true) {
try { zk.create("/master", serverId.getBytes(), OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); isLeader = true; break; } catch (NodeExistsException e) { isLeader = false; break; } catch (ConnectionLossException e) { } if (checkMaster()) break; } }
4
5
3
7
6
Getting Mastership Asynchronously (createメソッド)
• 全ての同期呼び出しに対応する非同期呼び出しが存在する
• 非同期のZooKeeper#create
– 同期版との相違
• 引数が2つ多い
– ① Callback関数を持つオブジェクト
– ② Callback関数内にデータを渡したい時に使用するユーザ定義オブジェクト
• Exceptionをthrowしない
– Errorはコード化されて、Callback関数の第1引数に渡される
void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, AsyncCallback.StringCallback cb, Object ctx)
1
2
Getting Mastership Asynchronously (Callback)
• StringCallback が定義するメソッド
– rc
• OK または例外発生を表すコード
– path
– ctx
• ZooKeeper#create で引数に渡されたObject
– name
• znode の名前
• znode の create が成功した場合、pathとnameは一致する
– create時に CreateMode.SEQUENTIAL を指定した場合は一致しない
• Callback Processing – single thread で全てのcallbackを処理するため、1つのcallbackがblockされると、それに続く全てのcallbackもblockされてしまう
– → callback の中で 集中的な操作やblockする操作をするべきではない
void processResult(int rc, String path, Object ctx, String name)
Getting Mastership Asynchronously (create)
– ① createの結果を 第1引数:rc で取得. Enumに変換
– ② CONNECTIONLOSS時は非同期版checkMasterメソッド(次頁で解説)を呼び出してシステムの状態をチェック
static StringCallback masterCreateCallback = new StringCallback() { void processResult(int rc, String path, Object ctx, String name) { switch(Code.get(rc)) { case CONNECTIONLOSS: checkMaster(); return;
case OK: isLeader = true; break;
default: isLeader = false;
} System.out.println("I'm " + (isLeader ? "" : "not ") + "the leader");
} };
void runForMaster() { zk.create("/master", serverId.getBytes(), OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, masterCreateCallback, null);
1 2
3
4
5
Getting Mastership Asynchronously (getData)
• #getData終了 → DataCallback
• 同期版と非同期版のメソッドの違い
– While loop が無い
– try-cathで囲まずに、エラー処理はCallback側で
DataCallback masterCheckCallback = new DataCallback() { void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) { switch(Code.get(rc)) { case CONNECTIONLOSS: checkMaster(); return; case NONODE: runForMaster(); return; } } }
void checkMaster() { zk.getData("/master", false, masterCheckCallback, null); }
Workers, Tasks, and Assignments (補足:2章の復習)
• 3つのparent znodeを、一番最初(taskのassignが始まる前:bootstrap処理など)に作っておく必要 (persistent znode) (contain no data) – /workers
– /tasks
– /assign
• masterは /workers と /tasks を watch
ls /workers true master1
ls /tasks true
watch
事前に作成
Setting Up Metadata
– ①dataは空で作成するので byte[0]
– ②第2引数は 作成されるznodeに書き込まれるdata. 第4 第6引数のdataはCallbackに渡すObject
public void bootstrap() { createParent("/workers", new byte[0]); createParent(“/assign”, new byte[0]); createParent(“/tasks”, new byte[0]); createParent(“/status”, new byte[0]); }
Void createParent(String path, byte[] data) {
zk.create(path, data, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, createParentCallback, data); }
StringCallback createParentCallback = new StringCallback() { public void processResult(int rc, String path, Object ctx, String name) {
…(次頁へ続く)
1
2
Setting Up Metadata
– ③ CONNECTIONLOSS発生時:simply retry the create
• #createPathメソッドを呼び出し
– 引数の ctx をそのまま#createPathメソッドの引数にセット
StringCallback createParentCallback = new StringCallback() { public void processResult(int rc, String path, Object ctx, String name) { switch (Code.get(rc)) { case CONNECTIONLOSS: createParent(path, (byte[]) ctx ); break; case OK: LOG.info("Parent created"); break; case NODEEXISTS: LOG.warn("Parent already registered: " + path); break; default: LOG.error("Something went wrong: ", KeeperException.create(Code.get(rc), path)); } } };
3
The Worker Role (補足:2章の復習)
• Worker は taskを実行できる事を masterに伝える
– /workers 以下にephemeral znodeを作る
• pathに自分のhostnameを使用
• → master は NodeChildrenChanged を観察する
• Worker は taskのassignを受け取るためのznodeを作る – /assign 以下にznodeを作り、watchする
master1
watch
worker1 create -e /workers/worker1 "worker1:2224"
ephemeral
create /assign/worker1 ""
ls /assign/worker1 true
watch
Registering Workers (ephemeral znodeの作成)
• /workers 以下にephemeral znodeを作る (workerが死んだ際には/workers から消えるように)
– Workerのstateを示すのに dataを利用する
public class Worker implements Watcher { …
void register() { zk.create("/workers/worker-" + serverId, "Idle".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, createWorkerCallback, null); }
StringCallback createWorkerCallback = new StringCallback() { public void processResult(int rc, String path, Object ctx, String name) { switch (Code.get(rc)) { case CONNECTIONLOSS: register(); break; case OK: LOG.info("Registered successfully: " + serverId); break; case NODEEXISTS: LOG.warn("Already registered: " + serverId); break; default: LOG.error("Something went wrong: " …
1
2
3
Registering Workers (非同期にstatusを更新する例)
StatCallback statusUpdateCallback = new StatCallback() { public void processResult(int rc, String path, Object ctx, Stat stat) { switch(Code.get(rc)) {
case CONNECTIONLOSS: updateStatus((String)ctx); return; } } };
synchronized private void updateStatus(String status) { if (status == this.status) { zk.setData("/workers/" + name, status.getBytes(), -1, statusUpdateCallback, status); } }
public void setStatus(String status) { this.status = status; updateStatus(status); }
1
2
3
4
5
The Client Role (補足:2章の復習)
• Client は /tasks 以下にznodeを作って、 taskを登録する
– Sequential
• taskの順序
• queueを提供
• masterはnew taskをcheckし、available workerにそのtaskをassignする – “/assign/worker名/task名” にznodeを作成
master1 watch
client
create /assign/worker1/task-0000000000 ""
create -s /tasks/task- "cmd"
ls /tasks/task-0000000000 true
watch
sequential
worker1 watch
The Client Role (補足:2章の復習)
• worker は /tasks 以下にstatus znodeを作って、 taskが完了した事を伝える
• client は status znodeをチェックしてtaskの完了状態を知る
master1 watch
client watch
worker1 watch
create /tasks/task-0000000000/status "done"
Queuing Tasks
• /tasks 以下にznodeを作成する
– sequential znode を利用する: 2つの利点 • sequence number が taskがキューで処理される順序を指し示す
• sequence number がtaskのためのunique pathを作り出す
public class Client implements Watcher { …
String queueCommand(String command) throws KeeperException { while (true) { try { String name = zk.create("/tasks/task-", command.getBytes(), OPEN_ACL_UNSAFE, CreateMode.SEQUENTIAL); return name; break; } catch (NodeExistsException e) { throw new Exception(name + " already appears to be running"); } catch (ConnectionLossException e) { } } } … }
1
2
3
4
Queuing Tasks (前頁の続き)
• ① 作成するznodeの名前のプレフィックス=“task-”
• ② CreateMode.SEQUENTIALを指定
– 単調増加するサフィックスが“task-”の後ろに追加される
– 各taskに対して、 unique nameになることが保証される
– taskの順序も確立される
• ③ #create呼び出し時点では“task-”の後ろのsequence numberが分からない
– #createの戻り値で取得
• ④ connectionのloss時
– simply retry the create
– create multiple znodes for the task
• → 「execute-at-least-once policy」 may work fine
– → must do more work
» Session ID などのunique IDを使って znodeを作る必要
• /tasks以下はephemeral znodeではないので、Client applicationの終了後も、各znodeは残る
The Admin Client
• #getData と #getChildren を使用 – don’t change the state of the system
• watch parameter は false を指定
– current state を知りたいだけ
public class AdminClient implements Watcher { …
void listState() throws KeeperException { try { Stat stat = new Stat(); byte masterData[] = zk.getData("/master", false, stat); Date startDate = new Date(stat.getCtime()); … System.out.println("Workers:"); for (String w: zk.getChildren("/workers", false)) { byte data[] = zk.getData("/workers/" + w, false, null); String state = new String(data); System.out.println("¥t" + w + ": " + state); } System.out.println("Tasks:"); for (String t: zk.getChildren("/assign", false)) { System.out.println("¥t" + t); } } …
1 2
3