文章目录
- [Kafka 面试完全指南(Go 校招向)](#Kafka 面试完全指南(Go 校招向))
-
- [一、Kafka 是什么](#一、Kafka 是什么)
- 二、核心概念(必须烂熟于心)
-
- 架构全景
- [Topic & Partition(分区)](#Topic & Partition(分区))
- Offset(偏移量)
- [Broker & 集群](#Broker & 集群)
- [Consumer Group(消费者组)](#Consumer Group(消费者组))
- [Replica(副本)与 ISR](#Replica(副本)与 ISR)
- GroupCoordinator(消费者组协调器)
- [Kafka 为什么不支持读写分离(面试高频)](#Kafka 为什么不支持读写分离(面试高频))
- 三、生产者原理
- 四、消费者原理
-
- 拉取模式(Pull)
- [Offset 提交方式](#Offset 提交方式)
- Rebalance(重平衡)
- 五、存储机制(高性能的基础)
- 六、高可用机制
-
- [Leader 选举](#Leader 选举)
- 消息保留策略
- 七、三大消息语义
- 八、生产环境常见问题
-
- 问题一:消息丢失
- 问题二:消息重复消费
- 问题三:消息顺序性
- 问题四:消息积压
- [问题五:重复消费 vs 漏消费(提交时机)](#问题五:重复消费 vs 漏消费(提交时机))
- [九、Go 中使用 Kafka(sarama / kafka-go)](#九、Go 中使用 Kafka(sarama / kafka-go))
-
- 生产者示例(sarama)
- [消费者示例(sarama ConsumerGroup)](#消费者示例(sarama ConsumerGroup))
- 十、面试高频问题速答
- 十一、一张图记住核心
Kafka 面试完全指南(Go 校招向)
一、Kafka 是什么
Kafka 是 LinkedIn 开源的分布式消息队列,现由 Apache 维护。核心定位:
生产者 → Kafka集群(持久化存储) → 消费者
解决三个核心问题:
- 异步解耦:订单服务下单后直接返回,不等库存/通知等下游处理
- 流量削峰:瞬时高并发写入 Kafka,消费者按自身速度消费
- 数据管道:日志收集、埋点数据、CDC(数据库变更同步)
与 RabbitMQ 的核心区别:
| 维度 | Kafka | RabbitMQ |
|---|---|---|
| 模型 | 拉取(Pull) | 推送(Push) |
| 吞吐量 | 百万级/秒 | 万级/秒 |
| 持久化 | 默认持久化,可回溯 | 消费后删除 |
| 消费方式 | 消费者组并行消费 | 竞争消费 |
| 适用场景 | 大数据、日志、高吞吐 | 业务解耦、任务队列 |
二、核心概念(必须烂熟于心)
架构全景
Producer Kafka Cluster Consumer
┌─────────────────────────┐
│ Broker 1 │
│ ┌─────────────────┐ │
写入 ──────────────▶│ │ Topic: order │ │──────────────▶ Consumer Group A
│ │ Partition 0 [Leader] │ │ (消费者1 拉取 P0)
│ │ Partition 1 [Leader] │ │──────────▶ Consumer Group B
│ └─────────────────┘ │ (消费者2 拉取 P1)
│ Broker 2 │
│ ┌─────────────────┐ │
│ │ Partition 0 [Follower]│ │
│ │ Partition 1 [Follower]│ │
│ └─────────────────┘ │
└─────────────────────────┘
│
ZooKeeper / KRaft
(元数据 & Leader 选举)
Topic & Partition(分区)
Topic:消息的逻辑分类,类似数据库的表名。
Partition :Topic 的物理分片,是 Kafka 并行度的基本单位。
Topic: order
├── Partition 0: [msg0] [msg1] [msg4] [msg7] ...
├── Partition 1: [msg2] [msg5] [msg8] ...
└── Partition 2: [msg3] [msg6] [msg9] ...
- 每个 Partition 内部消息有序 ,Partition 间无序
- Partition 数量决定最大消费并发数(消费者数 ≤ Partition 数才有意义)
- 消息写入哪个 Partition 由分区策略决定
Offset(偏移量)
每条消息在 Partition 内有唯一的自增编号,即 Offset。
Partition 0:
offset=0: {"order_id": 1001}
offset=1: {"order_id": 1002}
offset=2: {"order_id": 1003} ← 消费者当前消费到这里
offset=3: {"order_id": 1004} ← 待消费
- 消费进度由消费者自己维护 ,存储在 Kafka 内部 Topic
__consumer_offsets - 消费者崩溃重启后,从上次提交的 Offset 继续消费
- 支持重置 Offset,实现消息回溯(RabbitMQ 不支持)
Broker & 集群
- Broker:Kafka 集群中的单个节点(一个 JVM 进程)
- Controller:集群中特殊的 Broker,负责 Leader 选举、Partition 分配
- ZooKeeper / KRaft :
- Kafka 2.x 之前:依赖 ZooKeeper 存储元数据、做 Controller 选举
- Kafka 3.x 之后:KRaft 模式,去掉 ZooKeeper 依赖,Controller 内置
Consumer Group(消费者组)
Topic: order (3个Partition)
Consumer Group A(订单处理):
Consumer1 → Partition 0
Consumer2 → Partition 1
Consumer3 → Partition 2
Consumer Group B(数据统计):
Consumer1 → Partition 0, 1, 2 (只有一个消费者,消费所有分区)
核心规则:
- 同一 Consumer Group 内,一个 Partition 只能被一个消费者消费
- 不同 Consumer Group 之间互相独立,都能消费同一 Topic 的全量数据
- 消费者数 > Partition 数时,多余的消费者空闲(浪费资源)
Replica(副本)与 ISR
每个 Partition 有多个副本,分布在不同 Broker 上:
Partition 0:
Leader副本 → Broker1(负责读写)
Follower副本 → Broker2(同步数据,不提供读写)
Follower副本 → Broker3(同步数据,不提供读写)
ISR(In-Sync Replicas):与 Leader 保持同步的副本集合。
- Follower 落后 Leader 超过阈值(
replica.lag.time.max.ms,默认30s)→ 踢出 ISR - Leader 宕机时,只有 ISR 中的 Follower 才有资格被选为新 Leader
- 这是 Kafka 高可用的核心保障
GroupCoordinator(消费者组协调器)
每个 Consumer Group 由集群中某个 Broker 担任 GroupCoordinator,负责:
- 管理消费者的加入/退出
- 触发和执行 Rebalance
- 维护 Offset 提交(写入
__consumer_offsets)
如何找到自己组的 Coordinator:
hash(group.id) % __consumer_offsets 的 Partition 数(默认50)
= 该 Partition 的 Leader 所在的 Broker
= 该 Group 的 GroupCoordinator
消费者启动时,先向任意 Broker 发 FindCoordinator 请求,找到协调者后再发 JoinGroup。
Kafka 为什么不支持读写分离(面试高频)
Redis、MySQL 都支持从节点读,Kafka 的 Follower 为什么只同步不提供读?
原因一:强一致性要求
Producer 写入 Leader(offset=100)
Follower 还在同步中(offset=98)
如果此时消费者从 Follower 读,会读到"还不存在"的消息
或者读到的消息后来因 Leader 宕机被截断 → 数据不一致
原因二:消息队列的消费模式不需要读写分离
- 数据库需要读写分离是因为读远多于写(OLTP 场景)
- Kafka 的消费者直接并行消费不同 Partition,每个 Partition 只有一个 Leader
- 增加并发只需增加 Partition 数量,水平扩展天然支持,无需读写分离
原因三:Partition 分散在不同 Broker,已经是分布式读
Topic: order(3个Partition,3个Broker)
Partition 0 Leader → Broker1 ← Consumer1 读
Partition 1 Leader → Broker2 ← Consumer2 读
Partition 2 Leader → Broker3 ← Consumer3 读
不同消费者读不同 Broker,已经实现了负载分散,不需要额外的读写分离。
面试答法:Kafka 不支持读写分离,因为 Follower 与 Leader 存在同步延迟,从 Follower 读会造成数据不一致。且 Kafka 通过多 Partition + 多 Broker 已天然实现了并行读,消费端只需增加 Partition 数就能水平扩展,不需要读写分离来提升读性能。
三、生产者原理
消息发送流程
业务代码调用 send()
│
▼
拦截器(可选)
│
▼
序列化(key/value → bytes)
│
▼
分区器(决定写哪个 Partition)
│
▼
RecordAccumulator(消息缓冲区,按 Partition 分批)
│ ← 达到 batch.size 或 linger.ms 触发
▼
Sender 线程 → 网络发送 → Broker
分区策略
go
// 1. 指定 Partition:直接写入目标分区
producer.send(new ProducerRecord("order", 0, key, value))
// 2. 指定 Key:对 key 做 hash % partitionCount,相同 key 一定进同一分区
producer.send(new ProducerRecord("order", "user_123", value))
// 3. 不指定 Key:轮询(Round-Robin)各分区,均匀分布
producer.send(new ProducerRecord("order", value))
面试常问 :如何保证同一用户的消息有序?
答:用用户 ID 作为 Key,相同 Key 的消息落同一 Partition,Partition 内有序。
ACK 机制(消息可靠性)
acks 参数控制 Producer 收到 Broker 确认的级别:
| acks | 含义 | 可靠性 | 性能 |
|---|---|---|---|
0 |
不等待任何确认,发完即走 | 最低,可能丢消息 | 最高 |
1 |
Leader 写入本地日志后确认 | 中,Leader 宕机可能丢消息 | 中 |
-1/all |
ISR 中所有副本都写入后确认 | 最高,不丢消息 | 最低 |
生产环境通常用 acks=-1 + min.insync.replicas=2(ISR 至少2个副本才确认)。
幂等生产者与事务
幂等生产者 (enable.idempotence=true):
- Producer 携带
ProducerID和SequenceNumber - Broker 检测到重复的序列号,直接丢弃,不重复写入
- 解决:网络重试导致的消息重复写入问题
- 注意:只保证单 Partition 内的幂等
事务(跨 Partition / 跨 Topic 的原子写入):
begin transaction
写入 Partition A
写入 Partition B
提交 consumer offset
commit / abort
用于"精确一次(Exactly Once)"语义,性能开销较大。
四、消费者原理
拉取模式(Pull)
Kafka 消费者主动拉取消息,而非 Broker 推送:
go
for {
records := consumer.Poll(timeout) // 主动拉取
for _, record := range records {
process(record)
}
consumer.CommitSync() // 提交 offset
}
Pull 的优势:消费者按自身处理能力拉取,不会被压垮;支持批量拉取,提高吞吐。
Offset 提交方式
| 方式 | 说明 | 风险 |
|---|---|---|
自动提交(enable.auto.commit=true) |
定时自动提交,简单 | 可能丢消息(处理中崩溃,但 offset 已提交) |
手动同步提交 CommitSync() |
处理完成后手动提交 | 可能重复消费(处理完但提交前崩溃) |
手动异步提交 CommitAsync() |
异步提交,不阻塞 | 重试顺序问题,需配合回调处理 |
生产最佳实践:手动提交 + 业务幂等处理,接受"至少一次"语义。
Rebalance(重平衡)
触发条件:
- 消费者加入/离开 Consumer Group
- Topic 的 Partition 数量变化
- 消费者心跳超时(
session.timeout.ms,默认45s)
Rebalance 过程:
1. 所有消费者停止消费(Stop The World)
2. 向 GroupCoordinator 发送 JoinGroup 请求
3. Leader 消费者制定分配方案
4. 所有消费者执行新的分配,从新 Partition 继续消费
Rebalance 的问题 :Rebalance 期间所有消费者停止消费,影响吞吐量。
减少 Rebalance 的方法:
- 调大
session.timeout.ms和heartbeat.interval.ms - 调大
max.poll.interval.ms(处理时间上限) - 使用 静态成员 (
group.instance.id),重启后沿用原分配,避免触发 Rebalance
五、存储机制(高性能的基础)
日志文件结构
每个 Partition 对应磁盘上的一个目录,内部按**日志段(LogSegment)**存储:
/data/kafka/order-0/ ← Partition 目录
00000000000000000000.log ← 消息数据文件
00000000000000000000.index ← 稀疏索引(offset → 文件位置)
00000000000000000000.timeindex ← 时间索引
00000000000001234567.log ← 新的日志段(达到1GB后滚动)
00000000000001234567.index
文件名 = 该 Segment 第一条消息的 Offset。
消息查找过程
查找 offset=1234568 的消息:
1. 二分查找文件列表:1234568 >= 1234567 → 找到对应 Segment
2. 查 .index 文件:找到最近的索引项(稀疏索引,不是每条都记录)
3. 从索引指向的位置开始,顺序扫描 .log 文件,找到目标消息
四大高性能核心原理
1. 顺序写磁盘
随机写磁盘:~100 次/秒(磁头寻道时间)
顺序写磁盘:~600 MB/s(接近内存速度)
Kafka 的 .log 文件只追加写(Append Only),永不修改,充分利用顺序 I/O。
2. 页缓存(Page Cache)
Kafka 写消息时,先写操作系统的页缓存(内存),由 OS 异步刷盘:
Producer → Page Cache(内存) → (异步)磁盘
Consumer ← Page Cache(命中则不读磁盘)
热数据直接从内存读取,冷数据才读磁盘,大幅降低 I/O。
3. 零拷贝(Zero Copy)
传统文件传输:
磁盘 → 内核缓冲区 → 用户空间 → Socket缓冲区 → 网卡(4次拷贝)
Kafka 使用 sendfile 系统调用:
磁盘 → 内核缓冲区 → 网卡(2次拷贝,跳过用户空间)
减少 CPU 拷贝次数和上下文切换,消费者拉取消息时性能提升显著。
4. 批量压缩
Producer 将多条消息打包成一个 RecordBatch 发送:
- 减少网络请求次数
- 支持 GZIP / Snappy / LZ4 / ZSTD 压缩,减少网络和存储开销
六、高可用机制
Leader 选举
Leader 宕机时,Controller 从 ISR 列表中选第一个 Follower 为新 Leader:
ISR: [Broker1(Leader), Broker2, Broker3]
Broker1 宕机 → 新 ISR: [Broker2, Broker3]
→ Broker2 成为新 Leader
极端情况:ISR 全部宕机,只剩非 ISR 副本。此时:
unclean.leader.election.enable=false(默认):等待 ISR 副本恢复,保一致性,牺牲可用性unclean.leader.election.enable=true:允许非 ISR 副本当 Leader,保可用性,可能丢消息
消息保留策略
Kafka 消息不会消费完就删除,按策略保留:
yaml
# 按时间(默认7天)
log.retention.hours=168
# 按大小
log.retention.bytes=1073741824 # 1GB
# 按 Offset(compact 模式,只保留每个 key 最新的一条)
log.cleanup.policy=compact
七、三大消息语义
| 语义 | 含义 | 如何实现 |
|---|---|---|
| At Most Once(至多一次) | 可能丢消息,不重复 | acks=0,自动提交 offset |
| At Least Once(至少一次) | 不丢消息,可能重复 | acks=-1,手动提交,业务幂等 |
| Exactly Once(精确一次) | 不丢不重 | 幂等Producer + 事务,性能最低 |
生产环境通常选 At Least Once + 业务幂等,性价比最高。
八、生产环境常见问题
问题一:消息丢失
丢失场景:
Producer 侧:acks=0/1,网络故障或 Leader 宕机
Broker 侧: 页缓存未刷盘时宕机(同步刷盘 flush.ms=0 可避免但性能差)
Consumer 侧:自动提交 offset 后,消息处理失败
解决方案:
Producer:acks=-1 + min.insync.replicas=2 + 重试
Broker: replication.factor≥3,副本跨机架部署
Consumer:手动提交 offset,处理成功再提交
问题二:消息重复消费
原因:
消费者处理完消息,提交 offset 前崩溃
重启后从上次 offset 重新消费 → 消息被重复处理
解决方案(幂等处理):
方案1:数据库唯一键(insert ignore / on conflict do nothing)
方案2:Redis SET NX,记录已处理的消息 ID
方案3:业务状态判断(如订单已是"已支付"状态,不再处理支付消息)
问题三:消息顺序性
问题:Kafka 只保证单 Partition 内有序,多 Partition 无序
场景:用户操作日志,必须按顺序处理(注册→登录→下单)
解决方案:
1. 按业务 key(如 user_id)分区,同一用户消息进同一 Partition
2. 该 Partition 只分配一个消费者(Consumer Group 内)
3. 消费者单线程处理该 Partition 的消息
注意:多线程消费同一 Partition 会破坏顺序,需要串行处理或按 key 投递线程池
问题四:消息积压
原因:消费速度 << 生产速度
排查步骤:
1. 看 Lag(积压量)= 最新 Offset - 消费者已提交 Offset
2. 判断是消费慢还是生产突然变多
处理方案:
临时:增加消费者实例数(不能超过 Partition 数)
或临时扩 Partition 数(注意:扩后 key 的 hash 分布会变化)
根本:优化消费者处理逻辑(批量处理、异步写库等)
紧急:将积压消息转移到容量更大的临时 Topic,扩容消费
问题五:重复消费 vs 漏消费(提交时机)
go
// 危险:先提交再处理 → 处理失败,消息漏消费
consumer.CommitSync()
process(record) // 如果这里 panic,消息丢了
// 危险:自动提交 → 提交时业务可能还未处理完
enable.auto.commit=true
// 正确:先处理,处理成功后再提交
process(record) // 业务处理
consumer.CommitSync() // 确认提交
// 崩溃重启后会重复消费,需要业务幂等兜底
九、Go 中使用 Kafka(sarama / kafka-go)
生产者示例(sarama)
go
import "github.com/IBM/sarama"
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // acks=-1
config.Producer.Idempotent = true // 幂等
config.Producer.Return.Successes = true
config.Net.MaxOpenRequests = 1 // 幂等模式必须为1
producer, err := sarama.NewSyncProducer(brokers, config)
defer producer.Close()
msg := &sarama.ProducerMessage{
Topic: "order",
Key: sarama.StringEncoder(userID), // 按 userID 分区
Value: sarama.StringEncoder(payload),
}
partition, offset, err := producer.SendMessage(msg)
消费者示例(sarama ConsumerGroup)
go
type Handler struct{}
func (h *Handler) Setup(_ sarama.ConsumerGroupSession) error { return nil }
func (h *Handler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil }
func (h *Handler) ConsumeClaim(session sarama.ConsumerGroupSession,
claim sarama.ConsumerGroupClaim) error {
for msg := range claim.Messages() {
if err := process(msg); err != nil {
// 处理失败:记录日志,不提交 offset,等待重试
log.Printf("process failed: %v", err)
continue
}
// 处理成功后标记提交(异步批量提交)
session.MarkMessage(msg, "")
}
return nil
}
client, _ := sarama.NewConsumerGroup(brokers, "order-group", config)
client.Consume(ctx, []string{"order"}, &Handler{})
十、面试高频问题速答
Q:Kafka 为什么高性能?
四个原因:① 顺序写磁盘,避免随机 I/O;② 页缓存,读热数据不走磁盘;③ 零拷贝(sendfile),减少数据拷贝次数;④ 批量发送与压缩,减少网络开销。
Q:Kafka 如何保证消息不丢失?
三端配合:Producer 设置
acks=-1+ 重试;Broker 设置副本数 ≥ 3,min.insync.replicas=2;Consumer 手动提交 Offset,处理成功后再提交。
Q:Kafka 和 RabbitMQ 怎么选?
高吞吐、需要消息回溯、日志/埋点场景选 Kafka;业务解耦、复杂路由、低延迟任务队列选 RabbitMQ。
Q:Partition 数量怎么设置?
根据目标吞吐量 / 单 Partition 吞吐量得出,并发消费数不超过 Partition 数。不建议太多:Partition 过多会增加 Leader 选举和元数据管理压力。
Q:Consumer 数量超过 Partition 数会怎样?
多余的 Consumer 不会被分配任何 Partition,处于空闲状态,造成资源浪费。
Q:Rebalance 期间会发生什么,如何避免频繁 Rebalance?
Rebalance 期间所有消费者停止消费(Stop The World),可能造成消息积压。避免方法:调大心跳和 session 超时参数、调大单次 poll 处理时间上限、使用静态成员 ID。
Q:如何实现消息顺序消费?
同一业务 key 的消息通过 hash 分区写入同一 Partition,Consumer Group 中该 Partition 只分配一个消费者单线程处理。
Q:Kafka 消息积压怎么处理?
短期:扩容消费者实例(上限是 Partition 数量);中期:优化消费逻辑(批量处理、异步落库);紧急:建临时 Topic + 多 Partition,把积压消息迁移过去后扩消费者消费。
Q:什么是 ISR,它的作用是什么?
ISR 是与 Leader 保持同步的副本集合,只有 ISR 中的副本才能参与 Leader 选举。通过控制
min.insync.replicas,保证消息写入足够多的副本后才确认,防止 Leader 宕机数据丢失。
Q:Exactly Once 是怎么实现的?
两层保障:幂等 Producer(PID + SequenceNumber 去重单 Partition 内重复写入)+ 事务(跨 Partition/Topic 的原子提交),配合 Consumer 的
read_committed隔离级别,实现端到端精确一次。
十一、一张图记住核心
┌────────────────────────────────────┐
高性能来自: │ 顺序写 + 页缓存 + 零拷贝 + 批量 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
高可用来自: │ 多副本(ISR)+ Leader 自动选举 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
消息可靠来自: │ acks=-1 + 手动提交 + 业务幂等 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
高并发消费来自: │ Partition 并行 + Consumer Group │
└────────────────────────────────────┘
总结:面试 Kafka 的核心考点是"为什么快"(存储机制)、"怎么保证可靠"(ACK + 副本 + 手动提交)、"怎么处理常见问题"(丢失/重复/顺序/积压)。把这四个方向讲清楚,Kafka 面试基本稳了。