1. 請求接收數據庫
(1) I/O層接收來自客戶端的請求。NIOServerCnxn維護每個客戶端鏈接,客戶端與服務器端的全部通訊都是由NIOServerCnxn負責,其負責統一接收來自客戶端的全部請求,並將請求內容從底層網絡I/O中完整地讀取出來。服務器
(2) 判斷是不是客戶端會話建立請求。每一個會話對應一個NIOServerCnxn實體,對於每一個請求,Zookeeper都會檢查當前NIOServerCnxn實體是否已經被初始化,若是還沒有被初始化,那麼就能夠肯定該客戶端必定是會話建立請求。網絡
(3) 反序列化ConnectRequest請求。一旦肯定客戶端請求是不是會話建立請求,那麼服務端就能夠對其進行反序列化,並生成一個ConnectRequest載體。session
(4) 判斷是不是ReadOnly客戶端。若是當前Zookeeper服務器是以ReadOnly模式啓動,那麼全部來自非ReadOnly型客戶端的請求將沒法被處理。所以,服務端須要先檢查是不是ReadOnly客戶端,並以此來決定是否接受該會話建立請求。數據結構
(5) 檢查客戶端ZXID。正常狀況下,在一個Zookeeper集羣中,服務端的ZXID一定大於客戶端的ZXID,所以若發現客戶端的ZXID大於服務端ZXID,那麼服務端不接受該客戶端的會話建立請求。線程
(6) 協商sessionTimeout。在客戶端向服務器發送超時時間後,服務器會根據本身的超時時間限制最終肯定該會話超時時間,這個過程就是sessionTimeout協商過程。日誌
(7) 判斷是否須要從新激活建立會話。服務端根據客戶端請求中是否包含sessionID來判斷該客戶端是否須要從新建立會話,若客戶單請求中包含sessionID,那麼就認爲該客戶端正在進行會話重連,這種狀況下,服務端只須要從新打開這個會話,不然須要從新建立。server
2. 會話建立對象
(1) 爲客戶端生成sessionID。在爲客戶端建立會話以前,服務端首先會爲每一個客戶端分配一個sessionID,服務端爲客戶端分配的sessionID是全局惟一的。blog
(2) 註冊會話。向SessionTracker中註冊會話,SessionTracker中維護了sessionsWithTimeout和sessionsById,在會話建立初期,會將客戶端會話的相關信息保存到這兩個數據結構中。
(3) 激活會話。激活會話涉及Zookeeper會話管理的分桶策略,其核心是爲會話安排一個區塊,以便會話清理程序可以快速高效地進行會話清理。
(4) 生成會話密碼。服務端在建立一個客戶端會話時,會同時爲客戶端生成一個會話密碼,連同sessionID一同發給客戶端,做爲會話在集羣中不一樣機器間轉移的憑證。
3. 預處理
(1) 將請求交給PrepRequestProcessor處理器處理。在提交給第一個請求處理器以前,Zookeeper會根據該請求所屬的會話,進行一次激活會話操做,以確保當前會話處於激活狀態,完成會話激活後,則提交請求至處理器。
(2) 建立請求事務頭。對於事務請求,Zookeeper會爲其建立請求事務頭,服務端後續的請求處理器都是基於該請求頭來識別當前請求是不是事務請求,請求事務頭包含了一個事務請求最基本的一些信息,包括sessionID、ZXID(事務請求對應的事務ZXID)、CXID(客戶端的操做序列)和請求類型(如create、delete、setData、createSession等)等。
(3) 建立請求事務體。因爲此時是會話建立請求,其事務體是CreateSessionTxn。
(4) 註冊於激活會話。處理由非Leader服務器轉發過來的會話建立請求。
4. 事務處理
(1) 將請求交給ProposalRequestProcessor處理器。與提議相關的處理器,從ProposalRequestProcessor開始,請求的處理將會進入三個子處理流程,分別是Sync流程、Proposal流程、Commit流程。
Sync流程
使用SyncRequestProcessor處理器記錄事務日誌,針對每一個事務請求,都會經過事務日誌的形式將其記錄,完成日誌記錄後,每一個Follower都會向Leader發送ACK消息,代表自身完成了事務日誌的記錄,以便Leader統計每一個事務請求的投票狀況。
Proposal流程
每一個事務請求都須要集羣中過半機器投票承認才能被真正應用到內存數據庫中,這個投票與統計過程就是Proposal流程。
· 發起投票。若當前請求是事務請求,Leader會發起一輪事務投票,在發起事務投票以前,會檢查當前服務端的ZXID是否可用。
· 生成提議Proposal。若ZXID可用,Zookeeper會將已建立的請求頭和事務體以及ZXID和請求自己序列化到Proposal對象中,此Proposal對象就是一個提議。
· 廣播提議。Leader以ZXID做爲標識,將該提議放入投票箱outstandingProposals中,同時將該提議廣播給全部Follower。
· 收集投票。Follower接收到Leader提議後,進入Sync流程進行日誌記錄,記錄完成後,發送ACK消息至Leader服務器,Leader根據這些ACK消息來統計每一個提議的投票狀況,當一個提議得到半數以上投票時,就認爲該提議經過,進入Commit階段。
· 將請求放入toBeApplied隊列中。
· 廣播Commit消息。Leader向Follower和Observer發送COMMIT消息。向Observer發送INFORM消息,向Leader發送ZXID。
Commit流程
· 將請求交付CommitProcessor。CommitProcessor收到請求後,將其放入queuedRequests隊列中。
· 處理queuedRequest隊列請求。CommitProcessor中單獨的線程處理queuedRequests隊列中的請求。
· 標記nextPending。若從queuedRequests中取出的是事務請求,則須要在集羣中進行投票處理,同時將nextPending標記位當前請求。
· 等待Proposal投票。在進行Commit流程的同時,Leader會生成Proposal並廣播給全部Follower服務器,此時,Commit流程等待,直到投票結束。
· 投票經過。若提議得到過半機器承認,則進入請求提交階段,該請求會被放入commitedRequests隊列中,同時喚醒Commit流程。
· 提交請求。若commitedRequests隊列中存在能夠提交的請求,那麼Commit流程則開始提交請求,將請求放入toProcess隊列中,而後交付下一個請求處理器:FinalRequestProcessor。
5. 事務應用
(1) 交付給FinalRequestProcessor處理器。FinalRequestProcessor處理器檢查outstandingChanges隊列中請求的有效性,若發現這些請求已經落後於當前正在處理的請求,那麼直接從outstandingChanges隊列中移除。
(2) 事務應用。以前的請求處理僅僅將事務請求記錄到了事務日誌中,而內存數據庫中的狀態還沒有改變,所以,須要將事務變動應用到內存數據庫。
(3) 將事務請求放入隊列commitProposal。完成事務應用後,則將該請求放入commitProposal隊列中,commitProposal用來保存最近被提交的事務請求,以便集羣間機器進行數據的快速同步。
6. 會話響應
(1) 統計處理。Zookeeper計算請求在服務端處理所花費的時間,統計客戶端鏈接的基本信息,如lastZxid(最新的ZXID)、lastOp(最後一次和服務端的操做)、lastLatency(最後一次請求處理所花費的時間)等。
(2) 建立響應ConnectResponse。會話建立成功後的響應,包含了當前客戶端和服務端之間的通訊協議版本號、會話超時時間、sessionID和會話密碼。
(3) 序列化ConnectResponse。
(4) I/O層發送響應給客戶端。
2.2 SetData請求
服務端對於SetData請求大體能夠分爲四步,預處理、事務處理、事務應用、請求響應。
1. 預處理
(1) I/O層接收來自客戶端的請求。
(2) 判斷是不是客戶端"會話建立"請求。對於SetData請求,按照正常事務請求進行處理。
(3) 將請求交給PrepRequestProcessor處理器進行處理。
(4) 建立請求事務頭。
(5) 會話檢查。檢查該會話是否有效。
(6) 反序列化請求,並建立ChangeRecord記錄。反序列化並生成特定的SetDataRequest請求,請求中包含了數據節點路徑path、更新的內容data和指望的數據節點版本version。同時根據請求對應的path,Zookeeper生成一個ChangeRecord記錄,並放入outstandingChanges隊列中。
(7) ACL檢查。檢查客戶端是否具備數據更新的權限。
(8) 數據版本檢查。經過version屬性來實現樂觀鎖機制的寫入校驗。
(9) 建立請求事務體SetDataTxn。
(10) 保存事務操做到outstandingChanges隊列中。
2. 事務處理
對於事務請求,服務端都會發起事務處理流程。全部事務請求都是由ProposalRequestProcessor處理器處理,經過Sync、Proposal、Commit三個子流程相互協做完成。
3. 事務應用
(1) 交付給FinalRequestProcessor處理器。
(2) 事務應用。將請求事務頭和事務體直接交給內存數據庫ZKDatabase進行事務應用,同時返回ProcessTxnResult對象,包含了數據節點內容更新後的stat。
(3) 將事務請求放入commitProposal隊列。
4. 請求響應
(1) 建立響應體SetDataResponse。其包含了當前數據節點的最新狀態stat。
(2) 建立響應頭。包含當前響應對應的事務ZXID和請求處理是否成功的標識。
(3) 序列化響應。
(4) I/O層發送響應給客戶端。
2.3 GetData請求
服務端對於GetData請求的處理,大體分爲三步,預處理、非事務處理、請求響應。
1. 預處理
(1) I/O層接收來自客戶端的請求。
(2) 判斷是不是客戶端"會話建立"請求。
(3) 將請求交給PrepRequestProcessor處理器進行處理。
(4) 會話檢查。
2. 非事務處理
(1) 反序列化GetDataRequest請求。
(2) 獲取數據節點。
(3) ACL檢查。
(4) 獲取數據內容和stat,註冊Watcher。
3. 請求響應
(1) 建立響應體GetDataResponse。響應體包含當前數據節點的內容和狀態stat。
(2) 建立響應頭。
(3) 統計處理。
(4) 序列化響應。
(5) I/O層發送響應給客戶端。