聊一个很有意思的话题:JAVA消息中间件面试题。其实这类题目背八股是行不通的,面试官真正想看的,是你对分布式系统里“异步、解耦、削峰”这三件事的理解深度。我这些年既当过候选人,也坐在对面问过别人,发现能把消息中间件聊透的人,往往不是背得最多的,而是能把每个机制背后的业务场景、技术trade-off讲清楚的人。这篇就把我从面试题里总结出来的核心考点、回答思路、还有实操中踩过的坑,一次性整理出来。
1. 面试官为什么死磕消息中间件
1.1 一道面试题背后的能力地图
先搞清楚一件事:消息中间件在JAVA面试里出现的频率极高,但面试官考察的绝不只是“你用过Kafka吗”这种表面问题。通过一系列追问,他们实际在构建一张能力地图:
- 第一层:你是否理解消息中间件解决了什么业务问题。如果连“解耦、异步、削峰”都说不清楚,后面基本不用聊了。
- 第二层:你是否理解消息中间件的核心机制,比如消息存储、消费模型、高可用设计。这一层考察的是你有没有读过源码或者深入用过。
- 第三层:你是否能在真实场景里做技术选型和架构设计。比如订单系统要不要引入MQ,选Kafka还是RocketMQ,出现消息堆积怎么处理。这一层是区分普通开发者和资深开发者的分水岭。
我遇到过很多候选人,简历上写着“熟悉消息中间件”,但问“为什么要用消息中间件”时,只能说出“解耦、异步、削峰”六个字,再追问“具体怎么解耦、解耦解决了什么痛点”,就开始支支吾吾了。这类回答往往会在第一轮就被淘汰,因为暴露的是“只背了概念、没做过项目”的信号,而且很容易被后续场景题连环追问打穿。
1.2 三个考察层级与命题套路
以我面试别人的经验,消息中间件的考察基本上围绕着三个层级展开:
第一级是基础概念层。比如“什么是消息队列”“Kafka和RocketMQ有什么区别”“什么是消费组”。这个层级只要系统学过或者用过就能答上来,一般是开场热身题。
第二级是可靠性保障层。比如“如何保证消息不丢失”“如何保证消息不被重复消费”“如何保证消息的顺序性”。这是面试的重头戏,因为可靠性问题在真实生产环境里一定会遇到,而且没有任何中间件能替你兜底,必须靠架构设计来解决。
第三级是综合架构层。比如“设计一个秒杀系统,你怎么用MQ削峰”“下单流程里MQ挂了怎么办”“消息积压了怎么处理”。这类题目不仅考察技术深度,还考察你的系统设计能力、故障应急能力、以及对业务链路的理解。
命题套路也很明显:面试官会从一个基础概念切入,然后层层加码。比如先问“Kafka怎么保证高可用”,再问“Leader挂了怎么选举”,再问“如果ISR里的副本都挂了怎么办”。每一个追问都是在探测你的知识边界,看你是只知其然,还是也知其所以然。真正有效的准备方式,不是背标准答案,而是把每个机制背后的原理、触发条件、以及应对方案想明白。
2. 高频考点逐题拆解:先搞懂“为什么”
2.1 为什么要引入消息中间件:从三次握手说起
几乎每场面试的第一题都是“为什么项目里要用消息中间件,不用行不行”。这个问题的底层逻辑,其实是在考察你对分布式系统通信模型的理解。
在单体应用时代,模块之间是直接调用的。A模块调用B模块,B模块处理完返回结果,整个过程是同步阻塞的。同步调用最大的问题是链路耦合:如果B模块挂了,A模块也跟着失败;如果B模块响应慢,A模块的线程就被拖死;如果流量突增,整个系统会因为一个下游能力不足而雪崩。
引入消息中间件后,通信从同步转为异步。A模块把消息丢到队列里,立刻返回成功,B模块自己去队列里拉消息处理。这个模型的好处有三点:
- 解耦:生产者和消费者互不感知对方的存在。B模块升级、重构、甚至宕机,A模块都不受影响。
- 异步:A模块不需要等B模块处理完才能继续干活,响应时间可以大幅缩短。
- 削峰填谷:流量高峰期间,消息先进队列,消费者按照自己的处理能力慢慢消费,保护了下游系统的稳定性。
面试里回答这个问题时,我建议用一个具体的业务案例来说明。比如电商下单流程:用户下单后,系统需要扣库存、发优惠券、送积分、发短信通知。如果同步调用这些服务,下单接口的耗时可能从50ms膨胀到500ms,同时任何一个服务抖动都会导致下单失败。引入MQ后,下单只需要写库并发送一条“订单创建成功”的消息,积分、短信、优惠券服务各自订阅消息进行异步处理。这样即使短信服务宕机,也不影响用户下单。
还有一个“不用行不行”的点也要提前想好。如果系统QPS只有个位数,模块只有两三个,那确实没必要用MQ。引入消息中间件本身是有成本的:系统复杂度上升、消息可能丢失、需要额外运维、排查问题链路变长。回答时如果能主动说“低并发、简单业务场景下不推荐引入MQ”,反而会让面试官觉得你是一个有判断力的工程师,而不是一个盲目追技术的。
2.2 消息模型与消费语义:推拉之辨
消息模型是绕不开的基础考点,主要分两种:点对点模型和发布订阅模型。
点对点模型里,一条消息只会被一个消费者消费。消息从队列Producer发出,进入队列后,Consumer拉取并消费,消费完确认,消息就从队列里移除。这种模型适合任务分发场景,比如一批待处理的邮件发送任务,多个消费者实例并行处理,但每条消息只处理一次即可。
发布订阅模型里,消息会被广播给所有订阅了该Topic的消费者。每个消费者都可以收到同一份消息副本。这种模型适合事件驱动场景,比如“用户下单成功”这一事件,订单服务、积分服务、短信服务都要感知到。
此外,推和拉也是常见的追问点。Kafka和RocketMQ默认是拉模式,消费者主动去Broker拉取消息,好处是消费者可以自己控制消费速率和积压量;RabbitMQ则支持推模式,Broker主动把消息推给消费者,好处是消息实时性更高,但消费者可能被大量消息打爆。
这里有一个容易被忽略的细节:面试官问“推和拉有什么区别”时,想听的是“为什么主流消息中间件都选拉模式”。答案核心在于:推模式下Broker无法感知消费者的处理能力,容易把消费者压垮;拉模式下消费者可以按需拉取,实现真正的流量控制(Backpressure)。这个点能答出来,说明你对消费速率控制的本质有理解,而不只是停留在API使用层面。
2.3 Broker的高可用设计:副本与Leader选举
消息中间件的Broker如果宕机了怎么办,能不能做到不丢消息、服务不中断,这是面试中经久不衰的高频题。我的回答框架是从“副本”出发,然后聊“副本同步策略”,最后聊“故障转移”。
以Kafka为例,一个Topic的分区会有多个副本(Replica),其中一个是Leader,其余是Follower。生产者和消费者只跟Leader打交道,Follower负责同步Leader的消息。Leader宕机后,Controller(控制器)会负责从ISR(In-Sync Replicas,同步副本集合)中选出新的Leader。Controller是Kafka集群的大脑,负责分区Leader的选举和元数据管理。
面试官常用的追问式问题是“ISR里的副本都挂了怎么办”。这时候一定要答出Kafka的“可用性优先”策略:允许选出不在ISR中的副本作为Leader,但代价是可能丢失消息。如果副本全部不可用,Kafka会等待ISR中的副本恢复,如果是设置了unclean.leader.election.enable=true,那么就会选择一个不完全同步的副本作为Leader,保证服务持续可用。
对比来看,RocketMQ的高可用设计则有所不同:它采用主从同步复制模式,主节点宕机后,从节点可以继续提供读取服务,但写入需要等主节点恢复。面试中如果能横向对比Kafka和RocketMQ的可用性取舍,会是个不错的加分项。这个部分在很多候选人回答里比较薄弱,因为他们往往只对单一组件熟悉,没有做过系统性的横向对比,提前梳理一遍收益很高。
3. 可靠性三座大山:丢失、重复、乱序
3.1 消息丢失的四个环节排查法
“如何保证消息不丢失”是消息中间件面试题里几乎必考的一道核心题。关键在于:一条消息从生产到消费,经历了生产者发送、Broker存储、消费者拉取消费三个阶段。除此之外,Broker主从同步也是一个关键阶段。我习惯把它拆成四个环节来排查:
环节一:生产者发送消息到Broker。如果网络抖动,发送请求丢失,或者Broker返回失败,生产者没有重试机制,消息就丢了。解决方案是开启生产者的确认机制,比如Kafka的acks=all,RocketMQ的同步发送失败重试,或者使用事务消息。注意:acks=all表示所有副本都写入成功才返回成功,这是最严格的级别。
环节二:Broker存储消息。如果消息写入PageCache后机器宕机,数据还没来得及刷盘就会丢失。解决方案是开启刷盘策略,Kafka的log.flush.interval.messages=1(每条消息都刷盘),或者依靠副本机制,让多个副本都保存消息副本。
环节三:Broker主从同步。如果主节点写入成功后立刻宕机,消息尚未同步到从节点,也会丢。解决方案是使用同步复制(RocketMQ的主从同步复制)或者等待ISR中所有副本都写入成功再返回。
环节四:消费者消费消息并提交offset。如果消费者处理完业务逻辑,还没提交offset就宕机了,重启后会再次消费到这条消息,但这不算丢失,而是重复。如果消费者先提交offset,再处理业务,过程中宕机,那么消息就真正丢了。所以经典方案是“先处理业务逻辑,再提交offset”。
面试现场我建议用一句话总结:消息不丢失,需要生产者、Broker、消费者三个端配合,每一端都要有对应的确认和重试机制。这个回答框架完整清晰,面试官不太容易挑出逻辑漏洞。
3.2 重复消费的兜底方案:幂等设计
有了“消息不丢失”的保障,自然就会引出重复消费的问题。比如消费者处理完业务后还没来得及提交offset就宕机了,重启后就会再次消费同一条消息。再比如生产者发送消息时因为网络超时重试,Broker里可能已经存在同一条消息了,消费者还是会拉到两条一样的。
面试要答的是:消息中间件只能保证“至少一次”投递(At Least Once),无法保证“刚好一次”,重复消费只能靠消费者幂等来兜底。这个结论很重要,因为很多初级候选人会说“我用的MQ不会重复”,实际上没有任何MQ能做到。
幂等方案我实际用过的有四种:
- 唯一ID去重:消息体里带一个全局唯一的业务ID,消费者处理前先查数据库或Redis里是否已存在该ID,存在就跳过。需要注意并发场景下的竞态问题,建议用数据库唯一索引或者Redis的SETNX做原子判断。
- 状态机校验:业务数据本身有状态流转,比如订单已支付后再次收到支付成功消息,直接忽略即可。
- 版本号控制:用乐观锁,update语句里加上version=预期版本,更新行数为0则说明已经处理过。
- 数据库唯一索引:比如消费记录表里对消息ID建唯一索引,插入冲突说明重复,直接catch掉当成消费成功。
面试里答幂等时,最好主动聊一聊“你项目里实际选了哪种方案、为什么这么选”。比如订单支付回调场景,我选择的是“唯一ID去重+数据库唯一索引”双重保障。因为单靠Redis判断在极端情况下可能因缓存失效而出现重复处理,数据库唯一索引才是最终防线。主动给出这类选择理由,会让面试官觉得你有真实架构经验,而不仅仅是罗列方案。
3.3 顺序消息的取舍:全局顺序与局部顺序
顺序消息是可靠性的另一个大坑。面试里常见的问法是“如何保证消息的顺序性”,但很多人会直接回答“设置单分区就行”,这往往达不到面试官的期待,因为他真正想听的是对性能损耗的理解。
消息中间件里,Kafka只保证同一个分区内的消息有序,RocketMQ也只保证同一个MessageQueue内的有序。跨分区是无法保证全局顺序的。所以如果你想保证全局顺序,只能用一个分区,但这会让吞吐量下降得非常厉害,等于放弃了并行消费的能力。这里需要明确:全局顺序和分区数量是一对天然矛盾,必须结合业务场景做判断。
更常见的业务需求是局部顺序。比如电商场景里,同一个订单的“创建订单、支付成功、发货”三条消息必须按顺序处理,但不同订单之间可以并行。解决思路很简单:把消息按订单ID进行Hash路由,让同一个订单的消息都进入同一个分区。Kafka里的做法是自定义Partitioner,RocketMQ里是MessageQueueSelector,然后消费端用单线程或者带锁的线程池来处理同一个Queue里的消息。
我在项目里实际遇到过一个坑:明明设置了按订单ID路由到同一个分区,但消费者端用了多线程并发消费同一个分区的消息,依然出现了乱序。解决方式是消费者端对同一个订单号的并发消费加分布式锁,或者直接让一个Queue对应一个消费线程。如果你想追求高性能又想保持有序,也可以用“按序列号排序后批量写入”的折中方案,但复杂度会明显上升,需要根据实际业务取舍。
提示:面试答顺序消息时,一定不要一上来就说“搞单分区”。先从“局部顺序”讲起,再补充“全局顺序需要牺牲性能”的取舍,最后落到业务场景按需选型,这个回答的层次感会好很多。
4. 事务消息与最终一致性
4.1 RocketMQ事务消息原理:半消息机制
分布式事务在面试里是一块硬骨头,而RocketMQ因为支持事务消息,经常被拿来出题。这道题的关键在于:RocketMQ的事务消息核心并不是让MQ变成数据库,而是保证“本地事务和消息发送”的原子性。
RocketMQ事务消息的流程如下:
- 生产者发送一条半消息(Half Message)到Broker。半消息对消费者不可见。
- Broker存储成功后,返回发送成功。
- 生产者执行本地事务(比如订单表插入一条记录)。
- 根据本地事务结果,生产者向Broker提交Commit或Rollback。
- 如果Commit,消息对消费者可见,消费者可以正常消费。
- 如果生产者执行完本地事务后宕机了,没有提交最终状态,Broker会主动回查(回查事务状态),询问生产者这条消息到底该提交还是回滚。生产者需要提供一个回查接口,根据本地事务的执行结果来告诉Broker。
整个机制的关键在于第6步的“事务回查”,它保证了即使生产者宕机,“本地事务和消息发送”也能最终达成一致。
回答这道题时,不要只背流程,最好能说清楚为什么要设计半消息。半消息存在的意义是避免消费者在生产者本地事务还没完成时就看到消息,从而消费到未提交的数据。这个动机讲清楚,面试官才会觉得你是真的理解了,而不是背了流程图。
4.2 本地消息表兜底方案:老牌但依然有效
除了RocketMQ的事务消息,面试官还喜欢问“如果没有事务消息怎么办”。最经典的方案是本地消息表,也叫事务消息的最简版实现。
实现思路如下:在业务数据库里建一张消息表,业务操作和写消息表放在同一个本地事务里。比如下单操作时,事务里同时执行“插入订单记录”和“插入一条待发送消息”。事务提交后,有一个后台任务扫描消息表,把未发送的消息发送到MQ。发送成功后更新消息状态为已发送。
这个方案的巧妙之处在于:业务操作和消息记录的持久化被同一个数据库事务保护,两者要么同时成功,要么同时失败。而发送消息这个动作,即使失败也只是停留在“待发送”状态,由后台任务重试。因为消息表在本地库里,不会出现“数据库提交了但消息没发出去”的不一致状态。
面试中要主动提到这个方案的适用场景:适合事务边界明确、单库写入的场景。如果涉及跨多个数据库的事务,本地消息表的效果会打折扣,需要引入更复杂的分布式事务方案。这个回答能展示你的选型判断力,也会让面试官相信你是真的落过地、踩过坑。
4.3 面试现场要注意的细节
关于事务消息,我总结几个面试中常见的追问细节,提前准备会非常有优势:
第一问:事务回查失败怎么办?答案是可以手动补偿,通过监控和告警发现消息一直处于半消息状态,人工介入处理后端消息。回查机制只是“尽量自动恢复”,不能保证100%,所以必须要有监控。
第二问:事务消息和本地消息表对比,哪个更好?这个没有绝对答案。RocketMQ事务消息对业务侵入更小,本地消息表实现简单可控、不依赖特定MQ特性。但本地消息表需要额外维护一张表和定时任务,RocketMQ事务消息需要Broker支持回查,而Kafka至今没有原生事务消息(虽然有了事务API,但语义和实现机制有所不同)。
第三问:事务消息一定不会丢失吗?也不是,Broker本身的刷盘策略、副本策略仍然会影响消息的持久性。事务消息和“消息不丢失”是两套独立的机制,需要分别保障。
5. 主流消息中间件横向对比:Kafka、RocketMQ、RabbitMQ
5.1 三者原型的差异:日志、交易、路由
很多候选人简历上写着“熟悉Kafka”或者“用过RocketMQ”,但被问“三者之间怎么选型”就开始犯难。这道题的核心不在于比功能多少,而在于理解每个中间件诞生的初衷不同。
Kafka最初诞生于大数据领域,被设计用来处理海量日志和流式数据。它的核心定位是分布式日志系统,所以追求极致的吞吐量,对消息的可靠性采取批量、异步、顺序写磁盘等优化。只要整体链路稳定,偶发丢消息可以接受,这让它在大数据管道、用户行为日志、监控数据采集场景非常合适。但如果你用Kafka传递交易类强一致性数据,比如订单金额,就要在可靠性方面做很多额外配置,且跟RocketMQ相比仍然有一些劣势。
RocketMQ诞生于电商交易场景,所以对事务消息、消息可靠性、消息追踪、延迟消息等功能支持得非常好。它的吞吐量比Kafka稍低,但功能完整性高,适合订单、支付、库存等对可靠性要求极高的业务系统。
RabbitMQ是一个历史悠久的老牌消息队列,基于Erlang语言开发,特点是路由灵活、管理界面友好、支持多种消息协议(AMQP,MQTT,STOMP)。它的吞吐量一般,但功能稳定,适合企业内部系统集成、低并发的异步任务。比如传统的ERP系统、工作流系统里很常见。
面试中我用一句话总结选型原则:追求性能和数据管道,选Kafka;需要事务消息和高可靠,选RocketMQ;要求轻量灵活和丰富协议,选RabbitMQ。这句话虽然简单,但足以体现你对三者本质的把握。
5.2 选型回答公式:场景+参数+取舍
纯讲原理还不够,面试官喜欢听具体参数和实际取舍。我用一个选型公式来回答:首先明确业务场景需求(可靠性、吞吐、延迟、事务支持),然后对照不同产品的核心指标,最后给出取舍结论。
比如你要设计一个订单系统,核心需求是高可靠、支持事务消息、消息不能丢。此时Kafka的吞吐优势不是第一优先级,RocketMQ的事务消息和同步刷盘配合更合适。你需要解释为什么RocketMQ适合业务系统:它对消息失败重试、死信队列、延迟消息的支持很完善,这些都是交易链路的刚需。
换一个场景,假设你要做用户点击日志采集,每天TB级别的数据要写入数据仓库做离线分析。这时吞吐量是第一优先级,消息丢失一小部分可接受,Kafka显然更合适。它的顺序写盘、零拷贝、批量发送机制能让你用较少的机器撑起极大的QPS,硬件成本优势非常明显。
还有一个实际生产对比值得在回答里提:Kafka的消费端Rebalance机制在大规模消费组变更时会触发全局Rebalance,导致消费停止,甚至出现消费倾斜;RocketMQ的消费模式相对柔和,按Queue粒度分配,变更成本低。新人在面试时往往说不出这种细节差异,一旦说出来就能和只会背概念的人拉开明显差距。
6. 实战问答模拟:从场景题到系统设计
6.1 场景题:秒杀系统如何用MQ削峰
我在面试中常问的一道综合题是“如果让你设计秒杀系统,你怎么用消息中间件削峰”。这道题考验的是场景拆解能力,而不是单纯的知识记忆。
秒杀系统的核心痛点是瞬间涌入的高并发流量。如果不加任何限制,数据库会被打挂。引入MQ的思路是:用户请求先进入系统,系统进行简单的参数校验和库存预判后,直接返回“已受理”状态,同时把“秒杀请求”消息写入MQ。后端真正的扣减库存服务从MQ里按照自己的消费速率拉取消息,慢慢处理。
这里有一个关键问题:如何防止用户重复请求秒杀接口。常见的做法是在入口处对“用户ID+商品ID”做去重,只放行第一次请求到MQ,后面的请求直接拦截。去重可以用Redis的SETNX来实现。
第二个关键问题是:如果MQ里积压了大量秒杀消息,消费者处理不过来怎么办。此时秒杀本质上是先到先得,消费者消费到的消息就能成功,没消费到的就失败。所以消费者的处理速度决定了秒杀成功的数量,消费者要尽量保证高效,并且要做好监控和告警。
还有一个加分点:如果MQ宕机了怎么办。这时要考虑限流降级方案,比如直接拒绝部分流量。设计高并发系统时,降级预案也是一个必须答的维度。
6.2 场景题:消息积压了怎么排查和解决
消息积压是所有消息中间件生产环境里最容易出现的故障,也是面试综合题里出现频率极高的主题。回答框架建议从“先保服务可用,再查根因”的思路展开。
第一步是快速扩容。如果积压量短时间内无法消化,最简单的办法是增加消费者实例。但要注意:Kafka中增加消费者实例只有在实例数不超过分区数的情况下才有效,如果分区数不够,还需要临时增加Topic的分区数。这个过程需要小心的设计和执行,直接增减分区会带来消息顺序和元数据变更等额外问题。RocketMQ的扩消费者方案相对简单,但也要注意Queue数量上限。
第二步是排查消费慢的根因。常见根因包括:消费者中有耗时操作(比如调外部接口)、数据库瓶颈、消费线程数配置不合理、消费者代码出现死循环。我遇到过很多次“消息积压”其实是下游数据库慢查询导致的,消费者每次消费都要写库,数据库成为瓶颈后消费速率急剧下降。
第三步是积压消息的补偿处理。如果消息里的数据已经过时,比如实时价格类消息,可以选择跳过或者只处理最新一条;如果消息体里是重要的业务数据,则要保留原始消息,等待下游恢复后再逐条消费。
有一个实际的避坑经验:积压处理时,千万不要先停消费者再排查问题,因为积压量会越积越多。正确做法是尽可能扩容消费者先顶住,同时保留现场日志以便分析。这个建议很多人踩过坑,我提过多次仍有同事在故障时先按停Consumer的习惯,值得反复强调。
6.3 答题节奏与话术模板:从点到体的表达
最后分享一个面试答题的小技巧:用“总-分-总”的节奏来组织回答,并且每个回答都固定走“定义-场景-方案-取舍”四步。
比如面试官问“Kafka怎么保证消息不丢失”,不要直接开始背参数。建议第一步先给结论:“保证不丢失要从生产者、Broker、消费者三个环节分别处理。”第二步分别展开每个环节的具体措施。第三步做一个小结:“任何环节的保障都不是万无一失的,最好配合监控和告警。如果真正要做到严格不丢,需要从架构层面设计补偿机制。”
回答过程中主动提到一些真实的线上经验会加分。比如你可以说“我之前排查过一个消费端丢消息的问题,原因是先提交了offset再处理业务逻辑。后来改成先处理再提交,故障就消失了”。用真实案例佐证概念,比背一百个原理都有说服力。
面试里还有一个常见误区:过度强调自己用的技术栈,比如“我用的Kafka所以其他MQ不熟”。这句话在面试里等于主动暴露短板。更好的说法是“我主要用过Kafka,但对RocketMQ的原理也有一定了解,两者在可靠性设计上有这样那样的区别”。既不撒谎,也展示了知识面。
7. 最后聊点实在的
我面过不少候选人,也经历过很多次作为候选人的紧张时刻。消息中间件这个领域的面试题风格,本质上是用一套又一个的“为什么”来考察你是否真的做过事。所以准备的关键不在于背题库,而在于把每个知识点都能串成一个完整的故事:什么场景下用到,为什么这么选,出现问题时怎么排查,最后怎么优化的。
有几条具体的准备建议想分享给大家。第一,最好自己动手搭一套完整的环境,把生产消费、异常场景(比如杀掉Broker、模拟网络闪断、手动触发Rebalance)真实地跑一遍。纸上得来终觉浅,尤其是消息可靠性和故障转移这些机制,只有亲手折腾过才会印象深刻。第二,梳理你实际业务中的MQ使用案例,把当时遇到的问题、排查过程和最终方案写成文档,面试时这些真实细节会比任何标准答案都有说服力。第三,如果面试中遇到不会的问题,别慌,可以先讲自己熟悉的部分,再说明“这是我还没有深入了解过的领域,我的大致理解是……”,这比硬编一个答案更让面试官认可,因为技术面试的本质是看你的思考能力和诚实程度。
最后再分享一个小技巧:面试前把主流中间件都读一遍官方文档的“设计”章节。很多分布式系统设计的精髓都写在设计文档里,理解它比记住任何一个参数都有价值。面试官问到的绝大多数难题,其实都能在这些设计文档里找到原初的设计动机。这份功课做完,面对消息中间件面试题时,你会发现自己已经不是在背书,而是在跟面试官讨论一个自己真正理解的东西。