Kafka 面试题(2026-06-18)
【面试题】1. 生产者拦截器(ProducerInterceptor)能干哪些事?它和序列化器、分区器在发送链路里的执行顺序是什么?
【标准答案】拦截器是生产者发送链路里最灵活的切面,能做三件事:①消息发送前——在 onSend() 里修改消息内容(比如自动注入 traceId、时间戳、数据脱敏),甚至直接拦截不让发;②Broker 确认后——在 onAcknowledgement() 里拿到发送结果做自定义埋点、告警、失败记日志;③优雅关闭——close() 里释放拦截器自己申请的资源。执行顺序:拦截器链(可多个,按配置顺序依次执行)→ 序列化器(Key + Value 分别序列化)→ 分区器(决定发哪个分区)→ 攒批发送。这意味着拦截器拿到的还是未序列化的原始对象,可以做字段级操作。常见的落地场景:全链路追踪——在 onSend() 里从 ThreadLocal 取 traceId 塞进 Header,下游消费者不用改业务代码就能拿到链路信息。注意:如果拦截器链里某个抛异常,后续拦截器全部跳过、消息可能丢失——关键逻辑必须 try-catch 包裹。
【面试题】2. Kafka 内部大量用到"时间轮(TimingWheel)"来处理延迟任务——它比传统 DelayQueue 好在哪里?Kafka 用时间轮做了哪些事?
【标准答案】传统 DelayQueue 基于优先队列(堆),插入 O(logN),每次查找最早到期任务 O(1),但在高并发下堆的调整开销是瓶颈——Kafka 动辄几万个延迟任务同时挂着(如所有等待 ISR 确认的 produce 请求),堆操作会把 CPU 吃满。时间轮方案:把一个时间周期切成 N 个槽(比如 1 秒切 10 个槽,每个槽 100ms),任务按到期时间直接散列到对应槽位上 O(1),时钟指针每走一格,把当前槽里的所有任务取出来执行。Kafka 里有三个经典应用:①生产者的 ack 等待——发消息后挂一个延迟任务等 ISR 确认,超时没确认就回调异常;②消费者 fetch 的延迟等待——消费者带了 fetch.min.bytes,Broker 挂一个延迟任务等攒够数据或者超时再响应;③消费者加入消费组的延迟——组成员变化时不是立刻 Rebalance,而是等几秒让窗口期内的多个变化合并成一次 Rebalance。时间轮的代价是内存(预分配槽位),相比堆换来了 O(1) 插入和极低的 CPU 开销——这种设计选择面试说出来能直接体现你的架构洞察力。
【面试题】3. 消费者端多线程消费有几种模型?各有什么坑?生产推荐哪种?
【标准答案】Kafka 消费者是单线程设计的,KafkaConsumer 不是线程安全的,所以多线程必须自行设计。三种主流模型:①一 Consumer 多处理线程(Worker Pool):主线程 poll() 拉消息后扔到线程池并行处理,位移提交仍在主线程做。优点是拉取高效,缺点是消息处理完未必按顺序提交位移——如果第 100 条处理慢、第 101 条先处理完提了 offset=102,第 100 条处理失败没法回滚。所以必须"分区内部单线程处理 + 按分区粒度提交位移"。②多 Consumer 实例:每个线程持有独立的 KafkaConsumer 实例,相当于一个消费组里起了多个消费者。优点是利用了 Kafka 自带的分区分配,每个分区被一个线程串行消费,顺序天然保证;缺点是线程数受分区数限制——20 个分区最多 20 个消费者线程,多出来的 idle。③混合模型(推荐):线程数 = 分区数,每个线程一个 Consumer 实例,各线程独立 poll、独立处理、独立提交位移。Kafka 帮你做了分区分配和故障转移,你只管单分区串行消费。如果要进一步加速处理,在单线程内部用本地队列 + 异步写 DB 做削峰,不用跨分区线程池。
【面试题】4. Kafka 日志清理有 delete 和 compact 两种策略,分别怎么工作?什么场景下选 compact 比选 delete 更合理?
【标准答案】delete 是按时间或大小删。规则分两条:一是时间维度——消息在 log.retention.hours(默认 168 小时即 7 天)后删除;二是大小维度——分区日志总大小超过 log.retention.bytes 后从最旧 Segment 开始删。两条规则 whichever comes first。delete 适合日志、埋点、监控数据——只看最新的,老的没价值。compact 是按 Key 缩容:后台 Cleaner 线程异步扫描旧 Segment,对于同一个 Key 只保留最新的那条消息,删掉旧值。compact 适合"当前态"数据——比如用户资料、账户余额、配置快照,你不关心历史变化过程,只关心现在是什么。compact 不会主动删数据——如果 Key 只有一条且一直没新值,永远保留。delete 和 compact 可以同时开(delete+compact 策略):compact 让每个 Key 只留最新值,delete 再把超过保留时间的最后值也删掉。场景判断:日志类→delete;状态快照、CDC 数据、配置下发→compact;两类都要→compact+delete,但注意 compact 是异步的,Consumer 在清理完成前可能读到旧值。
【面试题】5. 线上 Kafka 集群要搭一套完整的监控体系,核心指标有哪些?告警阈值怎么设?
【标准答案】监控分三层。第一层——集群健康:Broker 存活(Up/Down)、UnderReplicated 分区数(必须 = 0,一旦 > 0 立刻告警)、ActiveController 数量(必须 = 1,多或没都严重故障)、OfflinePartitions(必须 = 0)。第二层——流量与性能:BytesIn/BytesOut 带宽、MessagesIn 生产 QPS、RequestQueueSize(请求排队数,> 500 说明 Handler 处理不过来了)、RequestHandlerAvgIdlePercent(Handler 空闲率,< 0.2 说明 IO 线程吃紧)。第三层——业务维度:消费 Lag(核心!Lag 持续上涨且无下降趋势→告警,阈值为峰值的 2 倍)、生产 Error Rate(> 0.01% 告警)、消费组 Rebalance 频率(> 2 次/小时需关注)。告警阈值建议:UnderReplicated > 0 → P0 立刻告警;消费 Lag > 预计处理量 × 5 分钟 → P1;生产失败率 > 0.1% → P2;Broker CPU > 80% 持续 5 分钟 → P2。用 Prometheus + Grafana(JMX Exporter 暴露指标)+ Kafka Lag Exporter 监控延迟,整体方案落地成本低、社区成熟。面试时说出"UnderReplicated 必须等于 0",面试官就知道你真正盯着过集群面板。
【面试题】6. 消费者拉消息时 Broker 是怎么做流量控制的?除了 Quota 之外,消费者自己能做反压吗?
【标准答案】Broker 侧反压分两层。第一层——全局 Quota:按 client.id 或 user 设 produce_byte_rate / fetch_byte_rate,超配额后 Broker 对请求返回 throttle 响应,客户端自动降速。第二层——连接层反压:Kafka 的 Socket Server 有请求队列大小限制(queued.max.requests,默认 500),队列满了新请求直接拒。消费者自己也能做反压:①调小 max.poll.records,每次少拉点,给下游处理留缓冲;②调大 fetch.min.bytes 和 fetch.max.wait.ms,让 Broker 攒够数据再给,减少空轮询但提升每条消息的处理质量;③pause() 暂停特定分区拉取,处理完再 resume()——这是最灵活的单分区级反压。注意 pause() 不会通知 Broker,完全客户端侧行为,不影响其他消费者。反压链条全景:下游处理变慢 → 消费者拉少了 → 积压未确认位移 → 如果开了 backpressure,消费者主动 pause() 或减 max.poll.records → 生产端感受不到,因为 Kafka 是存算不分离的——消息已经落盘了,消费者自己的事。这与 RabbitMQ 不同——RabbitMQ 是推模型,消费者处理慢会溢出到 Broker,可能触发 Flow Control 整条链路阻塞。
【面试题】7. Kafka Broker 的 JVM 堆内存和操作系统页缓存(Page Cache)要怎么分配?堆设大了反而性能差是为什么?
【标准答案】Kafka 的性能核心在 Page Cache(页缓存),不在 JVM 堆——消息直接通过 OS 的页缓存读写,JVM 只负责网络连接和请求调度。所以经典的分配策略是:堆内存给 6GB~8GB(G1GC),剩下的物理内存全部留给 OS 做页缓存。比如一台 64GB 机器,堆 8GB,剩下的 56GB 给页缓存——这部分不需要配置,OS 自己会用。堆设太大(比如堆占 32GB)的副作用:一、压缩了页缓存空间,热数据缓存不住导致磁盘频繁读取,消息消费延迟飙升;二、大堆的 GC(Full GC 停顿长,几十秒)可能让 Broker 的 ZK 心跳超时,触发没必要的 Leader 切换和 ISR 踢出;三、大堆增加内存碎片化和 GC Roots 扫描时间。所以 Kafka 堆内存原则是"够用就行,少比多好"。G1GC 关键设:MaxGCPauseMillis=20ms(控制 GC 停顿上限)、InitiatingHeapOccupancyPercent=35(提前触发并发标记,避免 Full GC)。堆外内存也重要——Kafka 用 DirectByteBuffer 做网络传输,socket.send.buffer.bytes 和 socket.receive.buffer.bytes 产生的直接内存不受堆限制,需要留意总内存。面试说出来"堆 8G、PageCache 吃剩下的",面试官直接递 Offer。
【面试题】8. Kafka 集群的高可用 RTO(恢复时间)和 RPO(数据丢失量)分别是多少?怎么量化这两个指标?
【标准答案】RTO = 从一台 Broker 宕机到该 Broker 上所有 Leader 分区都完成切换且消费者恢复消费的时间。主要由三块组成:①ZK Session 超时(zookeeper.session.timeout.ms,默认 18s)→ Controller 感知 Broker 下线;②Controller 遍历所有受影响分区执行 Leader 选举(毫秒级,但分区多了会拖长);③Controller 通过 LeaderAndIsr 请求通知各 Broker + 客户端刷新元数据(metadata refresh + 消费者 Rebalance,一般 30s 内)。整体 RTO 通常在 30s~60s,分区数 10 万+ 可能拖到 2 分钟。KRaft 模式下①这一步变成 Raft 心跳超时(秒级),RTO 可降到 10s 内。RPO = 宕机瞬间丢失的消息量。与 3 个配置挂钩:①acks=all + min.insync.replicas=2——丢 0;②acks=1——丢刚好写进旧 Leader 但没同步到 Follower 的数据,丢失窗口 = 副本同步延迟(通常 < 100ms 的数据量);③unclean.leader.election.enable=true——可能丢一个落后副本全部缺失的数据。量化 RPO:生产 QPS × 同步延迟秒数。比如 10 万条/秒、同步延迟 50ms,acks=1 的情况下 RPO ≈ 5000 条。面试时把 RTO/RPO 拆成参数级可量化数据,面试官会觉得你工程思维扎实。
【面试题】9. 一个线上跑了 3 年的 Kafka 集群要从 2.4 升级到 3.5,你怎么设计灰度升级方案?有哪些步骤绝对不能跳?
【标准答案】跨大版本升级不能一步到位,必须阶梯式升级。分五步:第一步——调研兼容性:读 Kafka 官方 Upgrade Guide,确认每次升级的 metadata.version、消息格式版本、协议版本的兼容范围。关键规则:Broker 版本 ≥ 消费者版本 ≥ 生产者版本(先升级 Broker)。第二步——灰度升级路线:2.4 → 2.8(修改消息格式到 2.8)→ 3.0(引入 KRaft 兼容模式但不切)→ 3.3(生产就绪)→ 3.5。每步间隔至少一周确认稳定。第三步——每步升级操作:①改 server.properties(inter.broker.protocol.version 和 log.message.format.version)②滚动重启 Broker(逐台执行 controlled.shutdown,等 ISR 恢复再下一台)③验证集群稳定性(UnderReplicated=0、生产/消费正常、无异常日志)④把上述版本号升到当前版本后继续下一步。绝对不能跳的步骤:一、永远先升 inter.broker.protocol.version 再升 log.message.format.version——先让 Broker 间通信用新协议,再改消息格式,反过来会新格式消息发给还不支持的老 Broker;二、不能跳过任何大版本——2.4 直接升 3.5 会因为缺失中间版本的格式转换逻辑导致消息不可读;三、必须逐台滚动,别同时停多台——ISR 里少了太多副本可能导致 min.insync.replicas 不满足而拒绝写入。第四步——切换 KRaft(可选):如果你要从 ZK 切 KRaft,这是另一场大活,需要建新集群 + MirrorMaker 2 迁数据,不能就地升级。第五步——全量切完后观察一周,把 log.message.format.version 升到最新释放性能优化。
【面试题】10. Kafka Connect 是什么?它解决了什么痛点?Source Connector 和 Sink Connector 分别举个生产场景。
【标准答案】Kafka Connect 是 Kafka 官方出的"数据搬运工"——专门解决"各种数据源→Kafka"和"Kafka→各种目标"的导入导出问题。以前你得自己写一堆生产者把 MySQL 的变更数据写进 Kafka,再写一堆消费者把 Kafka 数据写到 ES、HDFS、S3——代码重复、容错差、位移管理靠手撸。Kafka Connect 用插件化 Connector + 标准化的位移管理搞定了这些:你只要写一个 JSON 配置文件声明数据源和目标,Connect 框架自动帮你做并行、容错、位移追踪、重启恢复。Source Connector 例子:MySQL CDC Connector(Debezium)监听 MySQL binlog,把 INSERT/UPDATE/DELETE 变更实时写入 Kafka Topic——数据团队消费这个 Topic 就能做实时数仓。Sink Connector 例子:Elasticsearch Sink Connector 把 Kafka 的日志数据实时写入 ES 建索引——运维配个 JSON 就搞定,不用写消费者代码。Kafka Connect 的核心价值:让数据流转变成"配置",而不是"写代码",降低数据管道的开发和维护成本。注意:Connect 本身是分布式框架(Worker 集群),单个 Worker 挂掉任务自动漂移到其他 Worker——Failover 能力开箱即用。选型建议:数据导入导出类需求优先看有没有现成的 Connect 插件,别上来就撸代码。
生成时间:2026-06-18 23:40 覆盖考点:生产者拦截器机制、时间轮与延迟任务、消费者多线程模型、日志清理策略delete vs compact、Kafka监控指标体系、消费者反压与流控全景、Broker堆内存与页缓存分配、RTO/RPO高可用量化分析、集群跨大版本灰度升级方案、Kafka Connect数据管道生态