Zookeeper深入原理

1、系統(tǒng)模型

1.1、數(shù)據(jù)模型

Zookeeper 的視圖結(jié)構(gòu)是一個(gè)樹形結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)稱之為數(shù)據(jù)節(jié)點(diǎn)(即 ZNode),每個(gè)ZNode 上都可以保存數(shù)據(jù),同時(shí)還可以掛載子節(jié)點(diǎn)。并且Zookeeper的根節(jié)點(diǎn)為 "/"。

zookeeper數(shù)據(jù)結(jié)構(gòu).png

1.2、節(jié)點(diǎn)類型

在 Zookeeper 中,每個(gè)數(shù)據(jù)節(jié)點(diǎn)都是有生命周期的,其生命周期的長短取決于數(shù)據(jù)節(jié)點(diǎn)的節(jié)點(diǎn)類型。在 Zookeeper 中有如下幾類節(jié)點(diǎn):

節(jié)點(diǎn)類型 說明
持久節(jié)點(diǎn)(PERSISTENT) 指該數(shù)據(jù)節(jié)點(diǎn)被創(chuàng)建后,就會一直存在于 Zookeeper 服務(wù)器上,直到有刪除操作來主動清除這個(gè)節(jié)點(diǎn)。
持久順序節(jié)點(diǎn)(PERSISTENT_SEQUENTIAL) 基本特性和持久節(jié)點(diǎn)是一致的,額外的特性表現(xiàn)在順序性上,在 Zookeeper 中,每個(gè)父節(jié)點(diǎn)都會為它的第一級子節(jié)點(diǎn)維護(hù)一份順序,用于記錄下每個(gè)子節(jié)點(diǎn)創(chuàng)建的先后順序。基于這個(gè)順序特性,在創(chuàng)建子節(jié)點(diǎn)的時(shí)候,可以設(shè)置這個(gè)標(biāo)記,那么在創(chuàng)建節(jié)點(diǎn)過程中,Zookeeper 會自動為給定節(jié)點(diǎn)名加上一個(gè)數(shù)字后綴,作為一個(gè)新的、完整的節(jié)點(diǎn)名。另外需要注意的是,這個(gè)數(shù)字后綴的上限是整型的最大值。
臨時(shí)節(jié)點(diǎn)(EPHEMERAL) 臨時(shí)節(jié)點(diǎn)的生命周期和客戶端的會話綁定在一起,如果客戶端會話失效,那么這個(gè)節(jié)點(diǎn)就會被自動清理掉。另外,Zookeeper 規(guī)定了不能基于臨時(shí)節(jié)點(diǎn)來創(chuàng)建子節(jié)點(diǎn),即臨時(shí)節(jié)點(diǎn)只能作為葉子節(jié)點(diǎn)。
臨時(shí)順序節(jié)點(diǎn)(EPHEMERAL_SEQUENTIAL) 基本特性和臨時(shí)節(jié)點(diǎn)一致,只是添加了順序的特性。

1.3、狀態(tài)信息

每個(gè)數(shù)據(jù)節(jié)點(diǎn)中除了存儲了數(shù)據(jù)內(nèi)容之外,還存儲了數(shù)據(jù)節(jié)點(diǎn)本身的一些狀態(tài)信息(State)。

狀態(tài)屬性 說明
cZxid 即 Create ZXID,表示該數(shù)據(jù)節(jié)點(diǎn)被創(chuàng)建時(shí)的事務(wù)ID。
ctime 即 Create Time,表示該數(shù)據(jù)節(jié)點(diǎn)被創(chuàng)建的時(shí)間。
mZxid 即 Modified ZXID,表示該節(jié)點(diǎn)最后一次被更新時(shí)的事務(wù)ID。
mtime 即 Modified Time,表示該數(shù)據(jù)節(jié)點(diǎn)最后一次被更新的時(shí)間。
pZxid 表示該節(jié)點(diǎn)的子節(jié)點(diǎn)列表最后一次被修改時(shí)的事務(wù)ID。注意,只有子節(jié)點(diǎn)列表變更了才會變更 pZxid,子節(jié)點(diǎn)內(nèi)容變更不會影響pZxid。
cversion 表示子節(jié)點(diǎn)的版本號。
dataVersion 表示數(shù)據(jù)節(jié)點(diǎn)的版本號。
aclVersion 表示節(jié)點(diǎn)的 ACL 版本號。
ephemeralOwner 創(chuàng)建該臨時(shí)節(jié)點(diǎn)的會話的sessionID。如果該節(jié)點(diǎn)是持久節(jié)點(diǎn),那么這個(gè)屬性值為0。
dataLength 表示數(shù)據(jù)內(nèi)容的長度。
numChildren 表示當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)個(gè)數(shù)。

1.4、ZXID

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

ZXID 是一個(gè) 64 位的數(shù)字,其中低 32 位可以看作是一個(gè)簡單的單調(diào)遞增的計(jì)數(shù)器,針對客戶端的每一個(gè)事務(wù)請求,Leader 服務(wù)器在產(chǎn)生一個(gè)新的事務(wù) Proposal 的時(shí)候,都會對該計(jì)數(shù)器進(jìn)行加 1 操作;而高 32 位則代表了 Leader 周期 epoch 的編號,每當(dāng)選舉產(chǎn)生一個(gè)新的 Leader 服務(wù)器,就會從這個(gè) Leader 服務(wù)器上取出其本地日志中最大事務(wù) Proposal 的 ZXID,并從該 ZXID 中解析出對應(yīng)的 epoch 值,然后再對其進(jìn)行加 1 操作,之后就會以此編號作為新的 epoch,并將低 32 位置 0 來開始生成新的 ZXID。

1.5、版本

Zookeeper 中為數(shù)據(jù)節(jié)點(diǎn)引入了版本的概念,每個(gè)數(shù)據(jù)節(jié)點(diǎn)都具有三種類型的版本信息(在上面的狀態(tài)信息中已經(jīng)介紹了三種版本信息代表的意思),對數(shù)據(jù)節(jié)點(diǎn)的任何更新操作都會引起版本號的變化。其中我們以 dataVersion 為例來說明。在一個(gè)數(shù)據(jù)節(jié)點(diǎn)被創(chuàng)建完畢之后,節(jié)點(diǎn)的dataVersion 值是 0,表示的含義是 ”當(dāng)前節(jié)點(diǎn)自從創(chuàng)建之后,被更新過 0 次“。如果現(xiàn)在對該節(jié)點(diǎn)的數(shù)據(jù)內(nèi)容進(jìn)行更新操作,那么隨后,dataVersion 的值就會變成 1。即表示的是對數(shù)據(jù)節(jié)點(diǎn)的數(shù)據(jù)內(nèi)容的變更次數(shù)。

版本的作用是用來實(shí)現(xiàn)樂觀鎖機(jī)制中的 “寫入校驗(yàn)” 的。例如,當(dāng)要修改數(shù)據(jù)節(jié)點(diǎn)的數(shù)據(jù)內(nèi)容時(shí),帶上版本號,如果數(shù)據(jù)節(jié)點(diǎn)的版本號與傳入的版本號相等,就進(jìn)行修改,否則修改失敗。

1.6、Watcher

1.6.1、概述

Zookeeper 提供了分布式數(shù)據(jù)的發(fā)布/訂閱功能。一個(gè)典型的發(fā)布/訂閱模型系統(tǒng)定義了一種一對多的訂閱關(guān)系,能夠讓多個(gè)訂閱者同時(shí)監(jiān)聽某一個(gè)主題對象,當(dāng)這個(gè)主題對象自身狀態(tài)變化時(shí),會通知所有訂閱者,使它們能夠做出相應(yīng)的處理。在 Zookeeper 中,引入了 Watcher 機(jī)制來實(shí)現(xiàn)這種分布式的通知功能。Zookeeper 允許客戶端向服務(wù)端注冊一個(gè) Watcher 監(jiān)聽,當(dāng)服務(wù)端的一些指定事件觸發(fā)了這個(gè) Watcher,那么就會向指定客戶端發(fā)送一個(gè)事件通知來實(shí)現(xiàn)分布式的通知功能。

Watcher.png

從上圖可以看出 Zookeeper 的 Watcher 機(jī)制主要包括客戶端線程、客戶端WatchMananger 和 Zookeeper 服務(wù)器三部分。在具體工作流程上,簡單地講,客戶端在向 Zookeeper 服務(wù)器注冊 Watcher 的同時(shí),會將 Watcher 對象存儲在客戶端的 WatchMananger 中。當(dāng) Zookeeper 服務(wù)器端觸發(fā) Watcher 事件后,會向客戶端發(fā)送通知,客戶端線程從 WatchManager 中取出對應(yīng)的 Watcher 對象來執(zhí)行回調(diào)邏輯。

1.6.2、Watcher特性
  • 一次性:表示無論是服務(wù)端還是客戶端,一旦一個(gè) Watcher 被觸發(fā),Zookeeper 都會將其從相應(yīng)的存儲中移除。因此,開發(fā)人員在 Watcher 的使用上要記住的一點(diǎn)是需要反復(fù)注冊。
  • 客戶端串行執(zhí)行:客戶端 Watcher 回調(diào)的過程是一個(gè)串行同步的過程,這為我們保證了順序,同時(shí),需要開發(fā)人員注意的一點(diǎn)是,千萬不要因?yàn)橐粋€(gè) Watcher 的處理邏輯影響了整個(gè)客戶端的 Watcher 回調(diào)。
  • 輕量:WatchedEvent 是 Zookeeper 整個(gè) Watcher 通知機(jī)制的最小通知單元,這個(gè)數(shù)據(jù)結(jié)構(gòu)中只包含三部分內(nèi)容:通知狀態(tài)、事件類型和節(jié)點(diǎn)路徑。也就是說,Watcher通知非常簡單,只會告訴客戶端發(fā)生了事件,而不會說明事件的具體內(nèi)容。
1.6.3、watcher接口設(shè)計(jì)

Watcher是一個(gè)接口,任何實(shí)現(xiàn)了Watcher接口的類就是一個(gè)新的Watcher。Watcher內(nèi)部包含了兩個(gè)枚舉類:KeeperState、EventType

  • Watcher通知狀態(tài)(KeeperState)

    KeeperState是客戶端與服務(wù)端連接狀態(tài)發(fā)生變化時(shí)對應(yīng)的通知類型。路徑為org.apache.zookeeper.Watcher.Event.KeeperState,是一個(gè)枚舉類,其枚舉屬性如下:

枚舉屬性 說明
SyncConnected 客戶端與服務(wù)器正常連接時(shí)
Disconnected 客戶端與服務(wù)器斷開連接時(shí)
Expired 會話session失效時(shí)
AuthFailed 身份認(rèn)證失敗時(shí)
  • Watcher事件類型(EventType)

    EventType是數(shù)據(jù)節(jié)點(diǎn)(znode)發(fā)生變化時(shí)對應(yīng)的通知類型。EventType變化時(shí)KeeperState永遠(yuǎn)處于SyncConnected通知狀態(tài)下;當(dāng)KeeperState發(fā)生變化時(shí),EventType永遠(yuǎn)為None。其路徑為org.apache.zookeeper.Watcher.Event.EventType,是一個(gè)枚舉類,枚舉屬性如下:

枚舉屬性 說明
None
NodeCreated Watcher監(jiān)聽的數(shù)據(jù)節(jié)點(diǎn)被創(chuàng)建時(shí)
NodeDeleted Watcher監(jiān)聽的數(shù)據(jù)節(jié)點(diǎn)被刪除時(shí)
NodeDataChanged Watcher監(jiān)聽的數(shù)據(jù)節(jié)點(diǎn)內(nèi)容發(fā)生變更時(shí)(無論內(nèi)容數(shù)據(jù)是否變化)
NodeChildrenChanged Watcher監(jiān)聽的數(shù)據(jù)節(jié)點(diǎn)的子節(jié)點(diǎn)列表發(fā)生變更時(shí)

:客戶端接收到的相關(guān)事件通知中只包含狀態(tài)及類型等信息,不包括節(jié)點(diǎn)變化前后的具體內(nèi)容,變化前的數(shù)據(jù)需業(yè)務(wù)自身存儲,變化后的數(shù)據(jù)需調(diào)用get等方法重新獲取;

1.6.4、捕獲相應(yīng)的事件

上面講到zookeeper客戶端連接的狀態(tài)和zookeeper對znode節(jié)點(diǎn)監(jiān)聽的事件類型,下面我們來講解如何建立zookeeper的watcher監(jiān)聽。在zookeeper中采用zk.getChildren(path, watch)、zk.exists(path, watch)、zk.getData(path, watcher, stat)這樣的方式為某個(gè)znode注冊監(jiān)聽。

下表以node-x節(jié)點(diǎn)為例,說明調(diào)用的注冊方法和可監(jiān)聽事件間的關(guān)系:

注冊方式 Created ChildrenChanged Changed Deleted
zk.exists(“/node-x”,watcher) 可監(jiān)控 可監(jiān)控 可監(jiān)控
zk.getData(“/node-x”,watcher) 可監(jiān)控 可監(jiān)控
zk.getChildren(“/node-x”,watcher) 可監(jiān)控 可監(jiān)控

1.7、ACL

Zookeeper 中提供了一套完善的 ACL(Access Control List)權(quán)限控制機(jī)制來保障數(shù)據(jù)的安全。

1.7.1、概述

ACL 由三部分組成,分別是:權(quán)限模式(Scheme)、授權(quán)對象(ID)和權(quán)限(Permission),通常使用“scheme: ?id:permission”來標(biāo)識一個(gè)有效的ACL 信息。下面分別介紹:

  1. 權(quán)限模式(Scheme)

    方案 說明
    world 只有一個(gè)用戶:anyone,代表登錄 Zookeeper 所有人(默認(rèn))
    ip 對客戶端使用IP地址認(rèn)證。
    auth 使用已添加認(rèn)證的用戶認(rèn)證。
    digest 使用“用戶名:密碼”方式認(rèn)證。
  1. 授權(quán)對象(ID)

    授權(quán)對象ID是指,權(quán)限賦予的實(shí)體,例如:IP 地址或用戶。

  1. 權(quán)限(Permission)

    權(quán)限 ACL簡寫 描述
    create c 可以創(chuàng)建子節(jié)點(diǎn)。
    delete d 可以刪除子節(jié)點(diǎn)(僅下一級節(jié)點(diǎn))。
    read r 可以讀取節(jié)點(diǎn)數(shù)據(jù)或子節(jié)點(diǎn)列表。
    write w 可以對節(jié)點(diǎn)進(jìn)行更新操作。
    admin a 可以設(shè)置節(jié)點(diǎn)訪問控制列表權(quán)限。
1.7.2、特性
  • zooKeeper的權(quán)限控制是基于每個(gè)znode節(jié)點(diǎn)的,需要對每個(gè)節(jié)點(diǎn)設(shè)置權(quán)限。
  • 每個(gè)znode支持設(shè)置多種權(quán)限控制方案和多個(gè)權(quán)限。
  • 子節(jié)點(diǎn)不會繼承父節(jié)點(diǎn)的權(quán)限,客戶端無權(quán)訪問某節(jié)點(diǎn),但可能可以訪問它的子節(jié)點(diǎn)。
1.7.3、案例
  • world授權(quán)模式

    命令

    setAcl <path> world:anyone:<acl>
    

    案例

    [zk: localhost:2181(CONNECTED) 0] create /node1 "node1"
    Created /node1
    [zk: localhost:2181(CONNECTED) 1] getAcl /node1
    'world,'anyone
    : cdrwa
    [zk: localhost:2181(CONNECTED) 2] setAcl /node1 world:anyone:crwa
    cZxid = 0x100000004
    ctime = Fri May 29 14:31:54 CST 2020
    mZxid = 0x100000004
    mtime = Fri May 29 14:31:54 CST 2020
    pZxid = 0x100000004
    cversion = 0
    dataVersion = 0
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 5
    numChildren = 0
    
  • IP授權(quán)模式

    命令

    setAcl <path> ip:<ip>:<acl>
    

    案例

    注意:遠(yuǎn)程登錄zookeeper命令:./zkCli.sh -server ip

    [zk: localhost:2181(CONNECTED) 18] create /node2 "node2"
    Created /node2
    
    [zk: localhost:2181(CONNECTED) 23] setAcl /node2 ip:192.168.150.101:cdrwa
    cZxid = 0xe
    ctime = Fri Dec 13 22:30:29 CST 2019
    mZxid = 0x10
    mtime = Fri Dec 13 22:33:36 CST 2019
    pZxid = 0xe
    cversion = 0
    dataVersion = 2
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 20
    numChildren = 0
    
    [zk: localhost:2181(CONNECTED) 25] getAcl /node2
    'ip,'192.168.150.101
    : cdrwa
    
    #使用IP非 192.168.150.101 的機(jī)器
    [zk: localhost:2181(CONNECTED) 0] get /node2
    Authentication is not valid : /node2 #沒有權(quán)限
    
  • Auth授權(quán)模式

    命令

    addauth digest <user>:<password> #添加認(rèn)證用戶
    setAcl <path> auth:<user>:<acl>
    

    案例

    [zk: localhost:2181(CONNECTED) 6] create /node3 "node3"
    Created /node3
    
    #添加認(rèn)證用戶
    [zk: localhost:2181(CONNECTED) 7] addauth digest ld:123456
    
    [zk: localhost:2181(CONNECTED) 8] setAcl /node3 auth:ld:cdrwa
    cZxid = 0x10000000c
    ctime = Fri May 29 14:47:13 CST 2020
    mZxid = 0x10000000c
    mtime = Fri May 29 14:47:13 CST 2020
    pZxid = 0x10000000c
    cversion = 0
    dataVersion = 0
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 5
    numChildren = 0
    
    [zk: localhost:2181(CONNECTED) 9] getAcl /node3
    'digest,'ld:kesl2p6Yx58a+/mP+TKSFZkzkZ0=
    : cdrwa
    
    #添加認(rèn)證用戶后可以訪問
    [zk: localhost:2181(CONNECTED) 10] get /node3
    node3
    cZxid = 0x10000000c
    ctime = Fri May 29 14:47:13 CST 2020
    mZxid = 0x10000000c
    mtime = Fri May 29 14:47:13 CST 2020
    pZxid = 0x10000000c
    cversion = 0
    dataVersion = 0
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 5
    numChildren = 0
    
  • Digest授權(quán)模式

    命令

    setAcl <path> digest:<user>:<password>:<acl>
    

    這里的密碼是經(jīng)過SHA1及BASE64處理的密文,在SHELL中可以通過以下命令計(jì)算:

    echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64
    

    先來計(jì)算一個(gè)密文

    echo -n monkey:123456 | openssl dgst -binary -sha1 | openssl base64
    

    案例

    [zk: localhost:2181(CONNECTED) 12] create /node4 "node4"
    Created /node4
    
    [zk: localhost:2181(CONNECTED) 13] setAcl /node4 digest:monkey:Rk6u/zJJdOYrTZ6+J0p4/4gTILg=:cdrwa
    cZxid = 0x10000000e
    ctime = Fri May 29 14:52:50 CST 2020
    mZxid = 0x10000000e
    mtime = Fri May 29 14:52:50 CST 2020
    pZxid = 0x10000000e
    cversion = 0
    dataVersion = 0
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 5
    numChildren = 0
    
    #沒有權(quán)限無法讀取
    [zk: localhost:2181(CONNECTED) 14] getAcl /node4
    Authentication is not valid : /node4
    
    #添加認(rèn)證用戶
    [zk: localhost:2181(CONNECTED) 15] addauth digest monkey:123456
    
    [zk: localhost:2181(CONNECTED) 16] getAcl /node4               
    'digest,'monkey:Rk6u/zJJdOYrTZ6+J0p4/4gTILg=
    : cdrwa
    
    [zk: localhost:2181(CONNECTED) 17] get /node4
    node4
    cZxid = 0x10000000e
    ctime = Fri May 29 14:52:50 CST 2020
    mZxid = 0x10000000e
    mtime = Fri May 29 14:52:50 CST 2020
    pZxid = 0x10000000e
    cversion = 0
    dataVersion = 0
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 5
    numChildren = 0
    
  • 多種模式授權(quán)

    同一個(gè)節(jié)點(diǎn)可以同時(shí)使用多種模式授權(quán)

    [zk: localhost:2181(CONNECTED) 18] create /node5 "node5"
    Created /node5
    [zk: localhost:2181(CONNECTED) 19] addauth digest ld:123456
    [zk: localhost:2181(CONNECTED) 20] setAcl /node5 ip:192.168.150.101:cdrwa,auth:ld:cdrwa
    cZxid = 0x100000010
    ctime = Fri May 29 14:56:38 CST 2020
    mZxid = 0x100000010
    mtime = Fri May 29 14:56:38 CST 2020
    pZxid = 0x100000010
    cversion = 0
    dataVersion = 0
    aclVersion = 1
    ephemeralOwner = 0x0
    dataLength = 5
    numChildren = 0
    

1.7.4、ACL 超級管理員

zookeeper的權(quán)限管理模式有一種叫做super,該模式提供一個(gè)超管可以方便的訪問任何權(quán)限的節(jié)點(diǎn)

假設(shè)這個(gè)超管是:super:admin,需要先為超管生成密碼的密文

echo -n super:admin | openssl dgst -binary -sha1 | openssl base64

那么打開zookeeper目錄下的/bin/zkServer.sh服務(wù)器腳本文件,找到如下一行:

nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"

這就是腳本中啟動zookeeper的命令,默認(rèn)只有以上兩個(gè)配置項(xiàng),我們需要加一個(gè)超管的配置項(xiàng)

"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="

那么修改以后這條完整命令變成了

nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="\
    -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &

之后啟動zookeeper,輸入如下命令添加權(quán)限

addauth digest super:admin #添加認(rèn)證用戶

2、Leader 選舉

2.1、服務(wù)器狀態(tài)

  • looking:尋找leader狀態(tài)。當(dāng)服務(wù)器處于該狀態(tài)時(shí),它會認(rèn)為當(dāng)前集群中沒有Leader,因此需要進(jìn)入 Leader 選舉流程。
  • leading:領(lǐng)導(dǎo)者狀態(tài)。表明當(dāng)前服務(wù)器角色是leader。
  • following:跟隨者狀態(tài)。表明當(dāng)前服務(wù)器角色是follower。
  • observing:觀察者狀態(tài)。表明當(dāng)前服務(wù)器角色是observer。

2.2、服務(wù)器啟動時(shí)期的 Leader 選舉

在服務(wù)器集群初始化階段,我們以 3 臺機(jī)器組成的服務(wù)器集群為例,當(dāng)有一臺服務(wù)器server1 啟動的時(shí)候,它是無法進(jìn)行 Leader 選舉的,當(dāng)?shù)诙_機(jī)器 server2 也啟動時(shí),此時(shí)這兩臺服務(wù)器已經(jīng)能夠進(jìn)行互相通信,每臺機(jī)器都試圖找到一個(gè) Leader,于是便進(jìn)入了 Leader 選舉流程。

  1. 每個(gè)server發(fā)出一個(gè)投票。由于是初始情況,server1和server2都會將自己作為leader服務(wù)器來進(jìn)行投票,每次投票會包含所推舉的服務(wù)器的myid和zxid,使用(myid, zxid)來表示,此時(shí)server1的投票為(1, 0),server2的投票為(2, 0),然后各自將這個(gè)投票發(fā)給集群中其他機(jī)器。

  2. 集群中的每臺服務(wù)器接收來自集群中各個(gè)服務(wù)器的投票。

  3. 處理投票。針對每一個(gè)投票,服務(wù)器都需要將別人的投票和自己的投票進(jìn)行pk,pk規(guī)則如下

    • 優(yōu)先檢查zxid。zxid比較大的服務(wù)器優(yōu)先作為leader。
    • 如果zxid相同,那么就比較myid。myid較大的服務(wù)器作為leader服務(wù)器。

    ? 對于Server1而言,它的投票是(1, 0),接收Server2的投票為(2, 0),首先會比較兩者的zxid,均為0,再比較myid,此時(shí)server2的myid最大,于是更新自己的投票為(2, 0),然后重新投票,對于server2而言,其無須更新自己的投票,只是再次向集群中所有機(jī)器發(fā)出上一次投票信息即可。

  4. 統(tǒng)計(jì)投票。每次投票后,服務(wù)器都會統(tǒng)計(jì)投票信息,判斷是否已經(jīng)有過半機(jī)器接受到相同的投票信息,對于server1、server2而言,都統(tǒng)計(jì)出集群中已經(jīng)有兩臺機(jī)器接受了(2, 0)的投票信息,此時(shí)便認(rèn)為已經(jīng)選出了leader。

  5. 改變服務(wù)器狀態(tài)。一旦確定了leader,每個(gè)服務(wù)器就會更新自己的狀態(tài),如果是follower,那么就變更為following,如果是leader,就變更為leading。

2.3、服務(wù)器運(yùn)行時(shí)期的 Leader 選舉

在zookeeper運(yùn)行期間,leader與非leader服務(wù)器各司其職,即便當(dāng)有非leader服務(wù)器宕機(jī)或新加入,此時(shí)也不會影響leader,但是一旦leader服務(wù)器掛了,那么整個(gè)集群將暫停對外服務(wù),進(jìn)入新一輪leader選舉,其過程和啟動時(shí)期的Leader選舉過程基本一致。

假設(shè)正在運(yùn)行的有server1、server2、server3三臺服務(wù)器,當(dāng)前l(fā)eader是server2,若某一時(shí)刻leader掛了,此時(shí)便開始Leader選舉。選舉過程如下:

  1. 變更狀態(tài)。leader掛后,余下的非 Observer 服務(wù)器都會將自己的服務(wù)器狀態(tài)變更為looking,然后開始進(jìn)入leader選舉過程。
  2. 每個(gè)server會發(fā)出一個(gè)投票。在運(yùn)行期間,每個(gè)服務(wù)器上的zxid可能不同,此時(shí)假定server1的zxid為123,server3的zxid為122,在第一輪投票中,server1和server3都會投自己,產(chǎn)生投票(1, 123),(3, 122),然后各自將投票發(fā)送給集群中所有機(jī)器。
  3. 接收來自各個(gè)服務(wù)器的投票。
  4. 處理投票。對于投票的處理,和上面提到的服務(wù)器啟動期間的處理規(guī)則是一致的。在這個(gè)例子里面,由于 Server1 的 zxid 為 123,Server3 的 zxid 為 122,那么顯然,Server1 會成為 Leader。
  5. 統(tǒng)計(jì)投票。
  6. 改變服務(wù)器狀態(tài)。

2.4、Observer 角色及其設(shè)置

observer角色特點(diǎn):

  1. 不參與集群的leader選舉
  2. 不參與集群中寫數(shù)據(jù)時(shí)的ack反饋

為了使用observer角色,在任何想變成observer角色的配置文件中加入如下配置:

peerType=observer

并在所有server的配置文件中,配置成observer模式的server的那行配置追加:observer,例如:

server.3=192.168.60.130:2289:3389:observer
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容