zookeeper技術(shù)內(nèi)幕

【簡(jiǎn)介】zookeeper是為分布式應(yīng)用所設(shè)計(jì)的高可用、高性能且一致的開源協(xié)調(diào)服務(wù),是大數(shù)據(jù)分布式集群的基礎(chǔ)組件,為集群提供分布式鎖服務(wù)。同時(shí),用戶可以在分布式鎖的基礎(chǔ)上開發(fā)其他功能,例如配置維護(hù)、分布式通知/協(xié)調(diào)、組服務(wù)、分布式消息隊(duì)列等。

一. 典型應(yīng)用場(chǎng)景

ZooKeeper是一個(gè)高可用的分布式數(shù)據(jù)管理與協(xié)調(diào)框架,基于對(duì)ZAB算法的實(shí)現(xiàn),該框架能夠很好地保證分布式環(huán)境中數(shù)據(jù)的一致性。

1.1 數(shù)據(jù)發(fā)布/訂閱

數(shù)據(jù)發(fā)布/訂閱系統(tǒng),即所謂的配置中心,發(fā)布者將數(shù)據(jù)發(fā)布到ZooKeeper的一個(gè)或多個(gè)節(jié)點(diǎn)上,供訂閱者進(jìn)行數(shù)據(jù)訂閱,進(jìn)而達(dá)到動(dòng)態(tài)獲取數(shù)據(jù)的目的,實(shí)現(xiàn)配置信息的集中式管理和數(shù)據(jù)的動(dòng)態(tài)更新。
發(fā)布/訂閱系統(tǒng)一般有兩種設(shè)計(jì)模式,分別是推(Push)模式和拉(Pull)模式。在推模式中,服務(wù)端主動(dòng)將數(shù)據(jù)更新發(fā)送給所有訂閱的客戶端;而拉模式則是有客戶端主動(dòng)發(fā)起請(qǐng)求來(lái)獲取最新數(shù)據(jù),通??蛻舳硕疾捎枚〞r(shí)進(jìn)行輪詢拉取的方式。而ZooKeeper采用的是推拉相結(jié)合的方式:客戶端向服務(wù)端注冊(cè)自己需要關(guān)注的節(jié)點(diǎn),一旦該節(jié)點(diǎn)的數(shù)據(jù)發(fā)生變更,服務(wù)端就會(huì)向相應(yīng)的客戶端發(fā)送Watcher事件通知,客戶端接收到這個(gè)消息通知后,需要主動(dòng)到服務(wù)端獲取最新的數(shù)據(jù)。

1.2 命名服務(wù)

通過調(diào)用ZooKeeper節(jié)點(diǎn)創(chuàng)建的API接口可以創(chuàng)建一個(gè)順序節(jié)點(diǎn),并且在API返回值中會(huì)返回這個(gè)節(jié)點(diǎn)的完整名字。在ZooKeeper中,每一個(gè)數(shù)據(jù)節(jié)點(diǎn)都能維護(hù)一份子節(jié)點(diǎn)的順序序列,當(dāng)客戶端對(duì)其創(chuàng)建一個(gè)順序子節(jié)點(diǎn)的時(shí)候,ZooKeeper會(huì)自動(dòng)以后綴的形式在其子節(jié)點(diǎn)上添加一個(gè)序號(hào)。

全局唯一ID生成的節(jié)點(diǎn)示意圖.PNG

1.3 分布式協(xié)調(diào)/通知

ZooKeeper的Watcher注冊(cè)與異步通知機(jī)制,能夠很好地實(shí)現(xiàn)分布式環(huán)境下不同機(jī)器,甚至是不同系統(tǒng)之間的協(xié)調(diào)與通知,從而實(shí)現(xiàn)對(duì)數(shù)據(jù)變更的實(shí)時(shí)處理。基于ZooKeeper實(shí)現(xiàn)分布式協(xié)調(diào)與通知功能,通常的做法是不同的客戶端都對(duì)ZooKeeper上同一個(gè)數(shù)據(jù)節(jié)點(diǎn)進(jìn)行Watcher注冊(cè),監(jiān)聽數(shù)據(jù)節(jié)點(diǎn)的變化(包括數(shù)據(jù)節(jié)點(diǎn)本身及其子節(jié)點(diǎn)),如果數(shù)據(jù)節(jié)點(diǎn)發(fā)生變化,所有訂閱的客戶端都能夠接收到相應(yīng)的Watcher通知,并做出相應(yīng)的處理。

1.4 Master選舉

ZooKeeper的強(qiáng)一致性能夠很好地保證在分布式高并發(fā)情況下節(jié)點(diǎn)的創(chuàng)建一定能夠保證全局唯一性,即ZooKeeper將會(huì)保證客戶端無(wú)法重復(fù)創(chuàng)建一個(gè)已經(jīng)存在的數(shù)據(jù)節(jié)點(diǎn)。也就是說,如果同時(shí)有多個(gè)客戶端請(qǐng)求創(chuàng)建同一個(gè)節(jié)點(diǎn),那么最終一定只有一個(gè)客戶端請(qǐng)求能夠創(chuàng)建成功,那么這個(gè)客戶端所在的機(jī)器就成為了Master。同時(shí),其他沒有在ZooKeeper上成功創(chuàng)建節(jié)點(diǎn)的客戶端,都會(huì)注冊(cè)一個(gè)子節(jié)點(diǎn)變更的Watcher,用于監(jiān)控當(dāng)前Master機(jī)器是否存活,一旦發(fā)現(xiàn)當(dāng)前的Master掛了,其余的客戶端將會(huì)重新進(jìn)行Master選舉。

1.5 分布式鎖

分布式鎖:控制分布式系統(tǒng)之間同步訪問共享資源的一種方式,如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問這些資源的時(shí)候,往往需要通過一些互斥手段來(lái)防止彼此之間的干擾,以保證一致性。
排他鎖:所有客戶端都會(huì)試圖通過調(diào)用create接口創(chuàng)建臨時(shí)子節(jié)點(diǎn),ZooKeeper會(huì)保證所有的客戶端中最終只有一個(gè)客戶端能夠創(chuàng)建成功,那么就可以認(rèn)為該客戶端獲取了鎖。同時(shí),所有沒有獲取到鎖的客戶端需要注冊(cè)一個(gè)子節(jié)點(diǎn)變更的Watcher監(jiān)聽,以便實(shí)時(shí)監(jiān)聽到lock節(jié)點(diǎn)的變更情況。當(dāng)獲取鎖的客戶端機(jī)器宕機(jī)或政策執(zhí)行完業(yè)務(wù)邏輯后,客戶端會(huì)主動(dòng)刪除自己創(chuàng)建的臨時(shí)節(jié)點(diǎn)。其他客戶端再次重新發(fā)起獲取鎖。
共享鎖:所有客戶端在需要獲取共享鎖時(shí)創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn)(節(jié)點(diǎn)名區(qū)分讀寫請(qǐng)求)。根據(jù)共享鎖的定義,不同的事務(wù)都可以同時(shí)對(duì)同一個(gè)數(shù)據(jù)對(duì)象進(jìn)行讀取操作,而更新操作必須在當(dāng)前沒有任何讀取操作的情況下進(jìn)行?;谝陨显瓌t,則可以通過ZooKeeper的節(jié)點(diǎn)來(lái)確定分布式讀寫順序。

共享鎖節(jié)點(diǎn)示意圖.PNG

1.6 大型分布式系統(tǒng)常見應(yīng)用場(chǎng)景

HDFS-NameNode: Active/Standby選舉 < /hadoop-ha>;
YARN-ResourceManager: Active/Standby選舉< /yarn-leader-election>;
Hive-HiveServer2: HA高可用配置< /hiveserver2>;
HBase:Master Active/Standby選舉,基礎(chǔ)元數(shù)據(jù)管理 < /hbase-unsecure>;
kafka: 元數(shù)據(jù)管理,消費(fèi)索引維護(hù) < /brokers, /consumers>;
...

二. 數(shù)據(jù)模型

ZooKeeper采用樹形層次結(jié)構(gòu),樹中的每個(gè)節(jié)點(diǎn)被稱為—Znode:

ZK數(shù)據(jù)模型.PNG

Znode的節(jié)點(diǎn)路徑標(biāo)識(shí)方式和Unix文件系統(tǒng)路徑非常相似,都是由一系列使用斜杠(/)進(jìn)行分割的路徑表示,開發(fā)人員可以向這個(gè)節(jié)點(diǎn)中寫入數(shù)據(jù),也可以在節(jié)點(diǎn)下面創(chuàng)建子節(jié)點(diǎn)。

2.1 Znode結(jié)構(gòu)

Znode包含文件和目錄兩種特性,既像文件一樣維護(hù)著數(shù)據(jù)、元信息、ACL、時(shí)間戳等數(shù)據(jù)結(jié)構(gòu),又像目錄一樣可以作為路徑標(biāo)識(shí)的一部分。每個(gè)Znode由3部分組成:
① stat:維護(hù)Znode的狀態(tài)信息;
② data:該Znode關(guān)聯(lián)的數(shù)據(jù);
③ acl:訪問控制列表,用于控制Znode的訪問權(quán)限(讀寫、創(chuàng)建、刪除等);

stat存儲(chǔ)的狀態(tài)信息:
cZxid:創(chuàng)建Znode的事務(wù)ID;
ctime:Znode的創(chuàng)建時(shí)間;
mZxid:最后一次修改Znode的事務(wù)ID;
mtime:最近一次修改Znode的時(shí)間;
pZxid: Znode添加或刪除子節(jié)點(diǎn)操作的事務(wù)ID;
cversion:Znode子節(jié)點(diǎn)更改次數(shù);
dataVersion:Znode的數(shù)據(jù)更改次數(shù);
aclVersion:Znode ACL的更改次數(shù);
ephemeralOwner:臨時(shí)節(jié)點(diǎn)所有者的session id,如果此節(jié)點(diǎn)為持久節(jié)點(diǎn),則值為0;
dataLength:Znode數(shù)據(jù)長(zhǎng)度;
numChildren:Znode子節(jié)點(diǎn)個(gè)數(shù);

事務(wù)ID:能夠改變zookeeper服務(wù)器狀態(tài)的操作稱為事務(wù)操作,一般包括數(shù)據(jù)節(jié)點(diǎn)創(chuàng)建和刪除、數(shù)據(jù)節(jié)點(diǎn)內(nèi)容更新和客戶端會(huì)話創(chuàng)建與失效等操作。對(duì)于每一個(gè)事務(wù)操作,zookeeper都會(huì)為其分配一個(gè)全局唯一的事務(wù)ID,用Zxid來(lái)表示,通常是一個(gè)64位數(shù)字。每一個(gè)Zxid對(duì)應(yīng)一次更新操作,從這些Zxid中可以間接地之別出zookeeper出力這些更新操作請(qǐng)求的全局順序。

2.2 節(jié)點(diǎn)類型

持久節(jié)點(diǎn)(PERSISTENT): 節(jié)點(diǎn)創(chuàng)建后一直存在,只能被客戶端顯式刪除;
持久連續(xù)節(jié)點(diǎn)(PERSISTENT_SEQUENTIAL):同持久節(jié)點(diǎn),且該節(jié)點(diǎn)創(chuàng)建子節(jié)點(diǎn)時(shí),自動(dòng)為子節(jié)點(diǎn)的命名末尾添加遞增編號(hào),用于記錄下每個(gè)節(jié)點(diǎn)創(chuàng)建的先后順序;
臨時(shí)節(jié)點(diǎn)(EPHEMERAL):生命周期和客戶端的會(huì)話綁定,客戶端斷開連接后自動(dòng)刪除節(jié)點(diǎn);
臨時(shí)順序節(jié)點(diǎn)(EPHEMERAL_SEQUENTIAL):同臨時(shí)節(jié)點(diǎn),且節(jié)點(diǎn)命名末尾自動(dòng)添加遞增編號(hào);
容器節(jié)點(diǎn)(CONTAINER):如果節(jié)點(diǎn)中最后一個(gè)子Znode被刪除,將會(huì)觸發(fā)刪除該Znode;
持久定時(shí)節(jié)點(diǎn)(PERSISTENT_WITH_TTL):客戶端斷開連接后不會(huì)自動(dòng)刪除Znode,如果該Znode沒有子Znode且在給定TTL時(shí)間內(nèi)無(wú)修改,該Znode將會(huì)被刪除;
持久順序定時(shí)節(jié)點(diǎn)(PERSISTENT_SEQUENTIAL_WITH_TTL):同PERSISTENT_WITH_TTL,且Znode命名末尾自動(dòng)添加遞增編號(hào);

臨時(shí)節(jié)點(diǎn):生命周期依賴于創(chuàng)建它們的會(huì)話,一旦會(huì)話結(jié)束,臨時(shí)節(jié)點(diǎn)將被刪除(也可以在會(huì)話未結(jié)束時(shí)手動(dòng)刪除)。這一特性也決定了臨時(shí)節(jié)點(diǎn)不能包含子節(jié)點(diǎn)。
永久節(jié)點(diǎn):節(jié)點(diǎn)的生命周期不依賴于會(huì)話,只能被客戶端顯式刪除。
連續(xù)節(jié)點(diǎn):Znode命名結(jié)尾添加一個(gè)遞增的計(jì)數(shù),這個(gè)計(jì)數(shù)對(duì)于此節(jié)點(diǎn)的父節(jié)點(diǎn)來(lái)說是唯一的,它的格式為"%10d"(10位數(shù)字,沒有數(shù)值的數(shù)位用0補(bǔ)充,例如"0000000001")。當(dāng)計(jì)數(shù)值大于232-1時(shí),計(jì)數(shù)器將溢出。

2.3 Znode基本操作

create: 創(chuàng)建Znode (父Znode 必須已經(jīng)存在);
delete: 刪除Znode(該節(jié)點(diǎn)必須不包含任何子Znode );
exists: 測(cè)試Znode是否存在,如果存在則獲取Znode狀態(tài)信息;
getACL/setACL: 獲取/設(shè)置Znode ACL權(quán)限;
getChildren: 獲取子Znode的列表;
getData/setData: 獲取/設(shè)置Znode data;
sync: 同步client和zookeeper的znode信息;

三. 監(jiān)聽器

ZooKeeper允許客戶端向服務(wù)端注冊(cè)一個(gè)Watcher監(jiān)聽,當(dāng)服務(wù)端的一些指定事件觸發(fā)了這個(gè)Watcher,那么就會(huì)向指定客戶端發(fā)送一個(gè)事件通知來(lái)實(shí)現(xiàn)分布式的通知功能。
典型應(yīng)用場(chǎng)景:定義一個(gè)一對(duì)多的訂閱關(guān)系,能夠讓多個(gè)訂閱者同時(shí)監(jiān)聽某一個(gè)主題對(duì)象,當(dāng)這個(gè)主題對(duì)象自身狀態(tài)變化時(shí),會(huì)通知所有訂閱者,使他們能夠做出相應(yīng)的處理。

watcher機(jī)制.PNG

概述:客戶端在向ZooKeeper服務(wù)器注冊(cè)Watcher的同時(shí),會(huì)將Watcher對(duì)象存儲(chǔ)在客戶端的WatchManager中。當(dāng)ZooKeeper服務(wù)器端觸發(fā)Watcher事件后,會(huì)向客戶端發(fā)送通知,客戶端線程從WatchManager中取出對(duì)應(yīng)的Watcher對(duì)象來(lái)執(zhí)行回調(diào)邏輯。

接口類Watcher用于表示一個(gè)標(biāo)準(zhǔn)的事件處理器,其定義了事件通知的邏輯,包含KeeperState和EventType兩個(gè)枚舉類。分別代表了通知狀態(tài)和事件類型。同一個(gè)事件類型在不同的通知狀態(tài)中代表的含義有所不同。

回調(diào)方法process()

process()方法是Watcher接口中的一個(gè)回調(diào)方法,當(dāng)ZooKeeper想客戶端發(fā)送一個(gè)Watcher事件通知后,客戶端就會(huì)對(duì)相應(yīng)的process()方法進(jìn)行回調(diào),從而實(shí)現(xiàn)對(duì)事件的處理。process()方法的定義如下:

abstract public void process(WatchedEvent event);

WatchedEvent包含了每一個(gè)事件的三個(gè)基本屬性:

public class WatchedEvent {
  final private KeeperState keeperState;   //通知狀態(tài)
  final private EventType eventType;   //事件類型
  private String path;  //節(jié)點(diǎn)路徑

  ...

    /**
     *  Convert WatchedEvent to type that can be sent over network
     */
    public WatcherEvent getWrapper() {
        return new WatcherEvent(eventType.getIntValue(), 
                                keeperState.getIntValue(), 
                                path);
    }
}

WatchedEvent與WatcherEvent的區(qū)別:
WatchedEvent類路徑:org.apache.zookeeper.WatchedEvent;
WatcherEvent類路徑:org.apache.zookeeper.proto.WatcherEvent;

public class WatcherEvent implements Record{
  private int type;
  private int state;
  private String path;

  public void serialize(OutputArchive a_, String tag) throws IOException{ ... }
  public void deserialize(InputArchive a_, String tag) throws IOException{ ... }
}

籠統(tǒng)地講,兩者表示的是同一個(gè)事物,都是對(duì)一個(gè)服務(wù)端事件的封裝。不同的是,WatchedEvent是一個(gè)邏輯事件,用于服務(wù)端和客戶端程序執(zhí)行過程中所需的邏輯對(duì)象,而WatcherEvent因?yàn)閷?shí)現(xiàn)了序列化接口,因此可以用于網(wǎng)絡(luò)傳輸。
服務(wù)端在生成WatchedEvent事件之后,會(huì)調(diào)用getWrapper方法將自己包裝成一個(gè)可序列化的WatcherEvent事件,以便通過網(wǎng)絡(luò)傳輸?shù)娇蛻舳恕?蛻舳嗽诮邮盏椒?wù)端的這個(gè)事件對(duì)象后,首先會(huì)將WatcherEvent事件還原成一個(gè)WatchedEvent事件,并傳遞給process方法處理。

四. ACL

ACL(Access Control List) : ZooKeeper作為一個(gè)分布式協(xié)調(diào)框架,器內(nèi)部存儲(chǔ)的都是一些關(guān)乎分布式系統(tǒng)運(yùn)行時(shí)狀態(tài)的元數(shù)據(jù),尤其是一些涉及分布式鎖、Master選舉和分布式協(xié)調(diào)等應(yīng)用場(chǎng)景的數(shù)據(jù),會(huì)直接影響基于ZooKeeper進(jìn)行構(gòu)建的分布式系統(tǒng)的運(yùn)行狀態(tài)。因此,ZooKeeper提供了一套完善的ACL權(quán)限控制機(jī)制來(lái)保障數(shù)據(jù)的安全。
一個(gè)有效的ACL信息:scheme(權(quán)限模式):id(授權(quán)對(duì)象):permission(權(quán)限)

4.1 Scheme

權(quán)限模式用來(lái)確定權(quán)限驗(yàn)證過程中使用的檢驗(yàn)策略,ZooKeeper中包括以下四種權(quán)限模式:
IP: IP模式通過IP地址來(lái)進(jìn)行權(quán)限控制,例如配置了“ip:192.168.0.110”,即標(biāo)簽權(quán)限控制都是針對(duì)這個(gè)IP地址的(IP模式也支持按照網(wǎng)段的方式進(jìn)行配置);
Digest:以"username:password"形式的權(quán)限標(biāo)識(shí)來(lái)精選權(quán)限配置,便于區(qū)分不同應(yīng)用來(lái)進(jìn)行權(quán)限控制;
World:數(shù)據(jù)節(jié)點(diǎn)的訪問權(quán)限對(duì)所有用戶開放,即所有用戶都可以在不進(jìn)行任何權(quán)限校驗(yàn)的情況下操作ZooKeeper上的數(shù)據(jù),它只有一個(gè)權(quán)限標(biāo)識(shí),即"world:anyone";
Super: 超級(jí)用戶,可以對(duì)ZooKeeper上任意的數(shù)據(jù)節(jié)點(diǎn)進(jìn)行任何操作;

備注:ZooKeeper 除以上4種默認(rèn)權(quán)限模式外,還提供了特殊的權(quán)限控制插件體系,允許開發(fā)人員通過指定方式對(duì)ZooKeeper的權(quán)限精選擴(kuò)展。

4.2 ID

授權(quán)對(duì)象指的是權(quán)限賦予的用戶或一個(gè)指定實(shí)體,例如IP地址或是機(jī)器等。在不同的權(quán)限模式下,授權(quán)對(duì)象是不同的。

4.3 Permission

權(quán)限就是指那些通過權(quán)限檢查后可以被允許執(zhí)行的操作,所有對(duì)數(shù)據(jù)的操作權(quán)限分為以下五類:

CREATE(c): 數(shù)據(jù)節(jié)點(diǎn)的創(chuàng)建權(quán)限,允許授權(quán)對(duì)象在該數(shù)據(jù)節(jié)點(diǎn)下創(chuàng)建子節(jié)點(diǎn);
DELETE(d): 子節(jié)點(diǎn)的刪除權(quán)限,允許授權(quán)對(duì)象刪除該數(shù)據(jù)節(jié)點(diǎn)的子節(jié)點(diǎn);
READ(r): 數(shù)據(jù)節(jié)點(diǎn)的讀取權(quán)限,允許授權(quán)對(duì)象訪問該數(shù)據(jù)節(jié)點(diǎn)并讀取其數(shù)據(jù)內(nèi)容或子節(jié)點(diǎn)列表等;
WRITE(w): 數(shù)據(jù)節(jié)點(diǎn)的更新權(quán)限, 允許授權(quán)對(duì)象對(duì)該數(shù)據(jù)節(jié)點(diǎn)進(jìn)行更新操作;
ADMIN(a): 數(shù)據(jù)節(jié)點(diǎn)的管理權(quán)限,允許授權(quán)對(duì)象對(duì)該數(shù)據(jù)節(jié)點(diǎn)進(jìn)行ACL相關(guān)的設(shè)置操作;

4.4 設(shè)置ACL

在數(shù)據(jù)節(jié)點(diǎn)創(chuàng)建時(shí)進(jìn)行ACL權(quán)限的設(shè)置:

create [-s] [-e] path data acl
create -e /zk-book init digest:foo: MiGs3Eiy1pP4rvH1Q1N1wbP+oUF8=:cdrwa

對(duì)已經(jīng)創(chuàng)建的數(shù)據(jù)節(jié)點(diǎn)進(jìn)行ACL權(quán)限的設(shè)置:

set path acl
set /zk-book init digest:foo: MiGs3Eiy1pP4rvH1Q1N1wbP+oUF8=:cdrwa

查看數(shù)據(jù)節(jié)點(diǎn)的ACL設(shè)置:

getAcl path
getAcl /zk-book

五. Leader&Follower&Observer

ZooKeeper服務(wù)器角色:
Leader:① 事務(wù)請(qǐng)求的唯一調(diào)度和處理者,保證集群事務(wù)處理的順序性; ② 集群內(nèi)部各服務(wù)器的調(diào)度者;
Follower:①處理客戶端非事務(wù)請(qǐng)求,轉(zhuǎn)發(fā)事務(wù)請(qǐng)求給Leader服務(wù)器;②參與事務(wù)請(qǐng)求Proposal的投票;③參與Leader選舉投票;
Observer:①處理客戶端非事務(wù)請(qǐng)求,轉(zhuǎn)發(fā)事務(wù)請(qǐng)求給Leader服務(wù)器; ②參與事務(wù)請(qǐng)求Proposal的投票;

服務(wù)器狀態(tài):
LOOKING:尋找Leader狀態(tài)。當(dāng)服務(wù)器處于該狀態(tài)時(shí),它會(huì)認(rèn)為當(dāng)前集群中沒有Leader,需要進(jìn)入Leader選舉流程;
FOLLOWING:跟隨者狀態(tài),表明當(dāng)前服務(wù)器角色是Follower;
LEADING:領(lǐng)導(dǎo)者狀態(tài),表明當(dāng)前服務(wù)器角色是Leader;
OBSERVING:觀察者狀態(tài),表明當(dāng)前服務(wù)器角色是Observer;

5.1Leader選舉概述

當(dāng)超過一臺(tái)ZooKeeper服務(wù)器啟動(dòng),且服務(wù)器之間已經(jīng)能夠進(jìn)行互相通信,每臺(tái)服務(wù)器都試圖找到一個(gè)Leader時(shí),便需要進(jìn)入Leader選舉流程。ZooKeeper集群正常運(yùn)行過程中,一旦選舉出了Leader,那么所有服務(wù)器的集群角色一般不會(huì)發(fā)生變化(即使集群中有非Leader角色的服務(wù)器掛了或者有新機(jī)器加入到集群)。但是當(dāng)Leader服務(wù)器掛了,那么整個(gè)集群將無(wú)法對(duì)外提供服務(wù),直到新一輪的Leader選舉完畢。服務(wù)器啟動(dòng)時(shí)期的Leader選舉與運(yùn)行期間的Leader選舉過程基本一致。參考代碼: org.apache.zookeeper.server.quorum.FastLeaderElection

Leader選舉流程示意圖.PNG

當(dāng)服務(wù)器檢測(cè)到當(dāng)前服務(wù)器狀態(tài)為L(zhǎng)OOKING時(shí),就會(huì)調(diào)用FastLeaderElection.lookForLeader方法來(lái)進(jìn)行Leader的重新選舉:
1. 自增選舉輪次
FastLeaderElection.logicalclock用于標(biāo)識(shí)當(dāng)前Leader的選舉次數(shù),ZooKeeper規(guī)定了所有有效的投票都必須在同一輪次中。ZooKeeper在開始新一輪的投票(調(diào)用FastLeaderElection.lookForLeader方法)時(shí),會(huì)首先對(duì)logicalclock進(jìn)行自增操作。
2. 初始化選票

this.id = id;     //唯一標(biāo)識(shí)一臺(tái)ZooKeeper服務(wù)器(sid),與myid值一致(server.id)
this.zxid = zxid;    //事務(wù)ID,用來(lái)唯一標(biāo)識(shí)一次服務(wù)器狀態(tài)的變更
this.electionEpoch = -1;    //當(dāng)前服務(wù)器的選舉輪次
this.peerEpoch = peerEpoch;    //被推舉的服務(wù)器的選舉輪次
this.state = ServerState.LOOKING;

3. 發(fā)送初始化投票
每臺(tái)ZooKeeper服務(wù)器都會(huì)發(fā)起第一次投票(投給自己),然后將初始化投票放入sendqueue隊(duì)列中。

LinkedBlockingQueue<ToSend> sendqueue:選票發(fā)送隊(duì)列,用于保存待發(fā)送的選票;
LinkedBlockingQueue<Notification> recvqueue:選票接收隊(duì)列,用于保存接收到的外部選票;
WorkerSender ws:選票發(fā)送器,后臺(tái)線程;
WorkerReceiver wr:選票接收器,后臺(tái)線程;

4. 接收外部投票
每臺(tái)ZooKeeper服務(wù)器都會(huì)不斷從recvqueue隊(duì)列中獲取外部投票,如果服務(wù)器發(fā)現(xiàn)無(wú)法獲取任何外部投票,那么就會(huì)立即確認(rèn)是否和集群中其他服務(wù)器保持有效連接。

if (n == null) {
   if (manager.haveDelivered()) { sendNotifications(); }   //如果已經(jīng)建立連接,則再次發(fā)送自己當(dāng)前內(nèi)部投票
   else { manager.connectAll(); }   //如果連接未建立則重連
} 

5. 判斷選舉輪次
發(fā)送完初始化選票后,開始處理外部投票(其他服務(wù)器發(fā)送的投票)。ZooKeeper規(guī)定了所有有效的投票都必須在同一選舉輪次中,在處理外部投票時(shí),會(huì)根據(jù)選舉輪次進(jìn)行不同的處理。
外部投票的選舉輪次大于內(nèi)部投票
立即跟新自己的選舉輪次(logicalclock),并且清空所有已經(jīng)收到的投票,然后使用初始化的選票來(lái)進(jìn)行PK是否變更內(nèi)部投票(服務(wù)器自身當(dāng)前的投票),最終將內(nèi)部投票發(fā)送出去。
外部投票的選舉輪次小于內(nèi)部投票
忽略該外部投票,不做任何處理,繼續(xù)處理下一個(gè)外部投票。
外部投票的選舉輪次與內(nèi)部投票一致
開始進(jìn)行選票PK。
6. 選票PK
確定當(dāng)前服務(wù)器是否需要變更投票(FastLeaderElection.totalOrderPredicate)

  protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
        LOG.debug("id: " + newId + ", proposed id: " + curId + ", zxid: 0x" +
                Long.toHexString(newZxid) + ", proposed zxid: 0x" + Long.toHexString(curZxid));
        if(self.getQuorumVerifier().getWeight(newId) == 0){
            return false;
        }

        /*
         * We return true if one of the following three cases hold:
         * 1- New epoch is higher
         * 2- New epoch is the same as current epoch, but new zxid is higher
         * 3- New epoch is the same as current epoch, new zxid is the same
         *  as current zxid, but server id is higher.
         */

        return ((newEpoch > curEpoch) ||
                ((newEpoch == curEpoch) &&
                ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
    }

規(guī)則1:如果外部投票被推舉的Leader服務(wù)器選舉輪次大于內(nèi)部投票(newEpoch > curEpoch),需要變更投票;
規(guī)則2:如果如果選舉輪次一致,則比較兩者ZXID(newZxid > curZxid),如果外部投票的ZXID大于內(nèi)部投票,則需要變更投票;
規(guī)則3:如果兩者的ZXID也一致,則比較兩者的SID,如果外部投票的SID大于內(nèi)部投票,則需要變更投票;
7. 變更投票
選票PK后,如果確定了外部投票所推舉的服務(wù)器更適合成為L(zhǎng)eader,那么就需要變更投票——使用外部投票的選票信息來(lái)覆蓋內(nèi)部投票。變更完成后,需要將變更后的內(nèi)部投票再次發(fā)送出去。
8. 選票歸檔并統(tǒng)計(jì)
無(wú)論是否進(jìn)行投票變更,都會(huì)將剛剛處理的那份外部投票放入”投票集合“recvset(Map<Long, Vote> recvset = new HashMap<Long, Vote>();)進(jìn)行歸檔(recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));)。歸檔后開始本次投票統(tǒng)計(jì),如果集群中有過半(n/2+1)服務(wù)器認(rèn)可了當(dāng)前的投票termPredicate(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch)),則更新服務(wù)器狀態(tài)并終止投票。
9. 更新服務(wù)器狀態(tài)
統(tǒng)計(jì)投票后,如果確定有過半(n/2+1)服務(wù)器認(rèn)可了當(dāng)前的投票,則需要更新服務(wù)器狀態(tài)。

//更新服務(wù)器狀態(tài)
self.setPeerState((proposedLeader == self.getId()) ? ServerState.LEADING: learningState());
//最終的選票
Vote endVote = new Vote(proposedLeader,proposedZxid, proposedEpoch);
leaveInstance(endVote);    //清空recvqueue隊(duì)列的選票

六. 會(huì)話

ZooKeeper客戶端與服務(wù)端創(chuàng)建完成連接創(chuàng)建后,就建立了一個(gè)會(huì)話,該具有全局唯一的會(huì)話ID(Session ID)。客戶端和服務(wù)器之間維持的是一個(gè)長(zhǎng)連接,會(huì)話創(chuàng)建時(shí)會(huì)配置一個(gè)會(huì)話超時(shí)時(shí)間,客戶端會(huì)在會(huì)話超時(shí)時(shí)間過期范圍內(nèi),不斷向服務(wù)端發(fā)送PING請(qǐng)求來(lái)保持會(huì)話的有效性(心跳檢測(cè)),服務(wù)端接收到這個(gè)心跳檢測(cè)后會(huì)重新激活對(duì)應(yīng)的客戶端會(huì)話(會(huì)話激活的過程不僅能夠使服務(wù)端檢測(cè)到對(duì)應(yīng)客戶端的存活性,也能讓客戶端自己保持連接狀態(tài))。
ZooKeeper將所有的會(huì)話都分配在不同的區(qū)塊中(分桶管理),分配的原則是每個(gè)會(huì)話的“下次超時(shí)時(shí)間點(diǎn)”(ExpirationTime,會(huì)話最近一次可能超時(shí)的時(shí)間點(diǎn)),將ExpirationTime相同的會(huì)話放在同一區(qū)塊中進(jìn)行管理。ExpirationTime計(jì)算公式如下:

long now = Time.currentElapsedTime();
//timeout=SessionTimeout,該會(huì)話設(shè)置的超時(shí)時(shí)間
 Long newExpiryTime = roundToNextInterval(now + timeout);  
//expirationInterval=tickTime,默認(rèn)2000毫秒
  private long roundToNextInterval(long time) {
        return (time / expirationInterval + 1) * expirationInterval;
    }

session的分桶管理策略.PNG

每一次會(huì)話激活過程(心跳檢測(cè))都會(huì)將會(huì)話從老的區(qū)塊中取出,放入next_ExpirationTime對(duì)應(yīng)的新區(qū)塊中,會(huì)話激活的過程就是一次會(huì)話遷移org.apache.zookeeper.server.SessionTrackerImpl

客戶端激活會(huì)話流程圖.PNG

七. 事務(wù)日志與數(shù)據(jù)快照

7.1 事務(wù)日志

存儲(chǔ)路徑:${dataDIr}/version-2/log.***
文件命名:log.${寫入該事務(wù)日志文件第一條事務(wù)記錄ZXID的十六進(jìn)制}
日志內(nèi)容:每一次事務(wù)操作的內(nèi)容
查看方式:java -classpath .:${jar_path}/slf4j-api-1.6.1.jar:${jar_path}/zookeeper-3.4.5.jar org.apache.zookeeper.server.LogFormatter log.***

7.2 數(shù)據(jù)快照(snapshot)

存儲(chǔ)路徑:${dataDIr}/version-2/snapshot.***
文件命名:snapshot.${本次數(shù)據(jù)快照開始時(shí)刻服務(wù)器最新ZXID的十六進(jìn)制}
日志內(nèi)容:某一時(shí)刻的全量?jī)?nèi)存數(shù)據(jù)內(nèi)容
查看方式:java -classpath .:${jar_path}/slf4j-api-1.6.1.jar:${jar_path}/zookeeper-3.4.5.jar org.apache.zookeeper.server.SnapshotFormatter log.***

八. 四字命令

conf    查看當(dāng)前ZooKeeper服務(wù)器的配置信息;
cons    查看當(dāng)前這臺(tái)服務(wù)器上所有客戶端連接的詳細(xì)信息;
crst    重置所有的客戶端連接統(tǒng)計(jì)信息;
dump    查看當(dāng)前集群的所有會(huì)話信息;
envi    查看ZooKeeper所在服務(wù)器運(yùn)行時(shí)的環(huán)境信息;
ruok    查看當(dāng)前ZooKeeper服務(wù)器是否正在運(yùn)行;
stat    查看ZooKeeper服務(wù)器的運(yùn)行時(shí)狀態(tài)信息;
srvr    同stat,區(qū)別于srvr不會(huì)將客戶端的連接情況輸出;
srst    重置所有服務(wù)器的統(tǒng)計(jì)信息;
wchs    查看當(dāng)前服務(wù)器上管理的Watcher的概要信息;
wchc    同wchs,區(qū)別于以會(huì)話為單位進(jìn)行歸組,同時(shí)列出被該會(huì)話注冊(cè)了Watcher的節(jié)點(diǎn)路徑;
wchp    同wchc,區(qū)別于wchp以節(jié)點(diǎn)路徑為單位進(jìn)行歸組;
mntr    同stat,區(qū)別于mntr輸出內(nèi)容更詳細(xì);

echo cons | nc 127.0.0.1 2181

參考資料:<<從PAXOS到ZOOKEEPER分布式一致性原理與實(shí)踐>>
博客主頁(yè):http://m.itdecent.cn/u/e97bb429f278

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容