一、前言
在前面學(xué)習(xí)了Zookeeper中服務(wù)器的三種角色及其之間的通信,接著學(xué)習(xí)對(duì)于客戶端的一次請(qǐng)求,Zookeeper是如何進(jìn)行處理的。
二、請(qǐng)求處理
2.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)六大環(huán)節(jié),其大體流程如
1. 請(qǐng)求接收
(1) I/O層接收來(lái)自客戶端的請(qǐng)求。NIOServerCnxn維護(hù)每一個(gè)客戶端連接,客戶端與服務(wù)器端的所有通信都是由NIOServerCnxn負(fù)責(zé),其負(fù)責(zé)統(tǒng)一接收來(lái)自客戶端的所有請(qǐng)求,并將請(qǐng)求內(nèi)容從底層網(wǎng)絡(luò)I/O中完整地讀取出來(lái)。
(2) 判斷是否是客戶端會(huì)話創(chuàng)建請(qǐng)求。每個(gè)會(huì)話對(duì)應(yīng)一個(gè)NIOServerCnxn實(shí)體,對(duì)于每個(gè)請(qǐng)求,Zookeeper都會(huì)檢查當(dāng)前NIOServerCnxn實(shí)體是否已經(jīng)被初始化,如果尚未被初始化,那么就可以確定該客戶端一定是會(huì)話創(chuàng)建請(qǐng)求。
(3) 反序列化ConnectRequest請(qǐng)求。一旦確定客戶端請(qǐng)求是否是會(huì)話創(chuàng)建請(qǐng)求,那么服務(wù)端就可以對(duì)其進(jìn)行反序列化,并生成一個(gè)ConnectRequest載體。
(4) 判斷是否是ReadOnly客戶端。如果當(dāng)前Zookeeper服務(wù)器是以ReadOnly模式啟動(dòng),那么所有來(lái)自非ReadOnly型客戶端的請(qǐng)求將無(wú)法被處理。因此,服務(wù)端需要先檢查是否是ReadOnly客戶端,并以此來(lái)決定是否接受該會(huì)話創(chuàng)建請(qǐng)求。
(5) 檢查客戶端ZXID。正常情況下,在一個(gè)Zookeeper集群中,服務(wù)端的ZXID必定大于客戶端的ZXID,因此若發(fā)現(xiàn)客戶端的ZXID大于服務(wù)端ZXID,那么服務(wù)端不接受該客戶端的會(huì)話創(chuàng)建請(qǐng)求。
(6) 協(xié)商sessionTimeout。在客戶端向服務(wù)器發(fā)送超時(shí)時(shí)間后,服務(wù)器會(huì)根據(jù)自己的超時(shí)時(shí)間限制最終確定該會(huì)話超時(shí)時(shí)間,這個(gè)過(guò)程就是sessionTimeout協(xié)商過(guò)程。
(7) 判斷是否需要重新激活創(chuàng)建會(huì)話。服務(wù)端根據(jù)客戶端請(qǐng)求中是否包含sessionID來(lái)判斷該客戶端是否需要重新創(chuàng)建會(huì)話,若客戶單請(qǐng)求中包含sessionID,那么就認(rèn)為該客戶端正在進(jìn)行會(huì)話重連,這種情況下,服務(wù)端只需要重新打開(kāi)這個(gè)會(huì)話,否則需要重新創(chuàng)建。
2. 會(huì)話創(chuàng)建
(1) 為客戶端生成sessionID。在為客戶端創(chuàng)建會(huì)話之前,服務(wù)端首先會(huì)為每個(gè)客戶端分配一個(gè)sessionID,服務(wù)端為客戶端分配的sessionID是全局唯一的。
(2) 注冊(cè)會(huì)話。向SessionTracker中注冊(cè)會(huì)話,SessionTracker中維護(hù)了sessionsWithTimeout和sessionsById,在會(huì)話創(chuàng)建初期,會(huì)將客戶端會(huì)話的相關(guān)信息保存到這兩個(gè)數(shù)據(jù)結(jié)構(gòu)中。
(3) 激活會(huì)話。激活會(huì)話涉及Zookeeper會(huì)話管理的分桶策略,其核心是為會(huì)話安排一個(gè)區(qū)塊,以便會(huì)話清理程序能夠快速高效地進(jìn)行會(huì)話清理。
(4) 生成會(huì)話密碼。服務(wù)端在創(chuàng)建一個(gè)客戶端會(huì)話時(shí),會(huì)同時(shí)為客戶端生成一個(gè)會(huì)話密碼,連同sessionID一同發(fā)給客戶端,作為會(huì)話在集群中不同機(jī)器間轉(zhuǎn)移的憑證。
3. 預(yù)處理
(1) 將請(qǐng)求交給PrepRequestProcessor處理器處理。在提交給第一個(gè)請(qǐng)求處理器之前,Zookeeper會(huì)根據(jù)該請(qǐng)求所屬的會(huì)話,進(jìn)行一次激活會(huì)話操作,以確保當(dāng)前會(huì)話處于激活狀態(tài),完成會(huì)話激活后,則提交請(qǐng)求至處理器。
(2) 創(chuàng)建請(qǐng)求事務(wù)頭。對(duì)于事務(wù)請(qǐng)求,Zookeeper會(huì)為其創(chuàng)建請(qǐng)求事務(wù)頭,服務(wù)端后續(xù)的請(qǐng)求處理器都是基于該請(qǐng)求頭來(lái)識(shí)別當(dāng)前請(qǐng)求是否是事務(wù)請(qǐng)求,請(qǐng)求事務(wù)頭包含了一個(gè)事務(wù)請(qǐng)求最基本的一些信息,包括sessionID、ZXID(事務(wù)請(qǐng)求對(duì)應(yīng)的事務(wù)ZXID)、CXID(客戶端的操作序列)和請(qǐng)求類(lèi)型(如create、delete、setData、createSession等)等。
(3) 創(chuàng)建請(qǐng)求事務(wù)體。由于此時(shí)是會(huì)話創(chuàng)建請(qǐng)求,其事務(wù)體是CreateSessionTxn。
(4) 注冊(cè)于激活會(huì)話。處理由非Leader服務(wù)器轉(zhuǎn)發(fā)過(guò)來(lái)的會(huì)話創(chuàng)建請(qǐng)求。
4. 事務(wù)處理
(1) 將請(qǐng)求交給ProposalRequestProcessor處理器。與提議相關(guān)的處理器,從ProposalRequestProcessor開(kāi)始,請(qǐng)求的處理將會(huì)進(jìn)入三個(gè)子處理流程,分別是Sync流程、Proposal流程、Commit流程。
Sync流程
使用SyncRequestProcessor處理器記錄事務(wù)日志,針對(duì)每個(gè)事務(wù)請(qǐng)求,都會(huì)通過(guò)事務(wù)日志的形式將其記錄,完成日志記錄后,每個(gè)Follower都會(huì)向Leader發(fā)送ACK消息,表明自身完成了事務(wù)日志的記錄,以便Leader統(tǒng)計(jì)每個(gè)事務(wù)請(qǐng)求的投票情況。
Proposal流程
每個(gè)事務(wù)請(qǐng)求都需要集群中過(guò)半機(jī)器投票認(rèn)可才能被真正應(yīng)用到內(nèi)存數(shù)據(jù)庫(kù)中,這個(gè)投票與統(tǒng)計(jì)過(guò)程就是Proposal流程。
· 發(fā)起投票。若當(dāng)前請(qǐng)求是事務(wù)請(qǐng)求,Leader會(huì)發(fā)起一輪事務(wù)投票,在發(fā)起事務(wù)投票之前,會(huì)檢查當(dāng)前服務(wù)端的ZXID是否可用。
· 生成提議Proposal。若ZXID可用,Zookeeper會(huì)將已創(chuàng)建的請(qǐng)求頭和事務(wù)體以及ZXID和請(qǐng)求本身序列化到Proposal對(duì)象中,此Proposal對(duì)象就是一個(gè)提議。
· 廣播提議。Leader以ZXID作為標(biāo)識(shí),將該提議放入投票箱outstandingProposals中,同時(shí)將該提議廣播給所有Follower。
· 收集投票。Follower接收到Leader提議后,進(jìn)入Sync流程進(jìn)行日志記錄,記錄完成后,發(fā)送ACK消息至Leader服務(wù)器,Leader根據(jù)這些ACK消息來(lái)統(tǒng)計(jì)每個(gè)提議的投票情況,當(dāng)一個(gè)提議獲得半數(shù)以上投票時(shí),就認(rèn)為該提議通過(guò),進(jìn)入Commit階段。
· 將請(qǐng)求放入toBeApplied隊(duì)列中。
· 廣播Commit消息。Leader向Follower和Observer發(fā)送COMMIT消息。向Observer發(fā)送INFORM消息,向Leader發(fā)送ZXID。
Commit流程
· 將請(qǐng)求交付CommitProcessor。CommitProcessor收到請(qǐng)求后,將其放入queuedRequests隊(duì)列中。
· 處理queuedRequest隊(duì)列請(qǐng)求。CommitProcessor中單獨(dú)的線程處理queuedRequests隊(duì)列中的請(qǐng)求。
· 標(biāo)記nextPending。若從queuedRequests中取出的是事務(wù)請(qǐng)求,則需要在集群中進(jìn)行投票處理,同時(shí)將nextPending標(biāo)記位當(dāng)前請(qǐng)求。
· 等待Proposal投票。在進(jìn)行Commit流程的同時(shí),Leader會(huì)生成Proposal并廣播給所有Follower服務(wù)器,此時(shí),Commit流程等待,直到投票結(jié)束。
· 投票通過(guò)。若提議獲得過(guò)半機(jī)器認(rèn)可,則進(jìn)入請(qǐng)求提交階段,該請(qǐng)求會(huì)被放入commitedRequests隊(duì)列中,同時(shí)喚醒Commit流程。
· 提交請(qǐng)求。若commitedRequests隊(duì)列中存在可以提交的請(qǐng)求,那么Commit流程則開(kāi)始提交請(qǐng)求,將請(qǐng)求放入toProcess隊(duì)列中,然后交付下一個(gè)請(qǐng)求處理器:FinalRequestProcessor。
5. 事務(wù)應(yīng)用
(1) 交付給FinalRequestProcessor處理器。FinalRequestProcessor處理器檢查outstandingChanges隊(duì)列中請(qǐng)求的有效性,若發(fā)現(xiàn)這些請(qǐng)求已經(jīng)落后于當(dāng)前正在處理的請(qǐng)求,那么直接從outstandingChanges隊(duì)列中移除。
(2) 事務(wù)應(yīng)用。之前的請(qǐng)求處理僅僅將事務(wù)請(qǐng)求記錄到了事務(wù)日志中,而內(nèi)存數(shù)據(jù)庫(kù)中的狀態(tài)尚未改變,因此,需要將事務(wù)變更應(yīng)用到內(nèi)存數(shù)據(jù)庫(kù)。
(3) 將事務(wù)請(qǐng)求放入隊(duì)列commitProposal。完成事務(wù)應(yīng)用后,則將該請(qǐng)求放入commitProposal隊(duì)列中,commitProposal用來(lái)保存最近被提交的事務(wù)請(qǐng)求,以便集群間機(jī)器進(jìn)行數(shù)據(jù)的快速同步。
6. 會(huì)話響應(yīng)
(1) 統(tǒng)計(jì)處理。Zookeeper計(jì)算請(qǐng)求在服務(wù)端處理所花費(fèi)的時(shí)間,統(tǒng)計(jì)客戶端連接的基本信息,如lastZxid(最新的ZXID)、lastOp(最后一次和服務(wù)端的操作)、lastLatency(最后一次請(qǐng)求處理所花費(fèi)的時(shí)間)等。
(2) 創(chuàng)建響應(yīng)ConnectResponse。會(huì)話創(chuàng)建成功后的響應(yīng),包含了當(dāng)前客戶端和服務(wù)端之間的通信協(xié)議版本號(hào)、會(huì)話超時(shí)時(shí)間、sessionID和會(huì)話密碼。
(3) 序列化ConnectResponse。
(4) I/O層發(fā)送響應(yīng)給客戶端。
2.2 SetData請(qǐng)求
服務(wù)端對(duì)于SetData請(qǐng)求大致可以分為四步,預(yù)處理、事務(wù)處理、事務(wù)應(yīng)用、請(qǐng)求響應(yīng)。
1. 預(yù)處理
(1) I/O層接收來(lái)自客戶端的請(qǐng)求。
(2) 判斷是否是客戶端"會(huì)話創(chuàng)建"請(qǐng)求。對(duì)于SetData請(qǐng)求,按照正常事務(wù)請(qǐng)求進(jìn)行處理。
(3) 將請(qǐng)求交給PrepRequestProcessor處理器進(jìn)行處理。
(4) 創(chuàng)建請(qǐng)求事務(wù)頭。
(5) 會(huì)話檢查。檢查該會(huì)話是否有效。
(6) 反序列化請(qǐng)求,并創(chuàng)建ChangeRecord記錄。反序列化并生成特定的SetDataRequest請(qǐng)求,請(qǐng)求中包含了數(shù)據(jù)節(jié)點(diǎn)路徑path、更新的內(nèi)容data和期望的數(shù)據(jù)節(jié)點(diǎn)版本version。同時(shí)根據(jù)請(qǐng)求對(duì)應(yīng)的path,Zookeeper生成一個(gè)ChangeRecord記錄,并放入outstandingChanges隊(duì)列中。
(7) ACL檢查。檢查客戶端是否具有數(shù)據(jù)更新的權(quán)限。
(8) 數(shù)據(jù)版本檢查。通過(guò)version屬性來(lái)實(shí)現(xiàn)樂(lè)觀鎖機(jī)制的寫(xiě)入校驗(yàn)。
(9) 創(chuàng)建請(qǐng)求事務(wù)體SetDataTxn。
(10) 保存事務(wù)操作到outstandingChanges隊(duì)列中。
2. 事務(wù)處理
對(duì)于事務(wù)請(qǐng)求,服務(wù)端都會(huì)發(fā)起事務(wù)處理流程。所有事務(wù)請(qǐng)求都是由ProposalRequestProcessor處理器處理,通過(guò)Sync、Proposal、Commit三個(gè)子流程相互協(xié)作完成。
3. 事務(wù)應(yīng)用
(1) 交付給FinalRequestProcessor處理器。
(2) 事務(wù)應(yīng)用。將請(qǐng)求事務(wù)頭和事務(wù)體直接交給內(nèi)存數(shù)據(jù)庫(kù)ZKDatabase進(jìn)行事務(wù)應(yīng)用,同時(shí)返回ProcessTxnResult對(duì)象,包含了數(shù)據(jù)節(jié)點(diǎn)內(nèi)容更新后的stat。
(3) 將事務(wù)請(qǐng)求放入commitProposal隊(duì)列。
4. 請(qǐng)求響應(yīng)
(1) 創(chuàng)建響應(yīng)體SetDataResponse。其包含了當(dāng)前數(shù)據(jù)節(jié)點(diǎn)的最新?tīng)顟B(tài)stat。
(2) 創(chuàng)建響應(yīng)頭。包含當(dāng)前響應(yīng)對(duì)應(yīng)的事務(wù)ZXID和請(qǐng)求處理是否成功的標(biāo)識(shí)。
(3) 序列化響應(yīng)。
(4) I/O層發(fā)送響應(yīng)給客戶端。
2.3 GetData請(qǐng)求
服務(wù)端對(duì)于GetData請(qǐng)求的處理,大致分為三步,預(yù)處理、非事務(wù)處理、請(qǐng)求響應(yīng)。
1. 預(yù)處理
(1) I/O層接收來(lái)自客戶端的請(qǐng)求。
(2) 判斷是否是客戶端"會(huì)話創(chuàng)建"請(qǐng)求。
(3) 將請(qǐng)求交給PrepRequestProcessor處理器進(jìn)行處理。
(4) 會(huì)話檢查。
2. 非事務(wù)處理
(1) 反序列化GetDataRequest請(qǐng)求。
(2) 獲取數(shù)據(jù)節(jié)點(diǎn)。
(3) ACL檢查。
(4) 獲取數(shù)據(jù)內(nèi)容和stat,注冊(cè)Watcher。
3. 請(qǐng)求響應(yīng)
(1) 創(chuàng)建響應(yīng)體GetDataResponse。響應(yīng)體包含當(dāng)前數(shù)據(jù)節(jié)點(diǎn)的內(nèi)容和狀態(tài)stat。
(2) 創(chuàng)建響應(yīng)頭。
(3) 統(tǒng)計(jì)處理。
(4) 序列化響應(yīng)。
(5) I/O層發(fā)送響應(yīng)給客戶端。
三、總結(jié)
本篇博文講解了Zookeeper服務(wù)端對(duì)于客戶端不同請(qǐng)求的處理的具體流程,可能從文字上看步驟會(huì)顯得相對(duì)枯燥,但是會(huì)為之后的源碼分析打下很好的基礎(chǔ),謝謝各位園友的觀看~