Kafka到底有多高可靠?(RNG NB)

語言: CN / TW / HK

在聊Kafka高可靠之前,先在評論區來波RNG NB好不好!

什麼叫可靠性?

大家都知道,系統架構有三高:「高效能、高併發和高可用」,三者的重要性不言而喻。

對於任意系統,想要同時滿足三高都是一件非常困難的事情,大型業務系統或者傳統中介軟體都會搭建複雜的架構來保證。

除以上三種模式之外,還有一個指標方向也很重要,那就是高可靠,甚至你可能會將它和「高可用」混淆起來。

事實上兩者並不一樣,高可用會更偏向於整體服務的可用性,防止系統宕機等等。而高可靠是指資料的可靠性保證嘛,你可以理解”高可靠“相比於系統三高會是一個更細一點的概念。

那麼什麼是資料的高可靠呢,總結一下就是系統要提供可靠的資料支撐,不能發生丟失、重複等錯誤現象。

所以每個開源中介軟體在釋出版本時都會通過文件宣告自己是超可靠的,就像520那天每一位暖男說的那樣。

咱今天的主角kafka就是這麼一個例子。

一些重要概念

因為有一段時間沒講訊息隊列了嘛,為了幫助你更好理解文章,我們來先複習一下kafka的基礎概念:

  • record:訊息,訊息佇列基礎通訊單位。
  • topic:主題,目的就是將訊息進行分類,不同業務型別的訊息通常會被分發到不同的主題。
  • partition:分割槽,每個主題可以建立多個分割槽,每個分割槽都由一系列有序和不可變的訊息組成。
  • replica:副本,每個分割槽都有一個至多個副本存在,它的主要作用是儲存儲存資料,以日誌(Log)物件的形式體現。副本又分為leader副本和follower副本。
  • offset:偏移量,每一個訊息在日誌檔案中的位置都對應一個按序遞增的偏移量,你可以理解為類似陣列的儲存形式。
  • producer:生產者,生產訊息的那一方。
  • consumer:消費者,通常不同的業務都會有一到多個消費者組成消費者叢集。
  • broker:代理,一個Kafka叢集由一個或多個Kafka例項構成,每一個Kafka例項就稱為代理。

如上圖所示,一共存在主題1和主題2,主題1有兩個分割槽,主題2只有一個分割槽,並且每個分割槽都存在一個leader副本和兩個follower副本,它們分佈在每個不同的代理節點上。

partition裡只有leader副本負責與生產者、消費者之間資料的互動,follower副本會定期從leader副本拉取資料以保證整個叢集資料可用性。

如何保證資料高可靠

Kafka是通過副本機制實現資料的儲存的,所以就需要一些機制保證資料在跨叢集的副本之間能夠可靠地傳輸。

1、副本同步集合

業務資料封裝成訊息在系統中流轉,由於各個元件都是分佈在不同的伺服器上的,所以主題和生產者、消費者之間的資料同步可能存在一定的時間延遲,Kafka通過延遲範圍劃分了幾個不同的集合:

AR(Assigned Replicas)

指的是已經分配資料的分割槽副本,通常指的是leader副本 + follower副本。

ISR(In Sync Replicas)

指的是和leader副本資料保持同步的副本集合。當follower副本資料和leader副本資料保持同步,那麼這些副本就處在ISR裡面,ISR集合會根據資料的同步狀態動態變化。

OSR(Out Sync Replicas)

一旦follower副本的資料同步進度跟不上leader了,那麼它就會被放進叫做OSR的集合裡。也就是這個集合包含的是不處於同步狀態的分割槽副本。

OK,那有什麼標準判斷它是同步還是不同步呢?

通過replica.lag.time.max.ms這個引數來設定資料同步時間差,它的預設值是10s。

一旦從分割槽副本和主分割槽副本的訊息相差10s以上,那麼就認為訊息處於OSR不同步的狀態。若follower處於OSR集合裡,那麼在選取新的leader的時候就不會選舉它作為新leader。

2、ACK應答機制

我們剛剛說了kafka是通過ack來發送資料同步訊號的,那訊號傳送頻率又有幾種設定呢?

  • ack = 0

生產者傳送一次訊息就不再發送。不管是否傳送成功,若發出去的訊息處於通訊的路上就丟失,或者還未做磁碟持久化操作,那麼訊息就可能丟失。

它的好處就是效能很高,你想呀你傳送訊息都不需要等待對方回覆就持續傳送下一批,那麼訊息等待的時間就節省出來了。同一時間範圍內能比別人處理更多資料,缺點就是它的可靠性真的很低,資料真的是說丟就丟。

  • ack = 1

leader接收到訊息並且寫入到本地磁碟後就認為訊息處理成功。這種方式可靠性會比上一種好一些,當leader接收到訊息並且寫入到本地磁碟後就認為訊息處理成功,不論follower是否同步完這條訊息就會返回給producer。

但是假如此刻partition leader所在的broker宕機了,如果那麼資料也可能會丟失,所以follower副本的資料同步就很重要。

Kafka預設就採用這種方式。

  • ack = -1

producer只有收到分割槽內所有副本的響應ACK才會認為訊息已經push成功。

這種方式雖然對於資料的可靠保障做得很好,但是就是效能很差,影響吞吐量,所以一般也不會採取。

那麼它就絕對可靠嗎?也不一定。最重要的還是取決於副本資料是否同步完成。若producer收到響應訊息前leader副本掛掉,那麼producer會因未收到訊息重複傳送訊息,那就可能造成資料重複。怎麼解決呢?只要保證業務冪等就行。

我們可以通過request.required.acks這個引數控制訊息的傳送頻率。

3、訊息語義

訊息叢集整體是一個複雜的系統,所以過程中可能會因為各種原因導致訊息傳遞出錯,Kafka對於這些可能遇到的場景定義了對應的的訊息語義。

at most once

它代表訊息可能被消費者消費0次或者1次。若場景如下:

  • 訊息從partition分發給消費者叢集。
  • 消費者把自己收到的訊息告訴叢集,叢集收到之後offset就會往後移動。
  • 消費者將資料入庫做持久化。

你一定想到了。在第三步消費者將訊息入庫時若因任何原因消費者A掛了,那麼在將消費者切換到叢集的消費者B後,資料還沒入庫呢。此時partition是渾然不知的呀,那麼這就會造成一個問題:資料丟失。

at least once

它代表partition分發的訊息至少被消費一次。其通訊過程如下:

  • 訊息從partition分發給消費者叢集。
  • 消費者將資料入庫做持久化。
  • 消費者把自己收到的訊息告訴叢集,叢集收到之後offset就會往後移動。

假設consumer group在資料入庫之後,在將資料返回給partition的過程中消費者A掛了,那麼partition會因為接收不到響應ACK而重新發送資料,此時消費者B可能再次將原先的訊息入庫,這就造成了資料重複了。

在沒有做任何冪等性保護的情況下,像重複轉賬,重付疊加積分這種業務,那麼結果可能是致命的。

exactly once

代表訊息正好能被消費一次,不丟失,不重複。

在at least once的情況基礎上,假設consumerA在返回ack給partition的過程中宕機了。那麼consumerB不會跟著partition的offset走,它會先去資料庫裡面檢視最新訊息對應的偏移位,再根據這個偏移位返回Kafka叢集從對應的偏移位置出發,這就可以避免訊息重複和訊息丟失。

4、資料截斷機制

我們開頭說了真正處理資料的是leader副本,follower副本只負責資料的同步和儲存,那如果因為leader宕機了二者資料不一致會怎麼樣呢?

在講一致性保證過程之前還需瞭解兩個Kafka用於表示副本資料同步的概念:

HW(High Watermark):中文翻譯為高水位,用來體現副本間資料同步的相對位置,consumer最多隻能消費到HW所在的位置,通過HW我們可以判斷資料對副本是否可見。

LEO(Log End Offset):下一條待寫入訊息的記錄位置。

leader副本從生產者獲取訊息,follower副本實時從leder同步資料,此時它們的同步資料是一致的都同步到2這個位置,並且下一個寫入的訊息都是偏移位4:

假設因為意外leader發生宕機,follower即被選為新leader,此後從生產者寫入最新的偏移位4和5:

過了一段時間原leader通過修復恢復服務,它就會發現自己和新leader的資料是不一致的:

為了保證資料一致性就必須強行讓一方妥協。因為資料是不斷在重新整理的,所以舊leader此時的優先順序會小於新leader,因此它會將自己的資料截斷到與新leader相同的HW和LEO位置,確保和新leader的資料一定相同,這就是Kafka資料截斷機制。

5、資料清理機制

同其它中介軟體一樣,Kafka的主要作用是通訊,所以即使是將資料儲存在磁碟上它還是會佔用一定空間。為了節約儲存空間它會通過一些機制對過期資料進行清理。

日誌刪除

日誌刪除會直接刪除日誌分段,kafka會維護一個定時任務來週期性檢查和刪除「過期資料」。

  • 基於時間的日誌刪除

它在每一個日誌段檔案裡面都維護一個最大時間戳來確認當前配置的刪除時間,只要日誌段寫入新訊息該欄位都會被更新。一個日誌段被寫滿了之後就不會再接收新的訊息,它會去建立一個新的日誌段檔案往裡面寫資料。

每一個日誌段檔案被寫滿之後它的最大的時間戳都是保持不變的,Kafka只要通過當前時間與最大時間戳進行比較就可以判斷該日誌段檔案是否過期。

Kafka預設配置log.retention.hours = 168,也就是7天的日誌保留時間。

  • 基於容量大小的日誌刪除

這和以上是異曲同工的方式, 只不過這次從時間換成了空間。

Kafka會通過每個日誌段空間的大小計算一個總容量閾值,然後計算出當前的實際空間大小和總容量閾值的差值,如果這個差值大於單個日誌段檔案的大小那麼就會刪除掉最舊的那個日誌段檔案,反之則不做任何處理。

同理,這個閾值也可以通過log.retention.bytes引數來設定。

日誌壓縮

Kafka的訊息是由鍵值組成的,如果日誌段裡存在多條相同key但是不同value的資料,那麼它會選擇性地清除舊資料,保留最近一條記錄。

具體的壓縮方式就是建立一個檢查點檔案,從日誌起始位置開始遍歷到最大結束位置,然後把每個訊息的key和key對應的offset儲存在一個固定容量的SkimpyOffsetMap中。

這樣前面的值就會被後面的覆蓋掉,如果日誌檔案裡存在相同的key只有最新的那個會被保留。

總結

Kafka通過ACK應答機制保證了不同元件之間的通訊效率,通過副本同步機制、資料截斷和資料清理機制實現了對於資料的管理策略,保證整個系統執行效率。

作為一款高效能又同時兼顧高可靠性的訊息中介軟體來說,Kafka能吹的點實在太多。如果本篇文章對你有所幫助,點選一下右下角的大拇指,下一次我們來詳細講解Kafka是如何實現副本間資料傳遞的。

你知道的越多,不知道的越多,各位的點贊評論都對我很重要,如果這篇文章有幫助你多一點點了解Kafka的話,可以在評論區來一波“變得更強”。