1. 程式人生 > >應該在同一個Kafka主題中放入幾種事件型別嗎?

應該在同一個Kafka主題中放入幾種事件型別嗎?

採用Apache Kafka等流平臺,有個很重要的問題是:將使用哪些主題?特別是,如果要將一堆不同的事件作為訊息釋出到Kafka,是將它們放在同一主題中,還是將它們拆分為不同的主題?

主題最重要的功能是允許使用者指定它想要使用的訊息子集。在一個極端情況下,所有資料都放在一個主題中可不是一個好主意,因為這意味著消費者無法選擇感興趣的事件 - 給他們的只會是所有的內容。在另一個極端,擁有數百萬個不同的主題也不是一個好事,因為Kafka中的每個主題都會消耗資源消耗,因此擁有大量的主題就會對效能不利。

實際上,從效能的角度來看,重要的是分割槽數量。但由於Kafka中的每個主題至少有一個分割槽,如果你有n個主題,那麼就不可避免地至少有n個分割槽。不久之前,Jun Rao撰寫了一篇博文,解釋了擁有多個分割槽的成本(端到端延遲,檔案描述符,記憶體開銷,故障後的恢復時間)。根據經驗,如果您關注延遲問題,您應該關注每個代理節點上的(數量級)數百個主題分割槽。如果每個節點有數萬個甚至數千個分割槽,則延遲會受到影響。

該效能引數給設計主題的結構提供了一些指導:如果您發現自己有數千個主題,那麼將一些細粒度,低吞吐量的主題合併到粗粒度主題中是明智之舉,從而減少分割槽的擴散。

然而,效能問題並不是結束。在我看來,更重要的是您的主題結構的資料完整性和資料建模方面。我們將在本文的其餘部分討論這些內容。

主題=相同型別的事件的集合?

常見的想法(根據我所擁有的幾個對話,並根據郵件列表)似乎是:將同類型的所有事件放在同一主題中,並針對不同的事件型別使用不同的主題。這種思路讓人聯想到關係資料庫,其中表是具有相同型別(即同一組列)的記錄的集合,因此我們在關係表和Kafka主題之間進行類比。

該融合模式的登錄檔本質上強化了這種模式,因為它鼓勵你在主題中的所有訊息使用相同的Avro模式。該模式可以在保持相容性的同時進化(例如,通過新增可選欄位),但最終所有訊息都必須符合某種記錄型別。在我們介紹了更多背景之後,我們將在後面的帖子中再次討論這個問題。

對於某些型別的流資料(例如記錄的活動事件),要求同一主題中的所有訊息都符合相同的模式是有意義的。但是,有些人正在使用Kafka來實現更多類似資料庫的目的,例如事件溯源,或者在微服務之間交換資料。在這種情況下,我相信,它定義一個主題為一組具有相同模式的訊息並不重要。更重要的是Kafka維護主題分割槽內的訊息排序。

想象一下,您有一些事物(比如客戶),並且該事物可能發生許多不同的事情:建立客戶,客戶更改地址,客戶向其帳戶新增新信用卡,客戶進行客戶支援查詢,客戶支付發票,客戶關閉其帳戶。

這些事件的順序很重要。例如,我們期望在客戶做任何動作之前建立客戶,並且我們也期望在客戶關閉其帳戶之後不再發生任何其他事情。使用Kafka時,您可以通過將它們全部放在同一個分割槽中來保留這些事件的順序。在此示例中,您將使用客戶ID作為分割槽鍵,然後將所有這些不同的事件放在同一主題中。它們必須位於同一主題中,因為不同的主題意味著不同的分割槽,並且不會跨分割槽保留排序。

排序問題

如果你沒有使用(比方說)不同的主題customerCreatedcustomerAddressChangedcustomerInvoicePaid事件,然後這些議題的消費者可能會看到荒謬的事件順序。例如,消費者可能會看到不存在的客戶的地址更改(因為尚未建立,因為相應的customerCreate事件已被延遲)。

如果消費者暫停一段時間(可能是維護或部署新版本),則重新排序的風險尤其高。當消費者停止時,事件將繼續釋出,並且這些事件將儲存在Kafka代理的選定主題分割槽中。當消費者再次啟動時,它會消耗來自其所有輸入分割槽的積壓事件。如果消費者只有一個輸入,那就沒問題了:掛起的事件只是按照它們儲存的順序依次處理。但是,如果消費者有幾個輸入主題,它將選擇輸入主題以按任意順序讀取。它可以在讀取另一個輸入主題上的積壓之前從一個輸入主題讀取所有掛起事件,或者它可以以某種方式交錯輸入。

因此,如果你把customerCreatedcustomerAddressChanged以及customerInvoicePaid事件在三個獨立的主題,消費者可能會看到所有的customerAddressChanged事件,它看到任何之前customerCreated的事件。因此,消費者可能會看到一個customerAddressChanged客戶的事件,根據其對世界的看法,尚未建立。

您可能想要為每條訊息附加時間戳,並將其用於事件排序。如果要將事件匯入資料倉庫,您可以在事後對事件進行排序,這可能就可以了。但是在流程序中,時間戳是不夠的:如果你得到一個具有特定時間戳的事件,你不知道你是否仍然需要等待一個時間戳較低的先前事件,或者所有之前的事件是否已到達而你是’準備好處理這個事件。依靠時鐘同步通常會導致噩夢;

何時分割主題,何時結合?

鑑於這種背景,我將提出一些經驗法則來幫助您確定在同一主題中放入哪些內容,以及將哪些內容拆分為單獨的主題:

  1. 最重要的規則是,  任何需要保持固定順序的事件必須放在同一主題中(並且它們也必須使用相同的分割槽鍵)。最常見的是,如果事件的順序與同一事物有關,則事件的順序很重要。因此,根據經驗,我們可以說關於同一事物的所有事件都需要在同一主題中。如果您使用事件排序方法進行資料建模,事件的排序尤為重要。這裡,聚合物件的狀態是通過以特定順序重放它們來從事件日誌中匯出的。因此,即使可能存在許多不同的事件型別,定義聚合的所有事件也必須在同一主題中。

  2. 當您有關於不同事物的事件時,它們應該是相同的主題還是不同的主題?我想說,如果一個事物依賴於另一個事物(例如,一個地址屬於一個客戶),或者如果它們經常需要在一起,那麼它們也可能會出現在同一個主題中。另一方面,如果它們不相關並由不同的團隊管理,則最好將它們放在單獨的主題中。它還取決於事件的吞吐量:如果一個事物型別具有比另一個事物型別高得多的事件,它們是更好地分成單獨的主題,以避免壓倒性的消費者只想要具有低寫入吞吐量的事物(參見第4點)。但是,幾個都具有低事件率的事物可以很容易地合併。

  3. 如果一個事件涉及多個事物怎麼辦?例如,購買涉及產品和客戶,並且從一個帳戶到另一個帳戶的轉移涉及至少那兩個帳戶。我建議最初將事件記錄為單個原子訊息,而不是將其分成幾個訊息。主題,最好以完全按照您收到的方式記錄事件,並儘可能採用原始形式。您可以隨後使用流處理器拆分複合事件 - 但如果您過早地將其拆分,則重建原始事件要困難得多。更好的是,您可以為初始事件提供唯一ID(例如UUID); 以後,當您將原始事件拆分為每個涉及的事物的一個事件時,您可以將該ID轉發,從而使每個事件的起源都可追溯。

  4. 檢視消費者需要訂閱的主題數量。如果幾個消費者都閱讀了一組特定的主題,這表明可能應該將這些主題組合在一起。如果將細粒度的主題組合成粗粒度的主題,一些消費者可能會收到他們需要忽略的不需要的事件。這不是什麼大問題:消費來自Kafka的訊息非常便宜,所以即使消費者最終忽略了一半的事件,這種過度消費的成本可能也不大。只有當消費者需要忽略絕大多數訊息(例如99.9%是不需要的)時,我才建議從高容量流中分割低容量事件流。

  5. Kafka Streams狀態儲存(KTable)的更改日誌主題應與所有其他主題分開。在這種情況下,主題由Kafka Streams流程管理,不應與其他任何內容共享。

  6. 最後,如果上述規則都沒有告訴您是否將某些事件放在同一主題或不同主題中,該怎麼辦?然後,通過將相同型別的事件放在同一主題中,通過所有方法將它們按事件型別分組。但是,我認為這條規則是最不重要的。

模式管理

如果您使用的是資料編碼(如JSON),而沒有靜態定義的模式,則可以輕鬆地將許多不同的事件型別放在同一主題中。但是,如果您使用的是基於模式的編碼(如Avro),則需要更多地考慮在單個主題中處理多個事件型別。

如上所述,基於Avro的Kafka Confluent Schema Registry目前依賴於每個主題都有一個模式的假設(或者更確切地說,一個模式用於金鑰,一個模式用於訊息的值)。您可以註冊新版本的模式,登錄檔會檢查模式更改是向前還是向後相容。這個設計的一個好處是,您可以讓不同的生產者和消費者同時使用不同的模式版本,並且它們仍然保持彼此相容。

更確切地說,當Confluent的Avro序列化程式在登錄檔中註冊模式時,它會在主題名稱下注冊。預設情況下,該主題<topic>-key用於訊息鍵和<topic>-value訊息值。然後,模式登錄檔檢查在特定主題下注冊的所有模式的相互相容性。

我最近對Avro序列化程式進行了修補,使相容性檢查更加靈活。該補丁添加了兩個新的配置選項:(key.subject.name.strategy定義如何構造訊息鍵的主題名稱),以及value.subject.name.strategy(如何構造訊息值的主題名稱)。選項可以採用以下值之一:

  • io.confluent.kafka.serializers.subject.TopicNameStrategy(預設值):訊息鍵的主題名稱是<topic>-key<topic>-value對於訊息值。這意味著主題中所有訊息的模式必須相互相容。

  • io.confluent.kafka.serializers.subject.RecordNameStrategy:主題名稱是郵件的Avro記錄型別的完全限定名稱。因此,模式登錄檔會檢查特定記錄型別的相容性,而不考慮主題。此設定允許同一主題中的任意數量的不同事件型別。

  • io.confluent.kafka.serializers.subject.TopicRecordNameStrategy:主題名稱是<topic>-<type><topic>Kafka主題名稱在哪裡,並且是郵件的Avro記錄型別的完全限定名稱。此設定還允許同一主題中的任意數量的事件型別,並進一步將相容性檢查限制為僅當前主題。

使用此新功能,可以輕鬆,乾淨地將特定事物的所有不同事件放在同一主題中。現在,可以根據上述條件自由選擇主題的粒度,而不僅限於每個主題的單個事件型別。

歡迎工作一到五年的Java工程師朋友們加入Java程式設計師開發: 854393687
群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!