深入淺出Zookeeper

能找到的一些zookeeper的資料一上來不是扯一通paxos算法就是一大坨一大坨的代碼。很多人對zookeeper更多的是

聽過,所以這一篇文章就嘗試用盡可能用精簡的語言科普zookeeper

如果想學習Java工程化、高性能及分佈式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群裡有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家。

zookeeper是什麼

網上的定義:zookeeper作為一個開源的分佈式應用協調系統,作為一個正常人我看完這句話之後是——懵逼。理解一個工具最好的辦法是問它解決什麼問題的,舉個例子:

微服務中每個服務是可以被單獨部署的,為了便於Client調用,所有服務都必須在註冊中心完成註冊。這樣Client就可以通過查詢註冊中心獲取有哪些服務可以用(包括服務的調用地址、說明、版本之類的元數據信息)。

無論用什麼方式設計註冊中心最後我們都要解決一個問題——如何存放服務元數據。

  • 直接JSON序列化後存放在註冊中心的硬盤上是最簡單的,但是我們的註冊中心不應該是單點,它掛了整個系統也就無法正常工作了。所以我們必須集中存儲(這樣的設計可以讓註冊中心去中心,變成無狀態的服務更容易實現高可用)。
  • 集中存儲最好的辦法是數據庫。比如存放在Mysql或者MongoDB中,如果用Mysql需要使用主從來提高Mysql的可用性;如果是MongoDB我們也需要通過“集群”來提高MongoDB的可用性。這兩種技術都增加了系統複雜度——畢竟我只是想存放一些JSON數據而已。

Zookeeper是解決這個問題的一個簡單方案,它沒有提供像MongoDB的一樣的查詢、排序功能只提供的簡單的數據讀寫。它的數據結構類似於文件系統,使用API也非常類似讀寫操作系統的文件系統。比如我們的服務元數據可以設計成這樣

app├── app1│ └── metadata└── app2└── metadata

服務註冊的時候都會在/app節點下生成以自己服務名稱命名的節點(比如 app1)然後把自己的

元數據寫入到一個名稱為metadata的“數據節點”。

查詢服務的時候只需要獲取/app下的所有子節點,需要獲取某個服務元數據的時候則直接查詢對應目錄下的metadata節點。

ZooKeeper zooKeeper = //實例化ZooKeeper
//查詢服務
List<string> appNames = zooKeeper.getChildren("/app", false);
for (String appName : appNames) {
String meta = "/app/" + appName + "/meta";
if (zooKeeper.exists(meta, false) != null) {
//查詢服務元數據
byte metaBytes[] = zooKeeper.getData(meta, false, null);
//JSON反序列化
AppMetadata metaJava = mapper.readValue(metaBytes, AppMetadata.class);
}
}
/<string>

zookeeper的兩把刷子

Zookeeper的一個重要特性是提供了去中心化的數據一致性,在一個Zookeeper集群中我們向任何一臺服務器寫入數據都會被“同步”到其他服務器上。實現這樣的特性必須有兩把刷子——選舉算法和分佈式事務

選舉算法

很多人會把zookeeper和paxos算法聯繫在一起,非常負責任的說——它們沒有半毛錢的關係。

zookeeper的算法非常簡單,比如有3臺服務器,(server1的myid=1,server2的myid=2,server3的myid=3)

  • 啟動的時候服務器都會向集群中其他服務器發送選舉信息(zxid,myid)(相當於選自己做leader)。zxid(ZooKeeper Transaction Id)是最後一次寫入的節點數據的事務編號(越大說明數據越新),myid是配置的時候分配的一個編號。比如server1發送的是(0, 1),server2發送(0, 2) 、server3發送(1, 3)
  • 收到選舉信息的服務器會做一次比較:1).zxid比較大的一個作為leader(選擇最新的數據)2). 如果zxid相同(數據同步)則選擇myid比較大的作為leader。3). 把自己選中的leader再次發送出去。比如server1收到server3的信息後選擇server3作為leader,server2也選擇server3作為leader。二者選擇leader之後都會再次發送(1, 3)。
  • 當一臺服務器收到(n/2+1)個選舉信息的時候就認為leader已經選擇成功(n是集群中服務器的數據),停止發送選舉信息,進入follower狀態。比如3臺服務器有2臺服務器選擇server3作為leader那麼選舉就成功。

在系統運行過程中如果leader死掉了,所有的follower會重新按照上面的算法選舉出新的leader。如果你有過網絡相關的經驗不難發現這個選舉算法其實是OSPF的DB、BDR選舉算法。

分佈式事務

Zookeeper實現的分佈式事務是二兩階段提交算法。Client可以向集群中任何一臺服務器發送“寫入數據”請求,該請求會被轉發給leader,leader會通過兩階段提交協議來保證所有的follower都寫入成功。

  • leader發送寫入數據命令給所有的follower
  • 所有的follower寫入數據,返回leader ack確認
  • leader在收到半數的follower的ack之後向follower廣播commit數據包


zookeeper的用途

用作註冊中心只能算zookeeper的一個“不誤正業”的用途。除此之外它還可以用來實現通知/協調。

在使用zookeeper client的時候你可能已經注意到了,無論是getData還是setData你都可以傳遞一個Watch對象,這個對象用來監視某個節點。當節點發送變化的時候這個Watch對象會被調用。通過這個機制我們可以實現分佈式系統中的通知/協調,比如當某個模塊已經完成了任務就修改節點,另一個模塊就會感知到這個變化,這個動作相當於“任務推送”。(和MQ有異曲同工之妙,區別是zookeeper是一個去中心的分佈式系統)

除了上面的用途之外zookeeper還可以用來實現心跳(比如hadoop的namenode ha)。無論哪種應用基本上都是利用zookeeper提供的數據一致性Watch這兩個特性。

歡迎工作一到八年的Java工程師朋友們加入Java高級交流:854630135

本群提供免費的學習指導 架構資料 以及免費的解答

不懂得問題都可以在本群提出來 之後還會有直播平臺和講師直接交流噢


分享到:


相關文章: