記錄下和kafka相關的LEO和HW的內容,文中很多理解參考文末書籍還有某前輩。
LEO&HW基本概念
- Base Offset:是起始位移,該副本中第一條消息的offset,如下圖,這里的起始位移是0,如果一個日志文件寫滿1G后(默認1G后會log rolling),這個起始位移就不是0開始了。
- HW(high watermark):副本的高水印值,replica中leader副本和follower副本都會有這個值,通過它可以得知副本中已提交或已備份消息的范圍,leader副本中的HW,決定了消費者能消費的最新消息能到哪個offset。如下圖所示,HW值為8,代表offset為[0,8]的9條消息都可以被消費到,它們是對消費者可見的,而[9,12]這4條消息由於未提交,對消費者是不可見的。注意HW最多達到LEO值時,這時可見范圍不會包含HW值對應的那條消息了,如下圖如果HW也是13,則消費的消息范圍就是[0,12]。
- LEO(log end offset):日志末端位移,代表日志文件中下一條待寫入消息的offset,這個offset上實際是沒有消息的。不管是leader副本還是follower副本,都有這個值。當leader副本收到生產者的一條消息,LEO通常會自增1,而follower副本需要從leader副本fetch到數據后,才會增加它的LEO,最后leader副本會比較自己的LEO以及滿足條件的follower副本上的LEO,選取兩者中較小值作為新的HW,來更新自己的HW值。
LEO&HW更新流程
LEO和HW的更新,需要區分leader副本和follower副本,兩者上的更新情況不一樣,整體參考下圖簡單示例。
LEO
包括leader副本和follower副本。
leader LEO:leader的LEO就保存在其所在的broker的緩存里,當leader副本log文件寫入消息后,就會更新自己的LEO。
remote LEO和follower LEO:remote LEO是保存在leader副本上的follower副本的LEO,可以看出leader副本上保存所有副本的LEO,當然也包括自己的。follower LEO就是follower副本的LEO,因此follower相關的LEO需要考慮上面兩種情況。
- case 1:如果是remote LEO,更新前leader需要確認follower的fetch請求包含的offset,這個offset就是follower副本的LEO,根據它對remote LEO進行更新。如果更新時尚未收到fetch請求,或者fetch請求在請求隊列中排隊,則不做更新。可以看出在leader副本給follower副本返回數據之前,remote LEO就先更新了。
- case 2:如果是follower LEO,它的更新是在follower副本得到leader副本發送的數據並隨后寫入到log文件,就會更新自己的LEO。
HW
包括leader副本和follower副本。
leader HW:它的更新是有條件的,參考書籍中給出了四種情況,如下是其中的一種,就是producer向leader副本寫消息的情況,當滿足四種情況之一,就會觸發HW嘗試更新。如下圖所示更新時會比較所有滿足條件的副本的LEO,包括自己的LEO和remote LEO,選取最小值作為更新后的leader HW。
四種情況如下,其中最常見的情況就是前兩種。
1.producer向leader寫消息,會嘗試更新。
2.leader處理follower的fetch請求,先讀取log數據,然后嘗試更新HW。
3.副本成為leader副本時,會嘗試更新HW。
4.broker崩潰可能會波及leader副本,也需要嘗試更新。
follower HW:更新發生在follower副本更新LEO之后,一旦follower向log寫完數據,它就會嘗試更新HW值。比較自己的LEO值與fetch響應中leader副本的HW值,取最小者作為follower副本的HW值。可以看出,如果follower的LEO值超過了leader的HW值,那么follower HW值是不會超過leader HW值的。
更新流程示例分析
以下是參考《apache kafka實戰》的整理。
前提條件:考慮一個主題,只有一個分區,兩個副本的情況,並且剛開始都沒有任何消息在log日志文件。
在考慮fetch請求時,需要考慮兩種情況,接下來就只考慮第二種情況,第一種情況也可以參考第二種情況。
- producer暫時無法響應follower partition的請求,如沒有數據可以返回,這時fetch請求會緩存在一個叫做purgatory的對象里(請求不會無限期緩存,默認500ms)。在緩存期間,如果producer發送PRODUCE請求,則被喚醒,接下來會正常處理fetch請求。
- producer正常響應follower partition的請求。
下面分析第二種情況,即producer正常響應follower的情況。
當leader副本接受到了producer的消息,並且此時沒有follower副本fetch請求,在這樣的前提下,它會先做如下操作。
- 寫入消息到log日志文件,更新leader LEO為1。
- 嘗試更新remote LEO,由於沒有fetch請求,因此它是0,不需要更新。
- 做min(leader LEO,remote LEO)的計算,結果為0,這樣leader HW無需更新,依然是0。
第一次fetch請求,分leader端和follower端:
leader端:
- 讀取底層log數據。
- 根據fetch帶過來的offset=0的數據(就是follower的LEO,因為follower還沒有寫入數據,因此LEO=0),更新remote LEO為0。
- 嘗試更新HW,做min(leader LEO,remote LEO)的計算,結果為0。
- 把讀取到的log數據,加上leader HW=0,一起發給follower副本。
follower端:
- 寫入數據到log文件,更新自己的LEO=1。
- 更新HW,做min(leader HW,follower LEO)的計算,由於leader HW=0,因此更新后HW=0。
可以看出,第一次fetch請求后,leader和follower都成功寫入了一條消息,但是HW都依然是0,對消費者來說都是不可見的,還需要第二次fetch請求。
第二次fetch請求,分leader端和follower端:
leader端:
- 讀取底層log數據。
- 根據fetch帶過來的offset=1的數據(上一次請求寫入了數據,因此LEO=1),更新remote LEO為1。
- 嘗試更新HW,做min(leader LEO,remote LEO)的計算,結果為1。
- 把讀取到的log數據(其實沒有數據),加上leader HW=1,一起發給follower副本。
follower端:
- 寫入數據到log文件,沒有數據可以寫,LEO依然是1。
- 更新HW,做min(leader HW,follower LEO)的計算,由於leader HW=1,因此更新后HW=1。
這個時候,才完成數據的寫入,並且分區HW(分區HW指的就是leader副本的HW)更新為1,代表消費者可以消費offset=0的這條消息了,上面的過程就是kafka處理消息寫入和備份的全流程。
最后,使用HW來記錄消息在副本中提交或備份的進度,其實是存在缺陷的,在kafka 0.11.0.0后的版本中,使用leader epoch解決了。由於水平有限,先不深究了,后續如有需要再參考文末書籍內容學習。
以上,理解不一定正確,學習就是一個不斷認識和糾錯的過程。
4.4清明節,珍惜當下,感恩生命!
參考博文:
(1)《Apache Kafka實戰》