Kafka 面试题(2026-06-18)
随机考点覆盖 | 难度递进 | 口语化答案
【面试题】1. Kafka 消费者启动时找不到位移, auto.offset.reset 的 earliest、latest、none 各在什么场景下用?
【标准答案】earliest 是跳到分区现存最早的消息从头消费,适合新业务第一次上线、数据回溯、或者因为 Bug 需要全量重算的场景。latest 是只消费启动后新产生的消息,不管你之前的,这是默认值,日常消费就这么用。none 最严格——找不到位移直接抛异常,打死不自动决定从哪开始,适合那种"我百分百确定这个消费组有位移记录"的强一致性场景,丢了说明出事故了必须人工介入排查。现实里大部分业务用 latest 就行,离线数仓跑全量表用 earliest,金融风控这种丢不起数据的用 none 加告警。另外注意:offsets.retention.minutes 默认 7 天,消费组超过 7 天没活跃位移就过期了,这时候这个参数才真正起作用。
【面试题】2. __consumer_offsets 这个内部 Topic 存的是什么?它怎么和 GroupCoordinator 协作?
【标准答案】__consumer_offsets 是 Kafka 用来持久化消费组位移的系统 Topic。里面存的就是消费者组的每个分区消费到哪了——Key 是 group.id + topic + partition 组合,Value 是位移值和元数据。它默认 50 个分区,由 offsets.topic.num.partitions 控制。协作流程:消费者提交位移不是直接写这个 Topic,而是发给它对应的 GroupCoordinator(通过 group.id 哈希到 __consumer_offsets 某个分区所在的 Broker),Coordinator 再以生产者身份写入。读取也一样,消费者启动时向 Coordinator 发起位移请求,Coordinator 从 __consumer_offsets 读出来返回。消息清理策略是 Compact,同一个 Key 只保留最新值,所以不会无限膨胀。面试官追问问为什么是 50 个分区——因为 Coordinator 的并发提交能力受这个分区数限制,一个分区同时只能处理一个消费组的位移写入。
【面试题】3. Kafka 生产者的 retries 和 delivery.timeout.ms 怎么配合?重试会导致消息乱序吗,怎么解决?
【标准答案】retries 是重试次数上限,delivery.timeout.ms 是从 send() 到 Kafka 返回 ack 的总时限,包含所有重试时间。两者是"与"的关系——谁先到听谁的。比如 delivery.timeout.ms=120s,retries=10,如果 2 次重试就超 120 秒了,照样报超时失败。重试最大的坑是乱序:假设 batch0 发出去后网络抖动暂时失败,broker 还没来得及返回,batch1 先发出去了且成功了,这时候 batch0 重试成功——它到了 batch1 后面,消息顺序就反了。解决方式:开启 enable.idempotence=true,Kafka 给每个 Producer 分配 PID,每条消息带单调递增的 Sequence Number,Broker 发现序号跳跃就直接拒绝,保证即使重试写入也是有序的。配合 max.in.flight.requests.per.connection 设 ≤5(幂等开启后最大允许值)。
【面试题】4. 一个消费组内 3 个消费者挂了 1 个,从 GroupCoordinator 检测到分区重新分配完成,完整 Rebalance 经历了哪些步骤?
【标准答案】第一步:挂掉的消费者停止发心跳,Coordinator 在 session.timeout.ms(默认 45s)后判定它挂了。第二步:Coordinator 把组状态从 Stable 切到 PreparingRebalance,给剩余 2 个消费者发 JoinGroup 请求。第三步:2 个消费者收到后各自重新 JoinGroup,Coordinator 选一个做 Group Leader,把当前成员列表和订阅 Topic 信息发给 Leader。第四步:Leader 收到元数据后用配置的分区分配策略(Range/RoundRobin/Sticky)算出新方案,通过 SyncGroup 请求发给 Coordinator。第五步:Coordinator 把分配结果下发给每个消费者,组状态切回 Stable,各消费者按新分配开始消费。整个过程的耗时 = 心跳超时 45s + JoinGroup/SyncGroup 网络来回,大概 46-47 秒。优化点:session.timeout.ms 别太短(避免误判),max.poll.interval.ms 要给够(避免处理慢触发 Rebalance)。
【面试题】5. Kafka Broker 的 num.network.threads 和 num.io.threads 分别干什么?生产环境怎么配?
【标准答案】num.network.threads 负责网络层——从 Socket 读取请求、放入 RequestQueue、把响应写回 Socket,不做任何业务处理。num.io.threads 从 RequestQueue 拿请求去真正干活:写日志、查位移、拉消息、处理元数据请求,全都是它。类比一下:网络线程是餐厅前台,收单递单;IO 线程是后厨,真正炒菜。配置经验:网络线程跟 CPU 核数对齐就行,通常 8 核就配 8;IO 线程要配多一点,因为涉及磁盘 IO 会阻塞,一般是 CPU 核数 ×2,比如 8 核配 16。怎么判断该不该调?看 Kafka 的 requestHandlerAvgIdlePercent 指标——如果降到 30% 以下说明 IO 线程忙不过来,该加了。但如果 CPU 已经 80%+ 了,加线程也没用,得扩节点。
【面试题】6. Kafka 的 Follower 拉取数据很慢或者卡住了,对 Leader 和消费者有什么影响?
【标准答案】Follower 拉取慢直接影响 Leader 的 HW(高水位)推进——HW 取决于 ISR 里所有副本 LEO 的最小值,有一个拖后腿,HW 就卡在那不动。但生产者不一定受影响:如果 acks=1 或者 acks=all 但 min.insync.replicas 要求的副本数已经满足了,生产者照样能写成功。真正受伤的是消费者——消费者只能消费 HW 之前的消息,HW 不推进消费者就看不到新数据,Lag 会不断涨。解决办法靠 replica.lag.time.max.ms(默认 30s),超过这个时间没跟上,慢 Follower 被踢出 ISR,HW 立刻推进到剩下副本的最小 LEO。如果 acks=all 且 min.insync.replicas 设得高,踢掉一个后可能 ISR 数量不满足要求,生产就报 NOT_ENOUGH_REPLICAS,这时候要权衡保可用还是保一致性。
【面试题】7. Kafka Pull 模型和 RocketMQ Push 模型(长轮询)本质区别在哪?为什么 Kafka 坚持 Pull?
【标准答案】Pull 是消费者主动去 Broker 拉,Push 是 Broker 推给消费者——但 RocketMQ 的 Push 不是真的推,底层也是长轮询:消费者发请求,Broker 有消息就返回,没消息就 hold 住等。核心区别在于消费速率谁说了算:Pull 模型消费者自己掌握节奏,处理快就多拉,处理慢就歇着,天然支持背压(backpressure)。Push 模型如果 Broker 推太快消费者处理不过来,消息在消费者端堆着容易 OOM,所以 Push 框架都得做流控。Kafka 坚持 Pull 的原因很简单:只有消费者最清楚自己的处理能力,让消费者自主拉取比让 Broker 猜测消费速率靠谱得多。Pull 的缺点——频繁空轮询浪费资源——Kafka 用 fetch.min.bytes + fetch.max.wait.ms 解决:没攒够数据或时间不到就不返回,避免空转。
【面试题】8. Kafka 集群跨 3 个可用区部署,每个 AZ 3 个节点,replication.factor=3,min.insync.replicas=2,一整 个 AZ 整体断电了,集群还能正常读写吗?
【标准答案】能读,写的情况要分场景。能读是因为每个分区 3 副本均匀分布在 3 个 AZ,挂了 1 个 AZ 后还有 2 个副本活着,消费者可以正常消费。写的情况:如果 acks=1,Leader 大概率不在挂掉的 AZ(概率 2/3),能正常写——Leader 本地写成功就返回。如果 acks=all,只要剩余 2 个副本都在 ISR 里且 min.insync.replicas=2 满足条件,就能写。但如果 Leader 恰好在该 AZ,需要触发 Leader 选举,短暂不可写约几秒后恢复。最危险的情况:刚挂了 1 个 AZ,剩下 2 个 AZ 又挂 1 个,ISR 只剩 1 个不满足 min.insync.replicas=2,直接报 NOT_ENOUGH_REPLICAS 拒绝写入。生产建议:跨 AZ 场景 min.insync.replicas 通常设 replication.factor-1,acls=all 配合使用。
【面试题】9. 线上消费端偶发报错 "OffsetCommit with generation x failed because the consumer group has rebalanced",怎么排查和根治?
【标准答案】这个错误的本质:消费者尝试提交位移时,它持有的 generation(代际版本号)已经作废了——说明在两次 poll 之间触发了 Rebalance,消费者被踢出组后又重新加进来,有了新 generation,旧 generation 的提交请求就被 Coordinator 拒了。排查三步走:第一步看 max.poll.interval.ms(默认 5 分钟),如果单次 poll 回来的一批消息处理超过这个时间,消费者没发下一次 poll,Coordinator 认为它挂了触发 Rebalance。解决:要么调大这个参数,要么减小 max.poll.records 降低单批消息量。第二步看 session.timeout.ms,设太短网络一抖动就心跳超时。第三步检查消费逻辑——如果在异步提交位移的同时做 Rebalance,最好在 ConsumerRebalanceListener 的 onPartitionsRevoked() 里做最后一次同步提交,把位移落盘再让出分区。
【面试题】10. 一条完整的 Kafka + Flink 实时数仓链路,要求端到端 Exactly-Once,你怎么设计?有哪些容易踩的坑?
【标准答案】整条链路分三段看。第一段生产者→Kafka:开 enable.idempotence=true + acks=all + 配置唯一的 transactional.id,用事务 API 包裹发送逻辑,多分区原子提交。第二段 Kafka 内部:日志复制机制原生保证副本一致性,不需要额外操作。第三段 Flink 消费 Kafka 写入下游(如 ClickHouse/Hive):Flink 开启 checkpoint,设置 Kafka 消费者 isolation.level=read_committed(只读已提交事务消息,过滤掉中间状态)。Flink 通过 checkpoint 拍快照记录各分区的位移和算子状态,恢复时从快照重放,写入下游用 TwoPhaseCommitSink 保证 Sink 不重复。最容易踩的坑:transaction.timeout.ms 必须大于 Flink checkpoint 间隔,否则事务在 checkpoint 中间过期了,数据就丢了。还有 transactional.id 必须全局唯一,多个实例用同一个会导致脑裂互相踢,这个线上出过不少事故。