Kafka 不難,只是你用得不對

本文分享使用 Kafka 的一些經典模式。有時你感覺 Kafka 好難搞,可能是因為不了解這些模式。
讓我們從基礎開始:
1.每個事件類型一個主題
反模式:
orders-service-topic
shipping-service-topic
analytics-service-topic
每個服務都有自己的主題?不不不,你要這么搞,那就不是事件驅動設計了,這種設計只能看做是多了些步驟的遠程過程調用(RPC)。
正模式:
order.created
order.shipped
order.cancelled
主題應該表示一個什么什么事件,多個服務可以對同一事件做出反應,這樣你的架構才能保持松耦合。
2.消費者組 = 一個邏輯工作單元
消費者組很簡單:一個組 = 一個合理的業務目的。
但很多團隊都把這一點搞砸了。他們為每個實例創建一個消費者組,更糟糕的是,還會為不同的任務重復使用同一個組。
正模式:
- inventory-updater 倉庫更新器
- email-sender 郵件發送器
- analytics-processor 分析處理器
這些中的每一個都是獨立的組。它們都使用 order.created 并獨立完成自己的工作。
這將所有內容解耦。它還具有出色的擴展性——Kafka 會在同一組內的消費者之間對分區進行負載均衡。
3.避免無狀態的鏈式事件
假設你收到一個 order.created 的消息,然后你甚至沒有檢查任何內容就立即發布一個 order.verified 的消息。這很危險。不要把 Kafka 變成一個愚蠢的消息傳遞者。
正模式:事件 + 狀態。
不要為了模擬工作流程而將五個事件串聯在一起,而是讓事件觸發一個擁有邏輯和狀態的服務,并且僅在必要時發出下一個事件。
這樣可以避免意外的無限循環、幽靈事件和令人困惑的重放錯誤。
4.使用發件箱模式
有沒有試過在插入數據庫后向 Kafka 發送消息,結果 Kafka 調用失敗?現在你的數據庫已更新……但 Kafka 從未收到該消息。恭喜你——你剛剛制造了一個不一致性!
發件箱模式來解決這個問題
將事件作為同一事務的一部分存儲在你的數據庫中,如下所示:
BEGIN;
INSERT INTO orders (...) VALUES (...);
INSERT INTO outbox (event_type, payload) VALUES ('order.created', '{...}');
COMMIT;
然后運行一個后臺任務,讀取 outbox 表并將事件發送到 Kafka。
原子!可靠!可重放!
5.采用退避重試
人們認為“Kafka 讓一切都可重放”,所以他們濫用它。他們只是不假思索地重新消費失敗的事件。
但如果錯誤是由于下游系統宕機導致的呢?(比如A服務消費Kafka消息時要調用B服務做一些邏輯,此時B服務掛了,進而導致最終未能成功消費)
使用帶有指數退避的重試隊列,或者使用像Kafka Streams / Debezium這樣內置錯誤處理功能的框架。
或者手動創建如下主題存放消費失敗的消息,延遲重試:
- order.created.retry.1m
- order.created.retry.10m
- order.created.dlq
巴輝特提醒:這種 retry 隊列的模式很有意思,之前我也未曾關注過。通常來講,建議一個 topic + 一個 consumer group 作為顆粒度創建一套 retry 隊列,因為事件可能有多個 consumer group 來消費。這個知識有點繞,大家可以通過 GPT 了解更多細節。
舉例,比如 100 條事件里只有 1 條消費失敗,如果持續重試這一條,持續失敗,就會影響后面的事件消費。
當然,如果你們的業務邏輯就必須得嚴格要求順序,那另當別論,case by case 來看哈。
6. Schema 要全局統一
如果您的事件看起來像這樣:
{
"id": 123,
"user": "john",
"total": 100
}
這樣沒問題……直到有人添加了一個新字段:
{
"id": 123,
"user": "john",
"total": 100,
"vip": true
}
現在,你的使用者程序出現故障。或者更糟的是—— 悄無聲息地出現異常行為。
巴輝特提醒:這個意思是說,consumer 不知道事件格式發生變化,可能會引起故障,需要一個機制,讓所有人知道消息格式變了,而且消息格式需要兼容性。
使用 Avro/Protobuf + Schema Registry,你會得到:
- 向前/向后兼容性
- 嚴格類型標注
- 演進支持
當團隊壯大時,你會感謝自己的這種做法。
簡單的Kafka架構圖
+------------------+
| Order Service |
|------------------|
| Emits: order.created |
+--------+---------+
|
v
+--------+---------+
| Kafka |
|-------------------|
| Topics: |
| - order.created |
| - order.retry |
| - order.dlq |
+--------+----------+
|
+-----------+------------+
| | |
v v v
+-----------+ +--------------+ +------------------+
|Inventory | |Email Service | |Analytics Service |
|Updater | |(Consumer) | |(Consumer) |
+-----------+ +--------------+ +------------------+
每個服務都是一個獨立的消費者組,對相同的事件做出反應。發件箱模式(未顯示)在生產者端實現。
最終想法
Kafka 并不難。你跳過了那些讓事情變得易于處理的模式,結果反而把事情弄復雜了。
你越早遵循這些原則,Kafka就越早成為一個健壯、可擴展的事件驅動系統的支柱,而不是凌晨3點令人頭疼的調試難題。
原文:https://codingplainenglish.medium.com/kafka-is-hard-because-you-keep-ignoring-these-patterns-588b2ebac3c0

浙公網安備 33010602011771號