應該在同一個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等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章