客户端
- 生产者 (Producer)
- 功能: 负责将消息发送到指定的主题 (Topic)。
- 发送策略: 默认情况下,消息会轮询发送到各个分区。也可以通过分区器(Partitioner)基于键(Key)指定分区,确保相同键的消息发送到同一分区,从而保证消息的顺序性。
- 消费者 (Consumer)
- 功能: 从主题中读取消息。
- 消费机制: 消费者通过位移(Offset)跟踪已消费的消息位置,确保不重复消费或遗漏。
- 消费者组 (Consumer Group)
- 组成: 多个消费者实例组成的组,共同消费同一个主题的消息。
- 分区分配: 每个分区只能由一个消费者实例消费。例如,若主题有六个分区,消费者组有三个消费者,每个消费者可能消费两个分区。
- 重平衡 (Rebalance): 当消费者实例挂掉或新增时,消费者组会重新分配分区,确保消息消费的连续性和负载均衡。
服务端
- Broker
- 角色: Kafka的服务器节点,负责存储和管理消息。
- 功能: 每个Broker存储主题的部分分区,处理生产者和消费者的请求。
- 副本 (Replica)
- 分类:
- Leader Replica: 负责处理生产者写入和消费者读取操作。
- Follower Replica: 从Leader同步数据,保持数据一致性。
- 配置: 每个分区可配置多个副本,增加高可用性和容灾能力。副本数量影响性能和存储,需根据实际需求配置。
生产者 - 消费者 - Broker 流程
- 生产流程:
- 生产者将消息发送到主题的Leader副本。
- Leader副本处理写入请求,并将消息同步到Follower副本。
- Follower副本保持与Leader的数据一致性,确保高可用性。
- 消费流程:
- 消费者从Leader副本读取消息,确保低延迟和高吞吐量。
- 消费者通过位移跟踪消费位置,保证消息有序消费。
分区 (Partitioning)
- 作用: 提高主题的吞吐量和扩展性,允许消息并行处理。
- 配置: 根据主题的流量和性能需求配置分区数量,合理分配可以提升系统性能。
位移 (Offset)
- 功能: 跟踪消费者的消息消费位置。
- 管理: 消费者组通过内部机制(如Kafka协调)记录和管理位移,确保在重平衡时正确接管分区的消费位置。
主题层、分区层和消息层
- 主题层: 可配置多个分区,每个分区可配置多个副本,提升可靠性和扩展性。
- 分区层: 每个分区包含多个消息,按顺序排列,从0开始递增。
- 日志段 (Log Segment): 消息追加到最新的日志段,写满后切分新段,提高存储管理和查询效率。
重平衡 (Rebalance)
- 机制: 当消费者实例挂掉或分区故障时,消费者组重新分配分区,确保消息消费的连续性。
- 影响: 重平衡可能导致短暂的消息堆积,但通过高效的协调机制,尽量减少影响。
判断分区数不足的核心信号是消费者持续Lag、Broker CPU/网络长期超70%或消费者大量闲置;分区数建议设为Broker数量的整数倍(通常23倍),单Broker分区总数控制在100200个(上限4000);扩充分区数无需删除原Topic,直接在线增加即可。
一、如何判断分区数不够用
出现以下任一信号时,通常说明分区数已成为瓶颈:
- 消费者持续 Lag:消费速度跟不上生产速度,Lag 持续增长。这是最直接的信号,说明消费并行度已达上限。
- Broker 资源长期高负载:Broker 的 CPU、网络带宽、磁盘 I/O 长期处于 70%~80% 以上,说明单分区承载的流量已接近瓶颈。
- 消费者大量闲置:消费者实例数 > 分区数,多出的消费者永远空闲,说明分区数限制了消费并行度。
- 生产者写入延迟升高:单分区写入吞吐约 10~50MB/s,当业务峰值超过当前分区总吞吐能力时,写入延迟会明显上升。
- 频繁 ISR 变更或 Rebalance:说明集群压力已接近临界点。
二、分区数与 Broker 的建议比例
没有固定"黄金比例",但有以下核心原则:
- 分区数应为 Broker 数量的整数倍:确保分区均匀分布,避免部分 Broker 空转。例如 6 个 Broker,分区数设为 12、18、24。
- 单 Broker 分区总数控制在 100~200 个:这是 Confluent 推荐的经验值。超过 4000 个会压垮 Controller,导致元数据管理异常。
- 分区数 ≥ 消费者并发线程总数:一个分区同一时间只能被一个消费者线程消费,分区数必须覆盖峰值消费并发。
- 按集群规模参考倍数:
- 小型集群(<6 Broker):建议 3 倍 Broker 数
- 大型集群(>12 Broker):建议 2 倍 Broker 数
- 高吞吐场景:可设到 3 倍 Broker 数
举例:6 个 Broker、峰值 60 个消费线程 → 分区数至少 60,且为 6 的倍数,可设为 60 或 72。
三、扩充分区数是否需要删除原 Topic
不需要删除重建,直接在线增加即可。 Kafka 原生支持动态增加分区,无需删除原 Topic。
操作方式
通过命令行直接修改:
bash
kafka-topics.sh --bootstrap-server broker:9092 \
--alter --topic your-topic \
--partitions 24
关键注意事项
- 只能增加不能减少:Kafka 不支持缩减分区数,这是开源内核的限制。
- 历史数据不会重新分布:新增分区只接收新消息,原有分区的历史堆积不会自动迁移到新分区。
- Key 有序性可能被破坏:如果生产者按 Key 哈希分区,新增分区会改变 Key→Partition 的映射关系,同一 Key 的消息可能落入不同分区。
- 扩容后需手动均衡 :新增分区默认可能集中在部分 Broker 上,需配合
kafka-reassign-partitions.sh将分区均匀迁移到新 Broker。
何时才需要删除重建
只有在以下特殊场景才考虑删除原 Topic 重建:
- 需要减少分区数(Kafka 不支持,只能删了重建)
- 需要修改副本因子(replication factor,创建后不可更改)
- 需要彻底改变分区策略(如从 Key 哈希改为 RoundRobin)
删除重建意味着历史数据全部丢失,生产环境务必谨慎,通常通过创建新 Topic + 数据迁移来替代。
此时,单纯增加消费者数量(水平扩展)并不一定是"更好"的方案,甚至可能无效 。是否增加消费者,取决于你的分区数 和单条消息的处理耗时。
我们需要分两种情况来判断"增加消费者"还是"优化单条消费逻辑(提升 QPS)":
情况一:消费者数量 < 分区数(存在空闲消费者)
结论:增加消费者数量是有效的。
- 现象:比如 Topic 有 12 个分区,但你只启动了 8 个消费者实例。此时有 4 个分区没人消费,或者某些消费者承担了多个分区。
- 对策:直接增加消费者实例数量,直到等于分区数。这是最直接的负载均衡手段。
- 限制 :一旦消费者数量达到分区数(例如 12 个消费者对应 12 个分区),再增加消费者就完全无效了,多出来的消费者只会处于 Idle(空闲)状态。
情况二:消费者数量 ≥ 分区数(瓶颈在单线程处理能力)
结论:增加消费者数量无效,必须提升单消费者的 QPS(垂直优化)。
这是生产环境中最常见的情况。假设你有 12 个分区,已经跑了 12 个消费者,但 Lag 还在涨。这意味着每个分区对应的消费者线程处理太慢了。
此时增加消费者没用,你需要做的是提升单个消费者的吞吐量。以下是几种提升 QPS 的实战方案:
1. 开启多线程/异步处理(最推荐)
Kafka 消费者默认是单线程拉取并串行处理的。如果业务逻辑耗时(如写数据库、调远程接口),可以通过内部线程池来解耦。
- 做法 :消费者主线程只负责
poll()拉取消息,然后迅速丢给内部的ThreadPoolExecutor处理,主线程继续拉取下一批。 - 注意:这会打乱分区内的顺序性(除非你对每个 Key 做 Hash 路由到固定线程),且需要手动管理 Offset 提交(建议异步提交或定时提交)。
2. 批量消费与批量写入
- 做法 :不要来一条处理一条。设置
fetch.min.bytes或max.poll.records(例如一次拉取 500 条),攒够一批后,进行批量数据库插入 或批量 RPC 调用。 - 效果:将 1000 次网络 IO 变为 2 次,QPS 可能提升 5-10 倍。
3. 优化业务逻辑(代码级)
- 检查慢查询:数据库是否有索引?SQL 是否全表扫描?
- 减少同步调用:是否能将非核心逻辑(如发通知、记日志)异步化?
- 本地缓存:是否需要频繁查 Redis/DB?能否在消费者内存中做本地缓存(如 Caffeine)?
4. 调整 Kafka 客户端参数
max.poll.records:默认 500,如果处理很快,可以调大到 1000。fetch.max.bytes:单次拉取的最大字节数,适当调大可以减少网络交互次数。
总结决策树
当 Broker 负载低但 Lag 高时,请按此步骤排查:
- 看分配 :
消费者数 < 分区数吗?
- 是 → 加消费者实例(直到等于分区数)。
- 否 → 进入下一步。
- 看耗时:单条消息处理耗时多久?
- 很快(<10ms) → 检查是否拉取批次太小,调大
max.poll.records。 - 很慢(>50ms) → 代码优化(批量处理、异步化、查慢 SQL)。
- 看资源:消费者机器的 CPU/内存 满了吗?
- 满了 → 消费者机器性能不够,升级配置 或加机器配合多线程。
- 没满 → 说明是单线程阻塞 (如在等 DB 返回),必须上多线程/异步模型。
持久化
Kafka 的消息持久化依赖副本机制,消息至少写入 min.insync.replicas 个 ISR(In-Sync Replicas)副本后才算提交成功,之后即使部分 Broker 宕机,数据也不会丢失。
- Producer 导致的信息丢失,主要排查生产者端:
acks设置过低(如acks=0或acks=1),未等副本同步就返回成功。retries=0,网络抖动时不重试,消息直接丢失。- 异步发送未处理回调,发送失败无感知。
- Broker 全部宕机(极端场景)。
- Consumer 导致的信息丢失,根本原因是 offset 提交时机不当:
- 先提交 offset 再处理业务逻辑,若处理过程中崩溃,重启后 offset 已前移,未处理的消息被永久跳过。
- 保证先消费处理,再提交 offset,可避免此问题。

防消息丢失配置建议
- 不要使用
producer.send(msg),而要使用producer.send(msg, callback)。一定要使用带有回调通知的 send 方法,以便感知发送失败。 - 设置
acks=all(或acks=-1)。表示所有 ISR 副本都成功写入后,Producer 才收到确认,这是最高等级的持久性保证。 - 设置
retries为一个较大的值。当出现网络瞬时抖动时,Producer 能够自动重试消息发送,避免消息丢失。 - 设置
unclean.leader.election.enable=false。不允许落后的 Follower 竞选 Leader,避免数据丢失。 - 设置
replication.factor >= 3。消息多保存几份,冗余是防止数据丢失的主要机制。 - 设置
min.insync.replicas > 1。控制消息至少要被写入到多少个 ISR 副本才算是"已提交",生产环境千万不要使用默认值 1。 - 确保
replication.factor > min.insync.replicas。如果两者相等,只要有一个副本宕机,整个分区就无法正常工作。推荐设置成replication.factor = min.insync.replicas + 1。 - 确保消息消费完成再提交。Consumer 端将
enable.auto.commit设置成false,采用手动提交位移的方式,这对于单 Consumer 多线程处理的场景尤为重要。
