分佈式設計與開發(三)------高一致性服務ZooKeeper

分佈式環境中大多數服務是容許部分失敗,也容許數據不一致,但有些最基礎的服務是須要高可靠性,高一致性的,這些服務是其餘分佈式服務運轉的基礎,好比naming service、分佈式lock等,這些分佈式的基礎服務有如下要求:html

  • 高可用性
  • 高一致性
  • 高性能

對於這種有些挑戰CAP原則 的服務該如何設計,是一個挑戰,也是一個不錯的研究課題,Apache的ZooKeeper也許給了咱們一個不錯的答案。ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務, 它暴露了一個簡單的原語集,分佈式應用程序能夠基於它實現同步服務,配置維護和命名服務等。關於ZooKeeper更多信息能夠參見 官方文檔java

ZooKeeper的基本使用node

搭一個分佈式的ZooKeeper環境比較簡單,基本步驟以下:算法

1)在各服務器安裝 ZooKeepershell

下載ZooKeeper後在各服務器上進行解壓便可apache

tar -xzf zookeeper-3.2.2.tar.gzapi

2)配置集羣環境服務器

分別各服務器的zookeeper安裝目錄下建立名爲zoo.cfg的配置文件,內容填寫以下:數據結構

  1. # The number of milliseconds of each tick  
  2. tickTime=2000  
  3. # The number of ticks that the initial  
  4. # synchronization phase can take  
  5. initLimit=10  
  6. # The number of ticks that can pass between  
  7. # sending a request and getting an acknowledgement  
  8. syncLimit=5  
  9. # the directory where the snapshot is stored.  
  10. dataDir=/home/admin/zookeeper-3.2.2/data  
  11. # the port at which the clients will connect  
  12. clientPort=2181  
  13. server.1=zoo1:2888:3888  
  14. server.2=zoo2:2888:3888  
 

其中zoo1和zoo2分別對應集羣中各服務器的機器名或ip,server.1和server.2中1和2分別對應各服務器的zookeeper id,id的設置方法爲在dataDir配置的目錄下建立名爲myid的文件,並把id做爲其文件內容便可,在本例中就分爲設置爲1和2。其餘配置具體含義可見官方文檔。框架

3)啓動集羣環境

分別在各服務器下運行zookeeper啓動腳本

/home/admin/zookeeper-3.2.2/bin/zkServer.sh start

4)應用zookeeper

應用zookeeper能夠在是shell中執行命令,也能夠在java或c中調用程序接口。

在shell中執行命令,可運行如下命令:

bin/zkCli.sh -server 10.20.147.35:2181

其中 10.20.147.35爲集羣中任一臺機器的ip或機器名。執行後可進入zookeeper的操做面板,具體如何操做可見官方文檔

在java中經過調用程序接口來應用zookeeper較爲複雜一點,須要瞭解watch和callback等概念,不過試驗最簡單的CURD倒不須要這些,只須要使用ZooKeeper這個類便可,具體測試代碼以下:

  1. public static void main(String[] args) {  
  2.     try {  
  3.         ZooKeeper zk = new ZooKeeper("10.20.147.35:2181"30000null);  
  4.         String name = zk.create("/company""alibaba".getBytes(),  
  5.                 Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);  
  6.         Stat stat = new Stat();  
  7.         System.out.println(new String(zk.getData(name, null, stat)));  
  8.         zk.setData(name, "taobao".getBytes(), stat.getVersion(), nullnull);  
  9.         System.out.println(new String(zk.getData(name, null, stat)));  
  10.         stat = zk.exists(name, null);  
  11.         zk.delete(name, stat.getVersion(), nullnull);  
  12.         System.out.println(new String(zk.getData(name, null, stat)));  
  13.     } catch (Exception e) {  
  14.         e.printStackTrace();  
  15.     }  
  16. }  
 

以上代碼比較簡單,查看一下zooKeeper的api doc就知道如何使用了

ZooKeeper的實現機理

ZooKeeper的實現機理是我看過的開源框架中最複雜的,它的解決是分佈式環境中的一致性問題,這個場景也決定了其實現的複雜性。看了兩三天的源碼仍是有些摸不着頭腦,有些超出了個人能力,不過經過看文檔和其餘高人寫的文章大體清楚它的原理和基本結構。

1)ZooKeeper的基本原理

ZooKeeper是以Fast Paxos算法爲基礎的,在前一篇 blog 中大體介紹了一下paxos,而沒有提到的是paxos存在活鎖的問題,也就是當有多個 proposer交錯提交時,有可能互相排斥致使沒有一個proposer能提交成功,而Fast Paxos做了一些優化,經過選舉產生一個leader,只有leader才能提交propose,具體算法可見Fast Paxos 。所以,要想弄得ZooKeeper首先得對Fast Paxos有所瞭解。

2)ZooKeeper的基本運轉流程

ZooKeeper主要存在如下兩個流程:

  • 選舉Leader
  • 同步數據

選舉Leader過程當中算法有不少,但要達到的選舉標準是一致的:

  • Leader要具備最高的zxid 
  • 集羣中大多數的機器獲得響應並follow選出的Leader

同步數據這個流程是ZooKeeper的精髓所在,而且就是Fast Paxos算法的具體實現。一個牛人畫了一個ZooKeeper數據流動圖,比較直觀地描述了ZooKeeper是如何同步數據的。

以上兩個核心流程我暫時還不能悟透其中的精髓,這也和我尚未徹底理解Fast Paxos算法有關,有待後續深刻學習

ZooKeeper的應用領域

Tim在blog中提到了Paxos所能應用的幾個主要場景,包括database replication、naming service、config配置管理、access control list等等,這也是ZooKeeper能夠應用的幾個主要場景。此外, ZooKeeper官方文檔中提到了幾個更爲基礎的分佈式應用,這也算是ZooKeeper的妙用吧

1)分佈式Barrier

Barrier是一種控制和協調多個任務觸發次序的機制,簡單說來就是搞個閘門把欲執行的任務給攔住,等全部任務都處於能夠執行的狀態時,才放開閘門。它的機理能夠見下圖所示:

在單機上JDK提供了CyclicBarrier這個類來實現這個機制,但在分佈式環境中JDK就無能爲力了。在分佈式裏實現Barrer須要高一致性作保障,所以 ZooKeeper能夠派上用場,所採起的方案就是用一個Node做爲Barrer的實體,須要被Barrer的任務經過調用exists()檢測這個Node的存在,當須要打開Barrier的時候,刪掉這個Node,ZooKeeper的watch機制會通知到各個任務能夠開始執行。

2) 分佈式 Queue

與 Barrier相似 分佈式環境中 實現Queue也須要高一致性作保障, ZooKeeper提供了一個種簡單的方式,ZooKeeper經過一個Node來維護Queue的實體,用其children來存儲Queue的內容,而且 ZooKeeper的create方法中提供了順序遞增的模式,會自動地在name後面加上一個遞增的數字來插入新元素。能夠用其 children來構建一個queue的數據結構,offer的時候使用create,take的時候按照children的順序刪除第一個便可。 ZooKeeper保障了各個server上數據是一致的,所以也就實現了一個 分佈式 Queue。take和offer的實例代碼以下所示:

  1. /** 
  2.  * Removes the head of the queue and returns it, blocks until it succeeds. 
  3.  * @return The former head of the queue 
  4.  * @throws NoSuchElementException 
  5.  * @throws KeeperException 
  6.  * @throws InterruptedException 
  7.  */  
  8. public byte[] take() throws KeeperException, InterruptedException {  
  9.     TreeMap<Long,String> orderedChildren;  
  10.     // Same as for element.  Should refactor this.  
  11.     while(true){  
  12.         LatchChildWatcher childWatcher = new LatchChildWatcher();  
  13.         try{  
  14.             orderedChildren = orderedChildren(childWatcher);  
  15.         }catch(KeeperException.NoNodeException e){  
  16.             zookeeper.create(dir, new byte[0], acl, CreateMode.PERSISTENT);  
  17.             continue;  
  18.         }  
  19.         if(orderedChildren.size() == 0){  
  20.             childWatcher.await();  
  21.             continue;  
  22.         }  
  23.         for(String headNode : orderedChildren.values()){  
  24.             String path = dir +"/"+headNode;  
  25.             try{  
  26.                 byte[] data = zookeeper.getData(path, falsenull);  
  27.                 zookeeper.delete(path, -1);  
  28.                 return data;  
  29.             }catch(KeeperException.NoNodeException e){  
  30.                 // Another client deleted the node first.  
  31.             }  
  32.         }  
  33.     }  
  34. }  
  35. /** 
  36.  * Inserts data into queue. 
  37.  * @param data 
  38.  * @return true if data was successfully added 
  39.  */  
  40. public boolean offer(byte[] data) throws KeeperException, InterruptedException{  
  41.     for(;;){  
  42.         try{  
  43.             zookeeper.create(dir+"/"+prefix, data, acl, CreateMode.PERSISTENT_SEQUENTIAL);  
  44.             return true;  
  45.         }catch(KeeperException.NoNodeException e){  
  46.             zookeeper.create(dir, new byte[0], acl, CreateMode.PERSISTENT);  
  47.         }  
  48.     }  
  49. }  

3)分佈式lock

利用 ZooKeeper實現 分佈式lock,主要是經過一個Node來表明一個Lock,當一個client去拿鎖的時候,會在這個Node下建立一個自增序列的child,而後經過getChildren()方式來check建立的child是否是最靠前的,若是是則拿到鎖,不然就調用exist()來check第二靠前的child,並加上watch來監視。當拿到鎖的child執行完後歸還鎖,歸還鎖僅僅須要刪除本身建立的child,這時watch機制會通知到全部沒有拿到鎖的client,這些child就會根據前面所講的拿鎖規則來競爭鎖。

相關文章
相關標籤/搜索