Kafka 是一个分布式事件流平台,最初由 LinkedIn 开发,后来成为 Apache 顶级项目。
在实际后端系统中,Kafka 经常被用于异步解耦、日志采集、消息通知、数据同步、削峰填谷以及事件驱动架构。
如果你正在使用 Node.js、NestJS、Java、Go 等技术栈开发后端服务,那么 Kafka 基本属于非常值得掌握的一项基础设施。
本文不会只讲概念,而是从一个实际业务场景出发,逐步理解 Kafka 的核心概念以及生产环境中容易踩到的坑。
一、为什么需要 Kafka?
假设我们有一个用户系统。
用户修改部门信息之后,需要同时完成:
- 更新数据库
- 更新 Elasticsearch
- 更新缓存
- 通知其他服务
- 更新动态用户组
- 记录操作日志
最简单的写法可能是:
async updateUser(userId: string, data: UpdateUserDto) {
await this.userService.update(userId, data);
await this.elasticsearchService.updateUser(userId);
await this.redisService.delete(`user:${userId}`);
await this.dynamicGroupService.reload(userId);
await this.logService.write(userId);
}
这种方式存在一个明显的问题:
业务代码和所有下游业务强耦合。
如果 Elasticsearch 暂时不可用,那么整个用户更新请求可能失败。
更麻烦的是:
用户请求
│
▼
更新用户
│
├──> MySQL
│
├──> Elasticsearch
│
├──> Redis
│
├──> 动态用户组
│
└──> 日志
随着业务越来越复杂,这条链路会变得越来越长。
二、Kafka 的核心思想
Kafka 最重要的思想其实非常简单:
生产者负责产生消息,消费者负责消费消息,中间通过 Kafka 进行解耦。
改造之后:
┌──────────────┐
│ Kafka │
└──────┬───────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
Elasticsearch Redis Dynamic Group
业务服务只需要:
await kafka.send({
topic: 'user.updated',
messages: [
{
key: userId,
value: JSON.stringify(data),
},
],
});
至于后面谁消费、怎么处理,生产者并不需要关心。
这就是 Kafka 非常重要的一个价值:
通过消息实现系统之间的异步解耦。
三、Kafka 的几个核心概念
理解 Kafka,首先需要搞清楚下面几个概念:
| 概念 | 作用 |
|---|---|
| Broker | Kafka 服务节点 |
| Topic | 消息分类 |
| Partition | Topic 的分片 |
| Producer | 消息生产者 |
| Consumer | 消息消费者 |
| Consumer Group | 消费者组 |
| Offset | 消费位置 |
| Replica | 分区副本 |
它们之间的关系可以简单理解为:
Kafka Cluster
│
├── Broker 1
├── Broker 2
└── Broker 3
Topic: user-event
│
├── Partition 0
├── Partition 1
├── Partition 2
└── Partition 3
每个 Partition 内部的消息都有一个递增的 Offset:
Partition 0
Offset
0 message A
1 message B
2 message C
3 message D
4 message E
消费者实际上就是不断向 Kafka 请求:
“从 Offset = 3 的地方开始,把后面的消息给我。”
四、Topic 是什么?
Topic 可以理解成一个消息分类。
例如:
user.created
user.updated
user.deleted
order.created
order.paid
order.refunded
生产者:
user-service
│
▼
user.updated
消费者:
user.updated
│
┌───────────┼───────────┐
▼ ▼ ▼
ES Service Cache Service Audit Service
Topic 本身并不是一个真正保存所有消息的单一文件。
一个 Topic 通常会被划分成多个 Partition。
五、Partition 为什么重要?
假设:
Topic: user.updated
Partition 0
Partition 1
Partition 2
Partition 3
如果所有消息都放在一个 Partition:
Producer
│
▼
Partition 0
│
▼
Consumer
吞吐能力就比较有限。
如果有多个 Partition:
Producer
│
┌───────────┼───────────┐
▼ ▼ ▼
P0 P1 P2
│ │ │
▼ ▼ ▼
Consumer Consumer Consumer
Kafka 可以通过多个 Partition 实现并行处理。
所以:
Partition 是 Kafka 实现高吞吐和消费者并行消费的关键。
六、Partition 和消息顺序
这里是 Kafka 一个非常容易理解错的地方。
Kafka 只能保证单个 Partition 内的消息顺序。
例如:
Partition 0
Offset 0 -> A
Offset 1 -> B
Offset 2 -> C
Offset 3 -> D
Kafka 可以保证:
A -> B -> C -> D
但是如果消息分布到了不同 Partition:
Partition 0:
A
C
E
Partition 1:
B
D
F
Kafka 并不能保证:
A -> B -> C -> D -> E -> F
因此,如果业务要求某个业务实体严格有序,通常需要合理设置 Message Key。
例如:
await producer.send({
topic: 'user.updated',
messages: [
{
key: userId,
value: JSON.stringify(data),
},
],
});
Kafka 会根据 Key 计算 Partition。
这样同一个 userId 的消息通常会进入同一个 Partition,从而保证该 Key 范围内的顺序。
七、Producer:消息生产者
Producer 就是向 Kafka 写消息的程序。
例如使用 Node.js:
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
clientId: 'user-service',
brokers: [
'kafka-1:9092',
'kafka-2:9092',
'kafka-3:9092',
],
});
const producer = kafka.producer();
await producer.connect();
await producer.send({
topic: 'user.updated',
messages: [
{
key: 'user-10001',
value: JSON.stringify({
userId: 'user-10001',
name: '张三',
}),
},
],
});
await producer.disconnect();
八、Consumer:消息消费者
消费者负责从 Kafka 获取消息。
KafkaJS 示例:
const consumer = kafka.consumer({
groupId: 'user-service',
});
await consumer.connect();
await consumer.subscribe({
topic: 'user.updated',
fromBeginning: false,
});
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log({
topic,
partition,
offset: message.offset,
value: message.value?.toString(),
});
},
});
输出可能类似:
{
topic: 'user.updated',
partition: 3,
offset: '10231',
value: '{"userId":"user-10001","name":"张三"}'
}
九、什么是 Consumer Group?
这是 Kafka 最重要的概念之一。
假设:
Topic
├── P0
├── P1
├── P2
└── P3
消费者组:
Group A
Consumer A1
Consumer A2
Consumer A3
Consumer A4
Kafka 会把 Partition 分配给消费者:
P0 -> Consumer A1
P1 -> Consumer A2
P2 -> Consumer A3
P3 -> Consumer A4
这意味着:
同一个 Consumer Group 内,一个 Partition 同一时间只能由一个 Consumer 消费。
十、消费者数量超过 Partition 会发生什么?
假设:
Partition = 4
Consumer = 6
那么:
P0 -> Consumer 1
P1 -> Consumer 2
P2 -> Consumer 3
P3 -> Consumer 4
Consumer 5 -> 空闲
Consumer 6 -> 空闲
也就是说:
同一个 Consumer Group 中,Consumer 数量超过 Partition 数量时,多出来的 Consumer 不会获得 Partition。
因此,如果 Topic 有 20 个 Partition:
20 Partition
那么单个 Consumer Group 理论上最多可以让:
20 个 Consumer
同时进行有效的 Partition 级并行消费。
十一、多个 Consumer Group 是什么效果?
假设:
Topic: user.updated
存在两个消费者组:
Group A
├── Consumer A1
└── Consumer A2
Group B
├── Consumer B1
└── Consumer B2
那么两个 Group 都会收到完整的消息流。
可以理解成:
user.updated
│
┌───────┴───────┐
▼ ▼
Group A Group B
│ │
▼ ▼
ES Service Audit Service
这也是 Kafka 实现发布订阅的重要方式。
十二、Offset 是什么?
Kafka 中每条消息都有一个 Offset。
例如:
Partition 0
Offset 100
Offset 101
Offset 102
Offset 103
Offset 104
消费者已经消费到:
Offset 102
那么 Kafka 就知道:
下一条应该从 Offset 103 开始消费
所以 Offset 本质上就是:
消费者在某个 Partition 上的消费位置。
十三、什么是 Consumer Lag?
这是生产环境排查 Kafka 问题时非常重要的指标。
假设 Kafka 当前:
Log End Offset = 10000
消费者已经消费到:
Committed Offset = 9700
那么:
Lag = 10000 - 9700
= 300
也就是说:
还有大约 300 条消息没有被消费者追上。
如果 Lag 持续增长:
100
200
500
1000
5000
10000
通常说明:
- 消费速度低于生产速度
- Consumer 数量不足
- 下游服务处理过慢
- Consumer 出现异常
- 网络存在问题
- Kafka 本身存在性能瓶颈
十四、Kafka 的消息是不是消费之后就删除?
不是。
这是很多刚接触 Kafka 的开发者容易误解的地方。
Kafka 并不是:
Producer
↓
Kafka
↓
Consumer
↓
删除消息
Kafka 中消息通常会按照 Retention Policy 保留一段时间。
例如:
retention.ms = 7 days
那么消息可能保留 7 天。
即使 Consumer 已经消费:
Offset 100
消息依然可能存在。
这也是为什么不同 Consumer Group 可以从自己的消费位置读取同一批历史消息。
十五、Kafka 为什么吞吐量这么高?
Kafka 的高吞吐并不是因为某一个神奇的技术。
它实际上依赖多个设计:
1. 顺序写
Kafka 的日志文件主要采用追加方式写入:
A -> B -> C -> D -> E
而不是随机修改:
修改 A
修改 D
修改 B
修改 C
顺序 IO 对磁盘非常友好。
2. Partition 并行
P0
P1
P2
P3
不同 Partition 可以并行处理。
3. Page Cache
Kafka 大量利用操作系统 Page Cache。
因此 Kafka 的性能并不完全取决于:
磁盘裸 IO
操作系统缓存同样发挥了很大的作用。
4. Batch
Kafka Producer 不一定一条消息发送一次。
可以批量发送:
Message 1
Message 2
Message 3
Message 4
Message 5
这样可以降低网络请求次数。
十六、Kafka 副本机制
生产环境通常不会只运行一个 Kafka Broker。
例如:
Broker 1
Broker 2
Broker 3
Partition 可以拥有多个 Replica:
Partition 0
Leader
│
├── Replica Broker 2
└── Replica Broker 3
其中一个 Replica 会作为 Leader。
Producer 和 Consumer 通常与 Leader 进行数据交互。
如果 Leader 所在 Broker 故障,Kafka 可以从其他 Replica 中选举新的 Leader。
十七、ISR 是什么?
ISR:
In-Sync Replicas
可以理解为:
当前仍然跟 Leader 保持同步的副本集合。
例如:
Replication Factor = 3
Broker 1 -> Leader
Broker 2 -> ISR
Broker 3 -> ISR
如果 Broker 3 长时间没有追上 Leader:
Broker 1 -> Leader
Broker 2 -> ISR
Broker 3 -> 不在 ISR
那么此时虽然仍然存在 3 个 Replica:
Replica = 3
但真正同步的副本只有:
ISR = 2
这对于 Kafka 的数据可靠性非常重要。
十八、acks 到底怎么配置?
Producer 常见配置:
acks=0
acks=1
acks=all
acks=0
Producer 不等待 Kafka 确认。
Producer
│
├──> Kafka
│
└──> 立即返回
优点:
- 延迟低
- 吞吐高
缺点:
- 可靠性较低
acks=1
Leader 收到消息之后确认。
Producer
│
▼
Leader
│
▼
ACK
平衡了性能和可靠性。
acks=all
等待所有符合条件的 ISR 副本确认。
可靠性更高,但延迟通常也会增加。
十九、Kafka 最容易踩坑的地方
1. Partition 数量和 Consumer 数量不匹配
例如:
20 Partition
50 Consumer
那么至少有:
30 Consumer
无法获得 Partition。
2. Consumer Group 配置错误
例如两个本来应该独立消费的业务:
ES Sync
Notification
错误地使用:
groupId = authing-service
那么它们实际上是在竞争 Partition,而不是分别收到完整消息。
应该使用不同 Group:
es-sync
notification
3. 消费者处理太慢
例如:
await consumeMessage();
await doSomethingVerySlow();
await saveDatabase();
await callExternalApi();
如果单条消息处理需要:
3 秒
那么吞吐能力可能非常低。
4. 消费者异常退出
如果 Consumer:
连接 Kafka
↓
开始消费
↓
网络断开
↓
连接恢复
↓
没有正确重新加入 Group
就可能出现:
服务看起来正常
Kafka 也正常
但是消息不再消费
这类问题在生产环境中尤其需要注意。
二十、Kafka Rebalance
当 Consumer Group 中成员发生变化时,Kafka 可能进行 Rebalance。
例如:
原来:
P0 -> Consumer A
P1 -> Consumer B
P2 -> Consumer C
Consumer B 宕机之后:
P0 -> Consumer A
P1 -> Consumer C
P2 -> Consumer C
Kafka 需要重新分配 Partition。
这个过程就是:
Rebalance
频繁 Rebalance 会影响消费稳定性。
常见原因包括:
- Consumer 频繁启动/停止
- 心跳超时
- 消费处理时间过长
- 网络不稳定
- Session Timeout 配置不合理
二十一、NestJS 中使用 Kafka
如果使用 NestJS,可以通过 Kafka Transport:
import { Transport } from '@nestjs/microservices';
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.KAFKA,
options: {
client: {
brokers: [
'kafka-1:9092',
'kafka-2:9092',
],
},
consumer: {
groupId: 'user-service',
},
},
});
Controller:
@Controller()
export class UserController {
@MessagePattern('user.updated')
async handleUserUpdated(data: any) {
console.log('收到用户更新事件', data);
// 处理业务
}
}
生产者:
await this.client.emit(
'user.updated',
{
userId,
name,
},
);
二十二、生产环境排查 Kafka 的思路
遇到:
“Kafka 没有消费消息”
不要第一时间重启服务。
建议按照下面的顺序排查。
第一步:确认 Topic
kafka-topics.sh \
--bootstrap-server kafka:9092 \
--describe \
--topic user.updated
重点观察:
Partition
Leader
Replicas
ISR
第二步:确认 Consumer Group
kafka-consumer-groups.sh \
--bootstrap-server kafka:9092 \
--list
然后:
kafka-consumer-groups.sh \
--bootstrap-server kafka:9092 \
--describe \
--group user-service
重点关注:
CURRENT-OFFSET
LOG-END-OFFSET
LAG
例如:
TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
user.updated 0 10000 10000 0
user.updated 1 9800 10200 400
user.updated 2 9500 12000 2500
这里可以快速发现:
Partition 2
Lag = 2500
说明这个 Partition 的消费明显落后。
二十三、一个非常实用的排查模型
当 Kafka 出现问题时,可以按照:
Producer
│
▼
Topic
│
▼
Partition
│
▼
Consumer Group
│
▼
Consumer
│
▼
业务处理
逐层排查。
Producer 层
检查:
消息有没有发送成功?
Topic 对不对?
Key 是否正确?
发送有没有异常?
Topic 层
检查:
Topic 是否存在?
Partition 数量是多少?
Replication Factor 是多少?
ISR 是否正常?
Consumer Group 层
检查:
Group 是否存在?
Consumer 是否加入 Group?
Partition 是否分配?
Offset 是否推进?
Lag 是否增长?
Consumer 层
检查:
进程是否正常?
Kafka 连接是否正常?
是否发生 Rebalance?
是否频繁断开?
心跳是否正常?
Business 层
最后再检查:
业务处理是否异常?
数据库是否慢?
Redis 是否阻塞?
ES 是否超时?
第三方 API 是否不可用?
二十四、一个完整的 Kafka 架构示例
假设我们设计一个用户中心:
┌──────────────┐
│ User Service │
└──────┬───────┘
│
│ user.updated
▼
┌─────────────────┐
│ Kafka │
│ │
│ Partition 0 │
│ Partition 1 │
│ Partition 2 │
│ Partition 3 │
└───────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
ES Group Cache Group Audit Group
│ │ │
▼ ▼ ▼
Elasticsearch Redis PostgreSQL
这样 User Service 不需要知道:
ES 怎么更新
Redis 怎么更新
日志怎么保存
它只负责发布:
user.updated
这就是事件驱动架构非常典型的应用。
二十五、Kafka 不是什么?
Kafka 虽然强大,但它不是万能的。
Kafka 更适合:
- 事件流
- 日志流
- 异步任务
- 数据同步
- 服务解耦
- 削峰填谷
- 大规模消息传输
但如果只是:
用户点击按钮
↓
立即执行一个简单任务
并不一定需要 Kafka。
很多项目最后会变成:
一个简单功能
↓
Redis
↓
RabbitMQ
↓
Kafka
↓
Kubernetes
↓
Service Mesh
最后发现真正的问题只是:
update user
所以技术选型还是应该根据实际业务规模决定。
二十六、Kafka 核心知识总结
如果你准备面试或者开始正式使用 Kafka,可以先把下面这张图记住:
Kafka Cluster
│
┌──────────┴──────────┐
│ │
Topic Topic
│
┌──────┼──────┐
▼ ▼ ▼
P0 P1 P2
│ │ │
└──────┼──────┘
│
Consumer Group
│
┌──────┼──────┐
▼ ▼ ▼
C1 C2 C3
最重要的几个关系:
Topic
↓
Partition
↓
Offset
Consumer
↓
Consumer Group
↓
Partition Assignment
Producer
↓
Topic
↓
Partition
最后再记住几个核心原则:
1. Kafka 的并行能力主要来自 Partition。
2. 同一个 Consumer Group 中,一个 Partition 同时只能被一个 Consumer 消费。
3. 不同 Consumer Group 可以分别消费同一个 Topic 的完整消息流。
4. Kafka 只保证单个 Partition 内的消息顺序。
5. Offset 表示 Consumer 的消费位置。
6. Lag 可以帮助判断消费者是否跟得上生产速度。
7. Rebalance 是 Consumer Group 重新分配 Partition 的过程。
8. 生产环境排查 Kafka,应该从 Producer → Topic → Partition → Group → Consumer → Business 一层层排查。
二十七、结语
Kafka 真正难的地方并不是 API。
真正需要理解的是:
消息
↓
Topic
↓
Partition
↓
Consumer Group
↓
Offset
↓
Rebalance
↓
Lag
↓
副本与 ISR
当这些概念真正串起来之后,再去看 Kafka 的各种配置,例如:
acks
retries
max.poll.interval.ms
session.timeout.ms
heartbeat.interval.ms
fetch.min.bytes
max.poll.records
retention.ms
segment.bytes
replication.factor
min.insync.replicas
就不会再觉得它们是一堆杂乱的参数。
而是可以理解成:
Kafka 为了在吞吐量、可靠性、顺序性和消费稳定性之间进行权衡所提供的一系列控制开关。
这也是从“会用 Kafka”走向“能排查生产 Kafka 问题”的关键一步。
