本文將從系統(tǒng)模型、序列化與協(xié)議、客戶端工作原理、會(huì)話、服務(wù)端工作原理以及數(shù)據(jù)存儲(chǔ)等方面來揭示ZooKeeper的技術(shù)內(nèi)幕。
一、系統(tǒng)模型
1.1 數(shù)據(jù)模型
ZooKeeper的視圖結(jié)構(gòu)使用了其特有的“數(shù)據(jù)節(jié)點(diǎn)”概念,我們稱之為ZNode。ZNode是ZooKeeper中數(shù)據(jù)的最小單元,每個(gè)ZNode上都可以保存數(shù)據(jù),同時(shí)還可以掛載子節(jié)點(diǎn),因此構(gòu)成了一個(gè)層次化的命名空間,我們稱之為樹。
1.2 節(jié)點(diǎn)特性
我們已知,ZooKeeper的命名空間是由一系列數(shù)據(jù)節(jié)點(diǎn)組成的,我們將對(duì)數(shù)據(jù)節(jié)點(diǎn)做詳細(xì)講解。
節(jié)點(diǎn)類型
在ZooKeeper中,每個(gè)數(shù)據(jù)節(jié)點(diǎn)都是有生命周期的,其生命周期的長(zhǎng)短取決于數(shù)據(jù)節(jié)點(diǎn)的節(jié)點(diǎn)類型。在ZooKeeper中,節(jié)點(diǎn)類型可以分為持久節(jié)點(diǎn)(PERSISTENT)、臨時(shí)節(jié)點(diǎn)(EPHEMERAL)和順序節(jié)點(diǎn)(SEQUENTIAL)三大類,ju'ti具體在節(jié)點(diǎn)創(chuàng)建過程中,通過組合使用,可以生成以下四種組合型節(jié)點(diǎn)類型:
- 持久節(jié)點(diǎn)(PERSISTENT)
數(shù)據(jù)節(jié)點(diǎn)被創(chuàng)建后,就會(huì)一直存在于ZooKeeper服務(wù)器上,直到有刪除操作來主動(dòng)清除這個(gè)節(jié)點(diǎn)。
- 持久順序節(jié)點(diǎn)(PERSISTENT_SEQUENTIAL)
他的基本特性和持久節(jié)點(diǎn)是一致的,額外的特性表現(xiàn)在順序性上。在ZooKeeper中,每個(gè)父節(jié)點(diǎn)都會(huì)為他的第一級(jí)子節(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會(huì)自動(dòng)為給定節(jié)點(diǎn)加上一個(gè)數(shù)字后綴,作為一個(gè)新的、完整的節(jié)點(diǎn)名。另外需要注意的是,這個(gè)數(shù)字后綴的上限是整型的最大值。
- 臨時(shí)節(jié)點(diǎn)(EPHEMERAL)
臨時(shí)節(jié)點(diǎn)的生命周期和客戶端的會(huì)話綁定在一起,也就是說,如果客戶端會(huì)話失效,那么這個(gè)節(jié)點(diǎn)就會(huì)被自動(dòng)清理掉。這里提到的客戶端會(huì)話失效,而非TCP連接斷開。
- 臨時(shí)順序節(jié)點(diǎn)(EPHEMERAL_SEQUENTIAL)
在臨時(shí)節(jié)點(diǎn)基礎(chǔ)上,添加了順序的特性。
狀態(tài)信息
每個(gè)數(shù)據(jù)節(jié)點(diǎn)除了存儲(chǔ)了數(shù)據(jù)內(nèi)容外,還存儲(chǔ)了數(shù)據(jù)節(jié)點(diǎn)本身的一些狀態(tài)信息。
| 狀態(tài)屬性 | 說明 |
|---|---|
| czxid | 即Created ZXID,表示該節(jié)點(diǎn)被創(chuàng)建時(shí)的事務(wù)ID |
| mzxid | 即Modified ZXID,表示該節(jié)點(diǎn)最后一次被更新時(shí)的事務(wù)ID |
| ctime | 即Created Time |
| mtime | 即Modified Time |
| version | 數(shù)據(jù)節(jié)點(diǎn)的版本號(hào) |
| cversion | 子節(jié)點(diǎn)的版本號(hào) |
| aversion | 節(jié)點(diǎn)的ACL版本號(hào) |
| ephemeralOwner | 創(chuàng)建該臨時(shí)節(jié)點(diǎn)的會(huì)話的sessionID。如果該節(jié)點(diǎn)是持久節(jié)點(diǎn),那么這個(gè)屬性值為0 |
| dataLength | 數(shù)據(jù)內(nèi)容長(zhǎng)度 |
| numChildren | 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)個(gè)數(shù) |
| pzxid | 表示該節(jié)點(diǎn)的子節(jié)點(diǎn)列表最后一次被修改時(shí)的事務(wù)ID。注意,只有子節(jié)點(diǎn)列表變更了才會(huì)變更pzxid,子節(jié)點(diǎn)內(nèi)容變更不會(huì)影響pzxid。 |
1.3 版本-保證分布式數(shù)據(jù)原子性操作
ZooKeeper中為數(shù)據(jù)節(jié)點(diǎn)引入了版本的概念,每個(gè)數(shù)據(jù)節(jié)點(diǎn)都具有三種類型的版本信息,對(duì)數(shù)據(jù)節(jié)點(diǎn)的任何更新操作都會(huì)引起版本號(hào)的變化。
| 版本類型 | 說明 |
|---|---|
| version | 當(dāng)前數(shù)據(jù)節(jié)點(diǎn)數(shù)據(jù)內(nèi)容的版本號(hào) |
| cversion | 當(dāng)前數(shù)據(jù)節(jié)點(diǎn)子節(jié)點(diǎn)的版本號(hào) |
| aversion | 當(dāng)前數(shù)據(jù)節(jié)點(diǎn)ACL變更版本號(hào) |
在ZooKeeper中,version屬性正是用來實(shí)現(xiàn)樂觀鎖機(jī)制中的“寫入校驗(yàn)”的。
version = setDataRequest.getVersion();
int currentVersion = nodeRecord.stat.getVersion();
if(version != -1 && version != currentVersion) {
throw new KeeperException.BadVersionException(path);
}
version = currentVersion + 1;
1.4 Watcher-數(shù)據(jù)變更的通知
在ZooKeeper中,引入了Watcher機(jī)制來實(shí)現(xiàn)這種分布式的通知功能。ZooKeeper允許客戶端向服務(wù)端注冊(cè)一個(gè)Watcher監(jiān)聽,當(dāng)服務(wù)器的一些指定事件出發(fā)了這個(gè)Watcher,那么就會(huì)向指定客戶端發(fā)送一個(gè)事件通知來實(shí)現(xiàn)分布式的通知功能。

從圖中我們可以看到,ZooKeeper的Watcher機(jī)制主要包括ke'hu'duan'xian'c客戶端線程、客戶端WatcherManager和ZooKeeper服務(wù)器三部分。在具體工作流程上,客戶端在向ZooKeeper服務(wù)器注冊(cè)Watcher的同時(shí),會(huì)將Watcher對(duì)象存儲(chǔ)在客戶端的WatcherManager中。當(dāng)ZooKeeper服務(wù)器端觸發(fā)Watcher事件后,會(huì)向客戶端發(fā)送通知,客戶端線程從WatcherManager中取出對(duì)應(yīng)的Watcher對(duì)象來執(zhí)行回調(diào)邏輯。
1.5 ACL--保障數(shù)據(jù)的安全
提到權(quán)限控制,我們首先看看目前應(yīng)用最廣泛的權(quán)限控制方式--UGO(User、Group和Others)權(quán)限控制機(jī)制。簡(jiǎn)單地講,UGO就是針對(duì)一個(gè)文件或目錄,對(duì)創(chuàng)建者(User)、創(chuàng)建者所在的組(Group)和其他用戶(Other)分別配置不同的權(quán)限。從這里可以看出,UGO其實(shí)是一種粗粒度的文件系統(tǒng)權(quán)限控制模式,利用UGO只能對(duì)三類用戶jin'xing進(jìn)行權(quán)限控制,即文件的創(chuàng)建者、創(chuàng)建者所在的組以及其他所有用戶,很顯然,UGO無法解決下面這個(gè)場(chǎng)景:
用戶U1創(chuàng)建了文件F1,希望U1所在的用戶組G1擁有對(duì)F1讀寫和執(zhí)行的權(quán)限,另一個(gè)用戶組G2擁有讀權(quán)限,而另一個(gè)用戶U3則沒有任何權(quán)限。
下面我們來看另外一種典型的權(quán)限控制方式:ACL。ACL,即訪問控制列表,是一種相對(duì)來說比較新穎且更細(xì)粒度的權(quán)限管理方式。可以針對(duì)任何用戶和組進(jìn)行細(xì)粒度的權(quán)限控制。
ACL介紹
ZooKeeper的ACL權(quán)限控制和Unix/Linux操作系統(tǒng)中的ACL有一些區(qū)別,讀者可以從三個(gè)方面來理解ACL機(jī)制,分別是:權(quán)限模式(Scheme)、授權(quán)對(duì)象(ID)和權(quán)限(Permission),通常使用“scheme:id:permission”來標(biāo)識(shí)一個(gè)有效的ACL信息。
權(quán)限模式:Scheme
權(quán)限模式用來確定權(quán)限驗(yàn)證過程中使用的校驗(yàn)策略。在ZooKeeper中,開發(fā)人員使用最多的就是以下四種權(quán)限模式。
- IP
IP模式通過IP地址粒度來進(jìn)行權(quán)限控制。也支持按照網(wǎng)段的方式進(jìn)行配置。
- Digest
以類似于“username:password”形式的權(quán)限標(biāo)識(shí)來進(jìn)行權(quán)限配置,便于區(qū)分不同應(yīng)用來進(jìn)行權(quán)限控制。
- World
數(shù)據(jù)節(jié)點(diǎn)的訪問權(quán)限對(duì)所有用戶開發(fā),即所有用戶可以在不進(jìn)行任何權(quán)限校驗(yàn)的情況下操作ZooKeeper上的數(shù)據(jù)。另外,World模式也可以看作是一種特殊的Digest模式,他只有一個(gè)權(quán)限標(biāo)識(shí),即“world:anyone”。
- Super
超級(jí)用戶的意思。
授權(quán)對(duì)象:ID
權(quán)限賦予的用戶或一個(gè)指定實(shí)體。在不同的權(quán)限模式下,授權(quán)對(duì)象是不同的。
權(quán)限:Permission
在ZooKeeper中,所有對(duì)數(shù)據(jù)的操作權(quán)限分為以下五大類:
- CREATE(C)
- DELETE(D)
- READ(R)
- WRITE(W)
- ADMIN(A)
權(quán)限擴(kuò)展體系
實(shí)現(xiàn)自定義權(quán)限控制器
二、序列化協(xié)議
ZooKeeper的客戶端和服務(wù)端之間會(huì)進(jìn)行一系列的網(wǎng)絡(luò)通信以實(shí)現(xiàn)數(shù)據(jù)的傳輸。對(duì)于一個(gè)網(wǎng)絡(luò)通信,首先要解決的就是對(duì)數(shù)據(jù)的序列化和反序列化處理,在ZooKeeper中,使用了Jute這一序列化組件來進(jìn)行數(shù)據(jù)的序列化和反序列化操作。同時(shí),為了實(shí)現(xiàn)一個(gè)高效的網(wǎng)絡(luò)通信程序,良好的通信協(xié)議設(shè)計(jì)也是至關(guān)重要的。
通信協(xié)議
基于TCP/IP協(xié)議,ZooKeeper實(shí)現(xiàn)了自己的通信協(xié)議來完成客戶端與服務(wù)端、服務(wù)端與服務(wù)端之間的網(wǎng)絡(luò)通信。ZooKeeper通信協(xié)議整體上的設(shè)計(jì)非常簡(jiǎn)單,對(duì)于請(qǐng)求,主要包含請(qǐng)求頭和請(qǐng)求體,對(duì)于響應(yīng),則主要包含響應(yīng)頭和相應(yīng)體。
三、客戶端
客戶端是開發(fā)人員使用ZooKeeper最主要的途徑,因此我們有必要對(duì)ZooKeeper客戶端的內(nèi)部原理進(jìn)行詳細(xì)講解。ZooKeeper的客戶端主要由以下幾個(gè)核心組件組成。
- ZooKeeper實(shí)例:客戶端的入口。
- ClientWatchManager:客戶端Watcher管理器。
- HostProvider:客戶端地址列表管理器。
- ClientCnxn:客戶端核心線程,其內(nèi)部又包含兩個(gè)線程,即SendThread和EventThread。前者是一個(gè)I/O線程,主要負(fù)責(zé)ZooKeeper客戶端和服務(wù)端之間的網(wǎng)絡(luò)I/O通信;后者是一個(gè)事件線程,主要負(fù)責(zé)對(duì)服務(wù)端事件進(jìn)行處理。
客戶端的整個(gè)初始化和啟動(dòng)過程大體可以分為以下三個(gè)步驟。
- 設(shè)置默認(rèn)Watcher。
- 設(shè)置ZooKeeper服務(wù)器地址列表。
- 創(chuàng)建ClientCnxn。
3.1 一次會(huì)話的創(chuàng)建過程
初始化階段
- 初始化ZooKeeper對(duì)象。
- 設(shè)置會(huì)話默認(rèn)Watcher。
- 構(gòu)造ZooKeeper服務(wù)器地址列表管理器:HostProvider。
- 創(chuàng)建并初始化客戶端網(wǎng)絡(luò)連接器:ClientCnxn。
- 初始化SendThread和EventThread。
會(huì)話創(chuàng)建階段
- 啟動(dòng)SendThread和EventThread
- 獲取一個(gè)服務(wù)器地址。
- 創(chuàng)建TCP連接。
- 構(gòu)造ConnectRequest請(qǐng)求。
- 發(fā)送請(qǐng)求。
響應(yīng)處理階段
- 接受服務(wù)端響應(yīng)
- 處理Response
- 連接成功
- 生成時(shí)間:SyncConnected-None
- 查詢Watcher
- 處理事件
四、會(huì)話
會(huì)話(Session)是ZooKeeper中最重要的概念之一,客戶端和服務(wù)端之間的任何交互操作都與會(huì)話息息相關(guān),這其中就包括臨時(shí)節(jié)點(diǎn)的生命周期、客戶端請(qǐng)求的順序執(zhí)行以及Watcher通知機(jī)制等。
4.1 會(huì)話狀態(tài)
在ZooKeeper客戶端和服務(wù)端成功完成連接創(chuàng)建后,就建立了一個(gè)會(huì)話。ZooKeeper會(huì)話在整個(gè)運(yùn)行期間的聲明周期中,會(huì)在不同的會(huì)話狀態(tài)之間進(jìn)行切換,這些狀態(tài)一般可以分為CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。
如果客戶端需要與服務(wù)端創(chuàng)建一個(gè)會(huì)話,那么客戶端必須提供一個(gè)使用字符串表示的服務(wù)器地址列表:“host1:port,host2:port,host3:port”。一旦客戶端開始創(chuàng)建ZooKeeper對(duì)象,那么客戶端狀態(tài)就會(huì)變成CONNECTING,同時(shí)客戶端開始從上述服務(wù)器地址列表中逐個(gè)選取IP地址來嘗試進(jìn)行網(wǎng)絡(luò)連接,直到成功連接上服務(wù)器,然后將客戶端狀態(tài)變更為CONNECTED。
通常,伴隨著網(wǎng)絡(luò)閃斷或是其他原因,客戶端和服務(wù)器之間的連接會(huì)出現(xiàn)斷開情況。一旦碰到這種情況,ZooKeeper客戶端會(huì)自動(dòng)進(jìn)行重連操作,同時(shí)客戶端的狀態(tài)再次變?yōu)镃ONNECTING,直到重新連接上ZooKeeper服務(wù)器后,客戶端狀態(tài)又會(huì)再次轉(zhuǎn)變成CONNECTED。因此,在通常情況下,在ZooKeeper運(yùn)行期間,客戶端的狀態(tài)總是介于CONNECTING和CONNECTED兩者之一。
另外,如果出現(xiàn)諸如會(huì)話超時(shí)、權(quán)限檢查失敗或是客戶端主動(dòng)退出程序等情況,那么客戶端的狀態(tài)就會(huì)直接變?yōu)镃LOSE。
4.2 會(huì)話創(chuàng)建
Session
Session是ZooKeeper中的會(huì)話實(shí)體,代表了一個(gè)客戶端會(huì)話。其包含以下4個(gè)基礎(chǔ)屬性、
- sessionId:會(huì)話id,用來唯一標(biāo)識(shí)一個(gè)會(huì)話,每次客戶端創(chuàng)建新會(huì)話的時(shí)候,ZooKeeper都會(huì)為其分配一個(gè)全局唯一的sessionId。
- TimeOut:會(huì)話超時(shí)時(shí)間??蛻舳嗽跇?gòu)造ZooKeeper實(shí)例的時(shí)候,會(huì)配置一個(gè)sessionTimeout參數(shù)用于指定會(huì)話的超時(shí)時(shí)間。ZooKeeper客戶端向服務(wù)器發(fā)送這個(gè)超時(shí)時(shí)間后,服務(wù)器會(huì)根據(jù)自己的超時(shí)時(shí)間限制最終確定會(huì)話的超時(shí)時(shí)間。
- TickTime:下次會(huì)話超時(shí)時(shí)間點(diǎn)。
- isClosing:該屬性用于標(biāo)記一個(gè)會(huì)話是否已經(jīng)被關(guān)閉。
sessionID
在SeesionTracker初始化的時(shí)候,會(huì)調(diào)用initializeNextSession方法來生成一個(gè)初始化的sessionID,之后在ZooKeeper的正常運(yùn)行過程中,會(huì)在該sessionID的基礎(chǔ)上為每個(gè)會(huì)話進(jìn)行分配,其初始化算法如下:
public static long initializeNextSeesion(long id) {
long nextSid = 0;
nextSid = (System.currentTimeMillis() << 24) >> 8;
nextSid = nextSid | (id << 56);
return nextSid;
}
上面這個(gè)方法就是ZooKeeper初始化sessionID的算法,我們一起深入的探究下。從上面的代碼片段中,可以看出sessionID的生成大體可以分為以下5個(gè)步驟。
- 獲取當(dāng)前的毫秒表示。
- 左移24位。
- 右移8位。
- 添加機(jī)器標(biāo)識(shí):SID。
- 將步驟3和步驟4得到的兩個(gè)64位表示的數(shù)值進(jìn)行“|”操作。
簡(jiǎn)單地講,可以將上述算法概括為:高8位確定了所在機(jī)器,后56位使用當(dāng)前時(shí)間的毫秒進(jìn)行隨機(jī)。
SessionTracker
SessionTracker是ZooKeeper服務(wù)端的會(huì)話管理器,負(fù)責(zé)會(huì)話的創(chuàng)建、管理和清理等工作??梢哉f,整個(gè)會(huì)話的生命周期都離不開SessionTracker的管理。每一個(gè)會(huì)話在SessionTracker內(nèi)部都保留了三份,具體如下。
- sessionsById:這是一個(gè)HashMap<Long, SeesionImpl>類型的數(shù)據(jù)結(jié)構(gòu),用于根據(jù)sessionID來管理Session實(shí)體。
- sessionsWithTimeout:這是一個(gè)ConcurrentHashMap<Long, Integer>類型的數(shù)據(jù)結(jié)構(gòu),用于根據(jù)sessionID來管理會(huì)話的超時(shí)時(shí)間。該數(shù)據(jù)結(jié)構(gòu)和ZooKeeper內(nèi)存數(shù)據(jù)庫(kù)相連通,會(huì)被定期持久化到快照文件中去。
- sessionSets:這是一個(gè)HashMap<Long, SessionSet>類型的數(shù)據(jù)結(jié)構(gòu),用于根據(jù)下次會(huì)話超時(shí)時(shí)間來歸檔會(huì)話,便于進(jìn)行會(huì)話管理和超時(shí)檢查。
創(chuàng)建連接
服務(wù)端對(duì)于客戶端的“會(huì)話創(chuàng)建”請(qǐng)求的處理,大體可以分為四大步驟,分別是ConnectRequest請(qǐng)求、會(huì)話創(chuàng)建、處理器鏈路處理和會(huì)話響應(yīng)。
4.3 會(huì)話管理
分桶策略
ZooKeeper的會(huì)話管理主要是由SessionTracker負(fù)責(zé)的,其采用了一種特殊的會(huì)化管理方式,我們稱之為“分桶策略”。所謂分桶策略,是指將類似的會(huì)話放在同一區(qū)塊中進(jìn)行管理,以便于ZooKeeper對(duì)會(huì)話進(jìn)行不同區(qū)塊的格里處理以及同一區(qū)塊的統(tǒng)一處理。
ZooKeeper將所有的會(huì)話都分配在了不同的區(qū)塊之中,分配的原則是每個(gè)會(huì)話的“下次超時(shí)時(shí)間點(diǎn)”(ExpirationTime)。ExpirationTime是指該會(huì)話最近一次可能超時(shí)的時(shí)間點(diǎn),對(duì)于一個(gè)新創(chuàng)建的會(huì)話而言,其會(huì)話創(chuàng)建完畢后,ZooKeeper就會(huì)為其計(jì)算ExpirationTime,計(jì)算方式如下:
ExpirationTime = CurrentTime + SessionTimeout
在ZooKeeper的實(shí)際實(shí)現(xiàn)中,Zookeeper的Leader服務(wù)器在運(yùn)行期間會(huì)定時(shí)的進(jìn)行會(huì)話超時(shí)檢查,其時(shí)間間隔是ExpirationInterval,單位是毫秒,默認(rèn)值是tickTime的值,即默認(rèn)情況下,每隔2000毫秒進(jìn)行一次會(huì)話超時(shí)檢查。為了方便對(duì)多個(gè)會(huì)話同時(shí)進(jìn)行超時(shí)檢查,完整的ExpirationTime的計(jì)算方式如下:
ExpirationTime_ = CurrentTime + SessionTimeout
ExpirationTime = (ExpirationTime_/ExpirationInterval + 1) * ExpirationInterval
會(huì)話激活
為了保持客戶端會(huì)話的有效性,在ZooKeeper的運(yùn)行過程中,客戶端會(huì)在會(huì)話超時(shí)時(shí)間國(guó)企范圍內(nèi)向服務(wù)端發(fā)送PING請(qǐng)求來保持會(huì)話的有效性,我們俗稱“心跳檢測(cè)”。同時(shí),服務(wù)端需要不斷地接收來自客戶端的這個(gè)心跳檢測(cè),并且需要重新激活對(duì)應(yīng)的客戶端會(huì)話,我們將這個(gè)重新激活的過程稱為TouchSession。會(huì)話激活的過程,不僅能夠使服務(wù)端檢測(cè)到對(duì)應(yīng)客戶端的存活性,也能讓客戶端自己保持連接狀態(tài)。
會(huì)話超時(shí)檢查
在ZooKeeper中,會(huì)話超時(shí)檢查同樣是由SessionTracker負(fù)責(zé)的。SessionTracker中有一個(gè)單獨(dú)的線程專門進(jìn)行會(huì)話超時(shí)檢查,這里我們稱其為“超時(shí)檢查線程”,其工作機(jī)制的核心思路非常簡(jiǎn)單:逐個(gè)依次對(duì)會(huì)話桶中剩下的會(huì)話進(jìn)行清理。
4.4 會(huì)話清理
當(dāng)SessionTracker的會(huì)話超時(shí)檢查線程整理出一些已經(jīng)過期的會(huì)話后,那么就要開始進(jìn)行會(huì)話清理了。會(huì)話清理的步驟大致可以分為以下七步。
- 標(biāo)記會(huì)話狀態(tài)為“已關(guān)閉”
為了保證在清理期間不再處理來自該客戶端的新請(qǐng)求,SessionTracker會(huì)首先將該會(huì)話的isClosing屬性標(biāo)記為true。
- 發(fā)起“會(huì)話關(guān)閉”請(qǐng)求
為了使該會(huì)話的關(guān)閉操作在整個(gè)服務(wù)端集群中都生效,ZooKeeper使用了提交“會(huì)話關(guān)閉”請(qǐng)求的方式,并立即交付給PrepRequestProcessor處理器進(jìn)行處理。
- 收集需要清理的臨時(shí)節(jié)點(diǎn)
在ZooKeeper的內(nèi)存數(shù)據(jù)庫(kù)中,為每個(gè)會(huì)話都單獨(dú)保存了一份由該會(huì)話維護(hù)的所有臨時(shí)節(jié)點(diǎn)集合,因此在會(huì)話清理階段,只需要根據(jù)當(dāng)前即將關(guān)閉的會(huì)話的sessionID從內(nèi)存數(shù)據(jù)庫(kù)中獲取到這份臨時(shí)節(jié)點(diǎn)列表即可。
實(shí)際上,有如下細(xì)節(jié)需要處理:在ZooKeeper處理會(huì)話關(guān)閉請(qǐng)求之前,正好有以下請(qǐng)求到達(dá)了服務(wù)端并正在處理中:
- 節(jié)點(diǎn)刪除請(qǐng)求,刪除的目標(biāo)節(jié)點(diǎn)正好是上述臨時(shí)節(jié)點(diǎn)中的一個(gè)。
- 臨時(shí)節(jié)點(diǎn)創(chuàng)建請(qǐng)求,創(chuàng)建的目標(biāo)節(jié)點(diǎn)正好是上述臨時(shí)節(jié)點(diǎn)中的一個(gè)。
嘉定我們當(dāng)前獲取的臨時(shí)節(jié)點(diǎn)列表是ephemerals,那么針對(duì)第一類請(qǐng)求,我們需要將所有這些請(qǐng)求對(duì)應(yīng)的數(shù)據(jù)節(jié)點(diǎn)路徑從ephemerals中移除,以避免重復(fù)刪除。針對(duì)第二類,我們需要將所有這些請(qǐng)求對(duì)應(yīng)的數(shù)據(jù)節(jié)點(diǎn)路徑添加到ephemerals中去,以刪除這些即將會(huì)被創(chuàng)建但是尚未保存到內(nèi)存數(shù)據(jù)庫(kù)中去的臨時(shí)節(jié)點(diǎn)。
- 添加“節(jié)點(diǎn)刪除”事務(wù)變更
完成該會(huì)話相關(guān)的臨時(shí)節(jié)點(diǎn)收集后,ZooKeeper會(huì)逐個(gè)將這些臨時(shí)節(jié)點(diǎn)轉(zhuǎn)換成“節(jié)點(diǎn)刪除”請(qǐng)求,并放入事務(wù)變更隊(duì)列outstandingChanges中去。
- 刪除臨時(shí)節(jié)點(diǎn)
FinalRequestProcessor處理器會(huì)觸發(fā)內(nèi)存數(shù)據(jù)庫(kù),刪除該會(huì)話對(duì)應(yīng)的所有臨時(shí)節(jié)點(diǎn)。
- 移除會(huì)話
完成節(jié)點(diǎn)刪除后,需要將會(huì)話從SessionTracker中移除。主要就是從上面提到的三個(gè)數(shù)據(jù)結(jié)構(gòu)(sessionById、sessionsWithTimeout和sessionSets)中將該會(huì)話移除掉。
- 關(guān)閉NIOServerCnxn
最后,從NIOServerCnxnFactory找到該會(huì)話對(duì)應(yīng)的NIOServerCnxn,將其關(guān)閉。
4.5 重連
當(dāng)客戶端和服務(wù)端之間的網(wǎng)絡(luò)連接斷開時(shí),ZooKeeper客戶端會(huì)自動(dòng)進(jìn)行反復(fù)的重連,知道最終成功連接上ZooKeeper集群中的一臺(tái)機(jī)器。在這種情況下,再次連接上服務(wù)端的客戶端有可能會(huì)處于以下兩種狀態(tài)之一。
- CONNECTED:重連成功
- EXPIRED:如果是在會(huì)話超時(shí)時(shí)間以外重新連接上,那么服務(wù)端其實(shí)已經(jīng)對(duì)該會(huì)話進(jìn)行了會(huì)話清理操作,因此再次連接上的會(huì)話將被視為非法會(huì)話。
當(dāng)客戶端和服務(wù)端之間的連接斷開后,用戶在客戶端可能會(huì)看到兩類異常:CONNECTION_LOSS(連接斷開)和SESSION_EXPIRED(會(huì)話過期)。
五、服務(wù)器啟動(dòng)
我們首先看看ZooKeeper服務(wù)端的整體架構(gòu),如圖

5.1 單機(jī)版服務(wù)器啟動(dòng)
ZooKeeper服務(wù)器的啟動(dòng),大體可以分為以下五個(gè)主要步驟:配置文件解析、初始化數(shù)據(jù)管理器、初始化網(wǎng)絡(luò)I/O管理器、數(shù)據(jù)恢復(fù)和對(duì)外服務(wù)。下圖是單機(jī)版ZooKeeper服務(wù)器的啟動(dòng)流程圖。

預(yù)啟動(dòng)
預(yù)啟動(dòng)的步驟如下。
- 統(tǒng)一由QuorumPeerMain作為啟動(dòng)類
- 解析配置文件zoo.cfg
- 創(chuàng)建并啟動(dòng)歷史文件清理器DatadirCleanupManager
- 判斷當(dāng)前是集群模式還是單機(jī)模式的啟動(dòng)
- 再次進(jìn)行配置文件zoo.cfg的解析
- 創(chuàng)建服務(wù)器實(shí)例ZooKeeperServer
初始化
初始化的步驟如下。
- 創(chuàng)建服務(wù)器統(tǒng)計(jì)器ServerStats
- 創(chuàng)建ZooKeeper數(shù)據(jù)管理器FileTxnSnapLog
- 設(shè)置服務(wù)器tickTime和會(huì)話超時(shí)時(shí)間限制
- 創(chuàng)建ServerCnxnFactory
- 初始化ServerCnxnFactory
- 啟動(dòng)ServerCnxnFactory主線程
- 恢復(fù)本地?cái)?shù)據(jù)
- 創(chuàng)建并啟動(dòng)會(huì)話管理器
- 初始化ZooKeeper的請(qǐng)求處理鏈
- 注冊(cè)JMX服務(wù)
- 注冊(cè)ZooKeeper服務(wù)器實(shí)例
5.2 集群版服務(wù)器啟動(dòng)
集群版和單機(jī)版ZooKeeper服務(wù)器啟動(dòng)過程在很多地方是一致的,所以這里只會(huì)對(duì)有差異的地方展開進(jìn)行講解。下圖是集群版ZooKeeper服務(wù)器的啟動(dòng)流程圖。

預(yù)啟動(dòng)
預(yù)啟動(dòng)的步驟如下。
- 統(tǒng)一由QuorumPeerMain作為啟動(dòng)類
- 解析配置文件zoo.cfg
- 創(chuàng)建并啟動(dòng)歷史文件清理器DatadirCleanupManager
- 判斷當(dāng)前是集群模式還是單機(jī)模式啟動(dòng)
初始化
初始化的步驟如下
- 創(chuàng)建ServerCnxnFactory
- 初始化ServerCnxnFactory
- 創(chuàng)建ZooKeeper數(shù)據(jù)管理器FileTxnSnapLog
- 創(chuàng)建QuorumPeer
- 創(chuàng)建內(nèi)存數(shù)據(jù)庫(kù)ZKDatabase
- 初始化QuorumPeer
- 恢復(fù)本地?cái)?shù)據(jù)
- 啟動(dòng)ServerCnxnFactory主線程
Leader選舉
Leader選舉的步驟如下
- 初始化Leader選舉
- 注冊(cè)JMX服務(wù)
- 監(jiān)測(cè)當(dāng)前服務(wù)器狀態(tài)
- Leader選舉
Leader和Follower啟動(dòng)期交互過程

六、Leader選舉
6.1 Leader選舉概述
服務(wù)器啟動(dòng)時(shí)期的Leader選舉
要進(jìn)行Leader選舉的時(shí)候,隱式條件便是ZooKeeper的集群規(guī)模至少是2臺(tái)機(jī)器,只有一臺(tái)服務(wù)器啟動(dòng)的時(shí)候,是無法進(jìn)行Leader選舉的。
- 每個(gè)Server會(huì)發(fā)出一個(gè)投票
初始情況,對(duì)于Server1和Server2來說,都會(huì)投給自己,每次投票包含的最基本的元素包括:所推舉的服務(wù)器myid和ZXID。
- 接收來自各個(gè)服務(wù)器的投票
集群中每個(gè)服務(wù)器在收到投票后,首先會(huì)判斷投票的有效性,包含檢查是否是本輪投票,是否來自LOOKING狀態(tài)的服務(wù)器。
- 處理投票
在接收到來自其他服務(wù)器的投票后,針對(duì)每個(gè)投票,服務(wù)器都需要將別人的投票和自己的投票進(jìn)行PK,PK的規(guī)則如下
- 優(yōu)先檢查ZXID。ZXID比較大d服務(wù)器優(yōu)先作為L(zhǎng)eader
- 如果ZXID相同的話,那么就比較myid。myid比較大的服務(wù)器作為L(zhǎng)eader服務(wù)器。
- 統(tǒng)計(jì)投票
每次投票后,服務(wù)器都會(huì)統(tǒng)計(jì)所有投票,判斷是否已經(jīng)有過半機(jī)器接收到相同的投票信息。
- 改變服務(wù)器狀態(tài)
一旦確定了Leader,每個(gè)服務(wù)器就會(huì)更新自己的狀態(tài):如果是Follower,那么就變更為FOLLOWING,如果是Leader,那么就變更為L(zhǎng)EADING。
服務(wù)器運(yùn)行期間的Leader選舉
在ZooKeeper集群正常運(yùn)行過程中,一旦選出一個(gè)Leader,那么所有服務(wù)器的集群角色一般不會(huì)再發(fā)生變化,不管是是非Leader集群掛了還是新機(jī)器加入集群,都不會(huì)影響Leader。一旦Leader掛了,那么整個(gè)集群將暫時(shí)無法對(duì)外服務(wù),而是進(jìn)入新一輪的Leader選舉。
6.2 Leader選舉的算法分析
在ZooKeeper中,提供了三種Leader選舉的算法,分別是LeaderElection、UDP版本的FastLeaderElection和TCP版本的FastLeaderElection,可以通過在配置文件zoo.cfg中使用electionAlg屬性來指定,分別用數(shù)字0-3表示。0表示LeaderElection,1表示UDP版本的FastLeaderElection,并且是非授權(quán)模式,2表示UDP版本的FastLeaderElection,使用授權(quán)模式,3代表TCP版本的FastLeaderElection。從3.4.0版本開始,Zookeeper廢棄了0-2這三種算法,只保留了TCP版本的FastLeaderElection選舉算法。
術(shù)語(yǔ)解釋
- SID:服務(wù)器ID
- ZXID:事務(wù)ID
- Vote:投票
- Quorum:過半機(jī)器數(shù)
算法分析
進(jìn)入Leader選舉
當(dāng)ZooKeeper集群中的一臺(tái)服務(wù)器出現(xiàn)以下兩種情況時(shí),就會(huì)開始進(jìn)入Leader選舉
- 服務(wù)器初始化啟動(dòng)
- 服務(wù)器運(yùn)行期間無法和Leader保持連接
而當(dāng)一臺(tái)機(jī)器進(jìn)入Leadeader選舉流程時(shí),當(dāng)前集群也可能會(huì)處于以下兩種狀態(tài)
- 集群中本來就存在一個(gè)Leader
- 集群中確實(shí)不存在Leader
第一種情況,這種情況通常是某一臺(tái)服務(wù)器啟動(dòng)比較晚,在他啟動(dòng)之前,集群已經(jīng)可以正常工作。針對(duì)這種情況,當(dāng)該機(jī)器試圖去選舉Leader時(shí),會(huì)被告知當(dāng)前服務(wù)器的Leader信息,對(duì)于該機(jī)器來說,僅僅需要和Leader機(jī)器建立起連接,并進(jìn)行狀態(tài)同步即可。
下面我們看看集群中不存在Leader的情況下,如何進(jìn)行Leader選舉。
開始第一次投票
通常有兩種情況會(huì)導(dǎo)致集群中不存在Leader,一種是整個(gè)服務(wù)器剛剛初始化啟動(dòng)時(shí),另一種情況就是運(yùn)行期間當(dāng)前Leader所在的服務(wù)器掛了。此時(shí),集群中所有機(jī)器都處于LOOKING的狀態(tài)。當(dāng)一臺(tái)服務(wù)器處于LOOKING狀態(tài)時(shí),那么他就會(huì)向集群中所有其他機(jī)器發(fā)送消息,我們稱這個(gè)消息為“投票”。
在這個(gè)投票消息中包含了兩個(gè)最基本的信息:所推舉的服務(wù)器SID和ZXID,用(SID,ZXID)表示。一般都是投自己。
變更投票
集群中每臺(tái)機(jī)器發(fā)出自己的投票后,也會(huì)接收到來自集群中其他機(jī)器的投票。每臺(tái)機(jī)器都會(huì)根據(jù)一定的規(guī)則,來處理收到的其他機(jī)器的投票,并以此來決定是否需要變更自己的投票。這個(gè)規(guī)則也成了整個(gè)Leader選舉算法的核心所在。我們首先定義一些術(shù)語(yǔ)。
- vote_sid:接收到的投票中所推舉Leader服務(wù)器的SID
- vote_zxid:接收到的投票中所推舉Leader服務(wù)器的ZXID
- self_sid:當(dāng)前服務(wù)器自己的SID
- self_zxid:當(dāng)前服務(wù)器自己的ZXID
對(duì)比過程如下:
- 規(guī)則1:如果vote_zxid>self_zxid,就認(rèn)可當(dāng)前收到投票,并再次將該投票發(fā)送出去。
- 規(guī)則2:如果vote_zxid<self_zxid,就堅(jiān)持自己的投票,不作任何變更。
- 規(guī)則3:如果vote_zxid=self_zxid,就對(duì)比兩者的SID。如果vote_sid>self_sid,就認(rèn)可當(dāng)前收到的投票,并在此將該投票發(fā)出去。
- 規(guī)則4:如果vote_zxid=self_zxid,并且vote_sid<self_sid,那么同樣堅(jiān)持自己的投票,不作變更。
確定Leader
經(jīng)過這第二次投票后,集群中每臺(tái)機(jī)器都會(huì)再次受到其他機(jī)器的投票,然后開始統(tǒng)計(jì)投票。如果一臺(tái)機(jī)器收到了超過半數(shù)的相同的投票,那么這個(gè)投票對(duì)應(yīng)的SID機(jī)器ji'wei即為L(zhǎng)eader。
小結(jié)
通常哪臺(tái)服務(wù)器上的越新,那么越有可能成為L(zhǎng)eader,原因很簡(jiǎn)單,數(shù)據(jù)越新,ZXID越大,也就越能夠保證數(shù)據(jù)的恢復(fù)。
6.3 Leader選舉的實(shí)現(xiàn)細(xì)節(jié)
服務(wù)器狀態(tài)
- LOOKING
- FOLLOWING
- LEADING
- OBSERVING
投票數(shù)據(jù)結(jié)構(gòu)
| 屬性 | 說明 |
|---|---|
| id | 被推舉的Leader的SID值 |
| zxid | 被推舉的Leader的事務(wù)ID |
| electionEpoch | 邏輯時(shí)鐘,用來判斷多個(gè)投票是否在同一輪選舉周期中。該值在服務(wù)端是一個(gè)自增序列。每次進(jìn)入新一輪投票后,都會(huì)對(duì)該值進(jìn)行加一 |
| peerEpoch | 被推舉的Leader的epoch |
| state | 當(dāng)前服務(wù)器狀態(tài) |
QuorumCnxManager:網(wǎng)絡(luò)I/O
每臺(tái)服務(wù)器啟動(dòng)的時(shí)候,都會(huì)啟動(dòng)一個(gè)QuorumCnxManager,負(fù)責(zé)各臺(tái)服務(wù)器之間的底層Leader選舉過程中的網(wǎng)絡(luò)通信。
消息隊(duì)列
在QuorumCnxManager這個(gè)類內(nèi)部維護(hù)了一系列的隊(duì)列,用于保存接收到的、待發(fā)送的消息,以及消息的發(fā)送器。
- recvQueue:消息接收隊(duì)列
- queueSendMap:消息發(fā)送隊(duì)列,用于保存那些待發(fā)送的消息
- senderWorkerMap:發(fā)送器集合
- lastMessageSent:最近發(fā)送過的消息
建立連接
QuorumCnxManager在啟動(dòng)的時(shí)候,會(huì)創(chuàng)建一個(gè)ServerSocket來監(jiān)聽Leader選舉的通信接口(Leader選舉的通信端口默認(rèn)是3888)。開啟端口監(jiān)聽后,ZooKeeper就能夠不斷地接收到來自其他服務(wù)器的“創(chuàng)建連接”請(qǐng)求,在收到其他服務(wù)器的TCP連接請(qǐng)求時(shí),會(huì)交由receiveConnection函數(shù)來處理。為了避免兩臺(tái)機(jī)器之間重復(fù)的創(chuàng)建TCP連接,ZooKeeper設(shè)計(jì)了建立TCP連接的規(guī)則:只允許SID大的服務(wù)器主動(dòng)與其他服務(wù)器建立連接,否則斷開鏈接。
一旦建立起連接,就會(huì)根據(jù)遠(yuǎn)程服務(wù)器的SID來創(chuàng)建相應(yīng)的消息發(fā)送器SendWorker和消息接收器RecvWorker,并啟動(dòng)他們。
消息接收與發(fā)送
消息的接收過程是由消息接收器RecvWorker來負(fù)責(zé)的。ZooKeeper會(huì)為每個(gè)遠(yuǎn)程服務(wù)器分配一個(gè)單獨(dú)的RecvWorker,每個(gè)RecvWorker只需要不斷地從這個(gè)TCP連接中讀取消息,并將其保存到recvQueue隊(duì)列中。
消息發(fā)送過程也比較簡(jiǎn)單,由于ZooKeeper同樣已經(jīng)為每個(gè)遠(yuǎn)程服務(wù)器單獨(dú)分別分配了消息發(fā)送器SendWorker,那么每個(gè)SendWorker只需要不斷地從對(duì)應(yīng)的消息發(fā)送隊(duì)列中取出一個(gè)消息來發(fā)送即可,同時(shí)將這個(gè)消息放入lastMessageSent中來作為最近發(fā)送過的消息。
FastLeaderElection:選舉算法的核心部分
先約定幾個(gè)概念:
- 外部投票:其他服務(wù)器發(fā)來的投票
- 內(nèi)部投票:自身當(dāng)前的投票
- 選舉輪次:ZooKeeper服務(wù)器Leader選舉的輪次,即logicalclock
- PK:指對(duì)內(nèi)部投票和外部投票進(jìn)行一個(gè)對(duì)比來確定是否需要變更內(nèi)部投票
選票管理
- sendqueue:選票發(fā)送隊(duì)列,用于保存待發(fā)送的選票
- recvqueue:選票接收隊(duì)列,用于保存接收到的外部投票
- WorkerReceiver:選票接收器。該接收器會(huì)不斷地從QuorumCnxManager中獲取其他服務(wù)器發(fā)來的選舉消息,并將其轉(zhuǎn)換成一個(gè)選票,然后保存到recvQueue隊(duì)列中去。在選票接收過程中,如果發(fā)現(xiàn)該外部投票的選舉輪次小于當(dāng)前服務(wù)器,就直接忽略這個(gè)外部投票,同時(shí)立即發(fā)出自己的內(nèi)部投票。當(dāng)然,如果當(dāng)前服務(wù)器并不是LOOKING狀態(tài),即yi'j已經(jīng)選出了Leader,那么也將忽略這個(gè)外部投票,同時(shí)將Leader信息已投票信息發(fā)送出去。另外,如果接收到的消息來自O(shè)bserver服務(wù)器,那么就直接忽略掉,并將自己當(dāng)前的投票發(fā)送出去。
- WorkerSender:選票發(fā)送器,會(huì)不斷從sendqueue隊(duì)列中獲取待發(fā)送的選票,并將其傳遞到底層QuorumCnxManager中去。
算法核心

七、各服務(wù)器角色介紹
7.1 Leader
Leader服務(wù)器是整個(gè)ZooKeeper集群工作機(jī)制的核心,其主要工作有以下兩個(gè)。
- 事務(wù)請(qǐng)求的唯一調(diào)度和處理者,保證集群事務(wù)處理的順序性。
- 集群內(nèi)部各服務(wù)器的調(diào)度者。
7.2 Follower
Follower服務(wù)器是ZooKeeper集群狀態(tài)的跟隨者,主要工作
- 處理客戶端非事務(wù)請(qǐng)求,轉(zhuǎn)發(fā)事務(wù)請(qǐng)求給Leader服務(wù)器。
- 參與事務(wù)請(qǐng)求Proposal的投票
- 參與Leader選舉投票
7.3 Observer
工作原理與Follower基本一致,唯一區(qū)別在于Observer不參與任何形式的投票,包括事務(wù)請(qǐng)求Proposal的投票和Leader選舉投票。簡(jiǎn)單的講,Observer服務(wù)器只提供非事務(wù)服務(wù),通常用于在不影響集群事務(wù)處理能力的前提下提升集群的非事務(wù)處理能力。
7.4 集群間消息通信
ZooKeeper的消息類型大體上可以分為四類,分別是:數(shù)據(jù)同步型、服務(wù)器初始化型、請(qǐng)求處理型和會(huì)話管理型。
數(shù)據(jù)同步型
是指在Learner和Leader服務(wù)器進(jìn)行數(shù)據(jù)同步的時(shí)候,網(wǎng)絡(luò)通信所用到的消息,通常有DIFF、TRUNC、SNAP和UPTPDATE四種。
服務(wù)器初始化型
是指在整個(gè)集群或是某些新機(jī)器初始化時(shí),Leader和Learner之間相互通信所使用的消息類型,常見的有OBSEERVERINFO、FOLLOWERINFO、LEADERINFO、ACKEPOCH和NEWLEADER五種。
請(qǐng)求處理型
是指在進(jìn)行請(qǐng)求處理的過程中,Leader和Learner服務(wù)器之間相互通信所使用的消息,常見的有REQUEST、PROPOSAL、ACK、COMMIT、INFORM和SYNC六種。
會(huì)話管理型
是指ZooKeeper在進(jìn)行會(huì)話管理的過程中,和Learner服務(wù)器之間互相通信所使用的消息,常見的有PING和REVALIDATE兩種。
八、請(qǐng)求處理
8.1 會(huì)話創(chuàng)建請(qǐng)求
ZooKeeper服務(wù)端對(duì)于會(huì)話創(chuàng)建的處理,大體可以分為請(qǐng)求接收、會(huì)話創(chuàng)建、預(yù)處理、事務(wù)處理、事務(wù)應(yīng)用和會(huì)話響應(yīng)6大環(huán)節(jié)。
8.2 SetData請(qǐng)求
服務(wù)端對(duì)于SetData請(qǐng)求的處理,大體可以分為4大步驟,分別是請(qǐng)求的預(yù)處理、事務(wù)處理、事務(wù)應(yīng)用和請(qǐng)求響應(yīng)。
8.3 事務(wù)請(qǐng)求轉(zhuǎn)發(fā)
在事務(wù)請(qǐng)求的處理過程中,需要我們注意的一個(gè)細(xì)節(jié)是,為了保證事務(wù)請(qǐng)求被順序執(zhí)行,從而確保ZooKeeper集群的數(shù)據(jù)一致性,所有的事務(wù)請(qǐng)求必須由Leader服務(wù)器來處理。但是,并不是所有的ZooKeeper都和Leader服務(wù)器保持連接,那么如何保證所有的事務(wù)請(qǐng)求都由Leader來處理呢?
ZooKeeper實(shí)現(xiàn)了非常特別的事務(wù)請(qǐng)求轉(zhuǎn)發(fā)機(jī)制:所有非Leader服務(wù)器如果接收到了來自客戶端的事務(wù)請(qǐng)求,那么必須將其轉(zhuǎn)發(fā)給Leader服務(wù)器來處理。
8.4 GetData請(qǐng)求
服務(wù)端對(duì)于GetData請(qǐng)求的處理,大體可以分為3大步驟,分別是請(qǐng)求的預(yù)處理、非事務(wù)處理和請(qǐng)求響應(yīng)。
九、小結(jié)
ZooKeeper以樹作為其內(nèi)存數(shù)據(jù)模型,樹上的每一個(gè)節(jié)點(diǎn)是最小的數(shù)據(jù)單元,即ZNode。ZNode具有不同的節(jié)點(diǎn)特性,同時(shí)每個(gè)節(jié)點(diǎn)都具有一個(gè)遞增的版本號(hào),以此可以實(shí)現(xiàn)分布式數(shù)據(jù)的原子性更新。
ZooKeeper的序列化層使用從Hadoop中遺留下來的Jute組件,該組件并不是性能最好的序列化框架,但是在ZooKeeper中已經(jīng)夠用。
ZooKeeper的客戶端和服務(wù)端之間會(huì)建立起TCP長(zhǎng)連接來進(jìn)行網(wǎng)絡(luò)通信,基于該TCP連接衍生出來的會(huì)話概念,是客戶端和服務(wù)端之間所有請(qǐng)求和響應(yīng)交互的基石。在會(huì)話的生命周期中,會(huì)出現(xiàn)連接斷開、重連或是會(huì)話失效等一系列問題,這些都是ZooKeeper的會(huì)話管理器需要處理的問題--Leader服務(wù)器會(huì)負(fù)責(zé)管理每個(gè)會(huì)話的生命周期,包括會(huì)話的創(chuàng)建、心跳檢測(cè)和銷毀等。
在服務(wù)器啟動(dòng)階段,會(huì)進(jìn)行磁盤數(shù)據(jù)的恢復(fù),完成數(shù)據(jù)恢復(fù)后就會(huì)進(jìn)行Leader選舉。一旦選舉產(chǎn)生Leader服務(wù)器后,就立即開始進(jìn)行集群間的數(shù)據(jù)同步--在整個(gè)過程中,ZooKeeper都處于不可用狀態(tài),知道數(shù)據(jù)同步完畢(集群中絕大部分機(jī)器數(shù)據(jù)和Leader一致),ZooKeeper才可以對(duì)外提供正常服務(wù)。在運(yùn)行期間,如果Leader服務(wù)器所在的機(jī)器掛掉或是和集群中絕大部分服務(wù)器斷開連接,那么就會(huì)觸發(fā)新一輪的Leader選舉。同樣,在新的Leader服務(wù)器選舉產(chǎn)生之前,ZooKeeper無法對(duì)外提供服務(wù)。
一個(gè)正常運(yùn)行的ZooKeeper集群,其機(jī)器通常由Leader、Follower和Observer組成。ZooKeeper對(duì)于客戶端請(qǐng)求的處理,嚴(yán)格按照Z(yǔ)AB協(xié)議規(guī)范來進(jìn)行。每個(gè)服務(wù)器在啟動(dòng)初始化階段都會(huì)組裝一個(gè)請(qǐng)求處理鏈,Leader服務(wù)器能夠處理所有類型的客戶端請(qǐng)求,而對(duì)于Follower或是Observer服務(wù)器來說,可以正常處理非事務(wù)請(qǐng)求,而事務(wù)請(qǐng)求則需要轉(zhuǎn)發(fā)給Leader服務(wù)器來處理,同時(shí),對(duì)于每個(gè)事務(wù)請(qǐng)求,Leader都會(huì)為其分配一個(gè)全局唯一且遞增的ZXID,以此來保證事務(wù)處理的順序性。在事務(wù)請(qǐng)求的處理過程中,Leader和Follower服務(wù)器都會(huì)進(jìn)行事務(wù)日志的記錄。
ZooKeeper通過JDK的File接口簡(jiǎn)單實(shí)現(xiàn)了自己的數(shù)據(jù)存儲(chǔ)系統(tǒng),其底層數(shù)據(jù)存儲(chǔ)包括事務(wù)日志和快照數(shù)據(jù)兩部分,這些都是ZooKeeper實(shí)現(xiàn)數(shù)據(jù)一致性非常關(guān)鍵的部分。