3. Kafka核心概念与架构

Kafka 是什么?说白了,它就是一套「削峰填谷」的消息中间件。我最早接触 Kafka 是在 2016 年,那时候公司要接实时用户行为数据,日均几十亿条。选型时对比了 RabbitMQ 和 RocketMQ,最后还是上了 Kafka。为什么?因为它天生为高吞吐、持久化、可回溯设计。这一章,我把 Kafka 最核心的几个概念掰开揉碎讲清楚。

3.1 消息模型:从「队列」到「发布-订阅」

Kafka 的消息模型,本质上是「发布-订阅」模式。但它跟传统 JMS 队列有个关键区别——消费者拉取,而不是 Broker 推送。

核心概念速览

  • Producer:消息生产者,负责把数据发到 Topic
  • Consumer:消息消费者,从 Topic 拉取数据
  • Topic:消息的逻辑分类,类似数据库里的表
  • Partition:Topic 的物理分片,是并行度的基础
  • Broker:Kafka 服务器节点,集群中的一台机器

我习惯把 Topic 比作一个「大水池」。Producer 往里面倒水,Consumer 从里面接水。但水池不能无限大,所以 Kafka 把水池切成了多个小水槽——这就是 Partition。

个人经验:我遇到过很多新手把 Topic 和 Partition 混为一谈。记住一句话:Topic 是逻辑概念,Partition 是物理存储。一个 Topic 可以有 N 个 Partition,每个 Partition 就是一个有序的、不可变的日志文件。

3.2 分区与副本机制

分区是 Kafka 并行度的基石。你想想看,如果只有一个分区,那所有消费者都得排队读,吞吐量上不去。分区多了,消费者可以并行消费。

但分区多了也有问题——机器挂了怎么办?数据不就丢了吗?所以 Kafka 引入了副本机制。

3.2.1 副本的角色

角色 说明 关键行为
Leader 每个分区的「主副本」 负责所有读写请求
Follower Leader 的「从副本」 只从 Leader 同步数据,不对外服务
ISR In-Sync Replicas 与 Leader 保持同步的副本集合

这里有个关键点:只有 Leader 副本对外提供服务。Follower 只负责同步,不参与读写。为什么这样设计?我刚开始也觉得浪费,后来才明白——这样能保证数据一致性。如果 Follower 也能读,那读到旧数据的概率就大了。

避坑指南:我曾经在生产环境遇到过一个问题——某个分区的 ISR 列表只剩 Leader 自己了。查了半天,发现是 Follower 副本所在机器磁盘 IO 打满,导致同步延迟超过 replica.lag.time.max.ms(默认 30 秒),被踢出了 ISR。所以,监控 ISR 数量是 Kafka 运维的基本功。

3.2.2 副本的分布策略

Kafka 的副本分布有个原则:尽量分散到不同 Broker 上。比如你有 3 台 Broker,副本因子是 3,那每个分区的 3 个副本会各放一台机器。这样任何一台机器挂了,数据都不丢。

我建议你在部署时,副本因子至少设为 2。如果条件允许,设为 3 更稳妥。但注意,副本越多,写入延迟会稍微增加,因为 Leader 要等所有 ISR 副本确认后才返回。

3.3 生产者与消费者原理

3.3.1 生产者:如何把数据发出去?

Producer 的工作流程,我总结为三步:

  1. 确定分区:根据 key 的哈希值,或者轮询策略,决定数据发到哪个 Partition
  2. 批量发送:攒一批数据再发,减少网络开销
  3. 等待确认:根据 acks 参数,决定等不等 Broker 的确认

这里有个参数 acks,很多人搞不清楚。我直接给你结论:

acks 值 含义 适用场景
0 发出去就不管了 丢几条数据无所谓,比如日志
1 Leader 写入成功就算成功 大部分业务场景,性能与可靠性的平衡点
all 所有 ISR 副本都写入成功才算成功 金融、交易等强一致性场景

我的习惯:线上环境我一般用 acks=1。除非是跟钱相关的数据,才用 acks=all。但要注意,acks=all 配合 min.insync.replicas 使用才有意义。如果 min.insync.replicas=1,那 acks=all 其实跟 acks=1 没区别。

3.3.2 消费者:如何保证不丢数据?

Consumer 的核心机制是消费者组。同一个组内的消费者,共同消费一个 Topic 的分区。Kafka 保证:一个分区只能被同一个组内的一个消费者消费。

为什么会这样?你想想看,如果两个消费者同时消费同一个分区,那消息的 offset 就乱套了。谁提交 offset?谁负责重试?所以 Kafka 强制了「分区独占」原则。

消费者有个关键动作——提交 offset。offset 是消费者在分区中的「书签」,记录了消费到哪里了。

我曾经踩过的坑:有一次线上消费延迟飙升,查了半天发现是消费者提交 offset 的方式有问题。代码里用的是「自动提交」,每 5 秒提交一次。结果业务处理耗时超过 5 秒,offset 先提交了,但业务还没处理完。这时候消费者挂了,重启后从新 offset 开始消费,中间那批数据就丢了。解决方案很简单:改成手动提交,等业务处理完再提交 offset。

3.4 集群部署:从单机到集群

单机 Kafka 只能用来测试。生产环境至少 3 台 Broker 起步。我一般推荐 3 台起步,5 台标准,7 台以上算大规模。

3.4.1 部署架构图

下面这张图展示了 Kafka 集群的核心组件与数据流。我习惯用这种图来跟团队对齐架构认知。

Kafka 集群核心架构图 Kafka 集群核心架构 Producer 层 应用服务 / 数据采集 Agent / 日志收集器 Broker 集群 Broker 1 Topic-A P0 Leader Topic-B P1 Follower Topic-C P0 Follower Controller 角色 Broker 2 Topic-A P1 Leader Topic-B P0 Leader Topic-C P1 Follower ZooKeeper 连接 Broker 3 Topic-A P2 Leader Topic-B P1 Follower Topic-C P0 Leader 数据同步 Consumer 层 消费者组 Group-A / Group-B / 实时计算引擎 说明:每个 Partition 有 1 个 Leader 和 N 个 Follower,Leader 负责读写,Follower 负责同步 ISR 列表中的副本必须与 Leader 保持同步,否则会被踢出

3.4.2 部署关键参数

我整理了一份部署时的核心参数清单,这些是我在多个项目中沉淀下来的:

参数 推荐值 说明
num.partitions 3 ~ 6 Topic 默认分区数,根据吞吐量预估
default.replication.factor 2 或 3 自动创建 Topic 时的默认副本数
log.retention.hours 72 数据保留 3 天,按需调整
log.segment.bytes 1GB 单个日志段文件大小
min.insync.replicas 2 配合 acks=all 使用,保证至少 2 个副本同步
unclean.leader.election.enable false 禁止非 ISR 副本成为 Leader,防止数据丢失

个人建议:unclean.leader.election.enable 这个参数,我建议永远设为 false。虽然设为 true 能在极端情况下快速恢复服务,但代价是可能丢数据。我宁愿等 ISR 恢复,也不愿意丢数据。你想想看,运维的底线是什么?是数据不丢。

3.4.3 部署步骤简述

部署 Kafka 集群,我一般按这个顺序来:

  1. 部署 ZooKeeper 集群(3 或 5 台),Kafka 依赖 ZK 做元数据管理和 Leader 选举
  2. 配置 Kafka Broker,修改 server.properties 中的 broker.id、log.dirs、zookeeper.connect
  3. 启动 Broker,逐个启动,观察日志是否有报错
  4. 创建 Topic,用 kafka-topics.sh 创建业务 Topic,指定分区数和副本数
  5. 验证集群,用 kafka-producer-perf-test.sh 和 kafka-consumer-perf-test.sh 做压测

避坑指南:我曾经在部署时犯过一个低级错误——broker.id 重复了。两台 Broker 配了相同的 ID,结果集群一直报错,Leader 选举失败。排查了半天才发现。所以,部署前一定要检查 broker.id 是否唯一。另外,log.dirs 建议用多块磁盘,分散 IO 压力。

3.5 本章小结

Kafka 的核心概念,说白了就这几样:Topic、Partition、副本、Producer、Consumer。理解它们之间的关系,是运维 Kafka 的基础。

我个人的经验是:先搞懂数据流,再搞懂配置。数据怎么进来的?存在哪里?怎么被消费的?把这三个问题想清楚,Kafka 的架构就通了。