Chapter 9 ZooKeeper 內部原理

  • 羣首(leader):做爲中心點處理全部對ZooKeeper系統變動的請求,它就像一個定序器,創建了全部對ZooKeeper狀態的更新的順序。
  • 追隨者(follower):接收羣首所發出更新操做請求,並對這些請求進行處理,以此來保障狀態更新操做不會發生碰撞。
  • 觀察者(observer):不會參與決策哪些請求可被接受的過程,只是觀察決策的結果,觀察者的設計只是爲了系統的可擴展性。

1、請求、事務和標識符

  • 讀請求(exists、getData和getChildren):本地處理
  • 寫請求(create、delete和setData):被轉發給羣首,羣首執行相應的請求,並造成狀態的更新,稱爲事務(transaction)node

    • 一個事務爲一個單位,也就是說全部的變動處理須要以原子方式執行
    • ZooKeeper集羣以事務方式運行,並確保全部的變動操做以原子方式被執行,同時不會被其餘事務所幹擾
    • 同時一個事務還具備冪等性
    • 當羣首產生了一個事務,就會爲該事務分配一個標識符,稱之爲ZooKeeper會話ID(zxid),經過Zxid對事務進行標識,就能夠按照羣首所指定的順序在各個服務器中按序執行。
    • 服務器之間在進行新的羣首選舉時也會交換zxid信息,這樣就能夠知道哪一個無端障服務器接收了更多的事務,並能夠同步他們之間的狀態信息。
    • zxid爲一個long型(64位)整數,分爲兩部分:時間戳(epoch)部分和計數器(counter)部分,每一個部分爲32位。
    • zab協議,經過該協議來廣播各個服務器的狀態變動信息

2、羣首選舉

設置羣首的目的是爲了對客戶端所發起的ZooKeeper狀態變動請求進行排序,包括:create、setData和delete操做。算法

羣首將每個請求轉換爲一個事務,將這些事務發送給追隨者,確保集羣按照羣首肯定的順序接受並處理這些事務。apache

LOOKING --> LEADING --> FOLLOWING服務器

當一個服務器收到一個投票信息,該服務器將會根據如下規則修改本身的投票信息:網絡

  • 一、將接收的voteId和voteZxid做爲一個標識符,並獲取接收方當前的投票中的zxid,用myZxid和mySid表示接收方服務器本身的值。
  • 二、若是(voteZxid > myZxid)或者(voteZxid = myZxid且voteId > mySid),保留當前的投票信息。
  • 三、不然,修改本身的投票信息,將voteZxid賦值給myZxid,將voteId賦值給mySid。
簡而言之,只有最新的服務器將贏得選舉,由於其擁有最近一次的zxid。

【圖9-1:羣首選舉過程的示例】
clipboard.pngsession

圖9-1展現了三個服務器,這三個服務器分別以不一樣的初始投票值開始,其投票值取決於該服務器的標識符和其最新的zxid。每一個服務器會收到另外兩個服務器發送的投票信息,在第一輪以後,服務器s2和服務器s3將會改變其投票值爲(1,6),以後服務器服務器s2和服務器s3在改變投票值以後會發送新的通知消息,在接收到這些新的通知消息後,每一個服務器收到的仲裁數量的通知消息擁有同樣的投票值,最後選舉出服務器s1爲羣首。

【圖9-2:消息交錯致使一個服務器選擇了另外一個羣首】
clipboard.png數據結構

在圖9-2中,展現了另外一種狀況的例子。服務器 s2 作出了錯誤判斷,選舉了另外一個服務器 s3 而不是服務器 s1 ,雖然 s1 的zxid值更高,但在從服務器 s1 向服務器 s2 傳送消息時發生了網絡故障致使長時間延遲,與此同時,服務器 s2 選擇了服務器s 3 做爲羣首,最終,服務器 s1 和服務器 s3 組成了仲裁數量(quorum),並將忽略服務器 s2 。

【圖9-3:羣首選舉時的長延遲】
clipboard.png併發

若是想實現一個新的羣首選舉的算法,須要實現一個quorum 包中的Election接口。爲了可讓用戶本身選擇羣首選舉的實現,代碼中使用了簡單的整數標識符(請查看代碼中QuorumPeer.createElectionAlgorithm()),另外兩種可選的實現方式爲LeaderElection類和AuthFastLeaderElection類

3、Zab:狀態更新的廣播協議

在接收到一個寫請求操做後,追隨者會將請求轉發給羣首,羣首將探索性地執行該請求,並將執行結果以事務的方式對狀態更新進行廣播。

一個事務中包含服務器須要執行變動的確切操做,當事務提交時,服務器就會將這些變動反饋到數據樹上,其中數據樹爲ZooKeeper用於保存狀態信息的數據結構(請參考DataTree類)。ide

以後須要面對的問題即是服務器如何確認一個事務是否已經提交,由此引入了所採用的協議:
Zab:ZooKeeper原子廣播協議(ZooKeeper Atomic Broadcast protocol)。oop

假設如今有一個活動的羣首服務器,並擁有仲裁數量的追隨者支持該羣首的管理權,經過該協議提交一個事務很是簡單,相似於一個兩階段提交。

【圖9-4:提交提案的常規消息模式】
clipboard.png

  • 一、羣首向全部追隨者發送一個PROPOSAL消息p。
  • 二、當一個追隨者接收到消息p後,會響應羣首一個ACK消息,通知羣首其已接受該提案(proposal)。
  • 三、當收到仲裁數量的服務器發送的確認消息後(該仲裁數包括羣首本身),羣首就會發送消息通知追隨者進行提交(COMMIT)操做。
在應答提案消息以前,追隨者還須要執行一些檢查操做。追隨者將會檢查所發送的提案消息是否屬於其所追隨的羣首,並確認羣首所廣播的提案消息和提交事務消失的順序正確。

【Zab保障瞭如下幾個重要屬性:】

  • 若是羣首按順序廣播了事務T和事務T,那麼每一個服務器在提交T事務前保證事務T已經提交完成。
  • 若是某個服務器按照事務T、事務T的順序提交事務,全部其餘服務器也必然會在提交事務T前提交事務T。

第一個屬性保證事務在服務器之間的傳送順序的一致,而第二個豎向地保證服務器不會跳過任何事務。

多個併發的羣首可能會致使服務器提交事務的順序發生錯誤,或者直接跳過了某些事務。爲了阻止系統中同時出現兩個服務器自認爲本身是羣首的狀況是很是困難的,時間問題或消息丟失均可能致使這種狀況,所以廣播協議並不能基於以上假設。

【爲了解決這個問題,Zab協議提供瞭如下保障】:

  • 一個被選舉的羣首確保在提交完全部以前的時間戳內須要提交的事務,以後纔開始廣播新的事務。
  • 在任什麼時候間點,都不會出現兩個被仲裁支持的羣首。

第一個需求,羣首並不會立刻處於活動狀態,直到確保仲裁數量的服務器承認這個羣首新的時間戳值。一個時間戳的最初狀態必
須包含全部的以前已經提交的事務,或者某些已經被其餘服務器接受,但還沒有提交完成的事務。這一點很是重要,在羣首進行時間戳e的任何新的提案前,必須保證自時間戳開始值到時間戳e-1內的全部提案被提交。若是一個提案消息處於時間戳e'<e,在羣首處理時間戳e的第一個提案消息前沒有提交以前的這個提案,那麼舊的提案將永遠不會被提交。

第二個需求有些棘手,由於並不能徹底阻止兩個羣首獨立地運行。假如一個羣首l管理並廣播事務,在此時,仲裁數量的服務器Q判斷羣首l已經退出,並開始選舉了一個新的羣首l',假設在仲裁機構Q放棄羣首l時有一個事務T正在廣播,並且仲裁機構Q的一個嚴格的子集記錄了這個事務T,在羣首l'被選舉完成後,在仲裁機構Q以外服務器也記錄了這個事務T,爲事務T造成一個仲裁數量,在這種狀況下,事務T在羣首l'被選舉後會進行提交。不用擔憂這種狀況,這並非個bug,Zab協議保證T做爲事務的一部分被羣首l'提交,確保羣首l'的仲裁數量的支持者中至少有一個追隨者確認了該事務T,其中的關鍵點在於羣首l'和l在同一時刻並未得到足夠的仲裁數量的支持者。

【圖9-5:羣首發生重疊的狀況】
clipboard.png

在圖中,羣首l爲服務器s 5 ,l'爲服務器s 3 ,仲裁機構由s 1 到s 3 組成,事務T的zxid爲(1,1)。在收到第二個確認消息以後,服務器s 5 成功向服務器s 4 發送了提交消息來通知提交事務。

以前提到Zab保證新羣首l'不會缺失(1,1),如今來看看其中的細節。在新羣首l'生效前,它必須學習舊的仲裁數量服務器以前
接受的全部提議,而且保證這些服務器不會繼續接受來自舊羣首的提議。此時,若是羣首l還能繼續提交提議,好比(1,1),這條提議必須已經被一個以上的承認了新羣首的仲裁數量服務器所接受。咱們知道仲裁數量必須在一臺以上的服務器之上有所重疊,這樣羣首l'用來提交的仲裁數量和新羣首l使用的仲裁數量一定在一臺以上的服務器上是一致的。所以,l'將(1,1)加入自身的狀態並傳播給其跟隨者。

在羣首選舉時,選擇zxid最大的服務器做爲羣首。這使得ZooKeeper不須要將提議從追隨者傳到羣首,而只須要將狀態從羣首傳
播到追隨者。假設有一個追隨者接受了一條羣首沒有接受的提議。羣首必須確保在和其餘追隨者同步以前已經收到並接受了這條提議。可是,若是選擇zxid最大的服務器,將能夠完徹底全跳過這一步,能夠直接發送更新到追隨者。

在時間戳發生轉換時,Zookeeper使用兩種不一樣的方式來更新追隨者來優化這個過程:

  • DIFF :追隨者滯後於羣首很少,羣首隻須要發送缺失的事務點。由於追隨者按照嚴格的順序接收事務點,這些缺失的事務點

永遠是最近的。

  • SNAP:追隨者滯後好久,由於發送完整的快照會增大系統恢復的延時,發送缺失的事務點是更優的選擇。但是當追隨者滯後太遠的狀況下,只能選擇發送完整快照。

4、觀察者

觀察者和追隨者之間有一些共同點。具體說來,他們提交來自羣首的提議。不一樣於追隨者的是,觀察者不參與咱們以前介紹過的選舉過程。他們僅僅學習經由INFORM消息提交的提議。因爲羣首將狀態變化發送給追隨者和觀察者,這兩種服務器也都被稱爲學習者。

【注意:深刻INFORM消息】

由於觀察者不參與決定提議接受與否的投票,羣首不須要發送提議到觀察者,羣首發送給追隨者的提交消息只包含zxid而不包含提議自己。所以,僅僅發送提交消息給觀察者並不能使其實施提議。這是咱們使用INFORM消息的緣由。INFORM消息本質上是包含了正在被提交的提議信息的提交消息。

簡單來講,追隨者接受兩種消息而觀察者只接受一種消息。追隨者從一次廣播中獲取提議的內容,並從接下來的一條提交消息中獲取
zxid。相比之下,觀察者只獲取一條包含已提交提議的內容的INFORM消息。

參與決定那條提議被提交的投票的服務器被稱爲PARTICIPANT服務器。一個PARTICIPANT服務器能夠是羣首也能夠是追隨者。而觀察者則被稱爲OBSERVER服務器。

  • 引入觀察者的一個主要緣由是提升讀請求的可擴展性。
    經過加入多個觀察者,能夠在不犧牲寫操做的吞吐率的前提下服務更多的讀操做。寫操做的吞吐率取決於仲裁數量的大小。若是加入更多的參與投票的服務器,將須要更大的仲裁數量,而這將減小寫操做的吞吐率。增長觀察者也不是徹底沒有開銷的。每個新加入的觀察者將對應於每個已提交事務點引入的一條額外消息。然而,這個開銷相對於增長參與投票的服務器來講小不少。
  • 採用觀察者的另一個緣由是進行跨多個數據中心的部署。
    因爲數據中心之間的網絡連接延時,將服務器分散於多個數據中心將明顯地下降系統的速度。引入觀察者後,更新請求可以先以高吞吐率和低延遲的方式在一個數據中心內執行,接下來再傳播到異地的其餘數據中心獲得執行。值得注意的是,觀察者並不能消除數據中心之間的網絡消息,由於觀察者必須轉發更新請求給羣首而且處理INFORM消息。不一樣的是,當參與的服務器處於同一個數據中心時,觀察者保證提交更新必需的消息在數據中心內部獲得交換。

5、服務器的構成

羣首、追隨者和觀察者根本上都是服務器。
在實現服務器時使用的主要抽象概念是請求處理器。請求處理器是對處理流水線上不一樣階段的抽象。每個服務器實現了一個請求處理器的序列。能夠把一個處理器想象成添加到請求處理的一個元素。一條請求通過服務器流水線上全部處理器的處理後被稱爲獲得徹底處理。

【注意:請求處理器】
ZooKeeper代碼裏有一個叫RequestProcessor的接口。
這個接口的主要方法是processRequest,它接受一個Request參數。在一條請求處理器的流水線上,對相鄰處理器的請求的處理一般經過隊列現實解耦合。當一個處理器有一條請求須要下一個處理器進行處理時,它將這條請求加入隊列。而後,它將處於等待狀態直到下一個處理器處理完此消息。

一、獨立服務器

Zookeeper中最簡單的流水線是獨立服務器(ZeeKeeperServer類)。圖9-6描述了此類服務器的流水線。它包含三種請求處理器:PrepRequestProcessor、SyncRequestProcessor和FinalRequestProcessor。
【圖9-6:一個獨立服務器的流水線】:
clipboard.png

  • PrepRequestProcessor:接受客戶端的請求並執行這個請求,處理結果則是生成一個事務。咱們知道事務是執行一個操做的結果,該操做會反映到ZooKeeper的數據樹上。事務信息將會以頭部記錄和事務記錄的方式添加到Request對象中。同時還要注意,只有改變ZooKeeper狀態的操做纔會產生事務,對於讀操做並不會產生任何事務。所以,對於讀請求的Request對象中,事務的成員屬性的引用值則爲null。
  • SyncRequestProcessor:負責將事務持久化到磁盤上。實際上就是將事務數據按順序追加到事務日誌中,並生成快照數據。
  • FinalRequestProcessor:若是Request對象包含事務數據,該處理器將會接受對ZooKeeper數據樹的修改,不然,該處理器會從數據樹中讀取數據並返回給客戶端。

二、羣首服務器

當切換到仲裁模式時,服務器的流水線則有一些變化,首先咱們羣首的操做流水線(類LeaderZooKeeper),如圖9-7所示。
【圖9-7:羣首服務器的流水線】
clipboard.png

  • PrepRequestProcessor:
  • ProposalRequestProcessor:該處理器會準備一個提議,並將該提議發送給跟隨者。ProposalRequestProcessor將會把全部請求都轉發給CommitRequestProcessor,並且,對於寫操做請求,還會將請求轉發給SyncRequestProcessor處理器。
  • SyncRequestProcessor:處理器所執行的操做與獨立服務器中的同樣,即持久化事務到磁盤上。執行完以後會觸發AckRequestProcessor處理器。
  • AckRequestProcessor:這個處理器是一個簡單請求處理器,它僅僅生成確認消息並返回給本身。以前曾提到過,在仲裁模式下,羣首須要收到每一個服務器的確認消息,也包括羣首本身,而AckRequestProcessor處理器就負責這個。
  • CommitRequestProcessor:CommitRequestProcessor會將收到足夠多的確認消息的提議進行提交。實際上,確認消息是由Leader類處理的(Leader.processAck()方法),這個方法會將提交的請求加入到CommitRequestProcessor類中的一個隊列中。這個隊列會由請求處理器線程進行處理。
  • FinalRequestProcessor:它的做用與獨立服務器同樣。FinalRequestProcessor處理更新類型的請求,並執行讀取請求。在FinalRequestProcessor處理器以前還有一個簡單的請求處理器,這個處理器會從提議列表中刪除那些待接受的提議,這個處理器的名字叫ToBeAppliedRequestProcessor。待接受請求列表包括那些已經被仲裁法定人數所確認的請求,並等待被執行。羣首使用這個列表與追隨者之間進行同步,並將收到確認消息的請求加入到這個列表中。以後ToBeAppliedRequestProcessor處理器就會在FinalRequestProcessor處理器執行後刪除這個列表中的元素。
    注意,只有更新請求才會加入到待接受請求列表中,而後由ToBeAppliedRequest-Processor處理器從該列表移除。ToBeAppliedRequestProcessor處理器並不會對讀取請求進行任何額外的處理操做,而是由FinalRequestProcessor處理器進行操做。

三、追隨者和觀察者服務器

【圖9-8:追隨者服務器的流水線】
clipboard.png

  • FollowerRequestProcessor:首先從FollowerRequestProcessor處理器開始,該處理器接收並處理客戶端請求。FollowerRequestProcessor處理器以後轉發請求給CommitRequestProcessor,同時也會轉發寫請求到羣首服務器。
  • CommitRequestProcessor:會直接轉發讀取請求到FinalRequestProcessor處理器,並且對於寫請求,CommitRequestProcessor在轉發給FinalRequestProcessor處理器以前會等待提交事務。
爲了保證執行的順序,CommitRequestProcessor處理器會在收到一個寫請求處理器時暫停後續的請求處理。這就意味着,在一個寫請求以後接收到的任何讀取請求都將被阻塞,直到讀取請求轉給CommitRequestProcessor處理器。經過等待的方式,請求能夠被保證按照接收的順序來被執行。
  • SyncRequestProcessor:當羣首接收到一個新的寫請求操做時,直接地或經過其餘追隨者服務器來生成一個提議,以後轉發到追隨者服務器。當收到一個提議,追隨者服務器會發送這個提議到SyncRequestProcessor處理器。
  • SendRequestProcessor:會向羣首發送確認消息。當羣首服務器接收到足夠確認消息來提交這個提議時,羣首就會發送提交事務消息給追隨者(同時也會發送INFORM消息給觀察者服務器)。當接收到提交事務消息時,追隨者就經過CommitRequestProcessor處理器進行處理。

6、本地存儲

一、日誌和磁盤的使用

服務器經過事務日誌來持久化事務。在接受一個提議時,一個服務器(追隨者或羣首服務器)就會將提議的事務持久化到事物日誌中,該事務日誌保存在服務器的本地磁盤中,而事務將會按照順序追加其後。服務器會時不時地滾動日誌,即關閉當前文件並打開一個
新的文件。

【組提交和補白】:由於寫事務日誌是寫請求操做的關鍵路徑,所以ZooKeeper必須有效處理寫日誌問題。通常狀況下追加文件到磁盤都會有效完成,但還有一些狀況可使ZooKeeper運行的更快,組提交和補白。組提交(GroupCommits)是指在一次磁盤寫入時追加多個事務。這將使持久化多個事物只須要一次磁道尋址的開銷。

【沖刷(Flush)事務到磁盤介質】:沖刷在這裏就是指告訴操做系統將髒頁寫入磁盤,並在操做完成後返回。由於在SyncRequestProcessor處理器中持久化事務,因此這個處理器同時也會負責沖刷。在SyncRequestProcessor處理器中當須要沖刷事務到磁盤時,事實上是沖刷的是全部隊列中的事務,以實現組提交的優化。若是隊列中只有一個事務,這個處理器依然會執行沖刷。該處理器並不會等待更多的事務進入隊列,由於這樣作會增長執行操做的延時。

【補白(padding)】:指在文件中預分配磁盤存儲塊。這樣作,對於涉及存儲塊分配的文件系統元數據的更新,就不會顯著影響文件的順序寫入操做。假如須要高速向日志中追加事務,而文件中並無原先分配存儲塊,那麼不管什麼時候在寫入操做到達文件的結尾,文件系統都須要分配一個新存儲塊。而經過補白至少能夠減小兩次額外的磁盤尋址開銷:一次是更新元數據;另外一次是返回文件。

二、快照

快照是ZooKeeper數據樹的拷貝副本,每個服務器會常常以序列化整個數據樹的方式來提取快照,並將這個提取的快照保存到文件中。服務器在進行快照時不須要進行協做,也不須要暫停處理請求。由於服務器在進行快照時還會繼續處理請求,因此當快照完成時,數據樹可能又發生了變化,稱這樣的快照是模糊的(fuzzy),由於它們不能反映出在任意給點的時間點數據樹的準確狀態。

【舉例說明】

一個數據樹中只有2個znode節點:/z和/z'。一開始,兩個znode節點的數據都是1。
如今有如下操做步驟:
一、開始一個快照。
二、序列化並將/z=1到到快照。
三、使/z的數據爲2(事務T)。
四、使/z'的數據爲2(事務T')。
五、序列化並將/z'=2寫入到快照。

這個快照包含了/z=1和/z'=2。然而,數據樹中這兩個znode節點在任意的時間點上都不是這個值。這並非問題,由於服務器會重播(replay)事務。每個快照文件都會以快照開始時最後一個被提交的事務做爲標記(tag),將這個時間戳記爲TS。若是服務器最後加載快照,它會重播在TS以後的全部事務日誌中的事務。在這個例子中,它們就是T和T。在快照的基礎上重放T和T'後,服務器最終獲得/z=2和/z'=2,即一個合理的狀態。

接下來還須要考慮一個重要的問題,就是再次執行事務T'是會有問題,由於這個事務在開始快照開始以後已經被接受,而結果也被
快照中保存下來。就像以前所說的,事務是冪等的(idempotent),因此即便按照相同的順序再次執行相同的事務,也會獲得相同的結果,即使其結果已經保存到快照中。

爲了理解這個過程,假設重複執行一個已經被執行過的事務。如上例中所描述,一個操做設置某個znode節點的數據爲一個特定的值,這個值並不依賴於任何其餘東西,無條件(unconditionly)地設置/z'的值(setData請求中的版本號爲-1),從新執行操做成功,但由於遞增了兩次,因此最後以錯誤的版本號結束。
如如下方式就會致使問題出現,假設有以下3個操做併成功執行:

setData /z', 2, -1
setData /z', 3, 2
setData /a, 0, -1
第一個setData操做跟以前描述的同樣,然後又加上了2個setData操做,以此來展現在重放中第二個操做由於錯誤的版本號而未能
成功的狀況。假設這3個操做在提交時被正確執行。此時若是服務器加載最新的快照,即該快照已包含第一個setData操做。服務器仍然會重放第一個setData操做,由於快照被一個更早的zxid所標記。由於從新執行了第一個setData操做。而第二個setData操做的版本號又與指望不符,那麼這個操做將沒法完成。而第三個setData操做能夠正常完成,由於它也是無條件的。

在加載完快照並重放日誌後,此時服務器的狀態是不正確的,由於它沒有包括第二個setData請求。這個操做違反了持久性和正確性,以及請求的序列應該是完好口(no gap)的屬性。

這個重放請求的問題能夠經過把事務轉換爲羣首服務器所生成的state delta來解決。當羣首服務器爲一個請求產生事務時,做爲事務生成的一部分,包括了一些在這個請求中znode節點或它的數據變化的值(delta值),並指定一個特定的版本號。最後從新執行一個事務就不會致使不一致的版本號。

7、服務器與會話

會話(Session)是Zookeeper的一個重要的抽象。保證請求有序、臨時znode節點、監事點都與會話密切相關。所以會話的跟蹤機制對ZooKeeper來講也很是重要。

ZooKeeper服務器的一個重要任務就是跟蹤並維護這些會話。

  • 在獨立模式下,單個服務器會跟蹤全部的會話。
  • 在仲裁模式下,則由羣首服務器來跟蹤和維護。

羣首服務器和獨立模式的服務器實際上運行相同的會話跟蹤器(參考SessionTracker類和SessionTrackerImpl類)。而追隨
者服務器僅僅是簡單地把客戶端鏈接的會話信息轉發給羣首服務器(參考LearnerSessionTracker類)。

爲了保證會話的存活,服務器須要接收會話的心跳信息。心跳的形式能夠是一個新的請求或者顯式的ping消息(參考LearnerHandler.run())。兩種狀況下,服務器經過更新會話的過時時間來觸發(touch)會話活躍(參考SessionTrackerImpl.touchSession()方法)。
在仲裁模式下,羣首服務器發送一個PING消息給它的追隨者們,追隨者們返回自從最新一次PING消息以後的一個session列表。羣首服務器每半個tick(參考10.1.1節的介紹)就會發送一個ping消息給追隨者們。因此,若是一個tick被設置成2秒,那麼羣首服務器就會每一秒發送一個ping消息。

對於管理會話的過時有兩個重要的要點。一個稱爲 過時隊列(expiry queue)的數據結構(參考ExpiryQueue類),用於維護會話的過時。這個數據結構使用 bucket 來維護會話,每個bucket對應一個某時間範圍內過時的會話,羣首服務器每次會讓一個bucket的會話過時。爲了肯定哪個bucket的會話過時,若是有的話,當下一個底限到來時,一個線程會檢查這個expiry queue來找出要過時的bucket。這個線程在底限時間到來以前處於睡眠狀態,當它被喚醒時,它會取出過時隊列的一批session,讓它們過時。固然取出的這批數據也多是空的。
爲了維護這些bucket,羣首服務器把時間分紅一些片斷,以expirationInterval爲單位進行分割,並把每一個會話分配到它的過時時間對應的bucket裏,其功能就是有效地計算出一個會話的過時時間,以向上取正的方式得到具體時間間隔。更具體來講,就是對下面的表達式進行計算,當會話的過時時間更新時,根據結果來決定它屬於哪個bucket。
(expirationTime / expirationInterval + 1) * expirationInterval
舉例說明,好比expirationInterval爲2,會話的超時時間爲10。那麼這個會話分配到bucket爲12((10/2+1)*2的結果)。注意當觸發(touch)這個會話時expirationTime會增長,因此隨後須要根據以後的計算會話移動到其餘的bucket中。
使用bucket的模式來管理的一個主要緣由是爲了減小讓會話過時這項工做的系統開銷。在一個ZooKeeper的部署環境中,可能其客戶端就有數千個,所以也就有數千個會話。在這種場景下要細粒度地檢查會話過時是不合適的。若是expirationInterval短的話,那麼ZooKeeper就會以這種細粒度的方式完成檢查。目前expirationInterval是一個tick,一般以秒爲單位。

8、服務器與監視點

爲了在服務端管理監視點,ZooKeeper的服務端實現了監視點管理器(watch manager)。一個WatchManager類的實例負責管理當前已被註冊的監視點列表,並負責觸發它們。全部類型的服務器(包括獨立服務器,羣首服務器,追隨者服務器和觀察者服務器)都使用一樣的方式處理監視點。
DataTree類中持有一個監視點管理器來負責子節點監控和數據的監控,對於這兩類監控,請參考4.2節,當處理一個設置監視點的讀請求時,該類就會把這個監視點加入manager的監視點列表。相似的,當處理一個事務時,該類也會查找是否須要觸發相應的監視點。若是發現有監視點須要觸發,該類就會調用manager的觸發方法。添加一個監視點和觸發一個監視點都會以一個read請求或者FinalRequestProcessor類的一個事務開始。
在服務端觸發了一個監視點,最終會傳播到客戶端。負責處理傳播的爲服務端的cnxn對象(參見ServerCnxn類),此對象表示客戶端和服務端的鏈接並實現了Watcher接口。Watch.process方法序列化了監視點事件爲必定格式,以便用於網絡傳送。ZooKeeper客戶端接收序列化的監視點事件,並將其反序列化爲監視點事件的對象,並傳遞給應用程序。
監視點只會保存在內存,而不會持久化到硬盤。當客戶端與服務端的鏈接斷開時,它的全部監視點會從內存中清除。由於客戶端庫也會維護一份監視點的數據,在重連以後監視點數據會再次被同步到服務端。

9、客戶端

在客戶端庫中有2個主要的類:ZooKeeperClientCnxn

  • ZooKeeper類:實現了大部分API,寫客戶端應用程序時必須實例化這個類來創建一個會話。一旦創建起一個會話,ZooKeeper就會使用一個會話標識符來關聯這個會話。這個會話標識符其實是由服務端所生成的(參考SessionTrackerImpl類)。
  • ClientCnxn類管理鏈接到server的Socket鏈接。該類維護了一個可鏈接的ZooKeeper的服務器列表,並當鏈接斷掉的時候無縫地切換到其餘的服務器。當重連到一個其餘的服務器時會使用同一個會話(若是沒有過時的話),客戶端也會重置全部的監視點到剛鏈接的服務器上(參考ClientCnxn.SendThread.primeConnection())。重置默認是開啓的,可

以經過設置disableAutoWatchReset來禁用。

10、序列化

對於網絡傳輸和磁盤保存的序列化消息和事務,ZooKeeper使用了Hadoop中的Jute來作序列化。現在,該庫以獨立包的方式被引入,在ZooKeeper代碼庫中,org.apache.jute就是Jute庫。

對於Jute最主要的定義文件爲zookeeper.jute。它包含了全部的消息定義和文件記錄。下面是一個Jute定義的例子:

module org.apache.zookeeper.txn {
...
class CreateTxn {
    ustring path;
    buffer data;
    vector<org.apache.zookeeper.data.ACL> acl;
    boolean ephemeral;
    int parentCVersion;
    }
...
}
這個例子定義模塊,該模塊包括一個create事務的定義。同時。這個模塊映射到了一個ZooKeeper的包中
相關文章
相關標籤/搜索