Kafka之ReplicaManager(1)

devos發表於2015-12-15

基於Kafka 0.9.0版

ReplicaManager需要做什麼

Replicated Logs

Kafka的partition可以看成是一個replicated log, 每個replica就是這個replicated log其中的一個log。多個replica是為了容忍機器故障,因此同一個partition的不同replica需要被分配到不同的broker上。所以,對於一個partition,broker id即可唯一代表一個replica,也被當作replica id。

為了一致性,Kafka在同一個partition的replicas中選出一個作為leader,由它接受client的所有讀寫請求,而其它的replica作為follower,從leader處拉取資料,leader作為唯一的"source of truth"。在有些情況下,follower會truncate自己的log(這個log和以下的log都是指"replicated log"這個概念裡的log),然後重新從leader處抓取資料,以求與leader一致(下面會講到)。

leader和follower的角色區分,也主要是ReplicaManager來實現。具體地講

  • leader
    • leader會接受client的讀取請求和寫入請求。
    • leader需要接受follwer抓取message的請求,返回message給follower
    • leader需要維護ISR(in-sync replicas)列表。“保持同步”的含義有些複雜,0.9之前版本對這個概念的定義與0.9不同,詳情參見KIP-16 - Automated Replica Lag Tuning。0.9版本,broker的引數replica.lag.time.max.ms用來指定ISR的定義,如果leader在這麼長時間沒收到follower的拉取請求,或者在這麼長時間內,follower沒有fetch到leader的log end offset,就會被leader從ISR中移除。ISR是個很重要的指標,controller選取partition的leader replica時會使用它,因此leader選取ISR後會把結果記到Zookeeper上。
    • leader需要維護high watermark。high watermark以下的訊息就是所有ISR列表裡的replica都已經讀取的訊息(注意,並不是所有replica都一定有這些訊息,而只是ISR裡的那些才肯定會有)。因此leader會根據follower拉取資料時提供的offset和ISR列表,決定HW,並且在返回給follower的請求中附帶最新的HW。
  • follower
    • follower需要不停地去leader處拉取最新的log
    • follower需要根據leader在fetch reponse中提供的HW,更新自己本地儲存的leader的HW資訊。在它過行leader或follower轉變時,會用到這個HW。

HW與LEO

HW、ISR以及leader對於partition這個多副本系統算是一種後設資料。ISR和leader確要在controller和所有replica之間保持一致,HW需要在leader和follower之間保持一致,因為在leader轉換的時候,HW是安全線。

下面明確一下high watermark和log end offset在原始碼裡的意義

HW  high watermark  offset的資料小於被認為是commit的,注意,offset為high watermark的message並不是commit的。

LEO log end offset 這個replica的log裡最後一條訊息的下一條訊息的offset

這些資料根據實際需求,以不同的方式在Kafka中傳遞:

  • HW。隨著follower的拉取進度的即時變化,HW是隨時在變化的。follower總是向leader請求自己已有messages的下一個offset開始的資料,因此當follower發出了一個fetch request,要求offset為A以上的資料,leader就知道了這個follower的log end offset至少為A。此時就可以統計下ISR裡的所有replica的LEO是否已經大於了HW,如果是的話,就提高HW。同時,leader在fetch本地訊息給follower時,也會在返回給follower的reponse裡附帶自己的HW。這樣follower也就知道了leader處的HW(但是在實現中,follower獲取的只是讀leader本地log時的HW,並不能保證是最新的HW)。但是leader和follower的HW是不同步的,follower處記的HW可能會落後於leader。
  • ISR以及leader。 在需要選舉leader的場景下,leader和ISR是由controller決定的。在選出leader以後,ISR是leader決定。如果誰是leader和ISR只存在於ZK上,那麼每個broker都需要在Zookeeper上監聽它host的每個partition的leader和ISR的變化,這樣效率比較低。如果不放在Zookeeper上,那麼當controller fail以後,需要從所有broker上重新獲得這些資訊,考慮到這個過程中可能出現的問題,也不靠譜。所以leader和ISR的資訊存在於Zookeeper上,但是在變更leader時,controller會先在Zookeeper上做出變更,然後再傳送LeaderAndIsrRequest給相關的broker。這樣可以在一個LeaderAndIsrRequest裡包括這個broker上有變動的所有partition,即batch一批變更新資訊給broker,更有效率。另外,在leader變更ISR時,會先在Zookeeper上做出變更,然後再修改本地記憶體中的ISR。

Hight Watermark Checkpoint

以外,由於HW是隨時變化的,如果即時更新到Zookeeper,會帶來效率的問題。而HW是如此重要,因此需要持久化,ReplicaManager就啟動了單獨的執行緒定期把所有的partition的HW的值記到檔案中,即做highwatermark-checkpoint。

Epoch

 除了leader,ISR之外,在replica系統中還有其它三個對於一致性有重要作用的引數:controller epoch、leader epoch和zookeeper version。

  • controller epoch: 當新的controller開始工作後,舊的controller可能還在工作,這時就會有兩個自認為是的controller,那麼broker該聽哪個的呢?cpmtroller epoch是一個整數,記在Zookeeper的/controller_epoch path的資料中,當新的controller當選後,它更新Zookeeper中的這個資料,把這個整數的值+1,並且以每個命令中都附帶上controller epoch。這樣broker收到一個controller的命令後,就與自己記憶體中儲存的controller epoch比較,如果命令中的值小於記憶體中的值,就代表是舊的controller的命令,如果大於記憶體中的值,就更新記憶體中的controller epoch為新值,並且執行命令。
  • leader epoch: 對於同一個controller,也存在它的LeaderAndIsrRequest以錯誤的順序到達broker的可能,這樣broker就可以在檢查controller的epoch之後,再檢查leader epoch,以確認該執行哪個命令。
  • zkVersion 對於在zookeeper path中儲存的controller epoch, leaderAndIsr資訊進行更新時,始終都得進行條件更新,以避免產生競態。比如,在controller讀取Zookeeper上的leaderAndIsr資訊後,更新leaderAndIsr資訊前,如果leader更改了ISR的資訊,而controller以更改前的ISR進行leader選舉的話,就可能會產生異常狀態;或者在controller更新完leaderAndIsr之後,舊的leader又去更新zk上的這個資料,也會使叢集不一致。所以,就需要zkVersion來進行條件變更。controller和replica在記憶體中儲存上一次狀態更新時讀取到的zkVersion,當它依據此狀態做出決定時,需要帶上這個zkVersion做條件更新,以保證根據舊狀態做出的更新不會生效。這種條件更新是使用的kafka.utils.ZkUtils的conditionalUpdatePersistentPath方法。

由這三個版本號共同作用,Kafka基本都保證對於leader, ISR, controller的認知在各個broker間不會出現大問題(但是還會有bug和潛在的問題導致認知不一致)。

update MetadataCache

此外,當broker收到UpdateMetadataRequest時,它會把這個request交給ReplicaManager處理,而ReplicaManager在確定UpdateMetadataRequest的controller epoch有效之後,就會交由MetadataCache來處理。之所以不直接收MetadataCache處理,可能是ReplicaManager處會儲存controller epoch, 不過MetadataCache內部也可能獲取controller epoch,只是沒有做為單獨的一個field儲存起來。這樣做顯得有些混亂,不知道是什麼原因。

相關文章