1. 程式人生 > >zookeeper學習系列:三、利用zookeeper做選舉和鎖

zookeeper學習系列:三、利用zookeeper做選舉和鎖

之前只理解zk可以做命名,配置服務,現在學習下他怎麼用作選舉和鎖,進一步還可構建master-slave模式的分散式系統。

為什麼叫Zoo?“因為要協調的分散式系統是一個動物園”。

ZooKeeper是一箇中性化的Service,用於管理配置資訊、命名、提供分散式同步,還能組合Service。所有這些種類的Service都會在分散式應用程式中使用到。每次編寫這些Service都會涉及大量的修bug和競爭情況。正因為這種編寫這些Service有一定難度,所以通常都會忽視它們,這就使得在應用程式有變化時變得難以管理應用程式。即使處理得當,實現這些服務的不同方法也會使得部署應用程式變得難以管理。

下邊程式碼是參考文獻的java版本,通過service來協調各個獨立的PHP指令碼,並讓它們同意某個成為Leader(所以稱作Leader選舉)。當Leader退出(或崩潰)時,worker可檢測到並再選出新的leader。通過這種方式即可理解一般的master-slave結構分散式系統是如何實現如何排程的,zk是個好東西。

首先需要了解建立節點的模式:

PERSISTENT:持久化目錄節點,這個目錄節點儲存的資料不會丟失;

PERSISTENT_SEQUENTIAL:順序自動編號的目錄節點,這種目 錄節點會根據當前已近存在的節點數自動加 1,然後返回給客戶端已經成功建立的目錄節點名;

EPHEMERAL:臨時目錄節點,一旦建立這個節點的客戶端與伺服器埠也就是 session 超時,這種節點會被自動刪除;

EPHEMERAL_SEQUENTIAL:臨時自動編號節點。

臨時節點在leader選舉、鎖服務中起著非常重要的作用。 

一、選舉

程式邏輯:

1)首先建立根節點/cluster,並建立自身子節點,以 /cluster/w- 為字首,使用臨時自動編號節點模式建立節點

2)獲取/cluster的所有子節點並排序,當發現自身是第一個節點時,則自我選舉為leader,否則認定為follower

3)註冊監聽事件,當/cluster裡前一個節點有變動時,回到2)

這樣,便實現了自動選舉,當有節點在timeout時段後不可用時,自動產生新的leader,也可根據當前節點數進行預警。

複製程式碼
package zookeeper;

import org.apache.zookeeper.*;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * 
@author guanpu * Date: 14-10-22 * Time: 下午5:11 * To change this template use File | Settings | File Templates. */ public class Worker extends ZooKeeper implements Runnable, Watcher { public static final String NODE_NAME = "/cluster"; public String znode; private boolean leader; public Worker(String connectString, int sessionTimeout, Watcher watcher) throws IOException { super(connectString, sessionTimeout, watcher); } public boolean register() throws InterruptedException, KeeperException { if (this.exists(NODE_NAME, null) == null) { this.create(NODE_NAME, "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } znode = this.create(NODE_NAME + "/w-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); znode = znode.replace(NODE_NAME + "/", ""); String node = watchPrevious(); if (node.equals(znode)) { System.out.println("nobody here ,i am leader"); leader = true; } else { System.out.println("i am watching"); } return true; } private String watchPrevious() throws InterruptedException, KeeperException { List<String> works = this.getChildren(NODE_NAME, this); Collections.sort(works); System.out.println(works); int i = 0; for (String work : works) { if (znode.equals(work)) { if (i > 0) { //this.getData(NODE_NAME + "/" + works.get(i - 1), this, null); return works.get(i - 1); } return works.get(0); } } return ""; } @Override public void run() { try { this.register(); } catch (InterruptedException e) { } catch (KeeperException e) { } while (true) { try { if (leader) { System.out.println("leading"); } else { System.out.println("following"); } Thread.sleep(1000); } catch (InterruptedException e) { } } } public static void main(String[] args) { try { String hostPort = "10.16.73.22,10.16.73.12,10.16.73.13"; new Thread(new Worker(hostPort, 3000, null)).start(); } catch (IOException e) { } } @Override public void process(WatchedEvent event) { String t = String.format("hello event! type=%s, stat=%s, path=%s", event.getType(), event.getState(), event.getPath()); System.out.println(t); System.out.println("hello ,my cluster id is :"+znode); String node = ""; try { node = this.watchPrevious(); } catch (InterruptedException e) { } catch (KeeperException e) { } if (node.equals(znode)) { System.out.println("process: nobody here ,i am leader"); leader = true; } else { System.out.println("process: i am watching"); } } }
複製程式碼

 啟動至少三個終端,模擬Leader崩潰的情形。使用Ctrl+c或其他方法退出第一個指令碼。剛開始不會有任何變化,worker可以繼續工作。後來,ZooKeeper會發現超時,並選舉出新的leader。

php移植到java有兩個問題,第一個是watcher註冊,第一次父類初始化未完成時不能呼叫自身作為watcher,會報一次watcher呼叫空指標。

第二個問題:

 this.getData(NODE_NAME + "/" + works.get(i - 1), this, null);

這個不生效,看方法註釋是當改動和移除節點時,觸發watcher的process,但實驗中並未觸發,在java裡系統的自動刪除並不歸類在這兩個操作之內?

php版本的是正常的,作為遺留問題。為了程式正常執行,更改為 List<String> works = this.getChildren(NODE_NAME, this);   當子節點有變動時執行process方法。  但這樣會導致從眾效應,當叢集伺服器眾多且頻寬延時較大時候會很明顯,leader的狀態變化會引起所有follower的變化,follower之一短連,也會導致整個叢集去響應這個變化。

二、鎖 

加鎖:

1)zk呼叫create()方法建立一個路徑格式為"_locknode_/lock-"的節點,型別為sequence和ephemeral,臨時節點且順序編號

2)在建立的鎖節點上呼叫getChildren()方法,以獲取鎖目錄下最小編號節點,且不設定watch

3)如果步驟2獲得的節點是步驟1建立的節點,那麼客戶端獲得鎖,然後退出操作

4)客戶端在鎖目錄上呼叫exists()方法,設定watch來監視鎖目錄下序號相對自己小的連續臨時節點的狀態

5)監視節點狀態發生變化,則跳到步驟2,繼續後續操作,直到退出鎖競爭。

解鎖:

將加鎖操作步驟1中建立的臨時節點刪除即可。