关键组件
Kafka的核心组件协同工作,构建了高吞吐、可扩展的分布式消息系统。以下是其主要组件及功能解析:
🖥️ 1. Broker(代理节点)
-
角色:Kafka集群的基础服务节点,每个Broker对应一台物理服务器。
-
职责:
- 消息存储:持久化消息到磁盘分区,支持高吞吐读写。
- 请求处理:响应生产者的写入和消费者的拉取请求。
- 副本管理:维护分区的Leader和Follower副本,保障数据冗余。
📤 2. Producer(生产者)
-
功能:向指定Topic发送消息。
-
关键机制:
- 分区策略:支持轮询(Round Robin)、Key哈希(保证相同Key消息有序)、粘性分区(优化批量发送)。
- 可靠性控制:通过
acks参数(0/1/all)控制消息持久化级别。
📥 3. Consumer(消费者)
-
功能:从Topic拉取消息进行处理。
-
消费者组(Consumer Group):
- 组内消费者协同消费同一Topic,每个分区仅由组内一个消费者处理(负载均衡)。
- 支持位移管理(Offset),存储于
__consumer_offsets主题。
📂 4. Topic(主题)与 Partition(分区)
-
Topic:消息的逻辑分类,类似数据库表。
-
Partition:
- Topic的物理分片,分布在多个Broker上。
- 分区内有序:消息按Offset严格排序,跨分区无序。
- 副本机制:每个分区含Leader(处理读写)和Follower(数据同步)副本。
🧠 5. Controller(控制器)
-
角色:集群的全局管理者,由某个Broker兼任(集群唯一)。
-
核心职责:
- 分区Leader选举(Broker故障时触发)。
- 管理Topic创建/删除、分区重分配。
- 监控Broker状态,触发故障转移。
👥 6. Group Coordinator(组协调器)
-
角色:管理消费者组的协调者(每个Broker运行一个实例)。
-
职责:
- 处理消费者组的Rebalance(如成员变动时分区重分配)。
- 维护消费者心跳和位移提交。
🔄 7. ZooKeeper 与 KRaft(元数据管理)
-
ZooKeeper(旧版):
- 存储集群元数据(Broker列表、Topic配置)。
- 协助Controller选举和状态同步。
-
KRaft(Kafka 2.8+):
- 内置Raft协议替代ZooKeeper,简化部署并提升稳定性。
⚙️ 8. 高可用机制
-
ISR(In-Sync Replicas):
- 与Leader同步的副本集合,故障时优先从中选举新Leader。
-
HW(高水位):
- 标识已同步的消息偏移量,消费者仅可读取HW之前的消息。
💎 总结
| 组件 | 核心功能 | 关键设计 |
|---|---|---|
| Broker | 消息存储与请求处理 | 水平扩展集群容量 |
| Producer/Consumer | 消息生产与消费 | 分区策略、位移管理 |
| Topic/Partition | 逻辑分类与物理分片 | 分区内有序、副本冗余 |
| Controller | 集群协调与故障转移 | Leader选举、元数据同步 |
| Group Coordinator | 消费者组管理 | Rebalance协调 |
建议:
- 生产环境优先使用 KRaft模式(Kafka 2.8+),避免ZooKeeper运维复杂度。
- 分区数需综合磁盘I/O能力和消费者并发度设定(单Broker建议管理≤2000分区)。
生产者
生产者调用send api后消息就直接发送到broker中去了吗?
并不是,为了减少发起请求和IO的次数,kafka为每个topic的每个分区都创建了buffer区,即由一个双端队列组织起来的多个ProducerBatch,当batch的大小超过了batch.size或者时间超过了linger.ms,kafka才会将这个batch中的message一次性全部发送过去。
生产者如何保证message一定能被发送到指定topic的partition中去?
构造ProducerRecord时,可以指定partition。
生产者如何保证一组message一定可以发送到同一个partition中去?
构造ProducerRecord时,指定key即可,相同key的message可以保证被发送到同一个partition中去(kafka默认对key使用murmur2哈希算法对分区数取模)
发送message时,既未指定partition,也没有指定key时,message会被发送到哪个分区?
若未指定分区且无 Key,Kafka 采用粘性随机分配(Sticky Partitioning)算法。
粘性随机分配算法优先将新消息分配到上一个消息发送到的分区。每个新message都会追加到目标分区的ProducerBatch中去,当batch的大小超过了batch.size或者时间超过了linger.ms时,这个batch便会被发送出去,此时算法会选择新的分区发送,选择流程如下:
生产者如何保证一组有序的message发送到broker之后,在partition中仍然保持原来的顺序?
主要有如下方式:
-
设置
ack=all或者ack=1且发送消息时指定key(否则发送到不同的分区就无法保证消费者消费时按照顺序消费了),并每发送一条消息后收到ack以后才开始发送下一条消息。如果设置ack=0则无法达到这种效果,此时producer发送完消息后会立即返回(返回的record metadata中,offset是-1),无法判断消息是否被leader副本或者ISR副本写入,因此只是等到api返回后就发送下一条消息无法保证broker端的消息是否按照期望顺序存储。 -
启用生产者的幂等性特性,即设置
enable.idempotence=true,且发送消息时指定key。在此种情况下,kafka会为每一个producer实例分配一个PID(即Producer ID,通过一个内部的topic实现PID全局唯一),同时给每个发送的消息添加一个序列号(不同分区之间的序列号是独立的)。当消息到达broker后会对消息进行校验,如果序列号不连续或者有重复消息都会校验不通过。
以上策略只能保证单个生产者实例发送的消息的顺序性,无法保证多个生产者同时对指定分区发送消息的顺序性。
重要参数配置
max.in.flight.requests.per.connection=1是一个关键的生产者配置参数,用于控制单个连接上允许的未确认请求(即已发送但未收到服务端响应的请求)的最大数量。
消费者
消费者&消费者组&topic&分区
- 一个消费者组可以订阅多个topic
- 一个消费者组可以包含多个消费者,一个消费者只能属于一个消费者组,一个组的消费者订阅的topic都是一样的
- 一个topic的一个partition只能被消费者组内的一个消费者实例消费,但是一个消费者实例可以同时消费该topic内的多个partition
创建消费者时不指定group id会如何?
不同client端的实现不同,具体处理也不同,以java的kafka-client库为例,会直接抛出异常InvalidGroupIdException,某些client端也可能会自动生成一个groupId。
共享偏移量
消费者每次消费完一个消息后,需要提交分区的偏移量,这个偏移量保存在broker端的内部主题中,若是有该消费者所在的消费组的其他消费者去消费这个分区的消息时可以直接基于这个偏移量继续消费,这样可在消费组内某个消费者异常时,其他消费者实例可以继续处理该分区的消息。
此外,如果该分区没有偏移量,即之前存储的消息没有被消费过,则基于auto.offset.reset的配置进行消费,当设定为earliest,则消费者将从最早的消息开始消费;如果设置为latest,则消费者将从最新的消息开始消费。
单个consumer消费多个partition的问题
关于kafka中一个consumer消费多个partition时消息拉取的问题
分区再平衡
Kafka 的分区再平衡(Rebalance)是消费者组(Consumer Group)在动态变化时重新分配分区(Partition)所属权的机制,旨在确保负载均衡、高可用性和容错能力。以下是其核心要点:
一、触发条件
再平衡由以下事件触发:
-
消费者变动
- 新消费者加入组(如扩容)或现有消费者退出(如宕机、主动关闭)。
- 消费者心跳超时(
session.timeout.ms默认10秒未响应)或消费超时(max.poll.interval.ms默认5分钟未调用poll())。
-
分区数量变化
- 主题(Topic)新增分区(Kafka 不支持减少分区)。
-
订阅主题变化
- 消费者组通过正则表达式订阅主题时,新增匹配的主题。
二、核心流程
再平衡由 Group Coordinator(Broker 端的协调者)协调完成,分为以下阶段:
-
检测变化
- Coordinator 通过心跳或
LeaveGroup请求感知消费者状态变化,标记组状态为PreparingRebalance。
- Coordinator 通过心跳或
-
消费者加入与选举
- 存活消费者发送
JoinGroup请求,Coordinator 选举首个加入的消费者为 Leader,其余为 Follower。
- 存活消费者发送
-
分区分配
- Leader 根据分配策略(如
Range、RoundRobin)生成分配方案,通过SyncGroup请求提交给 Coordinator。
- Leader 根据分配策略(如
-
同步结果
- Coordinator 将分配方案下发给所有消费者,组状态转为
Stable,消费者开始消费新分配的分区。
- Coordinator 将分配方案下发给所有消费者,组状态转为
三、分区分配策略
| 策略 | 原理 | 优缺点 |
|---|---|---|
RangeAssignor | 按 Topic 分区范围分配(如分区0-2给C1,分区3-5给C2)。 | 简单,但 Topic 分区不均时易导致负载倾斜。 |
RoundRobinAssignor | 全局轮询分配所有分区(跨 Topic)。 | 负载均衡较好,但重平衡时分区迁移较多。 |
StickyAssignor | 尽量保留原有分配,仅调整必要分区(减少迁移)。 | 减少重复消费和资源开销,推荐生产环境使用。 |
CooperativeStickyAssignor | 增量式再平衡(Kafka 2.4+),分阶段释放和分配分区,减少停顿时间。 | 显著降低再平衡期间的不可用时间。 |
kafka 2.4 版本之后,默认的分区策略为Range + CooperativeSticky
当有多个策略的时候,按照顺序选择,例如range + CooperativeSticky,那么生效的是range 既然配置多个只有第一个生效,那配置多个有什么意义?官方的例子是做滚动升级的场景?大致意思是例如旧版本机器是低版本的,使用的是range,你新版本的直接配置CooperativeSticky就会报错,使用range + CooperativeSticky就可以兼容旧版本,然后你逐步重启每个消费,升级为CooperativeSticky。group当前使用了CooperativeSticky,但是你的某个consumer配置了range是无法joinGroup的,会报错 既然range很容易导致分区分配的倾斜,那为什么还要默认用它?stackoverflow上也有个老哥提出了相同的问题。Kafka RangeAssignor benefits over RoundRobin - Stack Overflow 只有一个答复说是range能让同一个topic下的分区连续分配给一个消费者,这样方便做连接操作
上述引用于来源于kafka消费者分区分配策略的魔鬼细节。
四、性能影响与优化
再平衡会导致 短暂消费中断 和 重复消费,可通过以下方式优化:
-
参数调优
session.timeout.ms:建议10-30秒,避免网络抖动误判。heartbeat.interval.ms:设为session.timeout.ms的1/3(如3秒)。max.poll.interval.ms:根据消息处理耗时调整,避免阻塞触发再平衡。
-
减少触发频率
- 使用 静态成员资格(
Static Membership):避免消费者短暂离线触发再平衡。 - 避免频繁增减消费者或分区。
- 使用 静态成员资格(
-
增量式再平衡
- 启用
CooperativeStickyAssignor,分阶段释放分区,减少停顿。
- 启用
五、常见问题与解决
-
频繁再平衡
- 原因:心跳超时或
poll()间隔过长。 - 解决:调整超时参数,优化消费者处理逻辑。
- 原因:心跳超时或
-
重复消费
- 原因:再平衡前偏移量未提交。
- 解决:启用手动提交(
enable.auto.commit=false),处理完成后提交偏移量。
-
消费滞后(Lag)
- 原因:再平衡期间分区迁移导致暂停。
- 解决:使用
StickyAssignor减少迁移。