原創(chuàng) 得物技術(shù)-胡強忠
1. 背景
對于zookeeper隘蝎,大家都比較熟悉,在Kafka巨柒、HBase、Dubbo中都有看到過他的身影柠衍,那zookeeper到底是什么洋满,都能做些什么呢?今天我們一起來了解下珍坊。
2. ZooKeeper簡介
2.1 概述
簡單理解牺勾,ZooKeeper 是分布式應(yīng)用程序的分布式開源協(xié)調(diào)服務(wù),封裝了分布式架構(gòu)中核心和主流的需求和功能阵漏,并提供一系列簡單易用的接口提供給用戶使用驻民。分布式應(yīng)用程序可以根據(jù)zookeeper實現(xiàn)分布式鎖、分布式集群的集中式元數(shù)據(jù)存儲履怯、Master選舉回还、分布式協(xié)調(diào)和通知等。下圖是官網(wǎng)上zookeeper的架構(gòu)簡圖:
從zk的架構(gòu)圖中也可以了解到叹洲,zk本身就是一個分布式的柠硕、高可用的系統(tǒng)。有多個server節(jié)點疹味,其中有一個是leader節(jié)點仅叫,其他的是follower節(jié)點帜篇,他們會從leader節(jié)點中復(fù)制數(shù)據(jù)糙捺。客戶端節(jié)點連接單個zk服務(wù)器笙隙,并維護(hù)一個TCP連接洪灯,通過該連接發(fā)送請求、獲取響應(yīng)以及監(jiān)聽事件并發(fā)送心跳竟痰,如果與服務(wù)器的連接中斷签钩,就會連接到另外的服務(wù)器掏呼。
2.2 技術(shù)特點
zookeeper有以下幾個特點:
集群部署:一般是3~5臺機器組成一個集群,每臺機器都在內(nèi)存保存了zk的全部數(shù)據(jù)铅檩,機器之間互相通信同步數(shù)據(jù)憎夷,客戶端連接任何一臺機器都可以。
順序一致性:所有的寫請求都是有序的昧旨;集群中只有l(wèi)eader機器可以寫拾给,所有機器都可以讀,所有寫請求都會分配一個zk集群全局的唯一遞增編號:zxid兔沃,用來保證各種客戶端發(fā)起的寫請求都是有順序的蒋得。
原子性:要么全部機器成功,要么全部機器都不成功乒疏。
數(shù)據(jù)一致性:無論客戶端連接到哪臺節(jié)點额衙,讀取到的數(shù)據(jù)都是一致的纳猪;leader收到了寫請求之后都會同步給其他機器贱鼻,保證數(shù)據(jù)的強一致,你連接到任何一臺zk機器看到的數(shù)據(jù)都是一致的瓷蛙。
高可用:如果某臺機器宕機械哟,會保證數(shù)據(jù)不丟失疏之。集群中掛掉不超過一半的機器,都能保證集群可用暇咆。比如3臺機器可以掛1臺锋爪,5臺機器可以掛2臺。
實時性:一旦數(shù)據(jù)發(fā)生變更爸业,其他節(jié)點會實時感知到其骄。
高性能:每臺zk機器都在內(nèi)存維護(hù)數(shù)據(jù),所以zk集群絕對是高并發(fā)高性能的扯旷,如果將zk部署在高配置物理機上拯爽,一個3臺機器的zk集群抗下每秒幾萬請求是沒有問題的。
高并發(fā):高性能決定的钧忽,主要是基于純內(nèi)存數(shù)據(jù)結(jié)構(gòu)來處理毯炮,并發(fā)能力是很高的,只有一臺機器進(jìn)行寫耸黑,但是高配置的物理機桃煎,比如16核32G,可以支撐幾萬的寫入QPS大刊。所有機器都可以讀为迈,選用3臺高配機器的話,可以支撐十萬+的QPS『可參考官網(wǎng)「基于3.2版本做的性能壓測【1】」
2.3 技術(shù)本質(zhì)
zookeeper的技術(shù)本質(zhì)決定了搜锰,一些開源項目為什么要使用zk來實現(xiàn)自己系統(tǒng)的高可用。zk是基于zab協(xié)議來實現(xiàn)的分布式一致性的耿战,他的核心就是圖中的Atomic Broadcast蛋叼,通過原子廣播的能力,保持所有服務(wù)器的同步剂陡,來實現(xiàn)了分布式一致性鸦列。
zookeeper算法即zab算法或者叫zab協(xié)議,需注意zab并不是paxos鹏倘,zab的核心是為了實現(xiàn)primary-back systems這種架構(gòu)模式薯嗤,而paxos實際上是叫 state machine replication,就是狀態(tài)復(fù)制機。這里簡單對比下纤泵。
primary-back systems:leader節(jié)點接受寫請求骆姐,先寫到自己的本地機器,然后通過原子協(xié)議或其他的方式捏题,將結(jié)果復(fù)制到其他的系統(tǒng)玻褪。
state machine replication:沒有一個明顯的leader節(jié)點。寫的時候公荧,把接收到的命令記錄下來带射,然后把命令復(fù)制給各個節(jié)點,然后各個節(jié)點再去執(zhí)行命令循狰,應(yīng)用到自己的狀態(tài)機里面窟社。
關(guān)于一致性算法這里不做具體對比,后續(xù)會詳細(xì)說下常見的分布式集群算法绪钥。
2.3.1 ZAB工作流程
ZAB協(xié)議灿里,即ZooKeeper Atomic Broadcas。集群啟動自動選舉一個Leader出來程腹,只有Leader是可以寫的匣吊,F(xiàn)ollower是只能同步數(shù)據(jù)和提供數(shù)據(jù)的讀取,Leader掛了寸潦,F(xiàn)ollower可以繼續(xù)選舉出來Leader色鸳。這里可以看下zab的工作流程:
Leader收到寫請求,就把請求廣播給所有的follower见转,每一個消息廣播的時候命雀,都是按照2PC思想,先是發(fā)起事務(wù)Proposal的廣播池户,就是事務(wù)提議咏雌,在發(fā)起一個事務(wù)proposal之前,leader會分配一個全局唯一遞增的事務(wù)id校焦,zxid赊抖,通過這個可以嚴(yán)格保證順序。每個follower收到一個事務(wù)proposal之后寨典,就立即寫入本地磁盤日志中氛雪,寫入成功之后返回一個ack給leader,然后過半的follower都返回了ack之后耸成,leader會推送commit消息給全部follower报亩,Leader自己也會進(jìn)行commit操作,commit之后,數(shù)據(jù)才會寫入znode井氢,然后這個數(shù)據(jù)可以被讀取到了弦追。
如果突然leader宕機了,會進(jìn)入恢復(fù)模式花竞,重新選舉一個leader劲件,只要過半的機器都承認(rèn)你是leader,就可以選舉出來一個新的leader约急,所以zookeeper很重要的一點是宕機的機器數(shù)量小于一半零远,他就可以正常工作。這里不具體展開詳細(xì)的選舉和消息丟棄的邏輯厌蔽。
從上述的工作過程牵辣,可以看出zookeeper并不是強制性的,leader并不是保證一條數(shù)據(jù)被全部follower都commit了才會讓客戶端讀到奴饮,過程中可能會在不同的follower上讀取到不一致的數(shù)據(jù)纬向,但是最終所有節(jié)點都commit完成后會一致性的。zk官方給自己的定義是:順序一致性戴卜。
從上述的這些技術(shù)特點上罢猪,我們也可以看出,為什么zookeeper只能是小集群部署叉瘩,而且是適合讀多寫少的場景膳帕。想象一下,如果集群中有幾十個節(jié)點薇缅,其中1臺是leader危彩,每次寫請求,都要跟所有節(jié)點同步泳桦,并等過半的機器ack之后才能提交成功汤徽,這個性能肯定是很差的。舉個例子灸撰,如果使用zookeeper做注冊中心谒府,大量的服務(wù)上線拼坎、注冊、心跳的壓力完疫,如果達(dá)到了每秒幾萬甚至上十萬的場景泰鸡,zookeeper的單節(jié)點寫入是扛不住那么大壓力的。如果這是kafka或者其他中間件共用了一個zookeeper集群壳鹤,那基本就都癱瘓了盛龄。
2.3.2 ZooKeeper角色
上述的場景中已經(jīng)提到了leader和follower,既然提到了性能問題芳誓,就額外說下余舶,除了leader和follower,zookeeper還有一個角色是:observer锹淌。
observer節(jié)點是不參與leader選舉的匿值;他也不參與zab協(xié)議同步時,過半follower ack的環(huán)節(jié)赂摆。他只是單存的接收數(shù)據(jù)千扔,同步數(shù)據(jù),提供讀服務(wù)库正。這樣zookeeper集群曲楚,可以通過擴展observer節(jié)點,線性提升高并發(fā)查詢的能力 褥符。
2.4 Znode數(shù)據(jù)模型
如果想使用好zk的話龙誊,必須要了解下他的數(shù)據(jù)模型。雖然zookeeper的實現(xiàn)比較復(fù)雜喷楣,但是數(shù)據(jù)結(jié)構(gòu)還是比較簡單的趟大。如下圖所示,zookeeper的數(shù)據(jù)結(jié)構(gòu)是一個類型文件系統(tǒng)的樹形目錄分層結(jié)構(gòu)铣焊,它是純內(nèi)存保存的逊朽。基于這個目錄結(jié)構(gòu)曲伊,可以根據(jù)自己的需要叽讳,設(shè)計的具體的概念含義。ZooKeeper 的層次模型稱作 Data Tree坟募,Data Tree 的每個節(jié)點叫作 znode岛蚤。如下圖所示:
ZNode在我們應(yīng)用中是主要訪問的實體,有些特點這里提出來懈糯,說一下:
- 【W(wǎng)atches】 監(jiān)聽
zookeeper支持 watch 的概念涤妒。客戶端可以在 znode 上設(shè)置監(jiān)聽赚哗,當(dāng) znode 發(fā)生變化時她紫,watch 將被觸發(fā)并移除硅堆。當(dāng) watch 被觸發(fā)時,客戶端會收到一個數(shù)據(jù)包贿讹,說明 znode 已更改渐逃,這個在分布式系統(tǒng)的協(xié)調(diào)中是很有用的一個功能。
- 【Data Access】 數(shù)據(jù)訪問
存儲在命名空間中每個 znode 的數(shù)據(jù)的讀取和寫入都是原子的围详。讀取操作獲取與 znode 關(guān)聯(lián)的所有數(shù)據(jù),寫入操作會替換所有數(shù)據(jù)祖屏。每個節(jié)點都有一個訪問控制列表 (ACL)助赞,用于限制誰可以做什么。
- 【Ephemeral Nodes】臨時節(jié)點
ZooKeeper 也有臨時節(jié)點的概念袁勺。只要創(chuàng)建 znode 的會話處于活動狀態(tài)雹食,這些 znode 就存在。當(dāng)會話結(jié)束時期丰,znode 被刪除群叶。臨時 znode 不允許有子節(jié)點。
- 持久節(jié)點
zooKeeper除了臨時節(jié)點還有持久節(jié)點钝荡,客戶端斷開連接街立,或者集群宕機,節(jié)點也會一直存在埠通。
- 【Sequence Nodes】 順序節(jié)點
創(chuàng)建 znode 時赎离,可以 在path路徑末尾附加一個單調(diào)遞增的計數(shù)器。這個計數(shù)器對于父 znode 是唯一的端辱,是全局遞增的序號梁剔。
3. ZooKeeper的應(yīng)用
對zookeeper有了一定的了解之后,我們看下他有哪些應(yīng)用場景舞蔽。前面的背景中也提到過荣病,在一些常見的開源項目中,都看到過zk的身影渗柿,那zk在這些開源項目中是如何使用的呢个盆?
3.1 典型的應(yīng)用場景
(1)元數(shù)據(jù)管理:Kafka、Canal朵栖,本身都是分布式架構(gòu)砾省,分布式集群在運行,本身他需要一個地方集中式的存儲和管理分布式集群的核心元數(shù)據(jù)混槐,所以他們都選擇把核心元數(shù)據(jù)放在zookeeper中编兄。
Dubbo:使用zookeeper作為注冊中心、分布式集群的集中式元數(shù)據(jù)存儲
HBase:使用zookeeper做分布式集群的集中式元數(shù)據(jù)存儲
(2)分布式協(xié)調(diào):如果有節(jié)點對zookeeper中的數(shù)據(jù)做了變更声登,然后zookeeper會反過來去通知其他監(jiān)聽這個數(shù)據(jù)的節(jié)點狠鸳,告訴它這個數(shù)據(jù)變更了揣苏。
kafka:通過zookeeper解決controller的競爭問題。kafka有多個broker件舵,多個broker會競爭成為一個controller的角色卸察。如果作為controller的broker掛掉了,此時他在zk里注冊的一個節(jié)點會消失铅祸,其他broker瞬間會被zookeeper反向通知這個事情坑质,繼續(xù)競爭成為新的controller,這個就是非常經(jīng)典的一個分布式協(xié)調(diào)的場景临梗。
(3)Master選舉 -> HA架構(gòu)
Canal:通過zookeeper解決Master選舉問題涡扼,來實現(xiàn)HA架構(gòu)
HDFS:Master選舉實現(xiàn)HA架構(gòu),NameNode HA架構(gòu)盟庞,部署主備兩個NameNode吃沪,只有一個人可以通過zookeeper選舉成為Master,另外一個作為backup什猖。
(4)分布式鎖
一般用于分布式的業(yè)務(wù)系統(tǒng)中票彪,控制并發(fā)寫操作。不過實際使用中不狮,使用zookeeper做分布式鎖的案例比較少降铸,大部分都是使用的redis.
3.2 開源產(chǎn)品使用zk的情況
下圖是有關(guān)Paxos Systems Demystified的論文中,常見的開源產(chǎn)品使用的一致性算法系統(tǒng)摇零,可以看到除了google系的產(chǎn)品用paxos算法或他們自己的chubby服務(wù)外垮耳,開源的系統(tǒng)大部分都使用zookeeper來實現(xiàn)高可用的。
4. 實踐-通過ZooKeeper來實現(xiàn)高可用
了解了zookeeper的一些技術(shù)特性及常見的使用場景后遂黍,考慮一個問題:我們平時在工作中终佛,大部分是使用一些開源的成熟的產(chǎn)品,如果出現(xiàn)部分產(chǎn)品是不能滿足我們的業(yè)務(wù)需求時雾家,我們是需要根據(jù)自己的業(yè)務(wù)場景去改造或重新設(shè)計的铃彰,但一般不會從頭開始,也就是我們說的不會重復(fù)造輪子。自己實現(xiàn)一個產(chǎn)品時芯咧,一般都是分布式集群部署的牙捉,那就要考慮,是否需要一個地方集中式存儲分布式集群的元數(shù)據(jù)敬飒?
是否需要進(jìn)行Master選舉實現(xiàn)HA架構(gòu)邪铲?是否需要進(jìn)行分布式協(xié)調(diào)通知?如果有這些需求无拗,zookeeper作為一個久經(jīng)考驗的產(chǎn)品带到,是可以考慮直接拿來使用的,這大大提高我們的效率以及提升產(chǎn)品的穩(wěn)定性英染。
基于以上對zookeeper技術(shù)特點的了解揽惹,如果使用zookeeper來實現(xiàn)系統(tǒng)的高可用時被饿,一般需要考慮4個問題,或者理解為4個步驟:
- 如何設(shè)計znode的path
- znode的類型如何選擇搪搏?比如是臨時節(jié)點狭握,還是順序節(jié)點?
- znode中存儲什么數(shù)據(jù)疯溺?如何表達(dá)自己的業(yè)務(wù)含義
- 如何設(shè)計watch论颅,客戶端需要關(guān)注什么事件,事件發(fā)生后需要如何處理囱嫩?
下面我們通過兩個案例恃疯,看下zookeeper是如何實現(xiàn)主備切換、集群選舉的挠说。
4.1 主備切換
主備切換在我們?nèi)粘S玫降姆植际较到y(tǒng)很常見澡谭,那我們自己如何通過zookeeper的接口來實現(xiàn)呢愿题?
簡單回顧下主備切換架構(gòu):
主備架構(gòu)的工作流程:
正常階段的話损俭,業(yè)務(wù)數(shù)據(jù)讀寫在主機,數(shù)據(jù)會復(fù)制到備機潘酗。當(dāng)主機故障了杆兵,數(shù)據(jù)沒辦法復(fù)制到備機,原來的備機自動升級為主機仔夺,業(yè)務(wù)請求到新的主機琐脏。原來的主機恢復(fù)后,成為新的備機缸兔,將數(shù)據(jù)從新的主機同步到備機上
4.1.1 實現(xiàn)方式
(1)設(shè)計Path
由于只有2個角色日裙,因此直接設(shè)置兩個 znode 即可,master和slave惰蜜,樣例:
- /com/dewu/order/operate/master
- /com/dewu/order/operate/slave昂拂。
(2)選擇節(jié)點類型
當(dāng) master 節(jié)點掛掉的時候,原來的 slave 升級為 master 節(jié)點抛猖,因此用 ephemeral 類型的 znode格侯。
因為當(dāng)一個節(jié)點成為master時,需要zk創(chuàng)建master節(jié)點财著,一旦這臺主機掛掉了联四,它到zk的連接就斷掉了,這個master節(jié)點會在超時之后撑教,被zk自動刪除朝墩,這樣的話,就知道原來的主機宕機了伟姐,所以選擇使用ephemeral類型的節(jié)點鱼辙。
(3)設(shè)計節(jié)點數(shù)據(jù)
由于 slave 成為 master 后廉嚼,會成為新的復(fù)制源,可能出現(xiàn)數(shù)據(jù)沖突倒戏,因此 slave 成為 master 后怠噪,節(jié)點需要寫入成為 master 的時間,這樣方便修復(fù)沖突數(shù)據(jù)杜跷。還可以寫入slave上最新的事務(wù)id傍念,這里可以根據(jù)自己的業(yè)務(wù)靈活設(shè)計,znode節(jié)點中應(yīng)該寫入什么數(shù)據(jù)葛闷。
(4)設(shè)計 Watch
節(jié)點啟動的時候憋槐,嘗試創(chuàng)建 master znode,創(chuàng)建成功則切換為master淑趾,否則創(chuàng)建 slave znode阳仔,成為 slave,并監(jiān)聽master節(jié)點; 如果 slave 節(jié)點收到 master znode 刪除的事件扣泊,就自己去嘗試創(chuàng)建 master znode近范,創(chuàng)建成功,則自己成為 master延蟹,刪除自己創(chuàng)建的slave znode评矩。
4.1.2 示意代碼
public class Node {
public static final Node INSTANCE = new Node();
private volatile String role = "slave";
private CuratorFramework curatorFramework;
private Node(){
}
public void start(String connectString) throws Exception{
if(connectString == null || connectString.isEmpty()){
throw new Exception("connectString is null or empty");
}
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(connectString).sessionTimeoutMs(5000).connectionTimeoutMs(3000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
String groupNodePath = "/com/dewu/order/operate";
String masterNodePath = groupNodePath + "/master";
String slaveNodePath = groupNodePath + "/slave";
//watch master node
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, groupNodePath, true);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
String childPath = event.getData().getPath();
System.out.println("child removed: " + childPath);
//如果是master節(jié)點刪除了,則自己嘗試變成master
if(masterNodePath.equals(childPath)){
switchMaster(client, masterNodePath, slaveNodePath);
}
} else if(event.getType().equals(PathChildrenCacheEvent.Type.CONNECTION_LOST)) {
System.out.println("connection lost, become slave");
role = "slave";
} else if(event.getType().equals(PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED)) {
System.out.println("connection connected……");
if(!becomeMaster(client, masterNodePath)){
becomeSlave(client, slaveNodePath);
}
}
else{
System.out.println("path changed: " + event.getData().getPath());
}
}
});
client.start();
pathChildrenCache.start();
}
public String getRole(){
return role;
}
private boolean becomeMaster(CuratorFramework client, String masterNodePath){
//try to become master
try {
client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL)
.forPath(masterNodePath, Longs.toByteArray(System.currentTimeMillis()));
System.out.println("succeeded to become master");
role = "master";
return true;
} catch (Exception e) {
System.out.println("failed to become master: " + e.getMessage());
return false;
}
}
private boolean becomeSlave(CuratorFramework client, String slaveNodePath) throws Exception {
//try to become slave
try {
client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL)
.forPath(slaveNodePath, Longs.toByteArray(System.currentTimeMillis()));
System.out.println("succeeded to become slave");
role = "slave";
return true;
} catch (Exception e) {
System.out.println("failed to become slave: " + e.getMessage());
throw e;
}
}
private void switchMaster(CuratorFramework client, String masterNodePath, String slaveNodePath){
if(becomeMaster(client, masterNodePath)){
try {
client.delete().forPath(slaveNodePath);
} catch (Exception e) {
System.out.println("failed to delete slave node when switch master: " + slaveNodePath);
}
}
}
}
參考【代碼樣例】【2】
4.2 實現(xiàn)集群選舉
集群選舉的方式比較多阱飘,主要是根據(jù)自己的業(yè)務(wù)場景斥杜,考慮選舉時的一些具體算法。
4.2.1 最小節(jié)點獲勝
就是在共同的父節(jié)點下創(chuàng)建znode沥匈,誰的編號最小蔗喂,誰是leader。
(1)設(shè)計 Path
集群共用父節(jié)點 parent znode高帖,也就是上圖中的operate缰儿,集群中的每個節(jié)點在 parent 目錄下創(chuàng)建自己的 znode。如上圖中棋恼,假如有5個節(jié)點返弹,編號是從node0000000001~node0000000005。
(2)選擇節(jié)點類型
當(dāng) Leader 節(jié)點掛掉的時候爪飘,持有最小編號 znode 的集群節(jié)點成為新的 Leader义起,因此用ephemeral_sequential 類型 znode。使用ephemeral類型的目的是师崎,leader掛掉的時候默终,節(jié)點能自動刪除,使用sequential類型的目的是,讓這些節(jié)點都是有序的齐蔽,我們選擇最小節(jié)點的時候就比較簡單两疚。
(3)設(shè)計節(jié)點數(shù)據(jù)
可以根據(jù)業(yè)務(wù)需要靈活寫入各種數(shù)據(jù)。
(4)設(shè)計 Watch
- 節(jié)點啟動或者重連后含滴,在 parent 目錄下創(chuàng)建自己的 ephemeral_sequntial znode;
- 創(chuàng)建成功后掃描 parent 目錄下所有 znode诱渤,如果自己的 znode 編號是最小的,則成為 Leader谈况,否則 監(jiān)聽 parent整個目錄;
- 當(dāng) parent 目錄有節(jié)點刪除的時候勺美,首先判斷其是否是 Leader 節(jié)點,然后再看其編號是否正好比自己小1碑韵,如果是則自己成為 Leader赡茸,如果不是繼續(xù) watch。
Curator 的 LeaderLatch祝闻、LeaderSelector 采用這種策略占卧,可以參考【Curator】【3】看下對應(yīng)的代碼 。
4.2.2 搶建唯一節(jié)點
集群共用父節(jié)點 parent znode联喘,也就是operate华蜒,集群中的每個節(jié)點在 parent 目錄下創(chuàng)建自己的 znode。也就是集群中只有一個節(jié)點耸袜,誰創(chuàng)建成功誰就是leader友多。
(1)設(shè)計 Path
集群所有節(jié)點只有一個 leader znode牲平,其實本質(zhì)上就是一個分布式鎖堤框。
(2)選擇 znode 類型
當(dāng) Leader 節(jié)點掛掉的時候,剩余節(jié)點都來創(chuàng)建 leader znode纵柿,看誰能最終搶到 leader znode蜈抓,因此用ephemeral 類型。
(3)設(shè)計節(jié)點數(shù)據(jù)
可以根據(jù)業(yè)務(wù)需要靈活寫入各種數(shù)據(jù)昂儒。
(4)設(shè)計 Watch
- 節(jié)點啟動或者重連后沟使,嘗試創(chuàng)建 leader znode,嘗試失敗則監(jiān)聽 leader znode;
- 當(dāng)收到 leader znode 被刪除的事件通知后渊跋,再次嘗試創(chuàng)建leader znode腊嗡,嘗試成功則成為leader ,失敗則繼續(xù)監(jiān)聽leader znode拾酝。
4.2.3 法官判決
整體實現(xiàn)比較復(fù)雜的一個方案燕少,通過創(chuàng)建節(jié)點,判斷誰是法官節(jié)點蒿囤。法官節(jié)點可以根據(jù)一定的邏輯算法來判斷誰是leader客们,比如看誰的數(shù)據(jù)最新等等。
(1)設(shè)計Path
集群共用父節(jié)點 parent znode,集群中的每個節(jié)點在 parent 目錄下創(chuàng)建自己的 znode底挫。
parent znode:圖中的 operate恒傻,代表一個集群,選舉結(jié)果寫入到operate節(jié)點建邓,比如寫入的內(nèi)容可以是:leader=server6盈厘。
法官 znode:圖中的橙色 znode,最小的 znode扑庞,持有這個 znode 的節(jié)點負(fù)責(zé)選舉算法/規(guī)則罐氨。
例如:實現(xiàn)Redis 存儲集群的選舉栅隐,各個 slave 節(jié)點可以將自己存儲的數(shù)據(jù)最新的 trxId寫入到 znode租悄,然后法官節(jié)點將 trxID 最大的節(jié)點選為新的 Leader泣棋。
成員 znode:圖中的綠色 znode,每個集群節(jié)點對應(yīng)一個澈吨,在選舉期間將選舉需要的信息寫入到自己的 znode修赞。
Leader znode:圖中的紅色 znode柏副,集群里面只有一個割择,由法官選出來锨推。
(2)選擇節(jié)點類型 當(dāng) Leader 節(jié)點掛掉的時候换可,持有最小編號 znode 的集群節(jié)點成為“法官”慨飘,因此用 ephemeral_sequential 類型 znode译荞。
(3)設(shè)計節(jié)點數(shù)據(jù)
可以根據(jù)業(yè)務(wù)需要靈活寫入各種數(shù)據(jù)圈膏,比如寫入當(dāng)前存儲的最新的數(shù)據(jù)對應(yīng)的事務(wù) ID稽坤。
(4)設(shè)計Watch
- 節(jié)點啟動或者重連后尿褪,在 parent 目錄下創(chuàng)建自己的 ephemeral_sequntial znode,并 監(jiān)聽 parent 目錄;
- 當(dāng) parent 目錄有節(jié)點刪除的時候摆马,所有節(jié)點更新自己的 znode 里面和選舉相關(guān)的數(shù)據(jù);
- “法官”節(jié)點讀取所有 znode的數(shù)據(jù)今膊,根據(jù)規(guī)則或者算法選舉新的 Leader伞剑,將選舉結(jié)果寫入parent znode;
- 所有節(jié)點監(jiān)聽 parent znode黎泣,收到變更通知的時候讀取 parent znode 的數(shù)據(jù),判斷自己是否成為 Leader坷澡。
4.2.4 集群選舉模式對比
這里簡單說下計算集群和存儲集群的應(yīng)用場景:
計算集群
無狀態(tài)项郊,不存在存儲集群里所有的數(shù)據(jù)覆蓋問題着降,計算集群只要選出一個leader或主節(jié)點就行蓄喇,對業(yè)務(wù)沒什么影響妆偏。
存儲集群
需要考慮比較復(fù)雜的選舉邏輯楼眷,考慮數(shù)據(jù)一致性罐柳,考慮數(shù)據(jù)盡量不要丟失不要沖突等等,所以需要一個復(fù)雜的選舉邏輯肮蛹。
這里也可以看到伦忠,并不是功能越強大越好,實際上需要考慮不同的應(yīng)用場景赋咽,特性脓匿,基于不同的業(yè)務(wù)要求選擇合適的方案陪毡。一切脫離業(yè)務(wù)場景的方案都是耍流氓爱咬。
5. Etcd
這里再跟大家簡答提一下etcd精拟,主要是zookeeper與etcd的應(yīng)用場景類似蜂绎,在實際的落地選型時也會拿來對比笋鄙,如何選擇。
5.1 etcd概述
除了zookeeper践美,etcd也是最近幾年比較火的一個分布式協(xié)調(diào)框架。
etcd是一種強一致性的分布式鍵值存儲,它提供了一種可靠的方式來存儲需要由分布式系統(tǒng)或機器集群訪問的數(shù)據(jù)杂曲。它優(yōu)雅地處理網(wǎng)絡(luò)分區(qū)期間的leader選舉擎勘,并且可以容忍機器故障。etcd是go寫的一個分布式協(xié)調(diào)組件颖榜,尤其是在云原生的技術(shù)領(lǐng)域里棚饵,目前已經(jīng)成為了云原生和分布式系統(tǒng)的存儲基石。下圖是etcd的請求示意圖:
通常朱转,一個用戶的請求發(fā)送過來蟹地,會經(jīng)由 HTTP Server 轉(zhuǎn)發(fā)給 邏輯層進(jìn)行具體的事務(wù)處理,如果涉及到節(jié)點的修改藤为,則交給 Raft 模塊進(jìn)行狀態(tài)的變更、日志的記錄夺刑,然后再同步給別的 etcd 節(jié)點以確認(rèn)數(shù)據(jù)提交耘斩,最后進(jìn)行數(shù)據(jù)的提交荚虚,再次同步。
5.2 應(yīng)用場景
與zookeeper一樣,有類似的應(yīng)用場景,包括:
- 服務(wù)發(fā)現(xiàn)
- 配置管理
- 分布式協(xié)調(diào)
- Master選舉
- 分布式鎖
- 負(fù)載均衡
比如openstack 使用etcd做配置管理和分布式鎖漏隐,ROOK使用etcd研發(fā)編排引擎脖隶。
5.3 簡單對比
在實際的業(yè)務(wù)場景中具體選擇哪個產(chǎn)品庄敛,要考慮自己的業(yè)務(wù)場景怖亭,考慮具體的特性峭跳,開發(fā)語言等等脊岳。目前zookeeper 是用 java 語言的账嚎,被 Apache 很多項目采用旁振,etcd 是用 go 開發(fā)的丛塌,在云原生的領(lǐng)域使用比較多。不過從實現(xiàn)上看彤敛,etcd提供了更穩(wěn)定的高負(fù)載穩(wěn)定讀寫能力袄秩。
結(jié)尾
綜上所述之剧,zookeeper是一個比較成熟的背稼,經(jīng)過市場驗證的分布式協(xié)調(diào)框架,可以協(xié)助我們快速地解決分布式系統(tǒng)中遇到的一些難題。另從上面的介紹中發(fā)現(xiàn)熄阻,zookeeper的核心是zab,etcd的核心是raft袖扛,那可以思考下砸泛,還有哪些一致性算法?在分布式存儲的架構(gòu)里中又有哪些關(guān)聯(lián)呢呢蛆封?這篇文章不做詳細(xì)介紹了唇礁,后面會針對這塊做個詳細(xì)講解。
附錄
【1】Zookeeper官網(wǎng)文檔
https://zookeeper.apache.org/doc/r3.7.0/zookeeperOver.html#zkPerfRW
【2】代碼樣例
https://github.com/yunhua-lee/zk-demo
【3】Curator
https://github.com/apache/curator
【4】【Consensus in the Cloud: Paxos Systems Demystified】
*文/胡強忠
關(guān)注得物技術(shù)惨篱,每周一三五晚18:30更新技術(shù)干貨
要是覺得文章對你有幫助的話盏筐,歡迎評論轉(zhuǎn)發(fā)點贊~
限時活動:
即日起至6月30日,公開轉(zhuǎn)發(fā)得物技術(shù)任意一篇文章到朋友圈砸讳,就可以在「得物技術(shù)」公眾號后臺回復(fù)「得物」琢融,參與得物文化衫抽獎。