#1 系列目錄node
#2 各服務器角色的請求處理器鏈服務器
先介紹下Leader、Follower、Observer服務器的請求處理器鏈微信
##2.1 Leader服務器session
PrepRequestProcessor-》ProposalRequestProcessor-》CommitProcessor-》ToBeAppliedRequestProcessor-》FinalRequestProcessor併發
ProposalRequestProcessor-》SyncRequestProcessor-》AckRequestProcessortcp
下面分別一一介紹.net
1 PrepRequestProcessor:首先爲請求分配請求號zxid,而後對客戶端用戶發送過來的請求或者Follower、Observer轉發過來的請求進行事務的區分,若是是事務請求則建立出事務請求頭,後面的請求處理器就是依據是否有事務請求頭來判斷該請求是不是事務請求。同時進行一些驗證工做如session是否過時,驗證權限等操做。線程
2 ProposalRequestProcessor:主要對事務請求,向全部的Follower服務器發起一個議案,同時觸發SyncRequestProcessor對事務請求的記錄日誌
3 SyncRequestProcessor:對事務請求記錄到事務日誌文件中,記錄完成後觸發AckRequestProcessorserver
4 AckRequestProcessor:對於上述議案,Leader也是投票的一份子,因此也要進行投票響應,只需執行下Leader的響應方法便可。而其餘Follower服務器的投票響應則是須要向Leader發送一個Leader.ACK響應,Leader接收到後,一樣去執行Leader的響應方法
在Leader的響應方法每執行一次,就會判斷是否已通過半機器響應了,若是過半,則Leader向全部的Follower和Observer發送Leader.COMMIT請求,同時Leader也向本身的CommitProcessor中提交該事務請求,即該事務請求是經過過半機器認同的,須要被提交的事務請求。
5 CommitProcessor:對於已經被過半機器認同的請求交給下一個處理器處理。而那些尚未被過半機器認同的,則處於阻塞狀態。
6 ToBeAppliedRequestProcessor:負責將請求交給下一個處理器FinalRequestProcessor,處理完畢後表示已經完成該項議案,而後就刪除了該項議案
7 FinalRequestProcessor:最後一個請求處理器。對於事務請求,執行事務的具體操做,如增刪改node、createSession、closeSession等。對於非事務操做如獲取數據等,從DataTree中獲取相應的數據。最終返回數據給客戶端。
##2.2 Follower服務器
FollowerRequestProcessor-》CommitProcessor-》FinalRequestProcessor
SyncRequestProcessor-》SendAckRequestProcessor
1 FollowerRequestProcessor:首先將請求交給下一個處理器即CommitProcessor處理器,若是是該請求是事務請求或者前面有事務請求在等待處理,則該請求會被阻塞。若是是事務請求,交給CommitProcessor處理器以後,又立馬將該請求轉發給Leader,即事務請求必需要通過Leader,而後Leader又會把該事務請求封裝成一個議案發給各個Follower服務器進行投票,各個Follower服務器接收到Leader發送過來議案後,首先要把這個議案請求記錄到事務日誌中,即調用SyncRequestProcessor來處理
2 CommitProcessor:一旦是事務請求,就須要等待該事務請求被過半數承認,接收到Leader的Leader.COMMIT請求,纔會繼續走下去
3 SyncRequestProcessor:把Leader發送過來的議案記錄到事務日誌中,而後交給下一個處理器SendAckRequestProcessor
4 SendAckRequestProcessor:當日志記錄完成以後,須要給Leader發送一個Leader.ACK響應,表示已經成功記錄在案。
Leader開始統計Follower發送過來的響應,一旦有過半機器發送過來響應,則認爲該事務能夠提交了。而後Leader就向全部的Follower發送Leader.COMMIT請求,帶上以前的請求號,向全部的Observer發送Leader.INFORM請求,帶上以前的整個請求內容,由於Follower在前面已經接收到了該請求,而Observer則沒有接收到該請求,因此要對Observer帶上整個請求內容。
Follower接收到Leader發送過來的Leader.COMMIT請求以後,根據帶過來的請求號,找到真個請求對象,而後放到Follower的CommitProcessor中,使之繼續走下去,交給FinalRequestProcessor
##2.3 Observer服務器
ObserverRequestProcessor-》CommitProcessor-》FinalRequestProcessor
SyncRequestProcessor
其中上述SyncRequestProcessor是經過配置zookeeper.observer.syncEnabled系統屬性的true or false來決定是否須要這個處理器,默認true
因此咱們看到Observer服務器和Follower服務器的處理器鏈基本差很少。不一樣之處就是Follower服務器還有一個SendAckRequestProcessor,向Leader發送投票反饋。而Observer不參與投票,則不須要這個處理器。
以上就大體說完了各個服務器角色的請求處理器鏈,下面就結合具體的請求案例,再來捋一下整個過程
#3 鏈接Leader創建session關聯的過程和session不斷激活的過程
這裏鏈接的服務器以Leader爲例,先說創建session關聯的過程,以後再說session不斷激活的過程
##3.1 創建session關聯的過程
這就須要從用戶建立ZooKeeper對象開始提及。
1 客戶端: 用戶建立ZooKeeper對象,內部建立出ClientCnxn,能夠簡單想象成ZooKeeper對象的內部管家,ClientCnxn有兩個主要的線程SendThread和EventThread
SendThread負責與服務器端的通訊,EventThread負責事件的通知。
1.1 SendThread啓動以後,就從建立ZooKeeper對象的地址列表(被隨機打亂了),取出一個服務器地址進行tcp鏈接操做
1.2 當tcp鏈接鏈接成功以後,就須要和服務器端創建session關聯。依託tcp鏈接,向服務器端發送ConnectRequest請求,會把建立ZooKeeper對象時指定的sessionTimeout時間帶上
2 Leader服務器端: 一旦和服務器端創建tcp鏈接以後,服務器端會給客戶端建立一個ServerCnxn,專門負責與該客戶端的通訊
2.1 當客戶端第一次發送ConnectRequest請求到ServerCnxn中,ServerCnxn首先會對tcp鏈接傳遞過來的數據序列化成ConnectRequest,拿到客戶端傳遞的sessionTimeout時間,因爲服務器端在啓動的時候指定了maxSessionTimeout、minSessionTimeout(即便沒有指定,也會使用默認的),要求客戶端傳遞過來的sessionTimeout時間必須在此二者之間,不符合要求的分別取對應的最大值或者最小值
2.2 而後就使用Leader服務器的SessionTracker(session管理器)根據上面協商後的sessionTimeout時間,分配出sessionId,建立出session
2.3 根據分配的sessionId和剛纔的ServerCnxn建立出一個請求,類型爲OpCode.createSession,將該請求提交到Leader的請求處理器鏈上
2.4 首先遇到的是PrepRequestProcessor處理器,認爲OpCode.createSession請求是一個事務請求,就建立了一個事務請求體,再次執行了session的添加操做,主要是做用於從Follower等轉發過來的建立session的請求。放心不會進行重複添加的,裏面進行來判斷的。
2.5 PrepRequestProcessor處理器執行完畢,交給下一個處理器ProposalRequestProcessor。ProposalRequestProcessor處理器將該建立session的請求立馬交給了下一個處理器CommitProcessor的處理隊列中(該請求被阻塞在那),而後又立馬返回將該請求封裝成一個議案,發送給全部的Follower服務器
2.6 Follower服務器接收到Leader發過來的議案後,使用SyncRequestProcessor將該請求即建立session的請求記錄到事務日誌中,而後交給Follower的下一個處理器SendAckRequestProcessor,使用該處理向Leader發送Leader.ACK反饋
2.7 每收到Follower的一次Leader.ACK反饋,就要統計下是否已通過半數了,若是過半數,則Leader向全部的Follower發送Leader.COMMIT命令,帶上以前的請求號,向全部的Observer發送Leader.INFORM命令,須要帶上以前的整個請求內容。同時Leader也向本身的CommitProcessor提交建立session的請求,CommitProcessor拿到該請求後,再也不阻塞,繼續走向下一個處理器ToBeAppliedRequestProcessor
2.8 ToBeAppliedRequestProcessor將建立session的請求先交給下一個處理器FinalRequestProcessor處理,當FinalRequestProcessor處理完成以後,刪除以前提出的針對該請求的議案
2.9 FinalRequestProcessor針對建立session的請求,會使用SessionTracker再次執行建立(不會重複的,內部進行了判斷,一旦已經有了sessionId對應的session則只須要查看是否過時,若是沒有過時則從新激活session,即從新計算session的過時時間)。而後就Leader從request中取出ServerCnxn,開始準備向客戶端發送響應了。響應內容是sessionId、根據sessionId計算出來的密碼、協商後的sessionTimeout時間
2.10 雖然上述Leader已經對客戶端進行了響應,可是其餘Follower和Observer接收到Leader發送的Leader.COMMIT命令和Leader.INFORM命令,他們接收到上述命令以後,也都從CommitProcessor中走出來了,再也不阻塞在那裏,走向了FinalRequestProcessor。仍然使用SessionTracker再次執行建立session的操做,可是Follower和Observer中的SessionTracker實現是LearnerSessionTracker,而Leader中的SessionTracker是SessionTrackerImpl。
session已經在Leader內部建立出來了,其餘的Follower、Observer僅僅是保存下sessionId和sessionTimeout時間,並無完整的session對象。
所謂的session在全部機器上共享,其實就是Leader中保存着全部的session信息,並負責檢查session的過時。其餘機器只負責簡單保存下sessionId以及對應的sessionTimeout時間,對於session問題下面再詳細說明。
FinalRequestProcessor執行完session添加以後,也從request中取出對應的ServerCnxn,固然爲null,而後就不執行任何操做。只有客戶端所鏈接的那臺服務器纔會有ServerCnxn
3 客戶端: 當客戶端收到服務器端的建立session的響應以後,首先判斷服務器端返回的sessionTimeout時間是否小於等於0
若是小於等於0則表示session建立失敗。向EventThread中發送兩個事件,第一個事件是KeeperState.Expired類型的事件,即session過時事件。第二個事件是eventOfDeath死亡事件,當EventThread收到eventOfDeath事件後,就會結束EventThread線程循環,EventThread線程走向死亡,即ZooKeeper對象再也不可用。
若是大於0則表示建立session成功。向EventThread中發送一個KeeperState.SyncConnected事件
##3.2 session不斷激活的過程
上面說完了創建session關聯的過程,下面就說說session是如何不斷的激活的
由於服務器端,Leader服務器的SessionTrackerImpl,會每隔tickTime時間就會執行一次session過時檢查,若是session沒有及時激活的話,就會過時,就會被SessionTrackerImpl清理掉,對應的客戶端ZooKeeper對象就不可用了。
1 客戶端:客戶端的SendThread線程在循環過程當中,不斷的向服務器端發送Ping請求。操做類型爲OpCode.ping
對於發送頻率,先說明下。
SendThread在循環過程當中發現idleSend時間已經超過了readTimeout的一半了,或者idleSend時間已經超過10s,就會執行一次發送Ping請求。
2 Leader服務器端:爲該客戶端分配的ServerCnxn接收到客戶端的請求後,先將請求封裝成一個Request對象,而後提交該請求到Leader的請求處理器鏈
2.1 在交給請求處理器以前,進行了session的激活操做。
SessionTrackerImpl對於session超時檢查,是進行的分桶策略。以tickTime的整數倍的時間點就是一個桶,存放着在該時間點過時的session。
session被激活的過程就是從某個tickTime的整數倍的時間點對應的桶中移到後面時間點對應的桶中
SessionTrackerImpl會每隔tickTime時間就會執行一次session過時檢查,有了分桶策略就比較方便了,不用遍歷每一個session執行檢查,只須要查看當前時間點對應的桶中是否含有session,若是有則表示該session沒有被及時激活,須要進行過時操做。
session的激活就是先檢查當前session是否過時,若是沒有過時,則從新計算session的過時時間,計算方式就是當前時間加上sessionTimeout時間而後取一個tickTime的整數值,即選擇了後面的一個桶進行存放該session。
2.2 首先是Leader的PrepRequestProcessor處理器:發現該請求是Ping請求,不會建立事務請求體,只會檢查下session是否過時。而後交給下一個處理器ProposalRequestProcessor
2.3 ProposalRequestProcessor對於非事務請求也僅僅是直接交給下一個處理器CommitProcessor
2.4 CommitProcessor中若是沒有正在等待處理的事務請求,則會直接交給下一個處理器ToBeAppliedRequestProcessor。若是有正在被處理的事務請求,則也須要進行等待,感受這裏不是太合理,Ping類型的請求,應該直接經過,不通過任何等待的。
2.5 ToBeAppliedRequestProcessor也沒有作什麼處理,直接交給下一個處理器FinalRequestProcessor
2.6 FinalRequestProcessor針對ping請求,直接進行響應便可。
3 客戶端:客戶端在接收到服務器端返回的ping響應以後也不作什麼操做。
能夠看到當客戶端鏈接的是Leader服務器時,session的不斷激活就是經過客戶端不斷髮送Ping請求給Leader服務器端,從新計算session過時時間達到激活session的目的。能夠看到Follower、Observer都不參與此過程,然而當客戶端鏈接的不是Leader服務器端,就不同了,過程就沒這麼簡單了。後面詳細說明
##3.3 session過時過程
一旦Leader發現某個session過時了,會先從Leader中刪除該session,而後建立一個OpCode.closeSession請求,提交到Leader的請求處理器鏈
1.1 首先是Leader的PrepRequestProcessor處理器,發現session過時是一個事務請求,建立出事務請求頭。而後設置該session的isClosing屬性爲true,而後交給下一個處理器ProposalRequestProcessor
1.2 ProposalRequestProcessor處理器先把該請求交給下一個處理器CommitProcessor,因爲該請求是事務請求,則針對該請求提出一個議案,發給全部的Follower進行投票,其實所謂的投票就是Follower記錄事務請求的過程,記錄成功併發送響應給Leader,就算是一次成功投票。一旦過半數的Follower進行了反饋,Leader就認爲這次事務請求能夠被提交了。而後向全部的Follower發送Leader.COMMIT請求,向全部的Observer發送Leader.INFORM請求。向Leader的CommitProcessor處理器的提交隊列中發送這個closeSession請求。使之繼續往下一個處理器走,下一個處理器即ToBeAppliedRequestProcessor
1.3 ToBeAppliedRequestProcessor也沒有作什麼處理,直接交給下一個處理器FinalRequestProcessor
1.4 在FinalRequestProcessor中對closeSession請求會作如下操做:
首先刪除這個session建立的全部臨時節點,並觸臨時節點刪除的事件,類型爲EventType.NodeDeleted,同時觸發父節點的children變化的事件,類型爲EventType.NodeChildrenChanged。
其次再次從sessionTracker中將該session刪除
最後關閉使用該session建立的ServerCnxn,即斷開了與客戶端的鏈接,至此,Leader就完成了closeSession的整個過程
1.5 closeSession請求在Follower和Observer做爲:仍然是到FinalRequestProcessor執行上述一樣的操做。至此,session在整個集羣中就完全被刪除了。
當客戶端鏈接的是Leader服務器,創建session關聯和session激活、session過時過程比較簡單。一旦是客戶端鏈接的是Follower或者Observer的時候,過程就稍微多了一些。
#4 鏈接Follower創建session關聯的過程和session不斷激活的過程
當客戶端鏈接的是Follower的話,和上面的狀況稍有差異。下面的部份內容和上面有不少重複的地方,爲了方便觀看,重複的部分直接複製過來了,同時要注意不一樣的地方
##4.1 創建session關聯的過程
這就須要從用戶建立ZooKeeper對象開始提及。
1 客戶端: 用戶建立ZooKeeper對象,內部建立出ClientCnxn,能夠簡單想象成ZooKeeper對象的內部管家,ClientCnxn有兩個主要的線程SendThread和EventThread
SendThread負責與服務器端的通訊,EventThread負責事件的通知。
1.1 SendThread啓動以後,就從建立ZooKeeper對象的地址列表(被隨機打亂了),取出一個服務器地址進行tcp鏈接操做
1.2 當tcp鏈接鏈接成功以後,就須要和服務器端創建session關聯。依託tcp鏈接,向服務器端發送ConnectRequest請求,會把建立ZooKeeper對象時指定的sessionTimeout時間帶上
2 Follower服務器端: 一旦和服務器端創建tcp鏈接以後,服務器端會給客戶端建立一個ServerCnxn,專門負責與該客戶端的通訊
2.1 當客戶端第一次發送ConnectRequest請求到ServerCnxn中,ServerCnxn首先會對tcp鏈接傳遞過來的數據序列化成ConnectRequest,拿到客戶端傳遞的sessionTimeout時間,因爲服務器端在啓動的時候指定了maxSessionTimeout、minSessionTimeout(即便沒有指定,也會使用默認的),要求客戶端傳遞過來的sessionTimeout時間必須在此二者之間,不符合要求的分別取對應的最大值或者最小值
2.2 而後就使用Follower服務器的SessionTracker(session管理器)根據上面協商後的sessionTimeout時間,分配出sessionId。
Leader服務器使用的SessionTracker是SessionTrackerImpl,而Follower使用的SessionTracker是LearnerSessionTracker。二者的區別以下:
SessionTrackerImpl:不只分配sessionId,還負責建立session對象,維護session對象,開啓線程不斷檢查session是否過時
LearnerSessionTracker:僅僅分配sessionId,只保存sessionId,沒有session對象。session對象的建立都是在Leader的SessionTrackerImpl中建立的
2.3 根據分配的sessionId和剛纔的ServerCnxn建立出一個請求,類型爲OpCode.createSession,將該請求提交到Follower的請求處理器鏈上
2.4 首先遇到的是Follower服務器的FollowerRequestProcessor處理器,它先將該請求交給下一個處理器CommitProcessor,而後會阻塞。因爲建立session請求是事務請求,則這個Follower會把該請求轉發給Leader服務器
3 Leader服務器端:Leader服務器爲上述Follower服務器分配的LearnerHandler會收到來自Follower服務器的上述建立session的請求,LearnerHandler把該請求交給了Leader請求處理器鏈來處理了
3.1 首先遇到的是PrepRequestProcessor處理器,認爲OpCode.createSession請求是一個事務請求,就建立了一個事務請求體,根據請求傳遞過來的sessionId和sessionTimeout時間,使用Leader服務器的SessionTrackerImpl建立出了session,保存來起來。而後就交給了下一個處理器ProposalRequestProcessor來處理
3.2 ProposalRequestProcessor處理器將該建立session的請求立馬交給了下一個處理器CommitProcessor的處理隊列中(該請求被阻塞在那),而後又立馬返回將該請求封裝成一個議案,發送給全部的Follower服務器
3.3 Follower服務器接收到Leader發過來的議案後,使用SyncRequestProcessor將該請求即建立session的請求記錄到事務日誌中,而後交給Follower的下一個處理器SendAckRequestProcessor,使用該處理向Leader發送Leader.ACK反饋
3.4 每收到Follower的一次Leader.ACK反饋,就要統計下是否已通過半數了,若是過半數,則Leader向全部的Follower發送Leader.COMMIT命令,帶上以前的請求號,向全部的Observer發送Leader.INFORM命令,須要帶上以前的整個請求內容。同時Leader也向本身的CommitProcessor提交建立session的請求,CommitProcessor拿到該請求後,再也不阻塞,繼續走向下一個處理器ToBeAppliedRequestProcessor
3.5 ToBeAppliedRequestProcessor將建立session的請求先交給下一個處理器FinalRequestProcessor處理,當FinalRequestProcessor處理完成以後,刪除以前提出的針對該請求的議案
3.6 FinalRequestProcessor針對建立session的請求,會使用SessionTracker再次執行建立(不會重複的,內部進行了判斷,一旦已經有了sessionId對應的session則只須要查看是否過時,若是沒有過時則從新激活session,即從新計算session的過時時間)。而後就Leader從request中取出ServerCnxn,發現爲null(只有客戶端鏈接的那臺服務器的纔會有對應的ServerCnxn),就什麼也不執行。至此Leader的任務已經完成。
4 Follower和Observer服務器:他們分別接收到Leader的Leader.COMMIT、Leader.INFORM命令以後,就會在他們的CommitProcessor處理器的提交隊列中接收到建立session的請求,而後交給下一個處理器即FinalRequestProcessor處理器來執行建立session的具體內容
Follower和Observer服務器都使用LearnerSessionTracker記錄下這個sessionId和對應的sessionTimeout時間。至此,全部的服務器上都保存了這個建立的sessionId了。
而後其餘的一些Observer和Follower從request中取出ServerCnxn,發現都爲null,什麼也不操做。只有客戶端鏈接的這臺Follower取出的ServerCnxn是有值的,須要給客戶端反饋建立session的響應了。
響應內容就是sessionTimeout、sessionId、根據sessionId計算出的密碼。
5 客戶端: 當客戶端收到Follower服務器端的建立session的響應以後,首先判斷服務器端返回的sessionTimeout時間是否小於等於0
若是小於等於0則表示session建立失敗。向EventThread中發送兩個事件,第一個事件是KeeperState.Expired類型的事件,即session過時事件。第二個事件是eventOfDeath死亡事件,當EventThread收到eventOfDeath事件後,就會結束EventThread線程循環,EventThread線程走向死亡,即ZooKeeper對象再也不可用。
若是大於0則表示建立session成功。向EventThread中發送一個KeeperState.SyncConnected事件
##4.2 session不斷激活的過程
客戶端仍是同樣,按期向鏈接的服務器這裏是Follower服務器,發送Ping請求
1 客戶端:客戶端的SendThread線程在循環過程當中,不斷的向服務器端發送Ping請求。操做類型爲OpCode.ping
對於發送頻率,先說明下。
SendThread在循環過程當中發現idleSend時間已經超過了readTimeout的一半了,或者idleSend時間已經超過10s,就會執行一次發送Ping請求。
2 Follower服務器端:爲該客戶端分配的ServerCnxn接收到客戶端的Ping請求後,先將請求封裝成一個Request對象,而後提交該請求到Follower的請求處理器鏈
2.1 在交給請求處理器以前,進行了session的激活操做。
LearnerSessionTracker對session的激活,僅僅是把該sessionId和sessionTimeout時間放到另外一個HashMap<Long, Integer> touchTable結構中,僅此而已。其實咱們想要達到的目的是:可以在Leader服務器上進行真正的session激活,否則的話,Leader服務器在不斷檢查session過時,一旦沒有及時激活就會刪除該session的。先提出一個疑問:客戶端的Ping請求如何來激活Leader服務器上保存的session呢?
2.2 首先是Follower的FollowerRequestProcessor處理器:直接把該請求交給下一個處理器CommitProcessor,因爲Ping請求並非事務請求,則不會把該請求轉發給Leader服務器
2.3 CommitProcessor中若是沒有正在等待處理的事務請求,則會直接交給下一個處理器FinalRequestProcessor。若是有正在被處理的事務請求,則也須要進行等待,感受這裏不是太合理,Ping類型的請求,應該直接經過,不通過任何等待的。
2.4 FinalRequestProcessor針對ping請求,直接發送進行響應。
3 客戶端:客戶端在接收到服務器端返回的ping響應以後也不作什麼操做。
至此整個客戶端的Ping請求就處理完成了。上面的疑問還沒解決呢?此次Ping請求的目的還沒達到呢?
還有一個過程以下:
1 Leader會在一個while循環中,經過循環遍歷全部的LearnerHandler不斷的向全部的其餘服務器發送Ping請求,用於檢測Leader和其餘服務器之間的正常鏈接,一旦超時沒有獲得及時的反饋,則就會刪除該LearnerHandler。一旦LearnerHandler的數量小於最初總數量的一半,則認爲該Leader該下臺了,須要從新選舉Leader了。
2 Follower和Observer接收到Leader的Ping請求以後,就會把LearnerSessionTracker中的touchTable(裏面的這些sessionId都是須要從新被激活的)所有傳遞給Leader
3 Leader接收到這些須要被激活的session後,一個一個挨個的從新計算了session的超時時間。至此,客戶端的Ping請求終於達到效果了
#5 結束語
本篇文章,詳細描述了鏈接不一樣服務器建立session的過程,以及session的激活過程和session過時過程。下一篇就開始講述Leader的選舉過程
歡迎關注微信公衆號:乒乓狂魔