Zookeeper:分佈式程序的基石

1、目錄

一、zookeeper是什麼?html

二、安裝、配置、啓動、監控java

三、javaApi基礎用法node

四、應用場景linux

五、CAP理論/paxos算法 算法

2、zookeeper簡介

官方版:zookeeper是 一個分佈式的,開放源碼的分佈式應用程序協調服務,它包含一個簡單的原語集,分佈式應用程序能夠基於它實現同步服務,配置維護和命名服務等。數據庫

歸納版:zookeeper是「一致、 有頭、數據樹」。apache

一致:數據一致性(核心)。例如,有一個1000臺機器的集羣,我想修改1000臺機器上相同的配置文件,那麼咱們是一臺一臺去改嗎?顯然這種解決辦法並不可靠,因此把配置信息註冊到zookeeper,集羣機器就成爲了觀察者,當配置信息改變的時候,通知集羣全部的機器改變。從而保證了集羣的配置文件一致性。vim

有頭:有頭領。爲何要有頭領?例如,有1000太機器的集羣,客戶端發送請求修改配置文件,那麼誰去處理?有人可能說,我想指定那臺機器,我就指定它ip唄?這樣的話,那怎麼實現負載均衡,手動控制嗎?因此,選擇一個頭領,當客戶端請求來的時候,由leader(頭領)去控制,把任務分配給follower(跟班)。設計模式

數據樹:綁定數據的樹狀結構。簡單理解,它也是一個數據庫,只不過它是存儲於內存的樹狀結構的數據庫。api

3、安裝-配置-啓動-監控

一、物理架構

  

     搭建zookeeper集羣,zookeeper規定是總共集羣的機器是奇數,而集羣正常運行的條件是:存活的機器大於集羣總數的二分之一。

二、安裝配置(注意:集羣的全部機器都須要設置)

  • 下載zookeeper-3.4.10.tar.gz

  • 解壓:tar xvf zookeeper-3.4.10.tar.gz

  • 配置  

    • 切換到/zookeeper/conf目錄:/usr/local/zookeeper-3.4.10/conf (個人路徑)
    • 拷貝:cp zoo_sample.cfg zoo.cfg
    • 修改zoo.cfg:vim zoo.cfg
      • dataDir=/tmp/zookeeper (數據存儲位置,生產環境須要修改,這個是linux的臨時目錄,可能會被刪除)
      • 在配置文件底部添加一下內容:我這個配置了域名,若是沒有配置域名就用ip。
      • server.1=master:2888:3888
      • server.2=slave2:2888:3888
      • server.3=slave3:2888:3888
    • 修改數據文件
      • 切換/tmp目錄:cd /tmp
      • 建立zookeeper目錄:mkdir zookeeper
      • 切換至zookeeper目錄:cd /tmp/zookeeper
      • 建立myid文件:vim myid
      • master上,輸入1保存;slave2,輸入2保存;slave3,輸入3保存。
  • 啓動、觀測

    • 切換至/zookeeper/bin目錄:/usr/local/zookeeper-3.4.10/bin
    • 服務端
      • 啓動:./zkServer.sh start
      • 查看:./zkServer.sh status
      • 中止:./zkServer.sh stop
    • jps(查看狀態)
      • 2289 QuorumPeerMain
      • 2302 Jps
    • 客戶端  .
      • ./zkCli.sh -server master:2181
      • create /tank tankservers
      • create /tank/server1 server1info
      • create /tank/server2 server2info
      • create /tank/server3 server3info
      • ls /tank
      • get /tank
      • set /tank tankserversinfo
      • get /tank
      • delete /tank/server3

4、javaApi基礎用法

/** * .測試zookeeper的基本操做 * Creator:邱勇Aaron * DateTime:2017/6/25 14:51 */
public class ZookeeperBasicOperator { public static String connectString="192.168.0.100,192.168.0.102,192.168.0.103:2181"; private ZooKeeper zk; public ZookeeperBasicOperator(){ this(connectString,2000,null); } public ZookeeperBasicOperator(String connectString,int sessionTimeout,Watcher watcher){ try { zk=new ZooKeeper(connectString,sessionTimeout,watcher); }catch (IOException e){ e.printStackTrace(); } } public static void main(String [] args) throws Exception { connectString="192.168.0.100,192.168.0.102,192.168.0.103:2181"; ZookeeperBasicOperator zkOperator=new ZookeeperBasicOperator(connectString,2000,new MyWatcher()); //建立目錄節點
        zkOperator.create("/testRootPath","testRootData",CreateMode.PERSISTENT); //查看目錄節點
        zkOperator.getData("/testRootPath"); //建立目錄子節點
        zkOperator.create("/testRootPath/testRootChild","testRootChild",CreateMode.PERSISTENT); //查看目錄節點的子節點目錄;
        zkOperator.getChildern("/testRootPath"); //從新設置目錄節點的數據
        zkOperator.setData("/testRootPath","helloTestRootData"); //查看目錄節點的數據
        zkOperator.getData("/testRootPath"); //刪除目錄節點的子數據節點
        zkOperator.delete("/testRootPath/testRootChild",-1); //刪除目錄節點
        zkOperator.delete("/testRootPath",-1); //關閉zookeeper
 zkOperator.closeZookeeper(); } //建立目錄節點、孩子節點
    public void create(String path,String bindingData,CreateMode mode) throws KeeperException, InterruptedException { zk.create(path,bindingData.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,mode); System.out.println(); } //修改目錄節點的數據
    public void setData(String path,String bindingData,int version) throws KeeperException, InterruptedException { zk.setData(path,bindingData.getBytes(),version); } public void setData(String path,String bindingData) throws KeeperException, InterruptedException { this.setData(path,bindingData,-1); } //刪除節點
    public void delete(String path,int version) throws KeeperException, InterruptedException { zk.delete(path,version); } //得到目錄節點數據
    public void getData(String path) throws KeeperException, InterruptedException { System.out.println(new String(zk.getData(path,false,null))); } //得到孩子節點數據
    public void getChildern(String path) throws KeeperException, InterruptedException { System.out.println(zk.getChildren(path,true)); } public void closeZookeeper(){ try { zk.close(); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyWatcher implements Watcher{ @Override public void process(WatchedEvent watchedEvent) { System.out.println("已經觸發了:"+watchedEvent.getType()+"事件!"); } }

5、ZooKeeper 典型的應用場景(轉自:http://www.cnblogs.com/ggjucheng/p/3370359.html)

Zookeeper 從設計模式角度來看,是一個基於觀察者模式設計的分佈式服務管理框架,它負責存儲和管理你們都關心的數據,而後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上註冊的那些觀察者作出相應的反應,從而實現集羣中相似 Master/Slave 管理模式,關於 Zookeeper 的詳細架構等內部細節能夠閱讀 Zookeeper 的源碼

下面詳細介紹這些典型的應用場景,也就是 Zookeeper 到底能幫咱們解決那些問題?下面將給出答案。

統一命名服務(Name Service)

分佈式應用中,一般須要有一套完整的命名規則,既可以產生惟一的名稱又便於人識別和記住,一般狀況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裏你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service 與 JNDI 可以完成的功能是差很少的,它們都是將有層次的目錄結構關聯到必定資源上,可是 Zookeeper 的 Name Service 更加是普遍意義上的關聯,也許你並不須要將名稱關聯到特定資源上,你可能只須要一個不會重複名稱,就像數據庫中產生一個惟一的數字主鍵同樣。

Name Service 已是 Zookeeper 內置的功能,你只要調用 Zookeeper 的 API 就能實現。如調用 create 接口就能夠很容易建立一個目錄節點。
 

配置管理(Configuration Management)

配置的管理在分佈式應用環境中很常見,例如同一個應用系統須要多臺 PC Server 運行,可是它們運行的應用系統的某些配置項是相同的,若是要修改這些相同的配置項,那麼就必須同時修改每臺運行這個應用系統的 PC Server,這樣很是麻煩並且容易出錯。

像這樣的配置信息徹底能夠交給 Zookeeper 來管理,將配置信息保存在 Zookeeper 的某個目錄節點中,而後將全部須要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,而後從 Zookeeper 獲取新的配置信息應用到系統中。

圖 2. 配置管理結構圖

 

集羣管理(Group Membership)

Zookeeper 可以很容易的實現集羣管理的功能,若有多臺 Server 組成一個服務集羣,那麼必需要一個「總管」知道當前集羣中每臺機器的服務狀態,一旦有機器不能提供服務,集羣中其它集羣必須知道,從而作出調整從新分配服務策略。一樣當增長集羣的服務能力時,就會增長一臺或多臺 Server,一樣也必須讓「總管」知道。

Zookeeper 不只可以幫你維護當前的集羣中機器的服務狀態,並且可以幫你選出一個「總管」,讓這個總管來管理集羣,這就是 Zookeeper 的另外一個功能 Leader Election。

它們的實現方式都是在 Zookeeper 上建立一個 EPHEMERAL 類型的目錄節點,而後每一個 Server 在它們建立目錄節點的父目錄節點上調用 getChildren(String path, boolean watch) 方法並設置 watch 爲 true,因爲是 EPHEMERAL 目錄節點,當建立它的 Server 死去,這個目錄節點也隨之被刪除,因此 Children 將會變化,這時 getChildren上的 Watch 將會被調用,因此其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是一樣的原理。

Zookeeper 如何實現 Leader Election,也就是選出一個 Master Server。和前面的同樣每臺 Server 建立一個 EPHEMERAL 目錄節點,不一樣的是它仍是一個 SEQUENTIAL 目錄節點,因此它是個 EPHEMERAL_SEQUENTIAL 目錄節點。之因此它是 EPHEMERAL_SEQUENTIAL 目錄節點,是由於咱們能夠給每臺 Server 編號,咱們能夠選擇當前是最小編號的 Server 爲 Master,假如這個最小編號的 Server 死去,因爲是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,因此當前的節點列表中又出現一個最小編號的節點,咱們就選擇這個節點爲當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。

圖 3. 集羣管理結構圖

void findLeader() throws InterruptedException { byte[] leader = null; try { leader = zk.getData(root + "/leader", true, null); } catch (Exception e) { logger.error(e); } if (leader != null) { following(); } else { String newLeader = null; try { byte[] localhost = InetAddress.getLocalHost().getAddress(); newLeader = zk.create(root + "/leader", localhost, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } catch (Exception e) { logger.error(e); } if (newLeader != null) { leading(); } else { mutex.wait(); } } } 

共享鎖(Locks)

共享鎖在同一個進程中很容易實現,可是在跨進程或者在不一樣 Server 之間就很差實現了。Zookeeper 卻很容易實現這個功能,實現方式也是須要得到鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,而後調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是否是就是本身建立的目錄節點,若是正是本身建立的,那麼它就得到了這個鎖,若是不是那麼它就調用exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到本身建立的節點是列表中最小編號的目錄節點,從而得到鎖,釋放鎖很簡單,只要刪除前面它本身所建立的目錄節點就好了。

圖 4. Zookeeper 實現 Locks 的流程圖
 

同步鎖思路:

加鎖: ZooKeeper 將按照以下方式實現加鎖的操做: 1 ) ZooKeeper 調用 create ()方法來建立一個路徑格式爲「 _locknode_/lock- 」的節點,此節點類型爲sequence (連續)和 ephemeral (臨時)。也就是說,建立的節點爲臨時節點,而且全部的節點連續編號,即「 lock-i 」的格式。 2 )在建立的鎖節點上調用 getChildren ()方法,來獲取鎖目錄下的最小編號節點,而且不設置 watch 。 3 )步驟 2 中獲取的節點剛好是步驟 1 中客戶端建立的節點,那麼此客戶端得到此種類型的鎖,而後退出操做。 4 )客戶端在鎖目錄上調用 exists ()方法,而且設置 watch 來監視鎖目錄下比本身小一個的連續臨時節點的狀態。 5 )若是監視節點狀態發生變化,則跳轉到第 2 步,繼續進行後續的操做,直到退出鎖競爭。 解鎖: ZooKeeper 解鎖操做很是簡單,客戶端只須要將加鎖操做步驟 1 中建立的臨時節點刪除便可。複製代碼
同步鎖代碼
void getLock() throws KeeperException, InterruptedException{ List<String> list = zk.getChildren(root, false); String[] nodes = list.toArray(new String[list.size()]); Arrays.sort(nodes); if(myZnode.equals(root+"/"+nodes[0])){ doAction(); } else{ waitForLock(nodes[0]); } } void waitForLock(String lower) throws InterruptedException, KeeperException { Stat stat = zk.exists(root + "/" + lower,true); if(stat != null){ mutex.wait(); } else{ getLock(); } } 
複製代碼

隊列管理

Zookeeper 能夠處理兩種類型的隊列:

  1. 當一個隊列的成員都聚齊時,這個隊列纔可用,不然一直等待全部成員到達,這種是同步隊列。
  2. 隊列按照 FIFO 方式進行入隊和出隊操做,例如實現生產者和消費者模型。

同步隊列用 Zookeeper 實現的實現思路以下:

建立一個父目錄 /synchronizing,每一個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,而後每一個成員都加入這個隊列,加入隊列的方式就是建立 /synchronizing/member_i 的臨時目錄節點,而後每一個成員獲取 / synchronizing 目錄的全部目錄節點,也就是 member_i。判斷 i 的值是否已是成員的個數,若是小於成員個數等待 /synchronizing/start 的出現,若是已經相等就建立 /synchronizing/start。

用下面的流程圖更容易理解:

圖 5. 同步隊列流程圖
 

同步隊列

 void addQueue() throws KeeperException, InterruptedException{ zk.exists(root + "/start",true); zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); synchronized (mutex) { List<String> list = zk.getChildren(root, false); if (list.size() < size) { mutex.wait(); } else { zk.create(root + "/start", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } }

當隊列沒盡是進入wait(),而後會一直等待Watch的通知,Watch的代碼以下:

public void process(WatchedEvent event) { if(event.getPath().equals(root + "/start") && event.getType() == Event.EventType.NodeCreated){ System.out.println("獲得通知"); super.process(event); doAction(); } } 

FIFO 隊列用 Zookeeper 實現思路以下:

實現的思路也很是簡單,就是在特定的目錄下建立 SEQUENTIAL 類型的子目錄 /queue_i,這樣就能保證全部成員加入隊列時都是有編號的,出隊列時經過 getChildren( ) 方法能夠返回當前全部的隊列中的元素,而後消費其中最小的一個,這樣就能保證 FIFO。

生產者代碼

 boolean produce(int i) throws KeeperException, InterruptedException{ ByteBuffer b = ByteBuffer.allocate(4); byte[] value; b.putInt(i); value = b.array(); zk.create(root + "/element", value, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); return true; }
複製代碼
消費者代碼
int consume() throws KeeperException, InterruptedException{ int retvalue = -1; Stat stat = null; while (true) { synchronized (mutex) { List<String> list = zk.getChildren(root, true); if (list.size() == 0) { mutex.wait(); } else { Integer min = new Integer(list.get(0).substring(7)); for(String s : list){ Integer tempValue = new Integer(s.substring(7)); if(tempValue < min) min = tempValue; } byte[] b = zk.getData(root + "/element" + min,false, stat); zk.delete(root + "/element" + min, 0); ByteBuffer buffer = ByteBuffer.wrap(b); retvalue = buffer.getInt(); return retvalue; } } } } 
6、CAP理論與Paxos算法

CAP理論:

一致性(Consistency):在分佈式系統中的全部數據備份,在同一時刻一樣的值。(等同於全部節點訪問同一份最新的數據副本)
可用性(Availability):在集羣中一個節點出現故障,集羣總體是否還能響應客戶端的讀寫請求。(對數據更新具有高可用)
分區容錯性(Partition Tolerance): 以實際效果而言,分區至關於對通訊的時限要求。系統若是不能在時限內達成數據一致性,就意味着發生了分區的狀況,必須就當前操做在C和A之間作出選擇。
總體總結來講,zookeeper集羣,只能知足三個條件的其中兩個,例如保證一致性與可用性,就不能保證分區容錯性,其餘同理。
 

Paxos算法(選舉算法):詳情參考http://www.cnblogs.com/yuyijq/p/4116365.html

新集羣啓動時候的選舉過程,啓動的時候就是根據zoo.cfg裏的配置,向各個節點廣播投票,通常都是選投本身。而後收到投票後就會進行進行判斷。若是某個節點收到的投票數超過一半,那麼它就是leader了。 

瞭解了這個過程,咱們來看看另一個問題:

一個集羣有3臺機器,掛了一臺後的影響是什麼?掛了兩臺呢? 

掛了一臺:掛了一臺後就是收不到其中一臺的投票,可是有兩臺能夠參與投票,按照上面的邏輯,它們開始都投給本身,後來按照選舉的原則,兩我的都投票給其中一個,那麼就有一個節點得到的票等於2,2 > (3/2)=1 的,超過了半數,這個時候是能選出leader的。

掛了兩臺: 掛了兩臺後,怎麼弄也只能得到一張票, 1 不大於 (3/2)=1的,這樣就沒法選出一個leader了。

7、版權聲明

  做者:邱勇Aaron

  出處:http://www.cnblogs.com/qiuyong/

  您的支持是對博主深刻思考總結的最大鼓勵。

  本文版權歸做者全部,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,尊重做者的勞動成果。

  參考:馬士兵zookeeper、zookeeper官方文檔

     選舉算法:http://www.cnblogs.com/yuyijq/p/4116365.html

     應用場景:http://www.cnblogs.com/ggjucheng/p/3370359.html

相關文章
相關標籤/搜索