1. 开场先聊聊:Kafka为什么是Java面试的“必考题”
做Java后端到现在,前后也面了不少候选人,也在跳槽季被面过很多轮。Kafka这个知识点出现的频率,高得让我一度怀疑面试官是不是人手一份同样的题库。其实也不奇怪,Kafka在大厂后端几乎是标配的消息中间件,日志采集、用户行为追踪、系统解耦、削峰填谷、流式计算,哪哪都有它。问Kafka,不光是考察你“会不会用”,更是在检验你对分布式系统的理解深度。
这篇先写基础篇,也就是你准备一、二轮面试时需要烂熟于心的那一层。我会把高频的面试题串起来讲,每题尽量说清“是什么、为什么、坑在哪”,而不是甩给你一段干巴巴的背诵答案。适合正准备跳槽的Java工程师,也适合刚接触消息队列、想系统梳理Kafka核心概念的开发者。读完你会对一些隐藏细节有更清晰的感觉,比如为什么acks=all也会丢消息、为什么分区数不等于消费并发上限、为什么消费者多了反而可能引发频繁重平衡。
2. 先搞清定位:Kafka到底强在哪
2.1 面试官为什么爱问Kafka
面试官爱问Kafka,很大程度上是因为它身上浓缩了分布式系统的一堆核心问题:网络通信、存储、一致性、高可用、顺序性、可靠性。一个Kafka问题能串起操作系统、网络协议、分布式理论,性价比极高。
理解这一点,你就知道面试准备的重点不该是“背用法”,而是抓设计思想。举例来说,Kafka为什么快?不是因为它用了什么黑魔法,而是顺序写磁盘、页缓存、零拷贝、批量处理这一整套组合拳。这些知识点放在操作系统课上都能找到出处。面试官真正想看到的,是你能否把底层原理和上层行为对应起来。
另一个原因是Kafka的定位和普通消息队列不太一样。很多人背过“Kafka适合大数据量的场景”,但说不清为什么。Kafka天然为日志型数据设计,追求的是吞吐优先而不是功能丰富,所以它早期的很多行为在RabbitMQ里会觉得很奇怪:消费后不删除消息、依赖消费者维护消费进度、允许重复消费。这些特征都跟它“数据管道”的身份有关。
2.2 和RabbitMQ、RocketMQ的横向对比
既然定位不同,面试时很可能让你横向对比主流消息队列。我建议你抓住“Kafka强在吞吐、弱在功能;RabbitMQ强在功能、弱在吞吐;RocketMQ在两者之间”这条主线。
| 维度 | Kafka | RabbitMQ | RocketMQ |
|---|---|---|---|
| 吞吐量 | 极高(百万级每秒常见) | 中低(万级到十万级) | 高(十万到百万级) |
| 消息模型 | 基于Topic/Partition,分区内有序 | 基于Exchange/Queue | 基于Topic/Queue |
| 消费模式 | 消费组,分区被组内成员分担 | 队列竞争 + 发布订阅 | 消费组,类似Kafka |
| 可靠性 | 强,配合ISR机制 | 强,支持多种确认机制 | 强,支持事务消息 |
| 典型场景 | 日志、大数据管道、流处理 | 业务解耦、定时任务、RPC | 交易消息、金融级场景 |
这里有个常见的坑:面试官问“你们为什么选Kafka”,如果你只会说“因为快”,那基本等于白答。比较好的回答思路是从场景出发:我们当时要处理的是海量埋点日志,峰值写入量很高,而且对顺序性有要求,Kafka的分区机制天然支持分区内有序;同时下游是多个消费者,需要利用消费组做扩展。RabbitMQ在这个量级下吞吐会出现瓶颈,而且消息积压时的处理能力不如Kafka灵活。这么一说,面试官会觉得你是带着业务理解在选型,而不是在背特性。
3. 核心架构:一条消息从生产到消费的完整旅程
3.1 Topic、Partition、Offset,先把这个三角关系刻在脑子里
Kafka的基础模型一句话就能说完:生产者把消息写到Topic,Topic被拆成多个Partition,消息在Partition内追加写入并带一个递增的Offset,消费者从指定Offset开始拉取。
Partition是整个Kafka并行能力的最小单位。说句人话,Kafka的并行度上限就是分区数。生产者写消息时会按一定规则选择目标分区,每个分区只能被消费者组里的一个消费者实例负责(同一个分区不能被组内两个消费者同时消费),这是Kafka实现“动态负载均衡”的根基。
有几个面试小题经常连环问:
- 同一个Partition里的消息有序吗?答:有序,因为生产者写入时是追加日志,消费者拉取时按offset顺序读。
- 不同Partition之间有顺序性吗?答:没有,全局顺序是Kafka不承诺的,想要严格全局顺序只能用一个分区,代价是吞吐骤降。
- 分区数能随意增加吗?答:可以增加,但要注意,新增分区后旧消息仍留在原分区,负载不会自动平衡到新分区,而且要预先考虑分区数量。一味的加分区在客户端内存和文件句柄上都有开销,此外也会加大重平衡的成本。
这些点,面试时最好主动说出来,显得你真的用过而不是只会背概念。
3.2 副本机制与Leader选举
Partition在物理上会有多个副本,分布在不同的Broker上,目的是保证某一个Broker挂掉时消息不丢。副本之间是一主多从的关系,所有读写都走Leader,Follower只负责同步数据。平时说的“副本数”通常包括Leader本身,比如副本数3,就是1个Leader加2个Follower。
这里需要理解两个核心概念:
- ISR(In-Sync Replicas):跟Leader保持同步的副本集合。只有ISR里的副本才有资格在Leader挂掉后选举为新Leader。
- AR(Assigned Replicas):分区所有的副本集合,ISR是AR的子集。
为什么要有ISR?因为不是所有Follower都一直跟得上的。如果某个Follower网络抖动或处理太慢,长时间落后于Leader,把它留在候选池里是很危险的——它可能成为新Leader后丢失消息。所以Kafka用两个参数控制ISR的收缩与扩张:replica.lag.time.max.ms,默认30秒,单位毫秒,表示Follower超过这个时间没跟上Leader就会被T出ISR;min.insync.replicas则规定了ISR的最小数量,配合acks=all才能实现“至少N个副本确认”的可靠性。
关于min.insync.replicas,有个很隐蔽的性能陷阱。如果你把这两个参数设为副本数,比如3副本就配3,那么任何一个Follower挂掉,ISR数量变成2,此时acks=all的写入会直接失败,因为没有足够多的ISR副本可以确认。生产环境推荐的是“副本数=3时,min.insync.replicas=2”,这样既保证了多数派同步,又容忍单副本故障。很多人踩过这个坑,以为配得越严格越安全,结果一跳机写入就大面积报错。
3.3 消费位移Offset是谁维护的
Offset这块是Kafka区别于RabbitMQ的一个重点。RabbitMQ的消息消费后会被删除,由Broker维护消费进度;Kafka的消息在一段时间内会一直存在磁盘上,消费进度由消费者自己提交。
早期Kafka把Offset存在ZooKeeper里,后来改为了内部Topic(名为__consumer_offsets)。这个设计的好处是,消费者的读取进度和消息写入解耦,你可以随时重置Offset从头消费,或者回退到某个历史位置重放数据。这在数据订正、故障恢复、从离线管道追数据时极其好用。
关于提交的两种方式,面试也很爱问:
- 自动提交
enable.auto.commit=true:每隔auto.commit.interval.ms自动提交已消费的Offset。实现简单,但存在重复消费窗口——消息被处理完但还没到下个提交点,消费者挂了,重启后会从上次提交的位置重新消费。 - 手动提交
enable.auto.commit=false:处理完业务后主动调用commitSync()或commitAsync()。commitSync()有重试保障但会阻塞;commitAsync()不阻塞但有提交失败丢失位移的风险。实践中常组合用:异步提交加回调,失败时记录日志或定时重试。
这个点好回答,但我见过不少候选人会把“重复消费”简单归因于网络超时,实际上很多重复消费是你自己的手动提交没处理好。你在消费逻辑里先commit再处理业务,或者在处理完业务后异步commit失败了,都会造成重复。记住一条原则:幂等消费是必须的,事务性的“处理完再提交”在很多场景下是做不到的,所以要在消费逻辑里兼容重复。
4. 生产者可靠性:面试必问的三个“层”
4.1 分区器与粘性分区
生产者写消息时怎么选分区,是一个容易被忽略但面试容易深挖的点。规则是这样的:如果消息指定了Partition,直接用;没指定但指定了Key,对Key做哈希取模(murmur2算法)选定分区,这样可以保证相同Key的消息落在同一个分区;如果连Key都没指定,就走轮询或粘性分区策略。
所谓粘性分区(Sticky Partition),就是Kafka 2.4之后改良的默认策略。旧版轮询策略是每条消息轮流往不同分区发,这会导致每个分区的批次都很小,频繁发送。粘性分区则是在一段时间内优先填满当前分区的批次,等一批攒够或超时后再切到下一个分区,显著提升了批量发送的效率和吞吐。
4.2 acks参数:三种取值背后的取舍
acks的语义值得仔细说说,因为不少面试者把它当成“背三句话”的问题:
acks=0:生产者只管发,不管成不成。吞吐最高,但消息发送失败了你完全不知道,最极端的“发后即忘”。acks=1:Leader写入成功就返回。默认值,性能和可靠性的折中,但Leader没来得及同步给Follower就挂了,消息会丢。acks=all:等ISR里所有副本都写入成功才返回。最安全,但如果min.insync.replicas配置不合理,可用性会下降。
这里我建议你理解一个关键点:acks=all并不等于100%不丢,因为ISR缩小也可能让某些副本根本没同步过数据。所谓“可靠”是一个综合工程,不能单靠某个参数。
面试官如果问“消息端到端不丢失配置该怎么给”,你可以这样回答(也已经是可以直接抄作业的配置):
acks=all retries=3 enable.idempotence=true max.in.flight.requests.per.connection=5 delivery.timeout.ms=120000enable.idempotence=true是Kafka 3.0之后默认开启的,它会自动把max.in.flight.requests.per.connection降为5,同时把retries调为极大值。这个配置组合可以从根源上避免“发送端重试导致乱序”,也避免重复写入。
4.3 幂等性到底解决了什么问题
幂等性(Idempotence)是个容易被错误理解的概念。严格说,Kafka的幂等并不是让消息本身不重复,而是让“同一个生产者在它自己的生命周期里,重复发送同一条消息”这件事变得没有副作用。
底层的实现是:每个Producer启动时分配一个PID(Producer ID),每条消息带上一个自增的Sequence Number,Broker端按照PID+序列号去重。相同PID和序列号的消息只会被写入一次。
但要注意,幂等是有边界的。PID只在Producer单次会话内有效,如果进程重启,PID变了,旧的序列号状态也就没了;同时它只保证同一个Partition内的严格顺序去重,跨分区的幂等需要引入事务。而且生产和消费是两段独立的过程,发送端幂等只能避免生产重复,消费端的重复还得靠消费者自己处理。
4.4 事务消息:能把多分区写入做成原子的关键
如果业务需要在多个分区甚至多个Topic之间“要么都成功,要么都失败”,比如从一个Topic读数据、处理后写结果Topic,再提交消费位移,那就要用到事务。
Kafka事务依赖一个专门的组件叫Transaction Coordinator,它的作用是给Producer分配一个transactional.id(事务ID),并管理事务状态。开启事务后,生产端可以跨分区原子写入,消费端配合read_committed隔离级别,也只消费已提交的事务消息。
事务这块面试一般不会让你手写代码,但你需要能讲清三件事:
- 事务和幂等的区别:幂等是“单分区、单会话”的防重,事务是“多分区、跨会话”的原子。
transactional.id的意义:它是跨会话维持Producer状态的关键,重启后还能继续之前的事务状态。- 消费端的
isolation.level会影响你看不看得见未提交消息,read_uncommitted能看到,read_committed看不到。
5. 消费者与重平衡:最容易翻车的环节
5.1 消费组模型:为什么说Kafka“天然就是广播+单播一体”
Kafka的消费模型是“组订阅模型”:一个Topic的消息,会被复制给每一个订阅它的消费组;同一个消费组内,每条消息只会被组内一个实例消费。这个设计同时实现了发布订阅和负载均衡,这也是Kafka给下游多个业务团队同时消费同一份数据铺路的原因。
面试中对这部分有三连问:
- 组内消费者数量大于分区数时怎么办?答:会空闲出消费者,不干活也不报错,因为一个分区同时只能被一个消费者消费。所以不要盲目加消费者来提并发,并发上限是分区数。
- 组内消费者数量小于分区数时怎么办?答:一个消费者会负责多个分区,分区分配算法会自动分摊。
- 两个消费者组可以消费同一个Topic的分区吗?答:可以,互不干扰,每个组各有独立的offset记录。
初学的人常以为分区数是吞吐上限的“平均数”,实际上不同消费者的处理能力不一样,分区分配只是数量上均衡,不保证处理时间的均衡。这会导致某些消费者忙死、某些闲死。想让负载更平均,可以把分区粒度调小,或者在业务侧做二次分发。
5.2 位移提交的底层逻辑
消费者从哪个位置开始消费,是面试中很容易埋坑的点。如果Consumer在启动时找不到已提交的位移,它会根据auto.offset.reset参数决定行为:latest表示从最新消息开始,earliest表示从最早消息开始,none表示抛异常。
而且这个参数只在“没有已提交位移”时生效。如果之前提交过位移,但位移已经超出日志保留范围或过期被删了,也会按auto.offset.reset处理。这里有个隐患:一个消费者第一次接入时用了latest,它会跳过启动前产生的所有消息,如果你期望的是“从头消费历史数据”,就完全跑偏了。
顺便说一句,位移提交这块还有一个“位移过期”的概念。Kafka的offsets.retention.minutes参数控制位移的保留时间,默认是7天。一个消费者组超过7天不活动,组内位移就会被清除,再次启动时就会回到auto.offset.reset的起点行为。这就可能导致“明明之前消费过,一重启全从头来”的诡异现象。
5.3 Rebalance全解析:为什么它被称为“风暴”
Rebalance本质上是一个“分区所有权重新分配”的过程。触发它的情况有三类:消费者加入或退出(主动关闭、崩溃、网络超时)、Topic的分区数变化、消费者组订阅的Topic集合变化。
Rebalance期间整个消费组是停止工作的,所有成员需要放弃当前分区,重新协调分配。这个过程也叫“停止-世界”事件(stop-the-world),所以频繁Rebalance会明显影响消费延迟。Kafka 2.4之后默认的CooperativeStickyAssignor协议已经改善了这一点,但从增量协议(Cooperative)变成静态组(group.instance.id)也能进一步减少重平衡。
与Rebalance相关的三个关键参数是:
session.timeout.ms:默认45秒,如果Consumer在这段时间内没有发送心跳,协调者会认为它挂了,触发Rebalance。heartbeat.interval.ms:默认3秒,心跳间隔。它决定协调者能多快感知消费者存活。理想情况是心跳间隔远小于超时时间。max.poll.interval.ms:默认5分钟,Consumer两次poll之间的最大间隔。如果业务处理时间超过它,就会被判定“处理能力不足”,强制踢出组触发Rebalance。
注意最后这个参数。很多团队消费逻辑里有慢查询或远程调用,耗时长,结果消费者被反复踢掉又加回,整个组一直处于Rebalance状态,消息根本消费不动。解决思路就是调大max.poll.interval.ms,或者把耗时的操作从poll线程里挪出去,用单独的线程池处理;小窍门是让poll线程保持活跃,比如先poll再异步处理,或者两段设计分别处理拉取和消费。
5.4 分区分配策略,聊聊几大算法的区别
理解Rebalance过程后,分配策略也是高频考题。主流有三种:
RangeAssignor:按Topic维度分配。比如一个Topic有100个分区,组内有3个消费者,第1个消费者拿34个,第2个拿33个,第3个拿33个。多个Topic的话,第一次分配完累加,会产生“前几个消费者拥有更多分区”的明显不均。它是旧版本默认策略。RoundRobinAssignor:把所有分区按字典序排成环形队列,逐个分发,整体均衡度好于Range,但当组内成员消费的Topic集合不同时,均衡会被破坏。StickyAssignor:在尽量保持上一次分配结果的前提下做均衡,减少了Rebalance发生时大规模移动分区带来的开销。它是新版默认策略,比前两种更合理。
实际项目中,如果你有多个Topic共享同一个消费组,务必考虑Sticky或者手动指定策略。默认策略是Sticky,但也有些老项目改造时依然沿用Range,加Topic后容易出现某个消费者独扛大半分区的情况。
6. 高性能真相:Kafka不是“消息队列”那么简单
6.1 顺序写磁盘与页缓存:理解“Kafka为什么快”的第一把钥匙
面试必问题“Kafka为什么吞吐这么高”,标准答法是“三个层面”:存储层、传输层、网络协议层。
存储层核心是顺序写。操作系统对机械硬盘和固态硬盘的顺序写性能都远好于随机写(机械硬盘顺序写可以接近控制器带宽,随机写则不断寻道,性能差距可达百倍)。Kafka每个Partition是一个有序的日志文件,新消息只追加写到文件末尾,不做更新、不做删除,这样写路径天然是顺序IO。
但这只是第一层。第二层是页缓存(Page Cache)。Kafka的读写并不强制刷盘,而是利用操作系统的页缓存机制,写入时直接写到页缓存里就算完成,操作系统异步刷到磁盘。这听起来“不保险”,但配合副本机制,把可靠性交给了多副本而不是单机刷盘——单机断电丢页缓存没关系,只要其他副本同步成功即可。
读完消息同样利用页缓存:如果消费速度跟生产速度差不多,数据还留在页缓存里,读的时候根本不用碰磁盘。这里有个运维层面的认知偏差:Kafka进程本身占用的内存看起来并不高,因为大量热点数据其实躺在操作系统的页缓存里。这也就是为什么给Kafka配JVM堆内存反而不要太大,堆设太大会挤掉留给页缓存的空间。
6.2 零拷贝:一次网络传输少复制4次
网络传输层面是零拷贝。传统的数据发送路径是:磁盘→内核缓冲区→用户态缓冲区→内核Socket缓冲区→网卡,经历了4次用户态/内核态切换和多次内存拷贝。Kafka在读消息时,如果数据在页缓存里,可以直接用sendfile系统调用,让DMA把数据从内核缓冲区直接搬运到网卡,用户态全程不参与。
Kafka Broker上有一个参数message.send.max.retries和文件传输相关的配置,通常你不需要手动开启零拷贝——它走的就是FileChannel.transferTo()那条路径。但面试时能讲清“数据没有经过JVM堆内拷贝”这一层,会非常加分。
6.3 批量与压缩:把吞吐再往上顶一个量级
批量处理贯穿Kafka的整个链路。生产者端有batch.size和linger.ms两个参数决定攒批策略:前者是批次大小,后者是等待时间。很多人对linger.ms有误解,以为设越大延迟越高就不好,实际上在高吞吐场景它是合理的取舍——攒一批再发,网络往返次数大幅减少,吞吐随之提升。
压缩也是,Kafka支持gzip、snappy、lz4、zstd几种压缩算法。压缩的收益在大数据量下非常明显,但要注意压缩发生在生产者端,解压发生在消费者端,Broker只是原样存储压缩后的数据。曾经有个项目MapReduce处理Kafka压缩数据时因为每个压缩批次要单独解压导致性能反而下降,因为压缩粒度控制不好。生产环境配合批量和压缩,通常在CPU没有瓶颈的情况下,吞吐可以再翻一到两倍。
7. 三大经典拷问:丢消息、重复消费、积压
7.1 消息丢失排查的思路,不是一句“配置好”就完事
丢消息是面试的重灾区,因为它可以把链路里的所有薄弱点都炸出来。正确的排查思路是分三段:
- 生产端丢:最常见的原因是
acks=0或者acks=1。acks=0时发送失败完全没感知;acks=1时Leader宕机,消息还没同步给Follower就丢。解决方案是acks=all+min.insync.replicas=2+ 重试机制。 - Broker端丢:磁盘损坏、副本不同步、消息过期被删除。这需要靠多副本和合理的日志保留策略兜底。另外注意
unclean.leader.election.enable参数,如果写成true,ISR中没有可用副本时会选举不在ISR里的“落后副本”为Leader,丢消息概率大增。生产环境建议设为false。 - 消费端丢:这是最大的隐性问题。很多人以为消息不丢就是生产端配置好,结果消费者拉取后先提交位移,再处理业务,一处理就挂,消息就再也找不回来。正确顺序是“处理完了再提交”,同时消费逻辑要幂等。
端到端只靠Kafka本身无法做到exactly-once,说的是“生产幂等+消费幂等+事务”才能对业务侧产生at-least-once 或者 exactly-once效果。这个区别面试中值得优先讲出来。
7.2 重复消费为什么无法根除,该怎么设计
重复消费的根源在于“确认”和“处理”不是原子操作。消费者在处理完消息后、提交位移前挂掉,重启就会重发;即使你做了事务也不行,因为事务只保证生产端原子,消费端依然是读到什么算什么。 应对思路最实用的就是“消费幂等”。具体手段有:
- 在数据库里消息增加唯一约束(比如消息ID或业务主键),插入时以要么忽略重复、要么更新为主的策略处理。
- 用Redis SetNX做去重,消费前先占位,处理完成后标记,重放时看到已处理就跳过。
- 如果消息本身带时间戳或版本号,可以在更新时加版本判断,避免旧消息覆盖新状态。
这里有实践中的额外提醒:幂等要按业务语义设计,不要在“消息ID”上加全局唯一,这个ID是Kafka自动生成的,不包含业务含义,不同业务场景需要自己带上业务主键。
7.3 消费积压的现场处理,别一上来就加机器
消息积压几乎是线上一定会遇到的事故。处理思路应该分层:
- 快速止损:先停掉导致积压的下游异常(比如数据库慢查询、依赖服务超时),必要时临时让消费逻辑只把消息转发到备用Topic或直接记录,保住数据不丢。
- 扩容消费:把Topic的分区数加大,同时增加消费者实例。注意如果分区数不足,加消费者是没用的。当分区数本身不够时,可以考虑临时方案”多个消费者实例去消费同一批数据并做负载,这种方案只适合临时挡故障。
- 追积压数据:等积压开始回落、消费速度跟上生产速度后,再把扩容的消费者撤掉,恢复正常拓扑。那个临时方案要提前设计好防止重复消费。
这里经常出现一个误区,看到积压就加消费者,但分区不够的情况下消费者增多不仅没用,还会引起不必要的Rebalance。我建议遇到积压先做“容量评估”,看看每个消费实例的消息积压量、单个消息的平均大小、单条消费耗时,计算出理论消费速率,再决定要不要扩容、扩多少,以及是否要单独处理积压数据。
7.4 顺序性保证的“受伤现场”
Kafka保证的是分区内有序。面试时如果被问“业务需要全局有序怎么办”,得体答案有:
- 如果业务允许,按业务主键作为Key,相同Key进同一分区,消费者单线程处理该分区,就能得到有序效果。
- 如果业务需要严格的全局有序,只能一个分区一个消费者,这样顺序保证最硬,但是吞吐也降得最狠。
- 如果只是“操作顺序不能乱”,可以考虑用版本号或时间戳做乐观锁,在消费端自己识别过期消息并丢弃;如果你需要处理本地事务,可以在消费者的状态里记录每个分区最后处理的消息序号,乱序到达就延迟处理或直接丢弃。
8. 基础篇的加分项与避坑经验
8.1 这些答案一听就是“背过八股”
基础题背答案很容易,但有些说法在实战经验丰富的面试官听来就是“背书机器的味道”。举个例子:
- “Kafka是消息队列”——严格说Kafka更接近“分布式流平台”,不仅仅是消息队列。你说“消息队列”没人说你错,但如果你能说出“它更像一个可重放的提交日志(Commit Log)”,瞬间就高级了一个层次。
- “Kafka不会丢消息”——这句话太绝对。只能说通过合理配置,每个环节都能做到不丢。但消费端处理失败、重复提交位移,依然能丢。正确说法是“Kafka保证Broker中已提交消息在多副本条件下不丢,同时在指定可靠性配置下将生产端消费端的丢失风险降到最低”。
- “acks=all就不会丢”——刚才已经讲了,它配合min.insync.replicas才真正有效。如果你在面试中把这一段讲完整,会显得非常扎实用心。
- “消费者数量=分区数时效率最高”——这是误解,效率不仅取决于数量,还和每个消费者处理的消息大小与耗时强相关。一个消费者可以扛多个分区,分区数决定并发上限而不是最佳并发数。
- “安装配置一下就能集群了”——很多人没答出“集群依赖元数据协调”以及可能出现的数据倾斜、Leader不均衡,实际上集群通常需要关注负载均衡、Broker宕机场景下的Leader重新分布。
8.2 从项目里找可以作为支撑的细节
面试官更愿意听你结合真实系统讲。你可以从三个角度提炼:
- 用Kafka做过“业务解耦+削峰”的方案。比如某系统下单高峰期每秒几千条请求,把请求直接写入Kafka,下游按能力慢慢处理。核心不只是“用了消息队列”,而是你计算过峰值流量、分区数和消费者吞吐的匹配关系。
- 踩过消息积压的坑。比如因为慢SQL导致消费阻塞,进而触发Rebalance,整个消费组停止消费。你的复盘、临时扩容方案和参数调整,这种故事比顺口溜有说服力。
- 做过Kafka与其他组件的数据管道集成。比如从Kafka读取数据后写入数据库或者与大数据平台衔接,要注意批量插入的节奏,也要考虑重复数据的溢出。
有一点建议:讲项目时不要“背流水账”,而是强调你做了什么决策,因为什么原因选了某个参数,出现问题后怎么排查的。Kafka的参数成百上千,面试官不会真指望你全背下来,他考的是你在真实环境下做出判断的思路。
8.3 我留的几条实用经验
最后分享几个我踩过多次坑后的实操经验,不只是为了面试,生产环境也能直接用到:
- 规划分区数时要考虑未来两到三年的增长。通常按“峰值生产速率/单分区生产速率”再乘个冗余因子。举个例子,如果每个分区能扛20MB/s写入,目标峰值是2GB/s,那需要100个分区,再多留20%冗余就是120个。如果你一开始只建了10个分区,后面辛辛苦苦加了大量Broker,但分区不够,并发上不去,扩容会变得昂贵。
- 不要把
acks=all当成万能安全密码,一定要配合min.insync.replicas一起看,否则可能卡可用性。 - 消费者poll循环里的“业务处理耗时”一定要监控,这能帮你提前预判Rebalance风险。我见过很多团队只看消费速率,从不看单条消息的处理时延,直到故障发生才排查到线性增长。
- 消息格式要有版本号。这个问题太常被忽略:Kafka消息体往往是JSON或二进制,一旦字段结构改了,历史消息会反序列化失败,如果没加版本号,重放历史数据就是灾难。加一个版本号字段很简单,但能让你在改协议时多一份从容。
Kafka的基础体系大概就这些。我自己的感受是,别背框架,把每条消息从生产到消费的路径在自己脑子里过一遍,把可靠性那个“端到端闭环”想清楚,比背一百道题都有效。下次有人突然问“Kafka如果单机运行要配几个分区”,你大概就知道他不是要一个数字,而是想听你怎么权衡。