一、先搞懂:Kafka 的底层结构到底是什么?
Kafka 的设计围绕 “如何高效处理海量消息” 展开,整体可分为逻辑三层架构和物理存储细节两部分,二者共同支撑起 “高吞吐、高可用” 的能力。
1. 逻辑三层:生产者 - Broker 集群 - 消费者
Kafka 的核心交互流程很简单,就是 “消息从生产者发出,经过 Broker 集群存储,再被消费者拉取”,三层职责清晰:
- 生产者层(Producer):消息的 “发送方”,比如业务系统产生的订单数据、日志采集工具(如 Flume)收集的日志。它会根据 “分区策略” 把消息送到指定 Topic 的分区,还能通过重试、批量发送提升效率。
- Broker 集群层:Kafka 的 “核心骨架”,由多个 Broker(Kafka 服务器)组成。每个 Broker 就是一台物理机 / 虚拟机,负责存储 Topic 的分区数据、处理生产者的写入请求和消费者的读取请求。集群还需要一个 “协调者”(旧版本用 ZooKeeper,新版本用 Kafka 自身的 Controller)来管理 Leader 选举、Rebalance 等集群状态。
- 消费者层(Consumer):消息的 “接收方”,比如数据分析系统、实时计算框架(如 Flink)。消费者必须属于某个 “消费者组”,通过拉取(Pull)的方式从 Broker 获取消息,再进行业务处理。
2. 物理存储:分区 - 副本 - 日志分段的 “组合拳”
光有逻辑架构不够,Kafka 能存海量数据,关键靠物理存储的设计:
- 分区(Partition):Topic 的 “物理拆分单元”。比如一个存储用户日志的 Topic,若设 3 个分区,消息会被分到 3 个分区中,生产者可并行写、消费者可并行读,直接提升吞吐量。而且每个分区内的消息是按写入顺序存储的,能保证 “分区内有序”。
- 副本(Replica):为了避免 Broker 宕机导致数据丢失,每个分区会有多个副本(比如 1 个 Leader + 2 个 Follower)。Leader 负责处理所有读写请求,Follower 只做一件事 —— 同步 Leader 的日志数据。如果 Leader 挂了,就从 Follower 里选一个新 Leader,保证数据不丢。
- 日志分段(Log Segment):每个分区的日志不会存在一个大文件里,而是按时间 / 大小拆成多个 “分段文件”(比如每个文件最大 1GB)。同时配套索引文件(.index 偏移量索引、.timeindex 时间戳索引),能快速定位消息位置,避免大文件读写变慢。
二、必掌握:8 个 Kafka 核心概念
搞懂 Kafka,先记牢这 8 个基础概念 —— 它们是理解后续机制的 “钥匙”:
1. Topic(主题):消息的 “分类标签”
- 作用:把不同业务的消息分开,比如 “order-topic” 存订单消息,“log-topic” 存日志消息,避免混乱。
- 关键:Topic 本身不存消息,只是 “逻辑容器”,真正存消息的是它下面的 Partition。一个 Topic 可以有 1 个到多个 Partition,分区数越多,并行能力越强。
2. Broker(节点):Kafka 集群的 “最小单位”
- 作用:一台运行 Kafka 的服务器就是一个 Broker,负责存储数据、处理请求。
- 关键:每个 Broker 有唯一 ID(比如 Broker0、Broker1),一台 Broker 可以存多个 Topic 的多个 Partition。比如 Broker0 可能存 “order-topic” 的 Partition0 和 “log-topic” 的 Partition1。
3. Partition(分区):并行读写的 “核心”
- 作用:拆分 Topic 的物理单元,实现并行读写。
- 关键:
- 每个 Partition 有唯一编号(从 0 开始);
- 分区内消息按写入顺序存储,保证 “分区内有序”(但跨分区无序);
- 是副本机制的最小单位 —— 每个 Partition 有自己的副本集(Leader+Follower)。
4. Producer(生产者):消息的 “发送者”
- 作用:把业务系统的消息发送到 Kafka。
- 关键:
- 会根据 “分区策略”(比如轮询、按 key hash)决定消息发往哪个 Partition;
- 支持批量发送、重试,还能通过 “pid+seq” 实现幂等性,避免重复发消息。
5. Consumer(消费者):消息的 “接收者”
- 作用:从 Kafka 拉取消息,处理业务逻辑(比如统计订单量、分析用户行为)。
- 关键:
- 必须属于某个 “消费者组”,不能单独存在;
- 同一个消费者组内,一个 Partition 只能被一个消费者消费(避免重复消费);
- 用 “offset” 记录自己消费到哪个位置了。
6. Consumer Group(消费者组):负载均衡的 “利器”
- 作用:多个消费者组成一个组,共同消费一个或多个 Topic 的 Partition,实现负载均衡和故障转移。
- 关键:
- 组内消费者数量不能超过 Partition 数量 —— 否则多余的消费者会 “空闲”;
- 不同消费者组可以独立消费同一个 Topic(比如 “分析组” 消费 “log-topic” 做统计,“存储组” 消费同一 Topic 存数据库)。
7. Offset(偏移量):消费进度的 “记事本”
- 作用:每个 Partition 内消息的唯一标识(从 0 开始的整数),记录消费者 “吃到哪了”。
- 关键:
- 比如消费者 A 消费到 offset=100,说明它已经处理完前 100 条消息,下次从 offset=101 开始拉;
- offset 可以自动提交(Kafka 帮你记)或手动提交(自己控制,避免漏消费 / 重复消费),默认存在 Kafka 的内置 Topic(__consumer_offsets)里。
8. Replica(副本):高可用的 “保障”
- 作用:为 Partition 创建冗余副本,防止 Broker 宕机导致数据丢失。
- 关键:
- 分为 Leader 和 Follower:Leader 处理所有读写,Follower 同步 Leader 数据;
- 只有 “ISR(同步副本集)” 里的 Follower 才能参与 Leader 选举 —— 如果 Follower 长时间没同步数据,会被踢出 ISR。
三、关键机制 1:Kafka 幂等性 —— 如何避免重复消息?
你有没有遇到过这种情况:生产者发消息后,没收到 Broker 的确认,以为发送失败,就重试了一次,结果 Broker 其实已经收到了,导致消息重复?Kafka 的幂等性就是为解决这个问题而生的。
1. 什么是幂等性?
简单说:生产者多次发送同一消息,Broker 最终只存一次,不会出现重复数据。
2. 实现原理:pid + seq 的 “双重校验”
Kafka 在 Broker 端做了一层校验,核心是 “生产者唯一标识(pid)+ 分区序列号(seq)”:
- 分配 pid:每个生产者启动时,会向 Kafka 的 “协调者” 申请一个唯一的 pid(Producer ID),这个 pid 会持久化,就算生产者重启也不变。
- 生成 seq:生产者向某个 Partition 发消息时,会给这条消息加一个 “序列号(seq)”—— 每个 < pid, Partition > 对独立维护 seq,从 0 开始递增(比如向 Partition0 发第一条消息 seq=0,第二条 seq=1)。
- Broker 校验:Broker 为每个 <pid, Partition> 存一个 “最新 seq”,收到消息后会对比:
- 如果消息的 seq > 最新 seq:说明是新消息,存起来,更新最新 seq;
- 如果消息的 seq ≤ 最新 seq:说明是重复消息(比如重试发送的旧消息),直接丢弃。
3. 注意:幂等性的局限性
- 只保证 “单生产者、单 Partition” 的幂等 —— 如果多个生产者发同一个 Partition,或者一个生产者发多个 Partition,幂等性不生效;
- 如果生产者重建(pid 变了),之前的 seq 就失效了,需要结合 “事务” 才能解决跨分区 / 多生产者的幂等问题。
四、关键机制 2:生产者分区写入策略 —— 消息该往哪发?
生产者发消息时,首先要解决的问题是:这条消息该发往 Topic 的哪个 Partition?Kafka 提供了 3 种常用策略,对应不同业务场景。
1. 轮询策略(默认):均匀分配,追求吞吐
- 逻辑:如果消息没指定 key,生产者就按 “顺序轮询” 的方式发消息。比如 Topic 有 3 个 Partition(0、1、2),消息 1→0、消息 2→1、消息 3→2、消息 4→0,循环往复。
- 优点:消息在各 Partition 间分布均匀,不会出现某个 Partition 负载过高的情况,吞吐量最高。
- 缺点:无法保证 “同一业务逻辑的消息” 进同一个 Partition(比如同一用户的两条操作日志可能分到不同 Partition,消费时会乱序)。
- 适用场景:对消息顺序没要求,只追求高吞吐(比如普通日志采集)。
2. 按 Key Hash 策略:解决乱序,保证关联
- 逻辑:如果消息指定了 key(比如用户 ID、订单 ID),生产者会计算 “Hash (key) % 分区数”,结果就是消息要发的 Partition 编号。比如 key=user123,Hash 后是 100,分区数 = 3,100%3=1,消息就发往 Partition1—— 同一 key 的消息永远进同一个 Partition。
- 优点:同一 key 的消息集中在一个 Partition,而 Partition 内消息有序,完美解决 “乱序问题”。
- 缺点:可能出现 “Hash 倾斜”—— 比如某个 key 的消息特别多,导致对应 Partition 负载过高;而且分区扩容时,Hash 结果会变,消息分布会重新调整。
- 适用场景:对消息顺序有要求(比如同一用户的操作日志要按时间顺序处理、同一订单的状态变更要有序)。
3. 指定分区策略:完全可控,特殊场景
- 逻辑:生产者直接在代码里指定 Partition 编号(比如写死 partition=2),消息就固定发往这个 Partition。
- 优点:完全可控,适合特殊业务(比如按地区拆分,北京的消息发 Partition0,上海的发 Partition1)。
- 缺点:灵活性低,需要手动维护分区映射关系。
- 适用场景:业务逻辑明确需要固定分区的场景。
五、关键机制 3:消费者组 Rebalance—— 分区如何重新分配?
消费者组里,“一个 Partition 只能被一个消费者消费”,但如果消费者数量变了(比如新增消费者、某个消费者崩溃),或者 Partition 数量变了,之前的 “分区 - 消费者” 映射就失效了。这时候,Kafka 会触发 “Rebalance(重新平衡)”,重新分配分区。
1. 什么时候会触发 Rebalance?
- 消费者数量变化:组内新增消费者、消费者主动退出或崩溃;
- Partition 数量变化:订阅的 Topic 新增了 Partition;
- 消费者心跳超时:消费者没在规定时间(默认 10 秒)内发心跳,被判定为 “死亡”;
- 订阅关系变化:消费者修改了订阅的 Topic。
2. Rebalance 的执行流程
Rebalance 由 “协调者(每个消费者组对应一个 Broker)” 主导,分 3 步:
- Join Group(加入组):所有消费者向协调者发请求,说 “我是这个组的,我订阅了这些 Topic”。协调者选一个消费者当 “Leader”(负责计算分配方案)。
- Sync Group(同步方案):Leader 根据 “分区分配策略”(比如 Range、RoundRobin)算出 “哪个消费者消费哪个 Partition”,发给协调者。协调者再把方案同步给所有消费者。
- 执行分配:消费者收到方案后,停止消费旧分区,开始从新分区拉消息,同时提交旧分区的 offset(避免重复消费)。
3. Rebalance 的坑与优化
- 坑:Rebalance 过程中,所有消费者会暂停消费(“消费停顿”),如果 offset 没提交,还会重复消费。
- 优化建议:
- 避免频繁上下线消费者(比如容器化部署时,减少不必要的重启);
- 合理设置心跳参数:heartbeat.interval.ms 设为 session.timeout.ms 的 1/3(比如 session=10 秒,heartbeat=3 秒),确保消费者及时发心跳;
- 用 “Cooperative Sticky” 分配策略:增量 Rebalance,只调整变化的分区,不用全量重新分配,减少停顿时间。
六、关键机制 4:消费者分区分配策略 —— 分区该分给谁?
Rebalance 时,Leader 消费者怎么决定 “哪个分区分给哪个消费者”?Kafka 提供了 4 种分配策略,各有优劣。
1. Range 策略(默认):按 Topic 分组分配
- 逻辑:按 Topic 维度,把每个 Topic 的 Partition 排序,平均分给消费者。如果分区数不能整除消费者数,前几个消费者会多拿 1 个分区。
- 例子:TopicA 有 5 个 Partition(0-4),2 个消费者(C1、C2):5÷2=2 余 1,所以 C1 拿 0、1、2,C2 拿 3、4。
- 优点:同一 Topic 的分区集中,减少消费者跨 Broker 的网络开销。
- 缺点:如果消费者组订阅多个 Topic,前几个消费者会分配到更多分区,导致负载不均。
- 适用场景:消费者组只订阅单个 Topic。
2. RoundRobin 策略:轮询分配所有分区
- 逻辑:把所有订阅 Topic 的 Partition 排好序,对消费者轮询分配,不管 Topic。
- 例子:订阅 TopicA(3 个 Partition:A0-A2)和 TopicB(2 个 Partition:B0-B1),2 个消费者(C1、C2):排序后是 A0、A1、A2、B0、B1,轮询分给 C1(A0、A2、B1)和 C2(A1、B0)。
- 优点:多个 Topic 场景下,负载更均匀。
- 缺点:同一 Topic 的分区可能分散在不同消费者,增加跨 Broker 开销。
- 适用场景:消费者组订阅多个 Topic,需要均衡负载。
3. Sticky 策略:尽量保持现有分配
- 逻辑:核心是 “能不动就不动”,只在必要时调整分区(比如消费者下线),优先把下线消费者的分区分给负载最轻的消费者。
- 优点:减少分区移动次数,降低 Rebalance 的停顿时间和重复消费风险。
- 缺点:实现复杂,需要维护历史分配状态。
- 适用场景:对消费连续性要求高的场景(比如核心业务)。
4. Cooperative Sticky 策略:协作式增量分配
- 逻辑:Sticky 的改进版,支持 “增量 Rebalance”—— 只重新分配需要调整的分区,其他消费者继续消费旧分区,不用暂停。
- 优点:彻底解决 Rebalance 时的整体停顿,适合大规模消费者组(比如几百个消费者)。
- 适用场景:大型消费者组、实时数据处理等对可用性要求极高的场景。
七、关键机制 5:Producer 的 ack 机制 —— 如何平衡可靠性与吞吐量?
生产者发消息后,什么时候算 “发送成功”?这由 ack 机制决定 —— 它是平衡 “消息可靠性” 和 “吞吐量” 的核心参数,Kafka 提供 3 种配置。
1. ack=0:无确认,追求极限吞吐
- 逻辑:生产者发完消息,不等 Broker 任何回复,直接认为成功,接着发下一条。
- 可靠性:最低 —— 消息可能在传输中丢失(比如 Broker 崩溃、网络断了),而且没法重试(因为不知道失败了)。
- 吞吐量:最高 —— 不用等响应,延迟最小。
- 适用场景:对可靠性要求极低,只追求速度(比如非核心监控数据上报)。
2. ack=1:Leader 确认,默认选择
- 逻辑:生产者发消息后,只等 “该 Partition 的 Leader” 确认(Leader 已把消息写入本地日志),就认为成功,不用等 Follower 同步。
- 可靠性:中等 —— 如果 Leader 在 Follower 同步前崩溃,消息会丢失;但可以通过 “重试”(retries 参数)应对临时网络问题。
- 吞吐量:中等 —— 只等 Leader 响应,延迟比 ack=0 高,但比 ack=-1 低。
- 适用场景:大部分业务场景(比如订单状态通知、普通业务消息)。
3. ack=-1(或 all):Leader+ISR 确认,最高可靠
- 逻辑:生产者发消息后,要等 “Leader + 所有 ISR 副本” 都确认(都写入本地日志),才认为成功。
- 可靠性:最高 —— 除非所有 ISR 副本同时崩溃(概率极低),否则消息不会丢失。
- 吞吐量:最低 —— 要等多个副本响应,延迟最高。
- 适用场景:核心业务,不允许消息丢失(比如金融交易消息、支付指令)。
配套参数:提升可靠性
- retries:重试次数(默认 0,建议设 3~5),应对网络抖动;
- retry.backoff.ms:重试间隔(默认 100ms),避免频繁重试压垮 Broker;
- min.insync.replicas:ISR 最小副本数(默认 1,建议和 ack=-1 配合设为 2),确保至少 2 个副本同步消息,进一步降低丢失风险。
八、总结:Kafka 设计的核心思想
梳理完这些内容,你会发现 Kafka 的所有设计都围绕两个核心目标:高吞吐和高可用:
- 高吞吐:靠分区并行读写、批量发送、日志分段存储实现;
- 高可用:靠副本机制(Leader+Follower)、Rebalance 故障转移、ack 确认机制实现。
10万+

被折叠的 条评论
为什么被折叠?



