寫在前面
小夥伴們大家好,距離上次更新已經有快一個月了。之所以一直沒更新,是因為整個十月份都比較忙。在忙什麼呢?沒錯!整個十月份所有的空閒時間,我都在打遊戲!!因為之前買了一些遊戲一直沒有打通,一直心心念,總感覺像吃飯吃了一半。所以十月份基本所有的空餘時間都在填之前的坑。戰神4,刺客信條:大革命/梟雄,看門狗、美末,基本所有的大作該打的都打完了。但是唯獨有一個,那就是 只狼!!!這個遊戲簡直變態,魂類遊戲的又一次巔峰,可玩性和難度都非常高。所以導致現在遊戲買了半年多,才打到蝴蝶!沒錯,就是第一個BOSS還沒過!太難了,真的太難了。感覺死的次數太多快要吧遊戲裡所有的人都染上龍咳在之前的文章聊聊Zookeeper的會話(上)和聊聊Zookeeper的會話(中篇)兩篇文章中,我們著重介紹了會話在Zookeeper客戶端側的創建和管理策略以及服務端側的管理策略。其中在中篇中,我們還結合會話的超時時間,詳細的介紹了一下Zookeeper在服務端側的會話管理機制。例如會話分桶、會話激活、會話超時檢查、會話清理等。在本章中,我們重點來說一下會話在Zookeeper是如何創建的。ZooKeeper服務端對於會話創建的處理,大體可以分為請求接收、會話創建、預處理、事務處理、事務應用和會話響應6個部分。由於事務處理和事務應用這兩部分涉及到ZAB協議,內容較多。所以我們放到下一篇文章中去講解,本文我們重點關注請求接收、會話創建、預處理、會話響應這四部分內容
在 ZooKeeper中,NIOServerCnxn實例維護每一個客戶端連接,客戶端與服務端的所有通信都是由NIOServerCnxn負責的,其負責統一接收來自客戶端的所有請求,並將請求內容從底層網絡 I/O中完整地讀取出來。核心方法在NIOServerCnxn#doIO()方法中NIOServerCnxn 在負責網絡通信的同時,自然也承擔了客戶端會話的載,每個會話都會對應一個NIOServerCnxn實體。因此,對於每個請求,ZooKeeper 都會檢查當前NIOServerCnxn 實體是否已經被初始化。如果尚未被初始化,那麼就可以確定該客戶端請求一定是"會話創建"請求。在會話創建初期,NIOServerCnxn尚未得到初始化,因此此時的第一個請求必定是"會話創建"請求。具體代碼如下:
initialized是一個布爾變量,默認為false,當第一次初始化完成後會變為true,保證不會重複執行會話創建請求。一旦確定當前客戶端請求是"會話創建"請求,那麼服務端就可以對其進行反序列化,並生成一個ConnectRequest 請求實體。具體代碼在ZooKeeperServer#processConnect方法中。
在ZooKeeper的設計實現中,如果當前ZooKeeper伺服器是以ReadOnly模式啟動的,那麼所有來自非 ReadOnly 型客戶端的請求將無法被處理。因此,針對ConnectRequest,服務端會首先檢查其是否是 ReadOnly 客戶端,並以此來決定是否接受該會話創建請求。具體代碼同樣在ZooKeeperServer#processConnect方法中:
在正常情況下,同一個ZooKeeper集群中,服務端的zxid必定大於客戶端的zxid,因此如果發現客戶端的 zxid值大於服務端的Zxid值,那麼服務端將不接受該客戶端的會話創建請求。zxid是Zookeeper的事務標識id,當Leader產生了⼀個事務,就會為該事務分配⼀個標識符,我們稱之為Zookeeper事務ID。通過Zxid對事務進⾏標識,就可以按照Leader所指定的順序在各個伺服器中按序執⾏。伺服器之間在進行新的Leader選舉時也會交換zxid信息,這樣就可以知道哪個⽆故障伺服器接收了更多的事務,並可以同步他們之間的狀態信息。具體代碼同樣在ZooKeeperServer#processConnect方法中:關於超時這裡我還想再多說一下。ZooKeeper的超時異常包括兩種:
ZooKeeper客戶端的readTimeout無法設置,它的值是根據會話超時時間計算出來的。計算規則為:
當客戶端還未完成連接(即服務端還未完成客戶端會話的創建,未通知客戶端)之前,此時readTimeout為客戶端設置的sessionTimeout * 2 / 3
當客戶端完成連接後,readTimeout為客戶端和服務端協商後sessionTimeout * 2 / 3。協商後的sessionTimeout取值取決於服務端配置的minSessionTimeout和maxSessionTimeout,默認情況下,ZooKeeper服務端對超時時間的限制介於2個 tickTime到20個tickTime之間。即如果我們設置 tickTime值為2000毫秒的話,那麼服務端就會限制客戶端的超時時間,使之介於4秒到40秒之間。如果連接斷開,重連後客戶端使用的sessionTimeout依然使用協商後的sessionTimeout,也就是,通過構造器傳入的sessionTimeout只在建立連接之前使用。
當發生連接丟失時:
客戶端的請求操作將拋出異常
org.apache.zookeeper.KeeperException.ConnectionLossException
2. 客戶端註冊的Watcher也將收到通知Watcher.Event.KeeperState.Disconnected
但是,這種時候一般還未發生會話超時,ZooKeeper客戶端在下次執行請求操作的時候,會先執行自動重連,重新連接成功後,再執行操作請求。因此下一次操作請求一般情況下並不會出現問題。服務端會話超時SessionTimeout導致的客戶端連接失效當客戶端連接丟失時,客戶端首先觸發了連接丟失的異常(該異常在會話超時異常之前被檢測到,因為連接丟失異常是客戶端主動檢測的,而會話超時需要等到客戶端下一次操作時,服務端檢測到會話超時才會通知客戶端)
服務端根據客戶端請求中是否包含sessionID來判斷該客戶端是否需要重新創建會話。如果客戶端請求中已經包含了sessionID,那麼就認為該客戶端正在進行會話重連。在這種情況下,服務端只需要重新打開這個會話,否則需要重新創建。具體代碼同樣在processConnect方法中:至此,請求接收階段的流程已經走完,如果會話需要創建,則進入會話創建階段會話創建
在為客戶端創建會話之前,服務端首先會為每個客戶端都分配一個sessionID。分配方式其實很簡單,每個ZooKeeper伺服器在啟動的時候,都會初始化一個會話管理器(SessionTracker),同時初始化 sessionID,這個在上一篇文章中已經介紹過,這裡就不再贅述了。創建會話最重要的工作就是向SessionTracker中註冊會話。SesionTracker 中維護了兩個比較重要的數據結構,分別是 sessionswithTimeout和sessionsById。前者根據sessionID保存了所有會話的超時時間,而後者則是根據sessiolID保存了所有會話實體。在會話創建初期,就應該將該客戶端會話的相關信息保存到這兩個數據結構中,方便後續會話管理器進行管理。
向SessionTracker註冊完會話後,接下來還需要對會話進行激活操作。激活會話過程涉及ZooKeeper會話管理的分桶策略,這個在上一篇文章中中已經詳細介紹過,這裡也不再贅述。
下面是保存sessionsWithTimeout集合的方法至此,預處理的流程結束,後面便是和zab協議相關的同步流程、Proposal流程、commit流程以及事務應用流程。這些內容我們放到下一篇來介紹。
最後,客戶端請求在經過ZooKeeper服務端處理鏈路的所有請求處理器的處理後,就進入最後的會話響應階段了。會話響應階段非常簡單,大體分為以下4個步驟。
至此,客戶端的"會話創建"請求已經從ZooKeeper 請求處理鏈路上的所有請求處理器間完成了流轉。到這一步,ZooKeeper會計算請求在服務端處理所花費的時間,同時還會統計客戶端連接的一些基本信息,包括lastZxid(最新的ZXID)、lastOp(最後一次和服務端的操作)和lastLatency(最後一次請求處理所花費的時間)等。ConnectResponse 就是一個會話創建成功後的響應,包含了當前客戶端與服務端之間的通信協議版本號protocolVersion、會話超時時間、sessionID和會話密碼。序列化 ConnctResponse。
I/O層發送響應給客戶端。
以上便是服務端創建會話的整個流程,其中關於ZAB協議相關的內容將放在下一篇文章中介紹,敬請期待吧~~