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

Zab:廣播狀態改變

當接收到一個寫請求時,跟隨者會將請求推送到首領,首領解析執行請求後將執行的結果以事務的形式表現爲一個狀態更新廣播出去。一個事務包含了一個服務器在提交該事務時必須應用到其數據數上的改變的精確集合。數據樹是Zookeeper保持狀態的數據結構(見DataTree)。

接下來的問題就是一個服務器如何確定一個事務是已經被提交了的。這裏使用了一個叫Zab:the zookeeper atomic broadcast protocol.的協議。假定現在有一個活躍的首領其擁有一個法定人數的跟隨者來支持它的首領權,提交一個事務的協議是非常簡單的,就像一個兩段提交:

1.   首領發送一個PROPOSAL信息 p到所有的跟隨者。

2.   當接收到p時,跟隨者會想首領返回ACK相應來聲明它已經接收到了這個提議。

3.   當接收的相應達到了其法定人數(法定人數中包含首領本身),首領會發送一個信息到所有的跟隨者來進行提交事務。

圖9-4解釋了這個步驟的流程,在圖表中,我們假定隱式的向自己發送了信息。


在提議的響應發送之前,跟隨者需要執行一些額外的檢查。跟隨者需要檢查該提議是來自自身當前跟隨的首領,這個響應提議和提交任務是按照首領廣播的順序來的。

Zab保證了兩個重要的屬性:

1.   如果首領先後廣播了T和T’,每一個必須在T’提交之前提交T。

2.   如果任何一個服務器先後提交了T和T’,所有其它的服務器也必須在T’之前提交T。

第一個屬性保證了事務是按照同樣的順序在服務器之間傳播的,而第一個屬性保證了服務器不會跳過一些事務。因爲一些事務都是狀態的改變,而每一個狀態的改變都依賴於之前的狀態改變,跳過事務可能會產生不一致型。連段提交保證了事務的順序。Zab在一個法定人數的服務器中記錄了事務。一個法定人數的集合必須在提交一個事務之前都承認這個事務,跟隨者也會在硬盤值記錄下其已經承認了這個事務。

 

就像我們在170頁的LocalStorage(本地存儲)這章看到的,事務可能在一些服務器上完整了,而再另一些服務器上沒有,原因是服務器在嘗試將該事務存儲時是可能失敗的。Zookeeper在一個新的首領被選中,形成一個新 的法定人數集合時將所有的服務器都更新到最新的狀態。

Zookeeper並不指望在真個生命中只有一個活躍的首領。首領是可能崩潰,並暫時性的無法連接的。因此服務器需要連接到一個新的首領來確保系統任然是可用的。Epochs(紀元)的概念就是表示領導權隨着時間的改變。一個紀元對應於一個服務器擁有領導權的一段時期。在這個紀元中,首領會廣播提議並通過計數器來標識這些提議。記得zxid包含epoch作爲其元素的第一部分,一次每一個zxid就能很容易的關聯到該事務創建的紀元。

 

每當一個新的首領選舉產生時,紀元(epoch)數據都會被替換。同樣的服務器可能是不同紀元的首領,但對已協議來說,一個服務器在不通的紀元都作爲首領也表現爲不同的首領。如果服務器s是曾經紀元4的首領,但當前是紀元6的首領,而紀元6中s的跟隨者會只處理在紀元6中發送的信息,該跟隨者在紀元6的恢復期間,在接收到來自紀元6的信息之前可能接受到來自紀元4,而這些提議都會被作爲紀元6信息的一部分發送。----究竟這些紀元4的信息會被處理嗎?

 

記錄接收到的是在一個法定人數的集合中是非常嚴格來確保所有的服務器最終都會提交哪些已經被一個或多個服務器提交的事務,即使首領會崩潰掉。精確的檢測到首領或任何一臺服務器的崩潰是非常難的,甚至是不可能的,在很多配置中,很有可能是錯誤的懷疑首領已經崩潰了。

 

實現一個廣播協議的最大困難是關於當前首領的存在性,而在分離大腦的場景中就不是必須的了。多個同時存在的首領可能使得服務器以混亂的順序提交任務或一起跳過一些任務,這樣就造成了服務器處於不一致的狀態。爲了防止系統出現同時有兩個          服務器相信他們都是首領是非常難的。時間問題和信息的丟失都可能導致這種場景的出現,因此廣播協議是不能依賴於假設的。爲了繞過這個問題,Zab保證:

1.   一個首領會在廣播一個新的事務之前確保提交所有的事務,哪怕是來自前一個紀元的事務。

2.   任何時間都不允許兩個服務器都有一個法定人數集合的支持。

 

爲了實現第一個需求,首領在確保有一個法定人數集合的服務器對新紀元啓動時的新狀態達成一致之後纔可以成爲活躍狀態。一個紀元初始狀態必須是完成了之前提交的所有的事務之後,可能還包括一些已經被接收但還沒有提交的狀態。這是非常重要的,也就是在一個首領做出一個紀元e的新的提議之前,必須提交所有包括紀元e-1或之前的紀元提交的所有事物。如果存在一個從紀元e’<e的提議,在首領做出他的第一個提議時還沒有被紀元e的首領提交,這個舊的提議就永遠不會被提交了。

 

第二點有一些奇怪,因爲它並沒有真正組織兩個手裏各自做出自己的前進。也就是說一個首領l當前是首領並在廣播事務,與此同時,一個法定人數的服務器集合Q認爲l已經崩潰了,並推選除了一個新的首領l’.讓我們考慮T作爲最爲在Q拋棄l的時間廣播的一個事務,並且Q的一部分自己已經成功記錄了T。在l’被選舉出來之後,不在Q中的足夠多的進程也記錄的T,形成了對應T的一個法定人數集合。在這種情況下,T會被提交哪怕是l’已經被選舉出來了。但是別擔心,這並不是一個bug.Zab保證T作爲l’提交的事務的一部分,通過保證支持l’的法定人數集合中至少包含一個跟隨者是承認T 的。關鍵是l’和l不會同時擁有複合法定人數要求的支持者。

 

圖9-5解釋了這種場景,在這個圖中,l就是服務器s5,l’是s3,Q包括s1到s3,T的zxid是<1,1>。當接收到第二個確認後,s5可以發送一個提交信息到s4了指示提交該事務。而其他的服務器在跟隨s3之後忽略了s5的信息。注意到s3承認了<1,1>,因此在他建立領導權的時候是意識到這個事務的。

 

我們已經承諾Zab會確保新的首領l’不會丟失掉<1,1>,但是究竟是如何發生的呢?在成爲激活狀態之前,l’必須瞭解就得法定人數集合中的服務器已經接受的所有提議,也必須保證這些服務器不會再接收到來自之前首領的更多的提議。在9-5的例子中,形成支持l’的法定人數的服務器集合保證他們不會再接受來自首領l的提議,同時首領l還可以提交任何的提議,就是事務<1,1>一樣,這些提議必須被支持新的首領的法定人數集合中的至少一個服務器接受。而重新構建法定人數集合必定形成至少一個服務器的重合,一次l用來提交的法定人數集合和l’的必定有一個服務器是相同的。結果就是l’會包含<1,1>的狀態並傳播到他的跟隨者上。


回憶下當選舉一個首領是,服務器集合會挑選出一個有最高zxid的服務器,這樣避免了Zookeeper出現提議從跟隨者傳遞向首領的情況。只需要從首領向跟隨者傳遞狀態。也就是說不會出現我們有一個跟隨者接收到了一個首領都沒有接收到的提議。在和跟隨者進行同步之前,首領會必須要接收並接收這個提議。然而,如果我們挑選了最高的zxid的服務器作爲首領,我們就能完全的跳過這個步驟直接跳到更新跟隨者到最新的步驟來。

 

當在紀元之間進行轉變是,zookeeper用戶使用兩種不同的方式來更新跟隨者來進行優化處理過程。如果跟隨者不是遠遠落後於首領,首領會指示簡單的發送丟失的事務,因爲跟隨者是按照嚴格的順序來接收事務的,所以這些往往都是一些最新的事務。這個跟新在代碼中稱呼爲DIFF。如果跟隨者是遠遠落後於首領,Zookeeper會進行一個完整鏡像的傳輸,在代碼中稱爲SNAP。進行一個完整鏡像的傳輸會增加恢復的時間,因此發送一些丟失的事務是更合適的,但如果跟隨者過於落後也是沒有辦法的。

 

DIFF中,首領會將它事務日記中的提議發送到跟隨中,而SNAP會將最新的合法的景象發送到跟隨中,本章的後面我們會討論保存在硬盤中的兩種類型的文件。

深入代碼

這是一個隊代碼的小的指導,大部分的Zab代碼在Leader,LearnerHandler和Follower中,Leader和LearnerHandler的實例會被首領服務器來執行,而Follower會被跟隨者來執行。兩個重要的方法是leader.lead和Follower.followLeader.他們是在當服務器狀態從LOOKING轉變爲Quourmpeer中的LEADING或FOLLOWING實際執行的方法。

對於DIFF和SNAP,跟隨LearnerHandler.run方法來查看代碼是如何決定哪些提議是通過DIFF發送,而鏡像是如何序列化併發送的

 

     

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章