Zookeeper chap03

30
ZooKeeper 3Getting Started with the ZooKeeper API 社内勉強会資料 大槌 剛彦 (@ohtsuchi)

Transcript of Zookeeper chap03

Page 1: Zookeeper chap03

ZooKeeper 第3章

Getting Started with the ZooKeeper API

社内勉強会資料

大槌 剛彦 (@ohtsuchi)

Page 2: Zookeeper chap03

第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

Page 3: Zookeeper chap03

Creating a ZooKeeper Session

• ZooKeeper client library

– ZooKeeper handleを構築する • ZooKeeper handle = ZooKeeper サーバとのsession を表す

– serverとのconnectionが切れた場合は別のserverにmigrateする

– sessionが有効な状態を保てるように、継続的にアクティブな接続を維持しようとする。

– handleをclose → serverにsessionをkillするように伝える

Page 4: Zookeeper chap03

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)

Page 5: Zookeeper chap03

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);

2

3

4

Page 6: Zookeeper chap03

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

2

3

4

Page 7: Zookeeper chap03

Running the Watcher Example

• Disconnected event

– 再接続のために新しくZooKeeper handleを作ってはいけない!

– client libraryが対応してくれる • ネットワーク障害 や

• サーバ障害 にも対応

• 3台中1台のserverが死んでもサービスは停止しない

• ZooKeeper Manages Connections

– 自分でconnectionを制御しようとしてはいけない

– client library はconnectionをモニタし、障害を通知するだけでなく、再接続も行い、中断を最小限に抑える

– 不必要に新しいsessionを作ろうとすると、loadが増えて、より長い停止をもたらす

Page 8: Zookeeper chap03

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 …

Page 9: Zookeeper chap03

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):

Page 10: Zookeeper chap03

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

Page 11: Zookeeper chap03

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

Page 12: Zookeeper chap03

Getting Mastership (例外処理)

• ZooKeeper#create は2つの例外をthrowする

– KeeperException

– InterruptedException

• 特に以下の例外は対処する必要あり (createが成功している可能性もある)

– ConnectionLossException(KeeperExceptionのサブクラス)

• ネットワークエラー時など

• 以下の事がクライアント側で不明

– request がserver に届いたかどうか

– request がserver で処理されたけれど responseを受け取れていないかどうか

• → Requestが処理されたかどうか、再requestする必要があるかどうか、要調査

– InterruptedException • client threadでThread.interruptが呼ばれた場合

• アプリのshutdown時など

Page 13: Zookeeper chap03

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)

Page 14: Zookeeper chap03

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) { } } }

2

Page 15: Zookeeper chap03

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

Page 16: Zookeeper chap03

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)

2

Page 17: Zookeeper chap03

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)

Page 18: Zookeeper chap03

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

Page 19: Zookeeper chap03

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); }

Page 20: Zookeeper chap03

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

事前に作成

Page 21: Zookeeper chap03

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) {

…(次頁へ続く)

2

Page 22: Zookeeper chap03

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

Page 23: Zookeeper chap03

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

Page 24: Zookeeper chap03

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

Page 25: Zookeeper chap03

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

Page 26: Zookeeper chap03

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

Page 27: Zookeeper chap03

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"

Page 28: Zookeeper chap03

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

Page 29: Zookeeper chap03

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は残る

Page 30: Zookeeper chap03

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