1. 程式人生 > >Kafka集群內復制功能深入剖析

Kafka集群內復制功能深入剖析

部分 replicat 分數 內存 shadow 其他 遞增 裏的 成長

Kafka是一個分布式發布訂閱消息系統。由LinkedIn開發並已經在2011年7月成為apache頂級項目。kafka在LinkedIn, Twitte等許多公司都得到廣泛使用,主要用於:日誌聚合,消息隊列,實時監控等。

0.8版本開始,kafka支持集群內復制,從而提高可用性和系統穩定性,這篇文章主要概述kafka復制的設計。

復制
有了復制後,kafka客戶端將會得到如下好處:

生產者能在出現故障的時候繼續發布消息,並且能在延遲和持久性之間選擇,取決於應用。
消費者能在出現故障的時候繼續實時接受正確的消息。
所有的分布式系統必須在一致性,可用性,分區容錯性之間進行權衡並做出取舍(參考CAP定理),kafka的目標是在單個數據中心裏的kafka集群也支持復制。網絡分區是比較少見的,因此kafka設計專註於高可用和強一致。強一致意味著所有副本數據完全一致,這簡化了應用程序開發人員的工作。

kafka是一個基於CA的系統(???),zookeeper是一個基於CP的系統(很確定),eureka是一個基於AP的系統(很確定)。

復制強一致
現有比較成熟的方案中,有兩種保持強一致性復制的典型方法。這兩種方法都需要副本中的一個被設計為leader,所有寫入都需要發布到該副本。leader負責處理所有的接入。並廣播這些寫到其他follower副本,並且要保證復制順序和leader的順序一致。

第一種方法是基於法定人數。leader等待直到大多數副本收到數據。當leader出現故障,大多數follower會協調選舉出新的leader。這種方法被用於Apache Zookeeper 和Google‘s Spanner.

第二種方法是leader等待所有副本收到數據(重要說明:在kafka中這個"所有"是所有的In-Sync Replicas)。如果leader出現故障,其他副本能被選舉為新的leader。
kafka復制選擇的是第二種方法,有兩個主要原因:

相同數量的副本情況下,第二種方法能承受更多的容錯。例如,總計2n+1個副本,第二種方法能承受2n個副本故障(只要還有一個ISR,就能正常寫入),而第一種方法只能承受n個副本故障。如果在只有兩個副本的情況下,第一種方法不能容忍任意一個副本故障。

第一種方法延遲性表現更好,因為只需要法定人數確認即可,所以會隱藏掉一些比較慢的副本的影響。而kafka副本被設計在相同的數據中心的一個集群下。所以網絡延遲這種變數是比較小的。

術語
為了了解kafka中的副本是如何實現的,我們首先需要介紹一些基本概念。在kafka中,消息流由topic定義,topic被切分為1個或者多個分區(partition),復制發生在分區級別,每個分區有一個或者多個副本。

副本被均勻分配到kafka集群的不同服務器(稱為broker)上。每個副本都維護磁盤上的日誌。生產者發布的消息順序追加到日誌中,日誌中每條消息被一個單調遞增的offset標識。

offset是分區內的邏輯概念, 給定偏移量,可以在分區的每個副本中標識相同的消息。 當消費者訂閱某個主題時,它會跟蹤每個分區中的偏移量以供使用,並使用它來向broker發出獲取消息的請求。

設計
kafka中增加副本的目標是為了更強的持久性和高可用。kafka要保證任何成功發布的消息不會丟失,且能被消費,即使在有一些服務器宕機的情況下。kafka復制的主要目標有:

可配置的持久化保證:例如,某些數據不能容忍丟失的應用,可以選擇更強的持久性,當然會伴隨延遲的增長。另一個產生海量允許部分數據丟失的應用,可以選擇稍微弱一點的持久性,但是更獲得更好的寫入響應時間,得到更好的吞吐量。

自動化的副本管理:kafka要簡化向broker分配副本的指配過程,並且能支持集群逐步擴容&縮容。

這樣的話,有兩個主要問題需要解決:

如何均勻的指配分區的副本給broker?
對於一個給定的分區,如何廣播每條消息到其他副本?
數據復制
kafka允許客戶端選擇異步或者同步復制,異步復制的話,發布的消息,當被1個副本接收到就能確認。同步復制的話,kafka盡最大努力確保消息到達多個副本(所以有效的ISR)後才確認。當客戶端嘗試發布消息到一個topic的分區時,kafka必須傳播這個消息到所有副本,kafka必須決定:

怎樣傳播;
在向客戶端確認前,需要多少副本接收消息;
一個副本故障後,該怎麽處理;
一個故障的副本恢復後該怎麽處理;
實現
保持副本同步有兩種常用的策略:主備復制和基於仲裁復制。這兩種情況下,一個副本被設計為leader,其他副本被稱為follower,所有寫請求都由leader處理,leader傳播寫請求給follower。

在主備復制下,leader等待直到寫在這個組裏每個副本都完成,才向客戶端發送確認。如果某個副本故障,leader把它從這個組移除,並繼續寫到剩余的副本。一個故障副本也被允許從新加入組,只要它恢復,並追趕上leader。在用n個副本的前提下,主備復制模式能容忍n-1個副本故障。

在基於仲裁方法下,leader等待直到寫在大多數副本上完成,副本組的大小不會因為某些副本故障發生改變(例如某個分區有5個副本,即使有2個副本故障,我們還是認為這個副本組有5個副本)。因此如果有2n+1個副本,基於仲裁復制的話,只能容忍n個副本故障。如果leader出現故障,需要至少n+1個副本才能選舉一個新的leader。

這兩種方法需要權衡:

基於仲裁比主備有更好的寫延遲,任何副本的延遲(例如FGC造成長時間的STW)將增加主備方法的寫延遲,但是不會增加仲裁方法的寫延遲。
在相同數量副本情況下,主備方法能容忍更多故障。
在主備方法前提下,副本因子是2,也能運行良好。但是在基於仲裁方法的復制,兩個副本必須持續工作保持有效狀態。
kafka選擇主備復制,因為它能容忍更多副本故障,並且只有2個副本也能正常工作。
同步復制
kafka同步復制是典型的主備方式,每個分區有n個副本,並且能容忍n-1個副本故障。只有一個副本被選舉為leader,其他都是follower。leader維護了一個ISR集合:這個副本集完全和leader保持同步狀態,kafka還會把當前的leader和當前的ISR保持到zookeeper中。

每個副本保存信息在本地日誌中,並且維護了一個日誌中重要的offset位置。LEO表示日誌尾部,HW是最新提交消息的offset。每個日誌周期性的同步到磁盤,已經刷新的偏移量之前的數據保證保留在磁盤上。


為了發布消息到分區,客戶端首先從zookeeper中找到分區的leader,然後發送消息到這個leader。leader寫消息到它的本地日誌,每個follower經常從leader拉取最新的消息。所以,follower接收到的所有消息的順序和leader保持一致,follower把每條接收到的消息寫入它的本地日誌,並向leader發送一個確認。一旦leader接收到所有ISR副本的確認,消息就能被提交。leader推進HW,然後向客戶端發送確認。為了更好的性能,每個follower在把消息寫入內存後,就發送確認。因此,對於每條提交的消息,我們保證它被保存到多個副本的內容中然而,不保證任何副本已經持久化已提交消息到磁盤上。

由於這種相關故障相對罕見,並且這種方法能給我們一個在響應時間和持久性之間一個很好的平衡。在將來,kafka可能考慮增加一個選項參數從而提供更強的保證。


為了簡化,讀也是leader提供服務,並且只有HW以上的消息才會被暴露給消費者讀取。

異步復制
為了支持異步復制,leader可以在消息寫入本地日誌後,馬上通知客戶端。唯一需要註意的是在追趕階段,follower必須截斷HW位置以後的數據。follower主要是異步復制,所以不能保證提交的消息在broker故障後不丟失。

復制實現
kafka復制示意圖如下所示:
技術分享圖片
集群總計4個broker(broker1~broker4);
1個topic,2個分區,3個副本;
分區1即topic1-part1的leader在broker1上,分區2即topic1-part2的leader在broker4上;
producer寫入消息到分區topic1-part1的leader上(在broker1上),然後復制到它的兩個副本,分別在broker2和broker3上。

producer寫入消息到分區topic1-part2的leader上(在broker4上),然後復制到它的兩個副本,分別在broker2和broker3上。

當生產者發布消息到topic的某個分區時,消息首先被傳遞到leader副本,並追加日誌。follower副本從leader中不停的拉取新消息,一旦有足夠的副本收到消息,leader就會提交這個消息。

這裏有個問題,leader是怎麽決定什麽是足夠的。kafka維護了一個 in-sync replica(ISR)集合。這個ISR副本集都是存活的,並且完全趕上leader的副本,沒有消息延遲(leader總是在ISR集合中)。當分區初始化創建時,每個副本都在ISR集合中。當新消息發布後,leader提交消息前一直等待直到所有ISR副本收到消息。如果某個follower副本故障,它將會被從ISR中移除。leader會繼續提交新的消息,只不過ISR數量相比分區創建時副本數量更少。

請註意,現在,系統運行在under replicated模式。

leader還會維護high watermark (HW,可以翻譯成高水位),是指分區中最後一次提交消息的offset。HW會被不斷傳播給follower副本:
技術分享圖片
kafka high watermark
當一個故障副本被重啟後,它首先從磁盤上恢復最新的HW,並將日誌截斷到HW。這是必要的,因為不能保證在HW之後的消息被提交,所以可能需要丟棄。然後副本成為follower,並繼續從leader那裏獲取HW以後的消息。一旦完全趕上leader,這個副本從新被加入到ISR中。系統將重新回到fully replicated模式。

故障處理
kafka依賴zookeeper檢測broker故障,kafka會用一個controller(broker集合中的一個)接收所有zookeeper關於故障,選舉新leader等相關通知,這樣還有一個好處,減少了對zookeeper的壓力。如果某個leader故障,controller就會從ISR副本中選舉一個新的leader,並發布新leader的消息給其他follower。

按照設計,leader選舉過程中,已經提交的消息總是會被保留,一些未提交的消息可能會丟失。leader和每個分區的ISR也會被保存在Zookeeper中,controller出現故障轉移時需要用到。由於broker級別的故障一般會非常少,所以預期的leader和ISR都會不經常改變。

對客戶端來說,broker僅向消費者公開已經提交的消息。broker故障期間,已提交的數據始終被保留。消費者使用相同的offset可以從另一個被選舉為leader的副本拉取消息。

生產者能選擇在broker收到消息後何時得到broker的確認。例如,它能等到消息被leader提交並被所有ISR確認(即acks=-1)。另外,也可以選擇消息只要被leader追加到日誌中,可能還沒有提交(acks=0表示無需等待leader確認,acks=1表示需要等待leader確認)。前一種情況即acks=-1,生產者需要等待更長的時間。但是確認的消息都保證在broker中保留。後一種情況即acks=0或者1,生產者有更低的延遲,更高的吞吐量,但一些確認的消息在broker故障時可能會丟失。如何抉擇,由你決定。

Kafka集群內復制功能深入剖析