Kafka 核心概念:Topic 与 Partition、Producer 与 Consumer、Broker 与 Cluster、Offset 与 Consumer Group
说实话,Kafka 的概念体系,是我见过消息中间件里最优雅的之一。它不像 RabbitMQ 那样有 Exchange、Binding 一堆抽象,也不像 RocketMQ 那样有 MessageQueue、Queue 的混淆。Kafka 的核心概念,掰着手指头数,就这几个:Topic、Partition、Producer、Consumer、Broker、Cluster、Offset、Consumer Group。搞懂了它们,你就拿到了 Kafka 的钥匙。
我当年第一次接触 Kafka 时,被 Partition 和 Offset 绕得有点晕。后来在量化交易系统里踩了几个坑,才真正理解它们的设计意图。今天咱们就一个一个掰开揉碎讲清楚。
1. Topic 与 Partition:数据的分片与并行
Topic 是 Kafka 里最顶层的逻辑概念。你可以把它理解成一个「消息类别」或「数据流」。比如在量化系统里,我会为「订单流」建一个 topic,为「行情快照」建另一个 topic。Producer 往 topic 里写,Consumer 从 topic 里读。
但光有 Topic 还不够。如果所有消息都挤在一个队列里,吞吐量上不去。所以 Kafka 引入了 Partition。
Partition 是 Topic 的物理分片。一个 Topic 可以拆成多个 Partition,每个 Partition 是一个有序的、不可变的日志文件。消息在 Partition 内部是有序的,但跨 Partition 就不保证了。说白了,Partition 就是 Kafka 实现水平扩展和并行处理的核心手段。
核心要点:
- Partition 数量决定了消费的并行度。Partition 越多,Consumer 可以拉得越猛。
- 消息在 Partition 内是有序的,但跨 Partition 无序。如果你需要全局有序,只能用一个 Partition——但那样吞吐量就废了。
- Partition 的副本机制(Replication)保证了高可用。每个 Partition 有 Leader 和 Follower。
我在量化系统里遇到过一个问题:行情 topic 的 partition 数设得太少,导致消费端处理不过来,行情延迟飙升。后来我把 partition 数从 3 扩到 12,消费端加了 4 个 consumer,延迟直接降了一个数量级。嗯,这里要注意:Partition 数不是越多越好,太多会导致文件句柄和元数据开销变大。
2. Producer 与 Consumer:生产与消费的两端
Producer 负责把消息发到 Kafka。它不直接跟 Partition 打交道,而是通过「分区器」来决定消息落到哪个 Partition。默认的分区策略是轮询(Round-Robin),或者根据消息的 key 做哈希。
我个人习惯在量化场景里用 key 来保证同一只股票的所有订单落到同一个 Partition。这样消费端可以按股票维度做状态聚合,省去了跨 Partition 排序的麻烦。
// 伪代码:按股票代码分区
ProducerRecord<String, Order> record = new ProducerRecord<>(
"order-topic",
order.getStockCode(), // key
order
);
producer.send(record);
Consumer 是拉取消息的一方。Kafka 的 Consumer 是 pull 模式,不是 push。为什么?你想想看,如果 Broker 主动 push,万一 Consumer 处理不过来,Broker 还得背锅。Pull 模式让 Consumer 自己控制消费速率,更稳健。
我曾经在行情系统里犯过一个低级错误:Consumer 拉取消息后,处理逻辑里做了数据库写入,结果写入太慢,导致消费线程阻塞,Lag 越来越大。后来我改成异步批量写入,Consumer 只负责解析和缓存,性能就上来了。
避坑指南:Consumer 的 poll() 循环里不要做耗时操作。如果处理不过来,要么加 Partition 和 Consumer 实例,要么用异步处理。我曾经见过有人把 HTTP 请求写在 poll 循环里,结果 Consumer 被踢出 Group——因为心跳超时了。
3. Broker 与 Cluster:集群的基石
Broker 就是 Kafka 的服务节点。每个 Broker 可以管理多个 Partition。一个 Kafka 集群由多个 Broker 组成,它们通过 ZooKeeper(或 KRaft)协调元数据。
Broker 的核心职责很简单:接收 Producer 的写入请求,管理 Partition 的日志文件,响应 Consumer 的拉取请求。但真正复杂的是集群模式下的副本同步和故障转移。
我记得有一次线上集群的一个 Broker 挂了,因为磁盘满了。幸好我们配置了 replication.factor=3,Leader 自动切换到另一个 Broker,业务无感知。但如果你只配了 1 个副本,那这个 Partition 就彻底不可用了。嗯,这里要强调:生产环境 replication.factor 至少 2,推荐 3。
集群架构要点:
- 每个 Partition 有一个 Leader 和多个 Follower。读写都走 Leader,Follower 只做同步。
- Leader 挂了,Controller(集群控制器)会从 ISR(In-Sync Replicas)里选一个新 Leader。
- ISR 是跟 Leader 保持同步的副本集合。如果 Follower 落后太多,会被踢出 ISR。
下面这张图展示了 Kafka 集群的核心架构,包括 Broker、Partition 的 Leader/Follower 关系,以及 Producer 和 Consumer 的交互路径。
4. Offset 与 Consumer Group:消费进度的管理
Offset 是 Partition 内每条消息的唯一编号。它从 0 开始递增,标识消息在日志中的位置。Consumer 每次拉取消息时,会记录自己消费到的 Offset。下次再拉,就从那个 Offset 继续。
Offset 的管理是 Kafka 实现「断点续传」的关键。如果 Consumer 挂了重启,它只需要告诉 Broker:「我从 Offset 100 开始拉」,就能接着上次的进度继续消费,不会丢消息也不会重复。
但 Offset 存在哪里?老版本存在 ZooKeeper 里,性能差。新版本存在 Kafka 内部的一个特殊 Topic 里,叫 __consumer_offsets。这个 Topic 默认有 50 个 Partition,专门存 Offset 信息。
注意:Offset 的提交方式有两种:自动提交和手动提交。自动提交省事,但可能丢消息(比如 Consumer 处理完消息但还没提交 Offset 就挂了)。手动提交更安全,但代码要写对。我建议生产环境用手动提交,尤其是在量化交易这种对数据一致性要求高的场景。
Consumer Group 是 Kafka 实现「广播」和「单播」的核心机制。同一个 Group 内的 Consumer 实例共同消费一个 Topic,每条消息只会被 Group 内的一个 Consumer 处理。不同 Group 之间互不干扰,各自独立消费。
说白了,Group 就是「逻辑消费者」。你想想看,如果多个 Consumer 实例属于同一个 Group,它们就分摊 Partition 的消费任务。如果每个 Consumer 实例属于不同的 Group,那每条消息就会被每个 Group 都消费一次——这就是广播。
我在量化系统里是这样用的:行情 Topic 有两个 Group——一个给「实时风控系统」用,一个给「历史数据存储」用。两个 Group 各自消费,互不影响。风控系统要求低延迟,所以 Partition 数多、Consumer 实例多;存储系统要求高吞吐,所以用批量拉取。
避坑指南:Consumer Group 的 Rebalance 是个大坑。当 Group 内有 Consumer 加入或离开时,会触发 Rebalance,重新分配 Partition。这个过程会导致短暂的消费暂停。我曾经在线上遇到过频繁 Rebalance,原因是 Consumer 的心跳超时。后来我把 session.timeout.ms 调大了一些,问题就解决了。
5. 这些概念如何协同工作?
咱们用一句话串起来:Producer 把消息发到 Topic 的某个 Partition 上,Broker 集群负责存储和副本同步,Consumer Group 内的 Consumer 实例各自负责消费不同的 Partition,并通过 Offset 记录消费进度。
这个模型的好处是:
- 水平扩展:加 Partition 就能加吞吐量,加 Broker 就能加存储。
- 高可用:副本机制保证单点故障不影响服务。
- 灵活消费:Group 机制让广播和单播随心切换。
嗯,这些概念看起来简单,但真正用好它们,需要你对业务场景有深刻理解。比如 Partition 数怎么定?Consumer 实例数怎么配?Offset 提交策略怎么选?这些没有标准答案,全靠经验和测试。
我个人建议:刚开始用 Kafka 时,先按「Partition 数 = Consumer 实例数」来配,然后根据实际负载慢慢调。别一上来就搞 100 个 Partition,你会后悔的。
公众号:蓝海资料掘金营,微信deep3321