女朋友看了也懂的Kafka(下篇)

清風畫扇發表於2021-06-09

前言:

在上篇中我們瞭解了Kafka是什麼,為什麼需要Kafka,以及Kafka的基本架構和各自的作用是什麼,這篇文章中我們將從kafka內部每一個組成部分去看kafka 是如何保證資料的可靠性以及工作機制。因為時間問題,或許排版多有瑕疵,有些內容未能做到詳盡。待之後有空會前來填坑。話不多說,正片開始:

4.Kafka工作流程

在這裡插入圖片描述

Kafka中訊息是以topic進行分類的,生產者生產訊息,消費者消費訊息,都是面向topic的。

topic是邏輯上的概念,而partition是物理上的概念,每個partition對應於一個log檔案,該log檔案中儲存的就是producer生產的資料。Producer生產的資料會被不斷追加到該log檔案末端,且每條資料都有自己的offset。消費者組中的每個消費者,都會實時記錄自己消費到了哪個offset,以便出錯恢復時,從上次的位置繼續消費。

為什麼分割槽?

1)方便在叢集中擴充套件,每個Partition可以通過調整以適應它所在的機器,而一個topic又可以有多個Partition組成,因此整個叢集就可以適應任意大小的資料了;

2)可以提高併發,因為可以以Partition為單位讀寫了。

Kafka 使用 Zookeeper 來維護叢集成員的資訊。每個 broker (每一個節點就是一個broker)都有一個唯一識別符號,這個識別符號可以自動生成,也可以在配置檔案裡指定(我們一般也這樣做,常見的做法是通過kafka安裝目錄下conf/server.properties 檔案進行配置) 。配置如下:

# see kafka.server.KafkaConfig for additional details and defaults

############################# Server Basics #############################

# The id of the broker. This must be set to a unique integer for each broker.
# 這個id值 叢集全域性唯一
broker.id=2

工作流程

在 broker 啟動的時候,它通過建立臨時節點把自己的 ID 註冊到 Zookeeper, Kafka 元件訂閱 Zookeeper 的/brokers/ids 路徑 (broker在Zookeeper 上的註冊路徑),當有 broker 加入叢集或退出叢集時,這些元件就可以獲得通知。

在broker 停機、出現網路分割槽或長時間垃圾回收停頓時,broker 會從 Zookeeper 上斷開連 接,此時 broker 在啟動時建立的臨時節點會自動從 Zookeeper 上移除。監聽 broker 列表的 Kafka 元件會被告知該 broker 已移除。

當叢集啟動之後,Kafka叢集開始工作了,如上圖所示:

首先,叢集啟動後,叢集中的broker會通過選舉機制選出一個控制器Controller,具體的選舉細節在後邊在進行細說。控制器除了具有一般的broker的功能之處,還負責分割槽leader的選舉。我們到現在已經知道:

  • kafka使用主題Topic來進行組織資料,

  • 每個主題被分成若干個分割槽(分割槽一般在我們建立topic的時候指定,預設是1個分割槽);

  • 每個分割槽有多個副本(副本數量一般同樣是我們建立的時候指定,預設為1 ,但是其值不能超過節點的個數,因為副本是均衡分佈的)。

    • 副本有兩種型別:leader 分割槽和follower分割槽。
    • 所有的生產者和消費者請求都經過leader分割槽進行處理,
    • follower分割槽不處理客戶端請求,只是從對應的leader分割槽同步訊息,保持與leader分割槽一致的狀態。當有leader分割槽崩潰,其中一個follower分割槽會被提升為新的leader分割槽。(同樣,具體的選舉細節我們稍後在展開)
1.生產者寫入分割槽策略

生產者會建立一個ProducerRecord物件通過指定的主題向叢集傳送訊息,ProducerRecord物件需要將訊息的鍵值序列化才能在網路中傳輸。資料被髮送到叢集中的某個broker的時候,這個時候會先經過分割槽器確認資料要寫入在那個分割槽。這時候分割槽器對於資料的鍵key進行檢測,會有如下三種情況:

  • 1.如果指定了分割槽,分割槽器不會做任何事,直接返回指定的分割槽;
  • 2.如果沒有指定分割槽,分割槽器會檢視ProducerRecord物件的鍵key,當鍵值存在的時候,會將鍵的hash值與topic的partition數進行取模得到對應的分割槽資訊:\(partition = Mod(hash(key),partitionNums)\)
  • 3.沒有指定分割槽,且對應的key不存在的情況下,
    • 【舊版本0.9x以前】:對每個連線,第一次會生成一個隨機數,分割槽資訊=隨機數對分割槽數取餘的值\(partition=Mod(round(),partitionNums)\),之後的資料對應的分割槽資訊=(隨機數+(N-1))取餘分割槽數;其中N為第幾次傳送資料:比如第二次,N=2,第三次,N=3 。。。依次類推
    • 【新版本】:第一次還是會生成一個隨機數,分割槽資訊=隨機數對分割槽數取餘的值\(partition=Mod(round(),partitionNums)\),但之後的資料會排除上次選擇的分割槽,在剩下的分割槽中隨機選擇一個:比如:TopicA有三個分割槽P0,P1,P2。第一次傳送資料到P1,那第二次就是在P0、P2中隨機選擇一個分割槽,假設選擇了P2,第三次傳送資料的時候則在P0、P1中隨機選擇一個作為分割槽資訊。。。依次處理

確定好分割槽資訊後,生產者就知道該往那個主題和那個分割槽傳送該條記錄了。但是這個訊息不會立即傳送,而是將這條記錄新增到一個記錄批次中,這個批次的所有訊息都是傳送同一主題和分割槽的。批次傳送有兩個引數:設定的時間和批次的容量,只要滿足其一就傳送。傳送是由一個獨立的執行緒負責處理。伺服器也就是broker收到訊息返回是否寫入成功,

  • 寫入成功則返回訊息對應的主題、分割槽資訊以及記錄在分割槽中的偏移量也就是offset值。
  • 如果寫入失敗,生產者有重試機制,再重試機制下都沒有傳送成功,則返回錯誤資訊。
2.Acks應答機制

當對應主題和分割槽的broker接收到一批資料寫入請求時,broker先進行一些驗證:

  • 生產者是否有許可權向主題寫入的許可權?
  • 請求裡的acks值是否有效(只允許出現0、1 或all[之後-1 等同於all])
  • 如果acks=all/-1,是否有足夠多的同步副本保證訊息安全寫入。

我們知道kafka是分散式訊息佇列,如何保證資料的可靠性和不丟失是重中之重。生產者寫入過程(Kafka 還從broker內部也就是分割槽的方面進行了可靠性的保障機制,稍後展開)如何進行可靠性的保證,Kafka採用了Acks應答機制。topic的每個partition收到producer傳送的資料後,都需要向producer傳送ack(acknowledgement確認收到),如果producer收到ack,就會進行下一輪的傳送,否則重新傳送資料。

Acks機制提供了三種可靠性級別:

  • acks=0 意味著如果生產者能夠通過網路把訊息傳送出去,那麼就認為訊息已成功寫入Kafka 。producer不等待broker的ack,這一操作提供了一個最低的延遲,broker一接收到訊息還沒有進行寫入磁碟的操作,Ack就已經返回,當broker故障時有可能丟失資料
  • acks=1 意味著leader在收到訊息並把它寫入到分割槽資料檔案(不 定同步到磁碟上)時,會返回確認或錯誤響應。這種模式下依然可能存在丟失資料的可能。如果生產者訊息已經被leader分割槽寫入了,ack返回成功,但是此時,leader分割槽的broker掛了或者崩潰了,之前有簡單提到,Controller會從follower分割槽中選擇一個follower作為新的leader分割槽,但是此時新的leader並沒有同步到剛剛的訊息,此時訊息就發生丟失。
  • acks=all/-1 意味著leader在返回確認或錯誤響應之前,會等待所有同步副本都收到並同步訊息。當前模式下,如果leader在所有follower都同步完訊息,傳送ack的時候,leader網路問題,導致超時沒有傳送成功ack,這時候producer會繼續傳送同樣的資料,或者leader掛了,新的leader已經有了這批資料,對於producer傳送的資料還要重新寫入。就導致資料重複了。
3.檔案儲存機制

這時候,broker開始寫入producer傳送的一批資料。Kafka是順序寫磁碟的方式持久化資料,在我們的認知中,是不是覺得寫磁碟很慢,但有大量資料請求寫入,怕是寫的黃花菜都涼了哦。別急,kafka 能夠如此火熱自然有其特殊之處,正所謂:沒有金剛鑽,不攬瓷器活嘛。Kafka是對於資料進行追加的方式順序寫入,這樣就減少了大量的磁頭定址的時間。官網資料表明:同樣的磁碟,順序寫能到600M/s,而隨機寫只有100K/s。

由於生產者生產的訊息會不斷追加到log檔案末尾,為防止log檔案過大導致資料定位效率低下,Kafka採取了分片索引機制,將每個partition分為多個segment。每個segment對應三個檔案——“.index”檔案、“.timeindex”檔案和“.log”檔案。這些檔案位於一個資料夾下,該資料夾的命名規則為:topic名稱+分割槽序號。

  • index:log檔案的索引
  • log: 資料儲存檔案
  • timeindex: log檔案資料的時間索引

segment的命名規則:
1、每個分割槽第一個segment的檔名= 0000000000000000000
2、後續第N個segment檔名 = 第N-1個segment中最後一個offset+1
segment給log檔案建索引的時候是每個一段範圍[4k]建一個索引,是為稀疏索引。

在這裡插入圖片描述

“.index”檔案儲存大量的索引資訊,“.log”檔案儲存大量的資料,索引檔案中的後設資料指向對應資料檔案中message的物理偏移地址。如果我們現在查詢第三條資料即offset=2的資料:則其索引為000000000000000002,通過在index檔案中確認其索引,找到對應的資料記錄的log中的地址,然後再找到對應的資料位置,讀取出來。

在這裡插入圖片描述

producer向leader寫入資料之後,follower需要向leader同步訊息資訊也就是需要複製多少份資料,但是我們應該配置多少個副本呢?又因為副本的均衡分佈,就是一個broker只會有同主題同分割槽的一個副本。那配置副本就是配置broker,也就是需要多少個節點可以滿足我們資料的可靠性保證呢?在Kafka中,每個分割槽的預設副本數=3。就是說最小叢集的配置數,HDFS的預設副本也是3,所以一般3副本就足以保證資料不會丟失,當然也要考慮機架配置的哦。如果複製係數為N ,那麼在N-1個broker 失效的情況下,仍然能夠從主題讀取資料或向主題寫入資料。所以,更高的複製係數會帶來更高的可用性、可靠性和更少的故障。另一方面,複製係數N需要至少N個broker ,而且會有N個資料副本。我們可以根據自身需求來確認,比如:銀行為了保證資料更高的可靠性,就可以將複製係數設定為5。如果我們可以接受主題偶爾的不可用,也可以配置為2,當一臺broker崩潰,另一臺broker作為新的controller繼續進行後續的工作。

在前邊我們瞭解Ack應答機制有三種應答級別。最為可靠的設定為all。在當前應答級別下,假設leader收到資料,所有follower都開始同步資料,但有一個follower,因為某種故障,遲遲不能與leader進行同步,那leader就要一直等下去,直到它完成同步,才能傳送ack。這個問題怎麼解決呢?

4.ISR

Leader維護了一個動態的in-sync replica set (ISR),意為和leader保持同步的follower集合。當ISR中的follower完成資料的同步之後,leader就會給producer傳送ack。如果follower長時間未向leader同步資料,則該follower將被踢出ISR,該時間閾值由replica.lag.time.max.ms引數設定。Leader發生故障之後,就會從ISR中選舉新的leader。ISR佇列中follower的選擇標準:

  • 舊版本【0.9x之前】:follower分割槽的通訊速率和副本的完整性,就是資料和leader相差越少,則完整性越高。這個很容易理解的
  • 新版本:follower分割槽與leader的通訊速率。

我們知道消費者也是隻和leader分割槽進行通訊進行消費資料,如下:

在這裡插入圖片描述

  • 當前消費者消費leader資料到offset=14這條,這時候leader崩潰了,需要重新選擇分割槽leader,假設follower1選為新的leader,那這個時候消費者向新leader消費offset=14的這個訊息,leader分割槽沒有這個資訊,如果生產者開始寫入資料,如果ack=1,那寫入的資料就是0ffset=16及之後的資料了,那麼就會導致新的leader丟失資料。如果是ack=all,那麼這時候生產者根據之前leader的返回資訊,假定在offset=10這筆寫完,leader給生產者返回了寫入成功的訊息,也就是說offset=11之後的資料是新的一批,這時候producer向新leader重新傳送資料,就會產生重複資料。
  • 針對於消費者,如果副本沒有同步的訊息,其實是“不安全”的,如果我們允許消費者消費leader分割槽中其他副本沒有完全同步的訊息就會破壞一致性,因此我們只能允許消費者消費全部副本都已經同步了的資料。Kafka在Log檔案中引入了HW和LEO
  • LEO:指的是每個副本最大的offset
  • HW:指的是消費者能見到的最大的offset,ISR佇列中最小的LEO。

在這裡插入圖片描述

1)follower故障

follower發生故障後會被臨時踢出ISR,待該follower恢復後,follower會讀取本地磁碟記錄的上次的HW,並將log檔案高於HW的部分擷取掉,從HW開始向leader進行同步。等該follower的LEO大於等於該Partition的HW,即follower追上leader之後,就可以重新加入ISR了。

2)leader故障

leader發生故障之後,會從ISR中選出一個新的leader,之後,為保證多個副本之間的資料一致性,其餘的follower會先將各自的log檔案高於HW的部分截掉,然後從新的leader同步資料。

注意:這隻能保證副本之間的資料一致性,並不能保證資料不丟失或者不重複。

Kafka消費者從屬於消費者群組。一個群組裡的消費者訂閱的是同一個主題,每個消費者接收主題一部分分割槽的訊息。假設主題T1有4個分割槽,我們建立了消費者C1,他是群組G1中唯一的消費者,我們用它訂閱主題T1,消費者C1將收到主題T1全部4個分割槽的訊息:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-3ei5a9GL-1623243131864)(C:\Users\lml\AppData\Roaming\Typora\typora-user-images\image-20210603113400258.png)]

如果群組G1新增一個消費者C2,那麼每個消費者將分別從兩個分割槽接受訊息。我們假設消費者C1消費分割槽0和分割槽2的訊息,消費者C2接收消費分割槽1和分割槽3的訊息,如圖:

在這裡插入圖片描述

如果群組G1有4個消費者,那麼每個消費者都分配到一個分割槽:

在這裡插入圖片描述

如果我們繼續往群組裡新增更多消費者,超過主題的分割槽數量,那麼有一部分消費者就會被閒置,不會接收到任何的訊息:

在這裡插入圖片描述

往群組裡增加消費者是橫向伸縮消費能力的主要方式。 Kafka 消費者經常會做一些高延遲 的操作,比如把資料寫到資料庫或 HDFS ,或者使用資料進行比較耗時的計算。在這些情況下,單個消費者無法跟上資料生成的速度,所以可以增加更多的消費者,讓它們分擔負載,每個消費者只處理部分分割槽的訊息,這就是橫向伸縮的主要手段。我們有必要為主題建立大量的分割槽,在負載增長時可以加入更多的消費者。不過要注意,不要讓消費者的數量超過主題分割槽的數量,多餘的消費者只會被閒置。

除了通過增加消費者來橫向伸縮單個應用程式外,還經常出現多個應用程式從同 主題 讀取資料的情況。實際上, Kafka 設計的主要目標之 ,就是要讓 Kafka 主題裡的資料能 夠滿足企業各種應用場景的需求。在這些場景裡,每個應用程式可以獲取到所有的訊息, 而不只是其中的部分。只要保證每個應用程式有自己的消費者群組,就可以讓它們獲取到主題所有的訊息。不同於傳統的訊息系統,橫向伸縮 Kafka 消費者和消費者群組並不 對效能造成負面影響。

在上面的例子裡,如果新增 個只包含 個消費者的群組 G2 ,那麼這個消費者將從主題 Tl 上接收所有的訊息,與群組 Gl 之間互不影響。群組 G2 可以增加更多的消費者,每個 消費者可以悄費若干個分割槽,就像群組 Gl 那樣,如圖所示。總的來說,群組 G2 還是 會接收到所有訊息,不管有沒有其他群組存在。

在這裡插入圖片描述

5.分割槽再均衡

我們通過上邊的例子知道,群組裡的消費者共同讀取主題的分割槽。一個新的悄費者加 入群組時,它讀取的是原本由其他消費者讀取的訊息。當一個消費者被關閉或發生崩憤時,它就離開群組,原本由它讀取的分割槽將由群組裡的其他消費者來讀取。在主題發生變化時 比如管理員新增了新的分割槽,會發生分割槽重分配。 分割槽的所有權從 個消費者轉移到另 個消費者,這樣的行為被稱為再均衡。再均衡非常 重要, 它為消費者群組帶來了高可用性和伸縮性(我們可以放心地新增或移除梢費者), 不過在正常情況下,我們並不希望發生這樣的行為。在再均衡期間,消費者無法讀取訊息,造成整個群組一小段時間的不可用。另外,當分割槽被重新分配給另一個消費者時,消費者當前的讀取狀態會丟失,它有可能還需要去重新整理快取,在它重新恢復狀態之前會拖慢應用程式。如何進行安全的再均衡,以及如何避免不必要的再均衡。

消費者通過向被指派為群組協調器的 broker (不同的群組可以有不同的協調器)傳送心跳來維持它們和群組的從屬關係以及它們對分割槽的所有權關係。只要消費者以正常的時間間隔傳送心跳,就被認為是活躍的,說明它還在讀取分割槽裡的訊息。消費者會在輪詢訊息 (為了獲取訊息)或提交偏移量時傳送心跳。如果消費者停止傳送心跳的時間足夠長,會話就會過期,群組協調器認為它已經死亡,就會觸發一次再均衡。 如果一個消費者發生崩潰,井停止讀取訊息,群組協調器會等待幾秒鐘,確認它死亡了才 觸發再均衡。在這幾秒鐘時間裡,死掉的消費者不會讀取分割槽裡的訊息。在清理消費者時,消費者會通知協調器它將要離開群組,協調器會立即觸發一次再均衡,儘量降低處理停頓。

6.分配分割槽的過程

當消費者要加入群組時,它會向群組協調器傳送 Join Group 請求。第1個加入群組的消費者將成為“群主”。群主從協調器那裡獲得群組的成員列表(列表中包含了所有最近傳送過心跳的消費者,它們被認為是活躍的),並負責給每一個消費者分配分割槽。它使用 個實現了 PartitionAssignor介面的類來決定哪些分割槽應該被分配給哪個消費者。分配完畢之後,群主把分配情況列表傳送給群組協調器,協調器再把這些資訊發 送給所有消費者。每個消費者只能看到自己的分配資訊,只有群主知道群組裡所有消費者的分配資訊。這個過程會在每次再均衡時重複發生。

上邊我們知道,分割槽會被分配個群組裡的消費者。PartitionAssignor 根據給定的消費者和主題,決定哪些分割槽應該被分配給哪個消費者。Kafka 有兩個預設的分配策:Range 和RoundRobin。

Range:

該策略會把主題的若干個連續的分割槽分配給消費者。假設消費者C1和消費者 C2 同時 訂閱了主題 T1 和主題 T2 ,井且每個主題有3個分割槽。那麼消費者 C1有可能分配到這兩個主題的分割槽1和分割槽3,而消費者 C2 分配到這兩個主題的分割槽2 。因為每個主題擁有奇數個分割槽,而分配是在主題內獨立完成的,第一個消費者最後分配到比第二個消費者更多的分割槽。只要使用了Range策略,而且分割槽數量無法被消費者數量整除,就會出現這種情況。

  • [x] Range分割槽是對於主題而言,對於每一個主題都會與消費者組進行一次分配:

在這裡插入圖片描述

如圖中,第一次先分配Topic1的三個分割槽,對於消費者,就是C1先分第一塊,然後C2分得第二塊,C1接著分配到第三塊。同樣對於第二個Topic,同樣的順序進行分配分割槽,C1分得第一塊分割槽,C2分得第二塊分割槽,C1接著分得第三塊。最後就是C1分配到4個分割槽進行消費,而C2只分得兩個分割槽。

RoundRobin

該策略把主題的所有分割槽逐個分配給消費者。如果使用 RoundRobin 策略來給消費者 C1和消費者 C2 分配分割槽,那麼消費者C1將分到主題 T1的分割槽1和分割槽3以及主題 T2 的分割槽2 ,消費者 C2 將分配到主題 T1分割槽2 以及主題T2的分割槽1和分割槽3。一般 來說 ,如果所有消費者都訂閱相同的主題(這種情況很常見), RoundRobin 策略會給所有消費者分配相同數量的分割槽(或最多就差1個分割槽)。

  • [x] RoundRobin 是對消費者組而言,把消費者訂閱的主題的所有分割槽都看做是統一的整體進行分配:
    在這裡插入圖片描述

如圖:RoundRobin策略相當於把當前消費者組訂閱的主題中所有分割槽看做統一的整體,然後對消費者群組中的每一個活躍者進行輪流分配。

7.提交和偏移量
7.1消費者消費流程

在此,我們有必要明白Consumer是如何消費的。呼叫了那些方法,做了那些行為來完成一次消費;

在這裡插入圖片描述

對於輪詢階段我們進行詳細分析說明:

  • 1.while(true) 使用無限迴圈,是因為消費者實際上是 個長期執行的應用程式,它通過持續輪詢向Kafka 請求資料。
  • 2.kafkaConsumer.poll(Duration.ofSeconds(1)):消費者必須持續對 Kafka進行輪詢,否則會被認為己經死亡,它的分割槽會被移交給群組裡的其他消費者。傳給 poll()方法的引數是一個超時時間,用於控制poll()方法的阻塞時間(在消費者的緩衝區裡沒有可用資料時會發生阻塞)。如果該引數被設為 0, poll()會立即返回 ,否則 它會在指定的時間內一直等待broker 返回資料。
  • 3.poll ()方法能返回一個記錄列表。每條記錄都包含了記錄所屬主題的資訊、記錄所在分割槽的資訊。記錄在分割槽裡的偏移量 ,以及記錄的鍵值對。我們一般會遍歷這個列表,逐條處理這些記錄。poll ()方法有一個超時引數,它指定了方法在多久之後可以返回, 不管有沒有可用資料都要返回。 超時時間的設定取決於應用程式對響應速度的要求, 比如要在多長時間內把控制權歸還給執行輪詢的執行緒。
  • 4.在退出應用程式之前使用 close()方法關閉消費者。網路連線和 socket 也會隨之關閉,並立即觸發一次再均衡,而不是等待群組協調器發現它不再傳送心跳井認定它已死亡, 因為那樣需要更長的時間,導致整個群組在一段時間內無法讀取訊息。

輪詢不只是獲取資料那麼簡單。在第一次呼叫新消費者的 poll()方法時,它會負責查詢 GroupCoordinator 然後加入群組,接受分配的分割槽。如果發生了再均衡,整個過程也 在輪詢期間進行 。當然,心跳也是從輪詢裡傳送出去的。所以,我們要確保在輪詢期間,所做的任何處理工作都應該儘快完成。

7.2偏移量維護

每次呼叫 poll ()方法,它總是返回由生產者寫入 Kafka 但還沒有被消費者讀取過的記錄 我們因此可以追蹤到哪些記錄是被群組裡的哪個消費者讀取的。這是 Kafka 個獨特之處。消費者可以使用 Kafka 來追蹤訊息在分割槽裡的位置(偏移量)。

我們把更新分割槽當前位置的操作叫作提交。消費者消費訊息是按照批次進行的。

那麼消費者是如何提交偏移量的呢?消費者往一個叫作 __consumer_offset 特殊主題傳送訊息,訊息裡包含每個分割槽的偏移量。如果消費者一直處於執行狀態,那麼偏移量就沒有什麼用處。不過,如果悄費者發生崩潰或者有新的消費者加入群組,就會觸發再均衡,完成再均衡之後,每個消費者可能分配到新的分割槽,而不是之前處理的那個。為了能夠繼續之前的工作,消費者需要讀取每個分割槽最後一次提交的偏移量,然後從偏移量指定的地方繼續處理。說人話也就是說消費者群組發生變化的時候或者消費者組重啟之後,要能從上一次消費的地方接著消費資料。消費者組會知道並記錄每一次消費的時候消費者消費主題分割槽的最後一個訊息的offset,這樣,下一次當前消費者組再開始消費的時候,就能從具體分割槽的最後一次消費的地方接著消費。

如果提交的偏移量小於客戶端處理的最後一個訊息的偏移量 ,那麼處於兩個偏移量之間的訊息就會被重複處理,如圖:

在這裡插入圖片描述

如果提交的偏移量大於客戶端處理的最後 個訊息的偏移量,那麼處於兩個偏移量之間的 訊息將會丟失:

在這裡插入圖片描述

所以,處理偏移量的方式對客戶端會有很大的影響。

7.3自動提交

最簡單的提交方式是讓悄費者自動提交偏移量。如果 enable .auto.commit 被設為 true ,那 麼每過5s,消費者會自動把從 poll()方法接收到的最大偏移量提交上去。提交時間間隔 auto.commit.interval.ms 控制,預設值是 5s 。與梢費者裡的其他東西一樣,自動提交也是在輪詢裡進行的。消費者每次在進行輪詢時會檢查是否該提交偏移量了,如果是,那麼就會提交從上一次輪詢返回的偏移量。

不過當前策略有什麼缺陷呢?可以想想。

假設我們仍然使用預設的 5s 提交時間間隔,在最近一次提交之後的 3s 發生了再均衡,再均衡之後,消費者從最後一次提交的偏移量位置開始讀取訊息。這個時候偏移量已經落後 3s ,所以在這 3s 內到達的訊息會被重複處理。可以通過修改提交時間間隔來更頻繁地提交偏移量,減小可能出現重複悄息的時間窗,不過這種情況是無法完全避免的。

在使用自動提交 ,每次呼叫輪詢方法上一次呼叫返回的偏移量提交上去,它並不知道具體哪些訊息已經被處理了,所以在再次呼叫之前最好確保所有當前呼叫返回的訊息都已經處理完畢(在呼叫 close()方位之前也 行自動提交)。

7.4手動提交

我們可以通過控制提交偏移量的時間儘可能消除丟失訊息的可能性和再均衡時重複消費資料的數量。此外消費者API 提供了另一種提交偏移量的方式 ,讓我們可以基於處理訊息的時候需要提交的去提交當前偏移盤,而不是基於時間間隔。

首先我們需要在消費者的配置中關閉自動提交引數:auto.commit.offset 設為false,讓應用程式決定何時提交偏移量。

7.5同步提交

使用 commitSync() 提交偏移量最簡單最可靠,因為這個方法是同步方法。這個方法會提交由 poll()方法返回的最新偏移量,提交成功後馬上返回,如果提交失敗就丟擲異常。

要記住, commitSync() 將會提交由 poll ()返回的最新偏移量,所以在處理完所有記錄後要確保呼叫了 commitSync() ,否則還是會有丟失訊息的風險。如果發生了再均衡,從最近一批訊息到發生再均衡之間的所有訊息都將被重複處理。

7.6非同步提交

同步提交有一個不足之處, 在broker對提交請求作出回應之前,應用程式會一直阻塞,這樣會限制應用程式的吞吐量。我們可以通過降低提交頻率來提升吞吐量,但如果發生了再均衡, 會增加重複訊息的數量。這時候我們可以使用非同步提交方式進行提交:commitASync()。

這時候我們只傳送提交請求,不用等待broker的響應.

總結

我們從kafka 的工作機制,從生產者 到broker叢集到消費者全套的工作流程和內部的細節都有一個比較清晰的認知了。今天匆匆就結束這個篇章,待之後再來細化排布。
怕什麼真理無窮,進一寸有一寸的歡喜。我是清風,希望這篇文章對你有幫助。如有不準確之處,還請評論區留言討論。

相關文章