如何對Kafka 中的訊息實現優先分級?

banq發表於2021-11-27

果您瞭解內部原理,那麼您可能想知道如何在 Kafka 中實現訊息優先順序。因為,就其工作方式而言,Kafka 無法直接實現此用例。如果你真的是 Kafka 的新手,那麼一定要堅持到文章結束。我將盡力分享我對 Kafka 的工作原理以及在 Kafka 中實現訊息優先順序的各種方法的知識。
你們中的一些人可能將 Kafka 視為一個訊息佇列系統。相信我,我最初也是這樣做的。但是,這並不完全正確。Kafka 是一個可水平擴充套件的訊息流平臺。它主要用於流處理用例。此外,在基於釋出者-訂閱者模式(通常稱為釋出-訂閱模型)或事件驅動架構設計的應用程式中用作訊息匯流排。
 

Kafka基礎知識

  • Kafka Brokers:Kafka Brokers是一個單獨的 Kafka 節點,持有主題和分割槽。在叢集中,多個 Kafka 代理共享負載並在其中一個代理出現故障時啟用彈性。
  • Zookeeper: Zookeeper 充當 Kafka 叢集的服務發現。它與各種 Kafka 代理進行協調,並在建立、刪除新主題、代理加入叢集以及代理出現故障時使它們保持同步。此外,當節點出現故障時,有助於領導者選舉。
  • 主題Topic:主題類似於訊息佇列。它儲存與給定主題相關的訊息。所有訊息要麼釋出到給定主題,要麼從給定主題消費。一個給定的主題可以有多個生產者,一個給定的主題可以有多個消費者。每個主題都是一個單一的、邏輯上分佈的訊息提交日誌。建立主題時也可以提供複製因子。這決定了主題下每個分割槽的複製。複製因子最多可以等於叢集中 Kafka 代理的數量。這可以提高彈性,以防 Kafka 代理出現故障。
    主題的複製因子 ≤ 叢集中的 Kafka broker 數
  • 分割槽:一個主題被分成多個分割槽。建立主題時可以指定分割槽數。此外,可以修改現有主題的編號。分割槽只不過是物理儲存在檔案系統中的僅附加檔案。您可以將其視為一個日誌檔案,其中每一行都是一條訊息,並且該檔案只會附加在每條傳入訊息上。
    這些訊息是不可變的。意思是,一旦訊息被髮送到一個主題,你基本上不能去編輯或刪除特定的訊息。在中間插入訊息也是不可能的,因為它是一個僅附加檔案。這意味著在 Kafka 中重新排序訊息是不可能的。

現在您可能理解了為什麼優先順序不是 Kafka 的內建功能背後的真正原因。但是,我們可以透過多種方式實現它。
 
回到分割槽。傳送到主題的每條訊息都將在內部僅傳送到其一個分割槽。在從主題消費時,消費者有責任從各個分割槽消費。這意味著訊息的 FIFO 排序僅在分割槽級別得到保證,而不能在主題級別得到保證。
  • 訊息: Kafka 期望在我們傳送到主題的每條訊息中都有一個可選的金鑰。該鍵將被雜湊以識別訊息將落入的分割槽。確保選擇合適的欄位作為鍵。如果選擇不當,那麼一些分割槽可能會保持空白,而其他分割槽則充滿了大量訊息。如果未提供金鑰,則 Kafka 基於迴圈方式將其路由到分割槽。此外,訊息在各自的分割槽中最多保留 7 天。可以透過修改retention.ms給定主題的保留配置來修改此保留時間。
    Partition Number = hash(Key) %(主題可用的分割槽數)
  • 消費者和消費者組:由於 Kafka 不是訊息佇列系統,消費者必須選擇是從給定主題的最早偏移量還是最新偏移量讀取。這可以使用 Kafka 客戶端上的屬性進行設定,auto.offset.reset。這為我們提供了消費者在第一次啟動時的控制,是從為主題保留的第一條訊息開始消費,還是從收到的最新訊息開始消費。Kafka 維護每個消費者的偏移量以進行分割槽。消費者客戶端將選擇非同步或同步提交在收到每組訊息後。消費者客戶端甚至可以選擇在處理完訊息後提交,允許 Kafka 代理在客戶端處理失敗的情況下重新傳送訊息。為了允許從消費者角度為給定主題進行負載共享,Kafka 允許使用消費者組。消費者組是屬於同一服務的一組例項的唯一標識。即使給定組的所有消費者都出現故障,Kafka 也會儲存消費者組的偏移量。這允許消費者組在重新上線時從它停止的地方開始處理。
  • 消費者群體如何分擔負載?在 消費者組中,每個消費者至少分配一個分割槽,這樣就不會有兩個消費者正在消費同一個分割槽。確保使用的消費者數量小於或等於主題中提供的分割槽。如果消費者的數量等於分割槽,那麼每個消費者將被分配一個分割槽。所以,如果消費者的數量超過分割槽的數量,那麼他們將無限捱餓,造成資源浪費。
    注意:消費者的分割槽分配預設由 Kafka 自己管理。因此,消費者群體客戶不必擔心相同的問題。當消費者組中的消費者出現故障時,它正在偵聽的分割槽將在同一消費者組中的其他消費者(如果有)之間共享。

 

Kafka 中訊息的優先順序排序方法

  • #1 蠻力方式:

解決此問題的最簡單方法是為每個優先順序建立單獨的主題。從生產者的角度來看,我們可以根據優先順序邏輯編寫一個釋出到各自主題的邏輯。從消費者的角度,我們可以寫一段程式碼,先監聽優先順序最高的topic,一直處理到沒有訊息為止。然後,我們可以回退到較低優先順序的佇列等等。
在此GitHub 儲存庫中檢視上述實現的程式碼。
使用一個PostConstruct方法初始化了類,該方法將在啟動期間由 Spring 建立物件後執行。如果你不明白這一點。它與建構函式非常相似。在這種情況下,一旦物件被初始化,這個方法就會被觸發並執行。我們正在List<TopicConsumer>以高優先順序主題消費者首先出現的方式初始化,然後是中,然後是低。
作為下一步。我已經建立了一個使用者,它將在應用程式準備就緒時啟動。
在這裡,我們以一種方式無限地消費,如果有任何訊息,我們每次都會檢查最高優先順序的主題。這樣,可以從消費者的角度實施優先順序排序。
這個實現是有問題的。如果高優先順序主題總是有一些訊息,那麼消費者將不會消費其他優先順序主題。但是在Flipkart 的 Incubator下的priority-kafka-client 中有更好的類似邏輯的實現。請檢視他們的文件!
 
  • #2 Resequencer 模式:

這是一種訊息路由設計模式,有助於解決我們的優先順序問題。重新排序器是一個自定義元件,它接收可能未按順序到達的訊息流。它有一個內部緩衝區來儲存一組訊息,還具有對訊息進行排序並將其釋出到輸出通道的邏輯。
與第一種方法不同,我們在這裡使用單個主題。這種模式可以透過在釋出者和消費者之間引入一個服務來實現。此服務使用包含要確定優先順序的訊息的 Kafka 主題。該服務負責處理預定義的訊息緩衝區。當訊息數量達到給定容量時,讓應用排序。如果我們沒有收到所需的訊息容量,還必須支援超時。應用排序後,緩衝區中的所有訊息都將釋出到傳出通道。這個傳出頻道主題可以被我們的實際消費者消費,它首先期望優先訊息。
我已經利用Spring Boot Apache Camel 的 Resequence來實現以下 Kafka 整合。
以在此GitHub 儲存庫 中找到此程式碼實現。
我們需要實現一個ExpressionResultComparator,以後可以在建立 Camel Route 時使用它。如果您對此完全陌生。別擔心,只關注比較方法。我們只是獲取訊息並根據 String 的compareTo方法進行比較。這意味著,無論我們傳送什麼,我們都希望輸出按字母順序排列。
這種模式不是可用於確定訊息優先順序的最佳解決方案。根據模式,重新排序器應該收集訊息,直到達到某個條件。當緩衝區達到最大容量或指定的時間用完時,可以滿足此條件。這意味著在實際消費者端接收優先順序訊息可能會有延遲。此外,如果我們希望此解決方案高效,那麼我們需要最大化緩衝區大小並減少超時。但這並不是提高效率的唯一因素。它還取決於傳入的訊息吞吐量。這是可以預測的,但我們不能確定這個因素。
 
  • #3 桶優先模式

這是迄今為止可用的最佳實現。此模式取決於主題分割槽。該模式透過在稱為桶的給定主題分割槽上建立抽象來解決優先順序問題。桶可能包含一個或多個分割槽。桶的優先順序直接取決於落入該桶的分割槽數。分割槽數越多,bucket 的優先順序就越高。從基礎知識,我們知道一個分割槽只能有一個消費者組中的一個消費者。這意味著每個儲存桶的並行消費者數量直接取決於該儲存桶中可用的分割槽數量。由於最高優先順序獲得最多的分割槽,因此它也獲得了最多的消費者。
與其他兩種方法不同,這種模式允許消費者在給定時間內消費所有優先順序訊息。只有給定優先順序的消費者數量會發生變化。
程式碼GitHub repository.
可以使用訊息中的鍵欄位將其路由到特定分割槽。從生產者的角度來看,我們可以將訊息路由到給定的bucket。我們可以期望負載平衡的消費者按照各自的優先順序使用它。
這是由Ricardo Ferreira實現的

相關文章