http://www.cnblogs.com/leesf456/p/6103870.html
一、前言
前面分析了Zookeeper客戶端的細節(jié),接著繼續(xù)學習Zookeeper中的一個非常重要的概念:會話。
二、會話
客戶端與服務端之間任何交互操作都與會話息息相關(guān),如臨時節(jié)點的生命周期、客戶端請求的順序執(zhí)行、Watcher通知機制等。Zookeeper的連接與會話就是客戶端通過實例化Zookeeper對象來實現(xiàn)客戶端與服務端創(chuàng)建并保持TCP連接的過程.
2.1 會話狀態(tài)
在Zookeeper客戶端與服務端成功完成連接創(chuàng)建后,就創(chuàng)建了一個會話,Zookeeper會話在整個運行期間的生命周期中,會在不同的會話狀態(tài)中之間進行切換,這些狀態(tài)可以分為CONNECTING、CONNECTED、RECONNECTING、RECONNECTED、CLOSE等。
一旦客戶端開始創(chuàng)建Zookeeper對象,那么客戶端狀態(tài)就會變成CONNECTING狀態(tài),同時客戶端開始嘗試連接服務端,連接成功后,客戶端狀態(tài)變?yōu)镃ONNECTED,通常情況下,由于斷網(wǎng)或其他原因,客戶端與服務端之間會出現(xiàn)斷開情況,一旦碰到這種情況,Zookeeper客戶端會自動進行重連服務,同時客戶端狀態(tài)再次變成CONNCTING,直到重新連上服務端后,狀態(tài)又變?yōu)镃ONNECTED,在通常情況下,客戶端的狀態(tài)總是介于CONNECTING和CONNECTED之間。但是,如果出現(xiàn)諸如會話超時、權(quán)限檢查或是客戶端主動退出程序等情況,客戶端的狀態(tài)就會直接變更為CLOSE狀態(tài)。

2.2 會話創(chuàng)建
Session是Zookeeper中的會話實體,代表了一個客戶端會話,其包含了如下四個屬性
1. sessionID。會話ID,唯一標識一個會話,每次客戶端創(chuàng)建新的會話時,Zookeeper都會為其分配一個全局唯一的sessionID。
2. TimeOut。會話超時時間,客戶端在構(gòu)造Zookeeper實例時,會配置sessionTimeout參數(shù)用于指定會話的超時時間,Zookeeper客戶端向服務端發(fā)送這個超時時間后,服務端會根據(jù)自己的超時時間限制最終確定會話的超時時間。
3. TickTime。下次會話超時時間點,為了便于Zookeeper對會話實行"分桶策略"管理,同時為了高效低耗地實現(xiàn)會話的超時檢查與清理,Zookeeper會為每個會話標記一個下次會話超時時間點,其值大致等于當前時間加上TimeOut。
4. isClosing。標記一個會話是否已經(jīng)被關(guān)閉,當服務端檢測到會話已經(jīng)超時失效時,會將該會話的isClosing標記為"已關(guān)閉",這樣就能確保不再處理來自該會話的心情求了。
Zookeeper為了保證請求會話的全局唯一性,在SessionTracker初始化時,調(diào)用initializeNextSession方法生成一個sessionID,之后在Zookeeper運行過程中,會在該sessionID的基礎(chǔ)上為每個會話進行分配,初始化算法如下
public static long initializeNextSession(long id) { long nextSid = 0; // 無符號右移8位使為了避免左移24后,再右移8位出現(xiàn)負數(shù)而無法通過高8位確定sid值 nextSid = (System.currentTimeMillis() << 24) >>> 8; nextSid = nextSid | (id << 56); return nextSid; }
其中的id表示配置在myid文件中的值,通常是一個整數(shù),如1、2、3。該算法的高8位確定了所在機器,后56位使用當前時間的毫秒表示進行隨機。SessionTracker是Zookeeper服務端的會話管理器,負責會話的創(chuàng)建、管理和清理等工作。
2.3 會話管理
Zookeeper的會話管理主要是通過SessionTracker來負責,其采用了分桶策略(將類似的會話放在同一區(qū)塊中進行管理)進行管理,以便Zookeeper對會話進行不同區(qū)塊的隔離處理以及同一區(qū)塊的統(tǒng)一處理。

Zookeeper將所有的會話都分配在不同的區(qū)塊一種,分配的原則是每個會話的下次超時時間點(ExpirationTime)。ExpirationTime指該會話最近一次可能超時的時間點。同時,Zookeeper Leader服務器在運行過程中會定時地進行會話超時檢查,時間間隔是ExpirationInterval,默認為tickTime的值,ExpirationTime的計算時間如下
ExpirationTime = ((CurrentTime + SessionTimeOut) / ExpirationInterval + 1) * ExpirationInterval
會了保持客戶端會話的有效性,客戶端會在會話超時時間過期范圍內(nèi)向服務端發(fā)送PING請求來保持會話的有效性(心跳檢測)。同時,服務端需要不斷地接收來自客戶端的心跳檢測,并且需要重新激活對應的客戶端會話,這個重新激活過程稱為TouchSession。會話激活不僅能夠使服務端檢測到對應客戶端的存貨性,同時也能讓客戶端自己保持連接狀態(tài),其流程如下

如上圖所示,整個流程分為四步
1. 檢查該會話是否已經(jīng)被關(guān)閉。若已經(jīng)被關(guān)閉,則直接返回即可。
2. 計算該會話新的超時時間ExpirationTime_New。使用上面提到的公式計算下一次超時時間點。
3. 獲取該會話上次超時時間ExpirationTime_Old。計算該值是為了定位其所在的區(qū)塊。
3. 遷移會話。將該會話從老的區(qū)塊中取出,放入ExpirationTime_New對應的新區(qū)塊中。

在上面會話激活過程中,只要客戶端發(fā)送心跳檢測,服務端就會進行一次會話激活,心跳檢測由客戶端主動發(fā)起,以PING請求形式向服務端發(fā)送,在Zookeeper的實際設計中,只要客戶端有請求發(fā)送到服務端,那么就會觸發(fā)一次會話激活,以下兩種情況都會觸發(fā)會話激活。
1. 客戶端向服務端發(fā)送請求,包括讀寫請求,就會觸發(fā)會話激活。
2. 客戶端發(fā)現(xiàn)在sessionTimeout/3時間內(nèi)尚未和服務端進行任何通信,那么就會主動發(fā)起PING請求,服務端收到該請求后,就會觸發(fā)會話激活。
對于會話的超時檢查而言,Zookeeper使用SessionTracker來負責,SessionTracker使用單獨的線程(超時檢查線程)專門進行會話超時檢查,即逐個一次地對會話桶中剩下的會話進行清理。如果一個會話被激活,那么Zookeeper就會將其從上一個會話桶遷移到下一個會話桶中,如ExpirationTime 1 的session n 遷移到ExpirationTime n 中,此時ExpirationTime 1中留下的所有會話都是尚未被激活的,超時檢查線程就定時檢查這個會話桶中所有剩下的未被遷移的會話,超時檢查線程只需要在這些指定時間點(ExpirationTime 1、ExpirationTime 2...)上進行檢查即可,這樣提高了檢查的效率,性能也非常好。
2.4 會話清理
當SessionTracker的會話超時線程檢查出已經(jīng)過期的會話后,就開始進行會話清理工作,大致可以分為如下七步。
1. 標記會話狀態(tài)為已關(guān)閉。由于會話清理過程需要一段時間,為了保證在此期間不再處理來自該客戶端的請求,SessionTracker會首先將該會話的isClosing標記為true,這樣在會話清理期間接收到該客戶端的心情求也無法繼續(xù)處理了。
2. 發(fā)起會話關(guān)閉請求。為了使對該會話的關(guān)閉操作在整個服務端集群都生效,Zookeeper使用了提交會話關(guān)閉請求的方式,并立即交付給PreRequestProcessor進行處理。
3. 收集需要清理的臨時節(jié)點。一旦某個會話失效后,那么和該會話相關(guān)的臨時節(jié)點都需要被清理,因此,在清理之前,首先需要將服務器上所有和該會話相關(guān)的臨時節(jié)點都整理出來。Zookeeper在內(nèi)存數(shù)據(jù)庫中會為每個會話都單獨保存了一份由該會話維護的所有臨時節(jié)點集合,在Zookeeper處理會話關(guān)閉請求之前,若正好有以下兩類請求到達了服務端并正在處理中。
· 節(jié)點刪除請求,刪除的目標節(jié)點正好是上述臨時節(jié)點中的一個。
· 臨時節(jié)點創(chuàng)建請求,創(chuàng)建的目標節(jié)點正好是上述臨時節(jié)點中的一個。
對于第一類請求,需要將所有請求對應的數(shù)據(jù)節(jié)點路徑從當前臨時節(jié)點列表中移出,以避免重復刪除,對于第二類請求,需要將所有這些請求對應的數(shù)據(jù)節(jié)點路徑添加到當前臨時節(jié)點列表中,以刪除這些即將被創(chuàng)建但是尚未保存到內(nèi)存數(shù)據(jù)庫中的臨時節(jié)點。
4. 添加節(jié)點刪除事務變更。完成該會話相關(guān)的臨時節(jié)點收集后,Zookeeper會逐個將這些臨時節(jié)點轉(zhuǎn)換成"節(jié)點刪除"請求,并放入事務變更隊列outstandingChanges中。
5. 刪除臨時節(jié)點。FinalRequestProcessor會觸發(fā)內(nèi)存數(shù)據(jù)庫,刪除該會話對應的所有臨時節(jié)點。
6. 移除會話。完成節(jié)點刪除后,需要將會話從SessionTracker中刪除。
7. 關(guān)閉NIOServerCnxn。最后,從NIOServerCnxnFactory找到該會話對應的NIOServerCnxn,將其關(guān)閉。
2.5 重連
當客戶端與服務端之間的網(wǎng)絡連接斷開時,Zookeeper客戶端會自動進行反復的重連,直到最終成功連接上Zookeeper集群中的一臺機器。此時,再次連接上服務端的客戶端有可能處于以下兩種狀態(tài)之一
1. CONNECTED。如果在會話超時時間內(nèi)重新連接上集群中一臺服務器 。
2. EXPIRED。如果在會話超時時間以外重新連接上,那么服務端其實已經(jīng)對該會話進行了會話清理操作,此時會話被視為非法會話。
在客戶端與服務端之間維持的是一個長連接,在sessionTimeout時間內(nèi),服務端會不斷地檢測該客戶端是否還處于正常連接,服務端會將客戶端的每次操作視為一次有效的心跳檢測來反復地進行會話激活。因此,在正常情況下,客戶端會話時一直有效的。然而,當客戶端與服務端之間的連接斷開后,用戶在客戶端可能主要看到兩類異常:CONNECTION_LOSS(連接斷開)和SESSION_EXPIRED(會話過期)。
1. CONNECTION_LOSS。此時,客戶端會自動從地址列表中重新逐個選取新的地址并嘗試進行重新連接,直到最終成功連接上服務器。若客戶端在setData時出現(xiàn)了CONNECTION_LOSS現(xiàn)象,此時客戶端會收到None-Disconnected通知,同時會拋出異常。應用程序需要捕捉異常并且等待Zookeeper客戶端自動完成重連,一旦重連成功,那么客戶端會收到None-SyncConnected通知,之后就可以重試setData操作。
2. SESSION_EXPIRED。客戶端與服務端斷開連接后,重連時間耗時太長,超過了會話超時時間限制后沒有成功連上服務器,服務器會進行會話清理,此時,客戶端不知道會話已經(jīng)失效,狀態(tài)還是DISCONNECTED,如果客戶端重新連上了服務器,此時狀態(tài)為SESSION_EXPIRED,用于需要重新實例化Zookeeper對象,并且看應用的復雜情況,重新恢復臨時數(shù)據(jù)。
3. SESSION_MOVED。客戶端會話從一臺服務器轉(zhuǎn)移到另一臺服務器,即客戶端與服務端S1斷開連接后,重連上了服務端S2,此時會話就從S1轉(zhuǎn)移到了S2。當多個客戶端使用相同的sessionId/sessionPasswd創(chuàng)建會話時,會收到SessionMovedException異常。因為一旦有第二個客戶端連接上了服務端,就被認為是會話轉(zhuǎn)移了。
三、總結(jié)
本篇博文介紹了Zookeeper會話的相關(guān)細節(jié),通過本篇的學習理解了會話的細節(jié),也謝謝各位園友的觀看~