Kafka学习笔记(五):面试高频问题总结
Kafka学习笔记(五):面试高频问题总结
学完 Kafka 的基本操作之后,面试里最容易遇到的情况是:名词都认识,但面试官再追问一句“为什么”,回答就接不上了。
例如,“Kafka 如何保证消息不丢?”如果只回答 acks=all,接下来就可能被问到:只有一个副本怎么办?消费者提前提交进度怎么办?订单已经落库但事件没发出去怎么办?
这篇把前四篇整理成一份面试复习笔记。每个问题先给一段简短回答,再补充追问要点。复习时先练习把主干讲清楚,再用故障场景解释机制。
**版本约定:**沿用本系列的 Kafka 4.0、KRaft 学习基线;消费组部分讨论
KafkaConsumer.subscribe()使用的常规消费组。提到 classic 与新 consumer 协议时会单独区分。配置默认值与行为应按实际客户端和 Broker 版本核对。
系列导航:
本文目录:
一、基础概念与架构
1. Kafka 是什么?主要解决哪些问题?
简短回答:
Kafka 是一个分布式事件流平台,核心能力是写入、保存和读取事件。它通过分区实现并行,通过副本提高容错能力,通过消费组让多个业务独立读取同一份事件流,常用于异步处理、服务解耦、流量缓冲和数据管道。
追问要点:
- 订单创建后,通知与统计可以使用不同消费组独立读取事件。
- 流量缓冲需要足够的存储与保留时间;长期消费速度低于生产速度,积压仍会增长。
- 引入 Kafka 后,还需要处理重复、重放、事件兼容性和最终一致性。
2. Kafka 的架构由哪些角色组成?还需要 ZooKeeper 吗?
简短回答:
Producer 写事件,Consumer 读事件,Broker 保存分区日志并处理读写请求,Controller 管理集群元数据并协调分区状态。Kafka 4.0 已移除 ZooKeeper 模式,使用 KRaft 管理元数据。
追问要点:
- Controller 的元数据共识,与业务分区的 Leader/Follower 复制是两层机制。
- 本地实验可让一个进程同时担任 Broker 和 Controller;生产部署需要结合故障域和容量设计。
- 回答旧系统时先确认版本,不要把 ZooKeeper 时代的架构套到 Kafka 4.0。官方升级说明明确了这个边界。
3. Topic、Partition、Replica 有什么区别?
简短回答:
Topic 是一类事件的逻辑集合,Partition 是其中的一条有序日志,Replica 是同一分区日志的副本。分区主要解决并行,副本主要解决故障冗余。
追问要点:
1 | 3 个分区 × 每个分区 3 个副本 = 9 份分区副本 |
增加副本不会直接增加同组消费者的可用并行度。副本还应该分散到不同节点,必要时跨机架或可用区,避免同一故障同时影响多份数据。
4. Offset 是什么?能当作消息的全局唯一 ID 吗?
简短回答:
Offset 是记录在某个分区中的逻辑位置,只在该分区内有意义。定位一条 Kafka 记录,需要 Topic、Partition、Offset 的组合;识别一次业务事件,通常还需要独立的 eventId。
追问要点:
- P0 的 Offset 10 与 P1 的 Offset 10 是不同记录。
- Offset 不保证连续,日志压缩、事务等可能造成可见记录之间的间隙。
- 同一次业务事件被重新发送后,可能获得新的 Offset,因此业务去重不能只依赖它。
5. 消费完消息后,Kafka 会删除它吗?delete 与 compact 有什么区别?
简短回答:
消费进度与消息清理是独立的。提交 Offset 不会删除消息;
delete根据时间、大小等保留规则清理旧日志段,compact则按 Key 清理旧值,使日志适合恢复某个 Key 的最新状态。
追问要点:
| 策略 | 更适合表达什么 | 容易答错的地方 |
|---|---|---|
delete |
保留一定窗口内的事件历史 | 未消费的旧数据也可能被清理 |
compact |
按 Key 恢复当前状态 | 清理是后台进行的,不会立即只剩一条 |
compact 也不是“订单号相同就不再写入”。消费者在清理发生前仍可能读到多个版本。分段存储可参考日志实现说明,压缩规则见Log Compaction 设计。
二、生产者与性能
6. 一条消息从 send() 到 Broker,大致经过哪些步骤?
简短回答:
生产者获取元数据,序列化 Key 和 Value,确定分区,将记录放入对应分区的批次缓冲区,再由后台发送线程发给分区 Leader,最后根据确认策略和发送结果完成 Future 或回调。
追问要点:
1 | 序列化 → 选择分区 → 批次缓冲 → 后台发送 → 确认或失败 |
send() 通常是异步的,但获取元数据或等待缓冲空间时也可能阻塞。调用返回不等于 Broker 已确认;应用需要检查 Future 或回调中的最终结果。KafkaProducer API说明了这些行为。
7. Kafka 如何选择分区?相同 Key 一定落到同一分区吗?
简短回答:
显式指定分区时使用指定值;否则按分区器与配置选择。有 Key 时,默认策略通常根据 Key 选择分区。在分区数、Key 序列化与分区策略稳定时,相同 Key 会进入同一分区。
追问要点:
- 无 Key 时不能一概回答“严格轮询”,默认策略与客户端版本、配置有关。
- 自定义分区器、忽略 Key 的配置,以及不同的序列化方式都可能改变映射。
- 扩分区可能使同一 Key 的新旧事件分布到不同分区,对严格顺序业务要设计迁移。
8. Kafka 为什么能实现较高吞吐?
简短回答:
Kafka 通过日志顺序追加、操作系统页缓存、批量传输、批次压缩和分区并行来分摊读写开销。在适用的网络路径下,还可以通过零拷贝减少数据复制。
追问要点:
- 零拷贝不代表从磁盘到网卡完全没有复制,也不能无条件套到所有加密与传输路径。
- Kafka 会持久化日志,不能回答成“全部存在内存里所以快”。
- 消息大小、压缩率、ACK、副本复制、磁盘和 Key 热点都会影响性能。官方效率设计可用于进一步展开。
9. batch.size 与 linger.ms 分别控制什么?如何取舍?
简短回答:
batch.size控制生产者按分区组批时的批次大小目标,linger.ms控制等待更多记录加入批次的时间。适当组批能提高吞吐,但可能增加等待延迟,需要按消息大小和延迟目标测量。
追问要点:
batch.size不是整个 Producer 的内存上限,也不是所有分区共用一个批次。- 不应说“只有填满批次才发送”;批次可因等待时间等条件发送。
- 确认策略、背压和网络状态也会影响实际延迟;
linger.ms不是端到端延迟上限。生产者配置列出了这些参数。
三、消息可靠性与一致性
10. acks=0、1、all 有什么区别?
简短回答:
0不等待 Broker 确认,1等待 Leader 本地日志写入确认,all等待当前 ISR 的复制确认。更强的确认策略通常提高可靠性要求,但实际保障还依赖副本、ISR 和故障范围。
追问要点:
| 配置 | 典型问题 |
|---|---|
0 |
应用无法据此知道 Broker 是否接收 |
1 |
Leader 失效时,尚未复制的记录可能丢失 |
all |
如果同步副本只剩一个,冗余仍可能不足 |
“本地日志写入”也不能直接等同于每条消息都已经完成 fsync。ACK 配置说明是回答这个问题的依据。
11. acks=all 配合 min.insync.replicas=2,是等任意两个副本吗?
简短回答:
不是。
min.insync.replicas设置最低同步副本要求,acks=all仍等待当前 ISR 所需的复制确认。ISR 有三个时,不是只要其中任意两个完成就返回;ISR 缩到两个时,可以在满足条件后继续写;只剩一个时无法成功确认。
追问要点:
假设副本数为 3:
1 | ISR = A、B、C → 按当前三个同步副本完成复制 |
这是本系列使用的经典 ISR 配置例子。副本不足时拒绝写入,是可靠性与可用性的取舍;应用必须保留失败事件并恢复发送。最低 ISR 配置解释了该门槛。
12. ISR 是什么?Leader 宕机后怎么恢复?
简短回答:
ISR 是保持同步的分区副本集合,包含 Leader。落后或失效的副本可能被移出 ISR。Leader 失效后,Controller 协调新 Leader 的选举与元数据更新,客户端更新元数据后继续访问。
追问要点:
- 在未启用 ELR 的经典机制中,干净选举从 ISR 中选择符合条件的副本。
- 如果没有合适副本,允许落后副本接任的非干净选举可能提高可用性,但会有数据丢失风险。
- Kafka 4.0 还引入了 ELR(Eligible Leader Replicas)机制,讨论具体候选集合时要确认是否启用,不能把所有版本都概括成“只能从当前 ISR 选”。ELR 官方说明介绍了这一扩展。
13. Kafka 生产者幂等如何实现?能解决所有重复吗?
简短回答:
幂等生产者使用生产者身份和分区序列号,让 Broker 识别客户端协议级重试造成的重复写入。它解决特定发送链路中的重复,不能把应用主动发送两次的业务事件自动合并,也不能避免消费者重复执行业务。
追问要点:
- 开启幂等需满足
acks=all、retries>0、max.in.flight.requests.per.connection<=5。 - 同一个 Key、Value 主动调用两次
send(),仍然是两次发送。 - 跨应用重试仍需要稳定的 eventId 与恢复策略。幂等配置约束可用于核对参数。
14. 如何保证消息不丢?
简短回答:
我会沿生产、存储、消费三段分析:生产端检查发送结果并可恢复重试;存储端配置合理的副本、ACK 和最低 ISR;消费端在业务持久化成功后提交进度。数据库到事件发送的双写窗口,还要用 Outbox 或其他恢复方案处理。
追问要点:
不能做无条件的“不丢”承诺,要把故障假设讲清楚:
- 发送超时可能是确认丢了,并不一定是消息没到。
- 多份副本同时永久丢失,不属于普通单节点容错能兜底的范围。
- 消费落后超过保留窗口,所需记录可能已经清理。
- 代码捕获发送或处理异常后直接忽略,配置再强也不能补回应用主动丢弃的事件。
15. 为什么会重复消费?如何实现消费幂等?
简短回答:
业务已经提交,但 Offset 未提交时进程崩溃,恢复后会再次处理。可以使用稳定 eventId,通过数据库唯一约束记录消费身份,并把去重记录与业务更新放进同一个本地事务,事务成功后再提交消费进度。
追问要点:
1 | 去重记录 + 业务更新:同一个数据库事务 |
唯一键可使用 (consumer_name, event_id),让不同业务各自去重。只使用 Redis 锁或先写 SETNX 标记,无法自动保证标记与业务结果一起成功;外部调用则需要下游支持幂等请求键等机制。
16. Kafka 能实现 Exactly-Once 吗?事务包含什么?
简短回答:
对 Kafka 到 Kafka 的处理,可以把输出记录与输入消费组进度放进同一个 Kafka 事务,并让读取方使用
read_committed,实现这个处理边界内的 Exactly-Once 语义。它不会自动把 MySQL、短信或第三方 HTTP 调用纳入同一个原子提交。
追问要点:
- 事务生产者配置
transactional.id,初始化后开始、提交或中止事务。 - 输入消费者关闭自动提交,用
sendOffsetsToTransaction()提交输入进度。 - 中止事务不会自动回退消费者的内存 Position,需要重新定位或重建客户端恢复。
- 不能只回答“开启生产者幂等就实现端到端 Exactly-Once”。事务 API 见KafkaProducer 文档。
17. 订单写入 MySQL 成功,Kafka 发送失败,怎么办?
简短回答:
用 Outbox 将订单和待发送事件写进同一个数据库事务。事务提交后,由投递器或 CDC 把事件发送到 Kafka。发送失败可以恢复;发送成功但投递状态未更新时可能重复,所以要复用 eventId 并让消费端幂等。
追问要点:
1 | MySQL 事务:订单 + Outbox |
本地数据库的 @Transactional 不会自动消除 Kafka 与 MySQL 的双写窗口。投递器还要处理并发抢占、错误重试、清理与同一业务实体的事件顺序。CDC 的一种实现可参考Debezium Outbox Event Router。
四、消费者与进度管理
18. Kafka 消费者是 Pull 还是 Push?会一直空轮询吗?
简短回答:
Kafka 使用拉取模型。客户端主动发起 Fetch 请求并通过
poll()向应用返回记录;Broker 可以等待数据或达到等待时间后返回,减少没有数据时的忙轮询。
追问要点:
- 拉取方便消费者按自身能力控制处理节奏,也便于批量获取。
fetch.min.bytes与fetch.max.wait.ms影响聚合与等待的取舍。poll()不只是简单的一次网络读取,还涉及客户端缓存、消费组协调等行为。
19. 消费组如何实现负载均衡?消费者越多越快吗?
简短回答:
常规消费组把订阅分区分配给组内成员,稳定状态下同一分区由组内一个成员负责,一个成员可以负责多个分区。并行度受订阅分区数限制,还受 Key 热点与下游能力影响。
追问要点:
一个组只订阅 3 个分区的 Topic,启动 5 个消费者,至少有两个分不到分区。不同业务想各读完整事件流,应使用不同组,而不是把通知和统计混在一个组里争抢分区。
20. Position 与 Committed Offset 有什么区别?提交的是哪一条?
简短回答:
Position 是客户端下一次读取的位置,随
poll()返回记录而推进;Committed Offset 是持久化的恢复位置。提交的是下一待处理位置,不是“最后一条成功记录的编号”本身。
追问要点:
1 | 处理完成:Offset 5、6、7 |
消息进入线程池,不代表业务成功,不能直接按 Position 提交。Kafka 4.0 的 ConsumerRecords.nextOffsets() 可在本批对应分区全部处理完成后提供下一位置。KafkaConsumer 的位置说明区分了这两个概念。
21. auto.offset.reset=earliest,为什么没有从头消费?
简短回答:
它只在没有有效初始提交位置,或位置已经越出可读范围等情况下起作用。已有有效提交位置时,消费者按该位置继续。
earliest指当前仍保留的最早位置,也不保证是 0。
追问要点:
| 场景 | 结果 |
|---|---|
| 保留 50~99,已提交 80 | 从 80 继续 |
| 保留 50~99,新组配置 earliest | 从 50 开始 |
| 保留 50~99,提交位置已过期 | 按重置策略恢复 |
重放应使用明确的进度重置或独立消费组,提前处理副作用。配置触发条件是这个问题的关键。
22. 自动提交、commitSync()、commitAsync() 怎么选?
简短回答:
自动提交按客户端机制周期推进进度;手动提交让应用控制提交时机。
commitSync()等待结果,失败以异常体现;commitAsync()不等待完成,通过回调观察结果。选择时要考虑业务完成边界、延迟和失败恢复。
追问要点:
- 自动提交不是必然丢消息,关键是每次轮询返回的记录是否在后续轮询或关闭前完成处理。
- 手动提交也不自动安全,不能吞掉处理异常后提交整批进度。
- 异步提交失败时,不应盲目重试一个过期的低 Offset;新进度可能已提交,需要设计单调推进与失败恢复。
- 同步提交本身也可能失败,不能写完一行
commitSync()就忽略恢复路径。
23. 什么是 Rebalance?什么时候发生?
简短回答:
Rebalance 是消费组重新分配分区工作。成员加入、退出、失效或订阅分区集合变化都可能触发。分区交接时,新负责人从有效提交进度恢复,因此可能重复处理旧负责人已执行但未提交的记录。
追问要点:
- 不要概括成“每次都让所有成员撤销全部分区”,具体影响取决于协议与分配方式。
- Rebalance 回调可以配合提交、清理和停止分区任务,但业务仍需幂等。
- Kafka 4.0 的新协议可通过
group.protocol=consumer使用,分配与心跳配置的管理方式与 classic 不同。官方协议说明介绍了区别。
24. session.timeout.ms 与 max.poll.interval.ms 有什么区别?
简短回答:
在 classic 协议下,前者用于组协调器判断成员心跳是否失联,后者限制应用两次调用
poll()的最大间隔。后台还有心跳,不意味着业务处理可以无限阻塞轮询。
追问要点:
例如每条同步处理 1 秒,一轮 500 条可能耗时约 500 秒。排查时应测量慢调用,减小本轮处理量或改进处理机制,再判断超时配置,而不是只把所有超时调大。
新 consumer 协议的心跳间隔与会话超时由对应 Broker 参数管理,不能直接照搬 classic 的配置。具体规则见消费者配置。
25. KafkaConsumer 线程安全吗?能把消息交给线程池吗?
简短回答:
KafkaConsumer不能由多个线程同时操作,wakeup()是用于跨线程通知的例外。可以让消费线程负责客户端、工作线程处理业务,但必须维护每个分区的连续完成进度,并处理分区撤销与旧任务停止。
追问要点:
1 | P0:100 成功,101 失败,102 成功 |
线程池还可能打乱同分区业务执行顺序。严格有序时可以按分区或业务 Key 串行执行,并限制任务队列、施加背压。线程模型与关闭方式见官方消费者 API。
五、顺序性与工程排查
26. Kafka 如何保证消息有序?
简短回答:
Kafka 提供分区内日志顺序,跨分区没有统一全局顺序。业务有序需要生产端按正确顺序发送,把同一业务实体路由到同一分区,控制发送重试的顺序,并让消费端按相应顺序完成业务。
追问要点:
orderId可以用作订单事件的 Key,但不能修复源头已颠倒的发送顺序。- 开启幂等且满足约束,可保持对应重试场景下的分区顺序;不必一律把
max.in.flight改为 1。 - 线程池、独立重试 Topic、扩分区都可能改变业务执行顺序。
- 单分区能给日志一个共同顺序,但不能自动定义多个生产者之间的业务先后关系,还会限制并行。
27. 出现消息积压,你会怎么排查?
简短回答:
先按分区看 Lag、消费速率和业务处理耗时,判断是总能力不足、Key 热点、下游变慢,还是异常与 Rebalance 让进度停住,再针对原因优化。只有增加消费者也能增加有效并行时,扩实例才有帮助。
追问要点:
| 现象 | 优先检查 |
|---|---|
| 所有分区一起落后 | 总消费能力、下游吞吐、资源限制 |
| 单个分区严重落后 | 热点 Key、异常事件、局部慢调用 |
| 进度反复停住 | 处理和提交失败、频繁 Rebalance |
| Lag 较低但业务慢 | 内存任务队列、下游排队、提交过早 |
Lag 通常是日志末尾位置与提交位置的差值,并不总等于业务消息的精确条数。追赶时间可粗略估为“积压量 /(消费速率 - 新增速率)”,要求两种速率可比且消费快于新增。监控指标可参考官方监控文档。
28. 一条消息一直处理失败,怎么重试?怎么处理死信?
简短回答:
先区分短暂故障与无法自动恢复的错误。短暂故障做有界重试;无法自动恢复的事件记录上下文并转入错误处理链路。无论重试还是隔离,都不能让输入进度提前越过尚未妥善处理的记录。
追问要点:
- Kafka 的普通消费者 API 不会根据应用异常自动完成整套业务重试与死信处理,需要应用或框架实现。
- 错误 Topic 应保留 eventId、原 Topic、Partition、Offset 和失败原因。
- 写入错误 Topic 与提交输入进度也有双写窗口,可用 Kafka 事务结合,或设计确认、去重与补偿。
- 严格顺序业务不能直接把失败事件移走、让后续事件继续,而忽略状态依赖。
29. 分区数怎么定?扩分区有什么影响?
简短回答:
根据目标吞吐、实测单分区处理能力、消费实例需求和集群开销确定,再预留增长空间。分区越多不一定越快;扩分区会增加并行机会,也可能改变 Key 映射,影响严格顺序和热点分布。
追问要点:
- 用真实消息大小、ACK、压缩与业务逻辑压测,不能引用一个固定“每分区吞吐”套所有系统。
- 多分区会增加文件、内存、复制和协调成本。
- 扩分区不会自动把已有历史记录均匀搬到新分区。
- 原地增加分区通常可以做,直接减少已有 Topic 分区数不支持;需要新 Topic 与迁移设计。Topic 操作说明介绍了分区调整。
30. 面试官让你介绍项目里怎么使用 Kafka,如何组织回答?
简短回答框架:
先介绍业务触发与事件,再说明 Topic、Key、消费组的设计;接着解释为什么异步,生产确认、消费提交与幂等如何配合;最后讲实际验证过的故障、监控指标和改进结果。
以订单事件设计为例:
1 | 业务:订单创建后,通知与统计需要独立处理。 |
这是设计示例。学习阶段可以表述为“我做过本地实验”或“如果设计这个业务,我会……”,并具体讲清已验证的部分。吞吐量、节点规模、故障案例与效果数字,都应来自自己的实际记录。
六、三个故障场景追问
场景一:三个副本,只剩 Leader,为什么写不进去了?
已知:副本数 3,acks=all,min.insync.replicas=2,当前 ISR 只有 Leader。
回答顺序:
- 当前同步副本数量不足,不能满足成功写入的最低要求。
- 配置在保护冗余要求,生产者需要处理失败并保留事件。
- 先排查副本失效或落后的原因,不应直接把最低 ISR 改为 1 来消除报错。
- 若业务选择降低门槛,需要明确接受的故障风险,并有恢复与对账措施。
这个问题考的是配置背后的取舍,不只是参数含义。
场景二:数据库已经扣库存,提交 Offset 前崩溃了,会发生什么?
**回答:**恢复后可能从旧提交位置再次消费。如果扣库存与去重记录在同一个事务中已经成功提交,重复事件会被指定唯一约束识别,不再重复扣减;然后再推进消费进度。
继续追问:“如果先写去重标记,再开另一个事务扣库存呢?”
**回答:**中间崩溃会留下“已处理”标记,但库存实际没扣,重试又被标记挡住。因此去重记录与业务结果必须共享成功或回滚的边界。
场景三:Offset 100 成功、101 失败、102 成功,应该提交多少?
在普通连续日志的这个例子中,最多推进到 101,表示 100 已完成,恢复时仍需要尝试 101。
不能提交 103,因为那会覆盖失败的 101。102 虽然已完成,也可能在恢复后重复,因此还需要幂等。
继续追问:“那是不是提交最大的成功 Offset 加一就行?”
**回答:**不行,要维护该分区已完成处理的连续边界,不能跨过失败或未完成的记录。这里的连续指处理顺序上的完整前缀,不要求实际可见 Offset 数字永远没有间隙。
七、面试前快速复习
7.1 六个容易答错的结论
| 容易答错的说法 | 更准确的表达 |
|---|---|
acks=all 保证任何情况都不丢 |
要结合副本、ISR、应用恢复和故障假设 |
| 生产者幂等等于业务幂等 | 它处理特定协议重试,不替代业务去重 |
earliest 每次都从 0 开始 |
没有有效位置时,从当前保留的最早位置开始 |
| 消息进入线程池就可以提交 | 应按业务完成的分区边界推进 |
| 相同 Key 就能保证业务有序 | 还要保证路由稳定、源头顺序和执行顺序 |
| Kafka 事务会一起提交 MySQL | 原子范围需要明确,外部副作用另行设计 |
7.2 用这几个参数串起知识点
| 参数 | 面试时应该连到哪个问题 |
|---|---|
acks |
生产者等待什么确认 |
min.insync.replicas |
同步副本不足时是否继续确认写入 |
enable.idempotence |
协议级重试如何避免重复写入 |
delivery.timeout.ms |
一次发送的整体交付时间与结果不确定性 |
group.id |
哪些消费者共享分工与进度 |
enable.auto.commit |
提交时机能否对齐业务完成 |
auto.offset.reset |
没有有效位置时如何恢复 |
max.poll.records |
单次返回给应用的记录数量 |
max.poll.interval.ms |
处理过慢与轮询间隔 |
group.protocol |
classic 与新 consumer 协议的区别 |
isolation.level |
是否只读取已提交事务记录 |
7.3 60 秒整体回答
Kafka 可以理解为一个分布式事件日志平台。Topic 通过分区实现并行,分区通过副本提供冗余,消费组独立管理分工与进度。可靠性需要沿生产、存储和消费三段设计:生产端观察发送结果,存储端结合 ACK 与 ISR,消费端在业务成功后提交进度,并用业务幂等处理重放。顺序性主要是分区级保证,还依赖 Key、发送顺序和业务执行方式。Kafka 到 Kafka 的处理可以用事务结合输出和输入进度,数据库双写则需要 Outbox 等方案。排查积压时,要看分区热点、处理耗时、提交失败和 Rebalance,而不是只增加消费者。
复习时可以围绕一个问题继续练习:如果现在这个位置崩溃,哪些数据已经保存,哪些进度还没提交,恢复后会发生什么? 能把这个故障窗口讲清楚,参数与机制就容易连起来。