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

相关推荐
indexsunny1 天前
互联网大厂Java面试实战:微服务与Spring生态技术解析
java·spring boot·redis·kafka·mybatis·hibernate·microservices
编程彩机1 天前
互联网大厂Java面试:从Spring Boot到分布式事务的技术场景解析
spring boot·kafka·分布式事务·微服务架构·java面试·技术解析
没有bug.的程序员1 天前
RocketMQ 与 Kafka 深度对垒:分布式消息引擎内核、事务金融级实战与高可用演进指南
java·分布式·kafka·rocketmq·分布式消息·引擎内核·事务金融
yumgpkpm1 天前
华为昇腾300T A2训练、微调Qwen过程,带保姆式命令,麒麟操作系统+鲲鹏CPU
hive·hadoop·华为·flink·spark·kafka·hbase
ApachePulsar1 天前
演讲回顾|谙流科技在 Kafka on Pulsar 之上的探索
分布式·科技·kafka
yumgpkpm2 天前
2026软件:白嫖,开源,外包,招标,晚进场(2025年下半年),数科,AI...中国的企业软件产业出路
大数据·人工智能·hadoop·算法·kafka·开源·cloudera
迎仔2 天前
09-消息队列Kafka介绍:大数据世界的“物流枢纽”
大数据·分布式·kafka
indexsunny2 天前
互联网大厂Java面试实录:Spring Boot微服务与Kafka消息队列实战解析
java·spring boot·微服务·面试·kafka·电商·技术解析
indexsunny3 天前
互联网大厂Java面试实战:从Spring Boot到微服务架构的技术问答解析
java·spring boot·redis·微服务·kafka·jwt·flyway
liux35283 天前
MySQL -> Canal -> Kafka-> ES 完整数据同步流程详解
mysql·elasticsearch·kafka