ZooKeeper - O'Reilly Media ----Zookeeper Internals (1)

第九章 ZooKeeper Internals

本章相對於其它章有點不同,不會詳細的解釋如何來構建ZooKeeper應用,而是描述ZooKeeper內部是如何工作,將會在高層面來描述內部的協議,在提供高性能的同時的容錯機制。本章幫助加對ZooKeeper工作的深層理解。內在的理解對計劃部署ZooKeeper來說是非常重要的,因此本章也作爲下一章的背景知識。

 

就如我們前面幾章描述的,ZooKeeper運行在一些服務器的集合上,客戶端連接到這些服務器來進行操作。但當客戶端請求過來時,這些服務器都具體進行了什麼操作?在第二章我們提到過我們會選舉一個服務器來作爲leader來和其他服務器區別開來。而其它的服務器後跟隨這個首領,因此稱呼爲跟隨者followers.首領是處理所有改變ZOOKEEPER系統的請求的中心點。首領也表現爲一個序列器,把對ZOOKEEPER更新狀態的操作進行排序。跟隨者會接受和表決首領提出的更新請求來保證狀態的更新防止崩潰。

 

首領和跟隨者是即使是崩潰情況保證狀態跟新順序的核心實體。另外還有第三類的服務器叫做觀察者observer。觀察者不會參與帶什麼請求被接受的決定進程中,他們僅僅是瞭解已經做出了什麼決定。觀察者是爲了可測量行的原因添加的。

 

本章中,我們會描述用來實現ZOOKEEPER集合,服務器和客戶端內部機制的協議。我們從一些在本章中用來關注客戶端請求和事物的通用概念開始討論。

 

代碼引用

因爲本章是關於內部機制的,我們任務如果提供代碼的引用會比較有趣,因此你可以將本章的描述和源代碼進行匹配,會在何時的地方提供類和方法的指引。

 

請求,事務和標識

ZOOKEEPER服務器會在本地處理讀請求(如exists,getData和getChildren)。當服務器接受到一個客戶端過來的讀請求,如getData,會讀取本身的狀態並返回給服務端。因爲是在服務器本地處理請求,因此ZOOKEEPER在主要處理讀請求的工作負載下是非常快的。我們可以添加更多的服務器到ZOOKEEPER集合中來處理更多的讀請求,以此來增大總的吞吐能力。

 

需要改變ZOOKEEPER狀態的客戶端請求(如create,delete和setData)會被傳遞到首領。首領會執行這個請求,並且生成一個我們稱爲事物的狀態更新行爲。相對於請求表示了客戶端發起的操作的方式,事務包含了對應於請求操作的改變ZOOKEEPER狀態的步驟。可能最直觀的解釋方式是提供一個簡單,非ZOOKEEPER操作。比如操作是inc(i),這個操作會對變量i的值加1.一個可能的請求就是inc(i).如果i的值是10,那增加之後就是11.這裏用請求和事務來描述的話,請求就是inc(i),事物就是I,11(i的值設置爲11)。

 

我們再來看一個ZOOKEEPER的例子。假設一個客戶端提交了一個/z Zookeeper節點上的setData請求。SetData應該改變節點上的數據並且提高版本數字。以此,這個請求對應的事務就包含兩個重要的域:節點的新值和新的版本數字。當應用一個事務是,服務器會簡單的替換事務中提供的/z的數據和版本數字,而不是進行增加。

 

一個事務是作爲一個單元來對待的,也就是說它包含的所有改變必須是原子性的。在setData這個例子中,如果只是改變了值而沒有同時改變相應的版本將會產生問題。因此,當一個ZOOKEEPER集合應用事務是,它必須保證所有的改變是事務性的,不會出現其它事物的干擾。這裏並沒有像傳統關係數據庫那樣的回滾機制。相反,ZOOKEEPER會確保事務的操作不會在不同事務之間產生干擾。在很長一段時間,使用一個服務器上單線程的設計來應用事務。一個單線程會保證事務是順序應用而不會相互干擾。最近,ZOOKEEPER已經添加了多閒扯個的支持來加快事務的應用速度。

 

事務同時也是一個冪等操作,也就是說我們可以對一個事務應用兩次但會得到同樣的結果。只要我們每一次都按照同樣的順序來進行,我們可以對不同的事務應用的多次後還能得到同樣的結果。這個冪等的特性會在恢復中使用。

 

當首領生成一個事務,它會給事務分配一個標識符,ZOOKEEPER TRANSACTION ID(ZXID)。 Zxids可以確認事務,也就可以按照首領建立的順序來作用到服務器的狀態上。當服務器選出了一個新的首領時同樣會改變zxids,因此也可以確認正常的服務器會接收到多個事務並同步他們的狀態。

 

Zxid是一個64位的整數,包括一個epoch和一個counter.每一個部分是32位。當我們討論用來向服務器廣播狀態改變的協議ZAB時,就可以理解兩個部分的內容了。

 

首領選舉

首領就是一個由服務器集合選擇出來並能持續得到集合支持的服務器。首領的目的是對客戶端改變ZOOKEEPER狀態的請求:create,setData和delet進行排序。首領將每一個請求轉變爲一個事務(見上章),並推送到集合的跟隨者,跟隨者接受並按照首領決定的順序來應用這些事務。

 

要成爲一個首領,一個服務器必須得到法定數量的服務器的支持。就像我們第二章所說的,法定數量必須交叉以避免出現成爲分離大腦的問題,兩個不同的服務器子集分別獨立的處理請求。這種情況會造成系統的不一致狀態,客戶端會由於連接到不同的服務器而獲得不同的結果。我們在ZOOKEEPER QUORUMS提供了一個相關的例子。

 

選舉和支持首領的組必須至少貫穿一個服務器進程,我們使用法定人數(quorum)術語來表示一個處理進程的集合。法定人數是相互交叉的。(注:也就是說不會出現相互分離的兩個不同的法定人數—分離大腦)。

 

         處理流程

對一個流程來說是必須存在一個法定人數的服務器的,ZOOKEEPER在許多服務器已經失敗造成無法形成一個法定人數的情況下是無法進行處理流程的。如果服務器奔潰後最終重啓是可以的,但是對於一個流程的處理,法定人數是必須最終啓動。我們會在下章討論重新配置集合放鬆這個限制。重新配置可以改變重新改變法定人數.

 

每一個服務器啓動的時候都處於LOOKING狀態,其必須要麼推選出一個首領或找到一個現存的首領。如果現在存在一個首領,其它的服務器會通知新的服務器哪一個服務器是首領。此時新的服務器會連接到首領並確保自身的狀態和首領的狀態是一致的。

 

如果一個服務器的集合中所有的服務器都處於LOOKING狀態,它們必須相互通訊來選舉出一個首領。他們交換信息聚合來找到一個共同的決定來決定首領。贏得選舉的服務器會進入LEADING狀態,而集合中的其他服務器會進入跟隨者狀態。

 

首領選舉信息被稱呼爲首領選舉通知(leaderelection notifications),或者簡單的稱呼爲通知(notifications)。選舉的協議也是非常簡單。當一個服務器進入LOOKING狀態,他會發送一批通知報文,每一個服務器一條。信息中包含當前的選票,包括服務器的標識sid和最近一個執行的事務的zxid。比如,(1,5)就是一個sid爲1的服務器發送的報文,其最新的zxid是5.(處於首領選舉的目的,zxid是一個簡單的數字,但在一些其他協議中會表現爲想epoch和counter)。

 

當收到一個選票是,服務器會改變按照下面的規則來改變自己的選票並重新發送新的選票:

1.      用voteId和voteZxid來表示收到選票的發送服務器的sid和zxid。用myZxid和mySid來表示服務器本身的zxid和sid。

2.      如果voteZxid > myZxid) 或者(voteZxid =myAzid 並且voteId > mySid)。使用接受到的選票最爲自己最新的選票並重新發送出去。

3.      否則將保持自己的選票不變。

 

簡單的說,最新的服務器會贏得選舉,因爲該服務器有最近的zxid。接下來我們會看到這樣會簡化當一個首領宕機後重啓一個法定人數的過程。如果多個服務器都有最近的zxid,而sid最大的服務器將當選。

 

當一個服務器接收到達到法定人數的服務器的相同選票是,該服務器會認爲首領已經選擇出來了,如果首領就是服務器本身,則會開始執行首領的角色,否則,該服務器會成爲一個跟隨者並嘗試去連接當選的首領。注意到這裏並不能保證跟隨者會連接到當選的首領,因爲當選的首領是可能宕機。當連接建立起來後,跟隨者會和首領同步他們的狀態,並且只有當同步完成之後,該跟隨者纔開始處理請求。

 

         尋找首領

ZooKeeper中實現選舉的Java類是Quorumpeer。它的run方法實現了服務器的主要循環。當處於LOOKING狀態,會執行lookForLeader來選舉一個首領,這個方法基本上就是實現了我們上面描述的協議。在這個方法返回之前,會設置服務器的狀態要麼是LEADING,要麼是FOLLOWING。OBSERVING也是一個選擇,下面我們在討論。如果服務器是首領,它會創建一個新的Leader並雲習慣。如果是跟隨者會創建一個新的Fellower並執行。

 

讓我們來看一個這個協議執行的例子。圖9-1顯示了3個服務器,每一個都由一個包含服務標識和自身最新zxid的選票來開始。每一個服務器都會接受到另外兩臺服務器的選票,在第一輪之後,服務器s2和s3都將他們的選票改爲(1,6),然後重新發出新的通知。再接收到這些新的通知後,所有服務器都有了一個法定人數的相同的選票,最終選擇了服務器s1來作爲首領。

並不是所有的操作都想圖9-1那邊很好的執行的,在圖9-2種,我們展示了服務器s2做了一個較早的決定,選擇了一個和服務器s1,s2決定不同的首領。這個情況是由於網絡中信息從s1到s2時較長的延遲造成的,(s1到s2的信息會提示s1有更高的zxid),同時,s2選擇了s3,結果就是s1和s3形成了一個法定人數,而移除了s2.

S2選擇了一個不同的首領並不會造成Zookeeper的服務不正確,是因爲s3不會以首領的身份來相應s2。最終,s2會在嘗試連接自己選擇的首領s3時出現超時並重新嘗試,重新嘗試,也意味着在這段時間s2無法作爲一個可用的服務器來處理客戶端請求,這本身就是不正確的。

 

這個例子中一個簡單的結論是如果s2等待再長一點時間做出首領的決定,就會做出正確的決定。圖9-3展示的就是這樣的情況。但是很那知道該等待多長時間。當前的實現是FastLeaderElection,首領選舉的默認實現,使用了一個固定的200ms作爲等待時間(常量finalizeWait)。這個值比當代的數據中心期望的延遲長(一般都是小於1毫秒到幾毫秒之間),但是在恢復時候也沒有出現本質的區別。在這種情況下這個延遲(或其他選擇的延遲)會不夠長,一個或多個服務器會很快的選擇出一個沒有足夠跟隨者的首領,這些服務器不定不重新進行首領選舉。錯誤的選擇一個首領可能會造成整個恢復時間變長,因爲這些服務器會進行不必要的連接和同步,並需要發送更多的信息來選擇其他的首領。

 

         FastLeaderElection的快指什麼

         如果你希望弄明白這點,我們把 當前默認首領選舉算法稱呼爲fast是有歷史原因的。最初的首領選舉算法實現是基於拉模型的,而一個服務器用來拉選票的間隔是1秒,這個方法增加了恢復的延遲。當我們使用當前的實現是,我們是可以更快的選擇一個首領的。

 

如果需要實現一個新的首領選舉算法,我們需要實現quoram包中的Election接口。爲了是的用戶可以在可用的首領選舉算法中進行選擇,代碼使用了簡單的整數標識來區別(見Quorumpeer.createElectionAlgorithm())。當前另外的兩個可用實現是LeaderElection和AuthFastLeaderElection,但 他們在3.4.0版本中都被推薦不適用了,可能在將來的版本中就看不到了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章