Kafka 基本用法入门:Java 后端初学者如何理解消息队列、生产者和消费者
1. Kafka 是什么
Kafka 可以把它理解成"高性能的分布式日志系统 + 消息队列"。它把消息顺序地写入磁盘日志(可持久化),通过分区并行提升吞吐,用多副本保证故障时不丢数据。对于 Java 后端,Kafka 最常见的价值:
- 解耦:下单服务不需要直接调用库存/通知服务,只要把消息写入 Kafka。
- 异步与削峰:高峰期先写入队列,消费者慢慢处理,系统更稳。
- 可回放:需要重算或补偿时,可从历史偏移量重新消费。
- 可靠:acks、重试、幂等配合使用,尽量做到"至少一次"交付并降低重复影响。
2. 用下单场景理解 Producer、Consumer、Topic
设想一个电商最小闭环:用户提交订单,订单服务落库成功后发送一条"订单已创建"的事件;库存服务与通知服务分别订阅并消费。
- Producer(生产者):订单服务。它把一条事件消息写到 Kafka。
- Consumer(消费者):库存服务、通知服务。它们从 Kafka 拉取并处理消息。
- Topic(主题):消息的"分类名"。例如 order.created。订单服务只负责往这个 Topic 写;谁订阅,它不关心。
事件内容建议:
- key:orderId(用于分区与局部有序)。
- value:JSON,例如 {orderId, skuId, qty, createdAt}。
- headers:可放 traceId、来源应用等,便于排错。
交付语义与幂等:
- 默认"至少一次"。同一条消息可能被投递多次,消费者应设计幂等(如用去重表、业务唯一键、CAS 更新)。
- 生产端建议开启 acks=all、重试,与幂等生产者(enable.idempotence=true)。
3. Partition 和 Consumer Group 怎么理解
- Partition(分区):同一个 Topic 可被切成多个分区,像多条并行的日志。并行度与吞吐靠分区数扩展。Kafka 只保证"同一分区内顺序"。所以要对同一 orderId 固定分区(key 一致),才能保证订单相关消息的局部有序。
- Consumer Group(消费组):同一组内的多个实例"分摊"一个 Topic 的分区,每条消息只会被组内一个实例处理;不同组互不干扰。这样库存服务(group=inventory)和通知服务(group=notify)可以各自独立消费同一条订单创建事件。
- Rebalance:当组内实例数变化时,会触发分区重新分配,短暂抖动是正常现象。生产要关注消费延迟监控与幂等处理。
- Offset:消费者的"进度指针"。可手动提交(处理成功再提交),避免未处理就前进导致丢单。
结合下单场景:
- 订单服务 -> 发送到 topic=order.created,key=orderId。
- 库存服务(group=inventory) -> 扣减库存、落账。
- 通知服务(group=notify) -> 推送短信/站内信。
- 若需要退款逆向流程,可再发 order.refund 等新事件,实现解耦的业务编排。
4. Java 项目中 Kafka 的基本使用思路
落地步骤可以照这个 Checklist:
- 明确 Topic 与事件模型:定义 order.created 的字段与数据约定,key=orderId。
- 生产端配置要点:acks=all、retries>0、enable.idempotence=true,合理批量与压缩(lz4/zstd)。
- 消费端配置要点:group.id 分服务划分;关闭自动提交或改为"处理成功后手动提交";并设置重试与死信策略。
- 幂等与一致性:用订单号/业务唯一键做去重;外部副作用(扣库存、发通知)要能够安全重试。
- 可观测性:埋点 traceId,监控 Lag、失败率与重平衡频次。
下面给出最小可用的 Spring Boot 代码骨架(演示核心思路,省略配置与异常处理):
java
@Service
public class OrderProducer {
@Autowired private KafkaTemplate<String, String> kafka;
public void sendCreated(String orderId, int skuId, int qty) {
String json = String.format("{\"orderId\":\"%s\",\"skuId\":%d,\"qty\":%d}", orderId, skuId, qty);
kafka.send("order.created", orderId, json); // key=orderId 保障局部有序
}
}
@Component
public class InventoryConsumer {
@KafkaListener(topics = "order.created", groupId = "inventory-service")
public void onMessage(ConsumerRecord<String, String> rec) {
String key = rec.key();
String body = rec.value();
// TODO: 解析 body,基于 orderId 做幂等扣减;成功后提交 offset
// 失败可抛出异常触发框架重试/进入 DLT(需在实际工程配置)
}
}
工程落地补充建议:
- Topic 规划:一个事件一类 Topic,避免"大杂烩"。为回放方便,不轻易删除历史数据。
- 错误处理:短暂失败重试;持续失败写入死信 Topic 供人工排查。
- 事务与一致性:若下单落库与发送消息需原子,可用"事务消息"或"本地事务表 + 异步补偿"。
- 压测与容量:根据峰值 QPS 反推分区数与消费者实例数;观察端到端延迟是否满足 SLA。
- 安全与合规:脱敏敏感字段;设置生产与消费权限,避免误读误写。