1. 程式人生 > >Kafka叢集內複製功能深入剖析

Kafka叢集內複製功能深入剖析

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複製示意圖如下所示:
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叢集內複製功能深入剖析
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故障時可能會丟失。如何抉擇,由你決定。