ZooKeeper Internals -- ZooKeeper內部工作方式
ZooKeeper Internals
介紹
原子廣播
保證,屬性和定義
領導者啟用
活動訊息
摘要
比較
法定人數
記錄
開發者指南
記錄在正確的級別
使用標準的slf4j成語
介紹
本文件包含有關ZooKeeper內部工作方式的資訊。到目前為止,它討論了以下主題:
原子廣播
記錄
原子廣播
ZooKeeper的核心是一個原子訊息系統,可以使所有伺服器保持同步。
保證,屬性和定義
ZooKeeper使用的訊息傳遞系統提供的特定保證如下:
可靠的交付:如果訊息m由一臺伺服器提供,它將最終由所有伺服器提供。
總訂單:如果一條伺服器在訊息b之前傳遞訊息,則所有伺服器將在b之前傳送訊息。如果a和b是傳遞的訊息,則a將在b之前傳遞或b將在傳遞之前傳遞。
因果順序:如果在b的傳送者傳送訊息a之後傳送訊息b,則必須在b之前訂購。如果傳送方在傳送b後傳送c,則必須在b之後訂購c。
ZooKeeper訊息傳遞系統還需要高效,可靠,易於實施和維護。我們大量使用訊息傳遞,因此我們需要系統能夠每秒處理數千個請求。雖然我們可以要求至少k + 1個正確的伺服器來發送新訊息,但我們必須能夠從相關故障中恢復,例如斷電。當我們實施該系統時,我們幾乎沒有時間和很少的工程資源,因此我們需要一個工程師可以訪問的協議,並且易於實現。我們發現我們的協議滿足了所有這些目標。
我們的協議假設我們可以在伺服器之間構建點對點FIFO通道。雖然類似的服務通常假設訊息傳遞可能丟失或重新排序訊息,但我們假設FIFO通道非常實用,因為我們使用TCP進行通訊。具體來說,我們依賴TCP的以下屬性:
有序傳遞:資料按照發送的順序傳送,只有在傳送完m之前傳送的所有郵件之後才傳送訊息m。(這樣做的必然結果是,如果訊息m丟失,m之後的所有訊息都將丟失。)
關閉後沒有訊息:一旦FIFO通道關閉,將不會收到任何訊息。
FLP證明,如果可能出現故障,則無法在非同步分散式系統中實現共識。為確保我們在出現故障時達成共識,我們會使用超時。但是,我們依靠活力時間而不是正確性。因此,如果超時停止工作(例如時鐘故障),則訊息傳遞系統可能會掛起,但不會違反其保證。
在描述ZooKeeper訊息傳遞協議時,我們將討論資料包,提議和訊息:
資料包:通過FIFO通道傳送的位元組序列
提案:協議單位。通過與法定數量的ZooKeeper伺服器交換資料包來商定提案。大多數提案都包含訊息,但NEW_LEADER提案是與訊息不對應的提案示例。
訊息:要以原子方式廣播到所有ZooKeeper伺服器的位元組序列。在提交之前提交併同意的訊息。
如上所述,ZooKeeper保證了訊息的總順序,並且它還保證了提議的總順序。ZooKeeper使用ZooKeeper事務id(zxid)公開總排序。所有提案在提議時都會加蓋zxid,並準確反映總排序。提案將傳送到所有ZooKeeper伺服器,並在法定人數確認提案時提交。如果提案包含訊息,則在提交提案時將傳遞訊息。確認意味著伺服器已將提議記錄到持久儲存。我們的法定人數要求任何一對仲裁必須至少有一個共同的伺服器。我們通過要求所有法定人數的大小(n / 2 + 1)來確保這一點)其中n是組成ZooKeeper服務的伺服器數量。
zxid有兩個部分:紀元和計數器。在我們的實現中,zxid是一個64位數字。我們使用高階32位用於紀元,低階32位用於計數器。因為它有兩個部分代表zxid既是數字又是一對整數,(epoch,count)。時代數字代表了領導層的變化。每當新領導人上臺時,它將擁有自己的紀元號碼。我們有一個簡單的演算法來為一個提議分配一個唯一的zxid:領導者只需遞增zxid以獲得每個提案的唯一zxid。領導啟用將確保只有一個領導者使用給定的紀元,因此我們的簡單演算法保證每個提案都具有唯一的ID。
ZooKeeper訊息傳遞包含兩個階段:
領導者啟用:在此階段,領導者建立正確的系統狀態,並準備開始提出建議。
主動訊息傳遞:在此階段,領導者接受建議和協調訊息傳遞的訊息。
ZooKeeper是一個整體協議。我們不關注個別提案,而是關注整個提案流。我們嚴格的訂購使我們能夠有效地完成這項工作並大大簡化我們的協議。領導啟用體現了這種整體概念。領導者只有在達到法定數量的粉絲時才會變得活躍(領導者也算作跟隨者。你總是可以為自己投票)與領導者同步,他們擁有相同的狀態。該州包括領導者認為已經提交的所有提案以及跟隨領導者的提議,NEW_LEADER提案。(希望你是在想自己,領導者認為已提交的提案包括所有真正提交的提案嗎?答案是肯定的。下面,我們說明原因。)
領導者啟用
領導者啟用包括領導者選舉。我們目前在ZooKeeper中有兩個領導者選舉演算法:LeaderElection和FastLeaderElection(AuthFastLeaderElection是FastLeaderElection的變體,它使用UDP並允許伺服器執行簡單形式的身份驗證以避免IP欺騙)。只要符合以下條件,ZooKeeper訊息傳遞並不關心選擇領導者的確切方法:
領導者已經看到了所有粉絲中最高的zxid。
法定數量的伺服器已承諾跟隨領導者。
在這兩個要求中,只有第一個,跟隨者需要保持正確操作的最高zxid。第二個要求,即法定數量的追隨者,只需要很高的概率。我們將重新檢查第二個要求,因此如果在領導者選舉期間或之後發生失敗並且法定人數丟失,我們將通過放棄領導者啟用和進行另一次選舉來恢復。
領導者選舉後,單個伺服器將被指定為領導者,並開始等待粉絲連線。其餘伺服器將嘗試連線到領導者。領導者將通過傳送他們遺失的任何提案與追隨者同步,或者如果追隨者缺少太多提案,它將向關注者傳送狀態的完整快照。
有一個角落案例,其中有一個跟隨者有提議,U,領導人看不到。提案按順序排列,因此U的提案的zxids高於領導者看到的zxids。追隨者必須在領導人選舉後到達,否則追隨者將被選為領導者,因為它已經看到更高的zxid。由於提交的提案必須由法定數量的伺服器看到,並且選出領導者的法定數量的伺服器沒有看到U,因此您的提議尚未提交,因此可以將其丟棄。當追隨者連線到領導者時,領導者將告訴追隨者丟棄U.
一個新的領導者建立一個zxid來開始使用新的提議,通過獲得它所見過的最高zxid的時代e,並設定下一個zxid用於(e + 1,0),領導者與跟隨者同步後,它將提出一個NEW_LEADER提案。提交NEW_LEADER提案後,領導者將啟用並開始接收和釋出提案。
這聽起來很複雜,但這裡是領導者啟用過程中的基本操作規則:
跟隨者將在與領導者同步後確認NEW_LEADER提案。
跟隨者只會使用來自單個伺服器的給定zxid確認NEW_LEADER提議。
當法定數量的粉絲確認後,新的領導者將提交NEW_LEADER提案。
當NEW_LEADER提議為COMMIT時,關注者將提交從領導者收到的任何州。
在NEW_LEADER提案獲得COMMITED之前,新領導者不會接受新提案。
如果領導者選舉錯誤地終止,我們就沒有問題,因為由於領導者沒有法定人數,因此不會提交NEW_LEADER提案。當發生這種情況時,領導者和任何剩下的追隨者將超時並返回領導者選舉。
活動訊息
領導者啟用完成所有繁重的工作。一旦領導者加冕,他就可以開始提出提案。只要他仍然是領導者,就不可能出現其他領導者,因為沒有其他領導者能夠獲得法定數量的粉絲。如果新的領導者確實出現,那就意味著領導者失去了法定人數,新的領導者將清理領導啟用期間留下的任何混亂。
ZooKeeper訊息傳遞的操作類似於傳統的兩階段提交。
所有通訊通道都是FIFO,所以一切都按順序完成。具體而言,遵守以下操作約束:
領導者使用相同的訂單向所有粉絲髮送提案。此外,此順序遵循收到請求的順序。因為我們使用FIFO通道,這意味著關注者也會按順序接收提案。
關注者按照收到的順序處理訊息。這意味著將按順序確認訊息,並且由於FIFO通道,領導者將按順序接收來自關注者的ACK。這也意味著如果訊息$ m $已寫入非易失性儲存器,則在$ m $之前建議的所有訊息都已寫入非易失性儲存器。
一旦有法定數量的粉絲確認訊息,領導者將向所有粉絲髮出COMMIT。由於訊息按順序被確認,因此將由跟隨者按順序接收的領導者傳送COMMIT。
COMMIT按順序處理。在提交提案時,關注者會發送提案訊息。
摘要
你去吧 它為什麼有效?具體而言,為什麼新領導人認為的一系列提案總是包含任何實際提交的提案?首先,所有提議都有一個唯一的zxid,因此與其他協議不同,我們永遠不必擔心為同一個zxid提出兩個不同的值; 粉絲(領導者也是粉絲)按順序檢視和記錄提案; 提案按順序提交; 由於粉絲一次只跟隨一位領導者,所以每次只有一位活躍的領導者; 一位新領導人已經看到了上一個時代的所有承諾提案,因為它已經從法定數量的伺服器中看到了最高的zxid; 新領導人看到的上一個時代的任何未提出的提議都將由該領導者在活躍之前承諾。
比較
這不只是Multi-Paxos嗎?不,Multi-Paxos需要一些方法來確保只有一個協調員。我們不指望這種保證。相反,我們使用領導者啟用來恢復領導層變革,或者讓老領導人相信他們仍然活躍。
這不僅僅是Paxos嗎?您的活動訊息傳遞階段看起來就像Paxos的第2階段?實際上,對我們來說,主動訊息傳遞看起來就像是2階段提交,而不需要處理中止。主動訊息傳遞與它們具有交叉提議排序要求的意義不同。如果我們不對所有資料包保持嚴格的FIFO排序,它就會崩潰。此外,我們的領導者啟用階段與他們兩個都不同。特別是,我們對epochs的使用允許我們跳過未提交的提議塊,而不用擔心給定zxid的重複提議。
法定人數
原子廣播和領導者選舉使用法定人數的概念來保證系統的一致檢視。預設情況下,ZooKeeper使用多數仲裁,這意味著在其中一個協議中發生的每個投票都需要多數投票。一個例子是承認領導者提案:領導者只有在收到法定數量的伺服器的確認後才能提交。
如果我們從使用多數性中提取我們真正需要的屬性,我們只需要保證用於通過投票驗證操作的程序組(例如,確認領導者提議)在至少一個伺服器中成對交叉。使用多數人保證這樣的財產。但是,還有其他方法可以構建與多數群體不同的法定人數。例如,我們可以為伺服器的投票分配權重,並說一些伺服器的投票更重要。為了獲得法定人數,我們得到足夠的票數,以便所有投票的權重總和大於所有權重總和的一半。
使用權重並且在廣域部署(共址)中有用的不同結構是分層結構。通過這種構造,我們將伺服器分成不相交的組併為程序分配權重。為了形成法定人數,我們必須從大多數G組獲得足夠的伺服器,這樣對於G中的每個組g,來自g的投票總和大於g中權重總和的一半。有趣的是,這種結構可以實現更小的法定人數。例如,如果我們有9個伺服器,我們將它們分成3組,併為每個伺服器分配1的權重,然後我們就可以形成大小為4的仲裁。注意,兩個程序的子集各佔大多數來自大多陣列中的每個組的伺服器必然具有非空交叉點。
使用ZooKeeper,我們為使用者提供配置伺服器以使用多數仲裁,權重或組層次結構的功能。
記錄
Zookeeper使用slf4j作為日誌記錄的抽象層。現在選擇版本1.2中的log4j作為最終的日誌記錄實現。為了更好地嵌入支援,計劃將來決定為終端使用者選擇最終的日誌記錄實現。因此,始終使用slf4j api在程式碼中編寫日誌語句,但配置log4j以瞭解如何在執行時進行日誌記錄。請注意,slf4j沒有FATAL級別,FATAL級別的舊訊息已移至ERROR級別。有關為ZooKeeper配置log4j的資訊,請參閱ZooKeeper管理員指南的“ 日誌記錄”部分。
開發者指南
在程式碼中建立日誌語句時,請遵循 slf4j手冊。 在建立日誌語句時,請閱讀有關效能的常見問題解答。補丁審閱者將查詢以下內容:
記錄在正確的級別
slf4j中有多個級別的日誌記錄。
選擇正確的一個很重要。按從高到低的順序:
ERROR級別指定可能仍允許應用程式繼續執行的錯誤事件。
WARN級別表示潛在的有害情況。
INFO級別指定資訊性訊息,以粗粒度級別突出顯示應用程式的進度。
DEBUG Level指定對除錯應用程式最有用的細粒度資訊事件。
TRACE Level指定比DEBUG更細粒度的資訊事件。
ZooKeeper通常在生產中執行,以便將INFO級別嚴重性和更高(更嚴重)的日誌訊息輸出到日誌。
使用標準的slf4j成語
靜態訊息記錄
LOG.debug("process completed successfully!");
但是,當需要建立引數化訊息時,請使用格式化錨點。
LOG.debug("got {} messages in {} minutes",new Object[]{count,time});
命名
記錄器應以其使用的類命名。
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class); .... public Foo() { LOG.info("constructing Foo");
異常處理
try {
// code
} catch (XYZException e) {
// do this LOG.error("Something bad happened", e); // don't do this (generally) // LOG.error(e); // why? because "don't do" case hides the stack trace // continue process here as you need... recover or (re)throw
}