消息中间件面试题库
生成时间:2026-05-22 22:12
【面试题】1. Kafka的ISR机制是什么?为什么需要ISR而不是简单地等所有副本同步完成?
【标准答案】ISR是In-Sync Replicas,即与Leader保持同步的副本集合。它解决的核心问题是:如果要求所有副本都确认写入才返回成功,那任何一个副本宕机都会阻塞写入,可用性极差。ISR的做法是只等ISR集合内的副本确认即可,兼顾了可靠性和可用性。具体机制:Follower从Leader拉取数据,如果Follower延迟超过replica.lag.time.max.ms(默认10秒),就被踢出ISR;追上后重新加入。配合acks参数使用——acks=all意味着等ISR中所有副本确认,而不是等所有副本。面试要点:ISR是动态的,不是固定的;Leader选举只从ISR中选,保证不丢数据;ISR收缩到只剩Leader时等于单副本,生产环境要监控ISR数量。
【面试题】2. RabbitMQ的死信队列(DLX)是什么?哪些场景下消息会进入死信队列?如何设计一个基于DLX的延迟队列?
【标准答案】死信队列本质就是一个普通Exchange,只不过别的队列把消息路由到它上面。消息进入死信的三种情况:①消费者basic.nack或basic.reject且requeue=false;②消息TTL过期;③队列达到最大长度,溢出的消息。延迟队列的典型设计:先声明一个普通队列绑定TTL(不设消费者),再把这个队列的DLX指向目标Exchange。消息发到TTL队列后,过期后自动进入死信Exchange,再路由到实际消费队列,实现延迟消费。用rabbitmq_delayed_message_exchange插件更优雅——Exchange直接支持x-delayed-message类型,消息头带x-delay字段指定延迟毫秒数,不需要额外队列中转。面试加分点:TTL队列方案注意先入先出问题——队列头消息TTL未到,后面的消息即使TTL到了也出不去;插件方案没有这个问题。
【面试题】3. RocketMQ的CommitLog和ConsumeQueue是什么关系?为什么这么设计?
【标准答案】CommitLog是所有消息的顺序写日志,所有Topic的消息统一追加写入,顺序写保证极高的吞吐。ConsumeQueue是逻辑消费队列,每条记录固定20字节(8字节CommitLog偏移量+4字节消息大小+8字节Tag哈希),相当于CommitLog的索引。关系:Consumer先读ConsumeQueue拿到偏移量,再去CommitLog读完整消息。为什么这么设计?核心是读写分离:写端所有消息顺序追加到一个CommitLog,利用顺序写磁盘性能(6百MB/s);读端每个Topic+QueueId有独立的ConsumeQueue,支持按Topic/Queue并行消费和随机读。如果每个Topic独立文件,写端就是随机写,性能急剧下降。面试要点:ConsumeQueue本身也支持顺序读;实际运行时热点数据在PageCache中,随机读CommitLog的性能也有保障;这也解释了为什么RocketMQ推荐物理机部署——PageCache不受容器限制。
【面试题】4. 消费端如何保证幂等性?请对比三种常见方案的优缺点。
【标准答案】幂等性保证的核心是:同一条消息消费多次结果一致。三种方案:①唯一ID+数据库去重表——消费前用消息ID插入去重表(主键或唯一索引),插入成功才处理业务,重复消息插入失败直接跳过。优点:强一致,实现简单;缺点:数据库是瓶颈,高并发下性能差。②唯一ID+Redis SETNX——消费前用消息ID做Redis SETNX,设置成功才处理。优点:性能高,Redis单线程天然幂等;缺点:Redis宕机或key过期可能导致重复消费,不是强一致。③业务层天然幂等——设计业务逻辑本身具备幂等性,比如用状态机(只有待支付→已支付单向流转)、金额校验(已扣款不重复扣)。优点:最优雅无侵入;缺点:不是所有业务都能设计成天然幂等。实战建议:关键业务用方案①保底,高并发链路用方案②提性能,设计阶段尽量方案③。
【面试题】5. Kafka的Exactly-Once语义是怎么实现的?能说说幂等Producer和事务的关系吗?
【标准答案】Exactly-Once分两层理解。第一层:生产端不丢不重——通过幂等Producer实现。核心是Broker端为每个Producer分配PID(Producer ID),每条消息带Sequence Number,Broker发现重复序号就丢弃。幂等只能保证单分区单会话的去重。第二层:跨分区的原子写入+消费端精确一次——通过事务实现。Producer开启事务后,一批消息要么全部提交,要么全部回滚,同时可以将Consumer Offset的提交纳入同一事务。实现依赖Transaction Coordinator和两阶段提交:先写入Transaction Log标记PREPARE,数据写入成功后标记COMMIT。Consumer端设isolation.level=read_committed只读已提交事务的消息。面试关键:幂等Producer是事务的基础(事务内必须开启幂等);Exactly-Once不是万能的——它保证的是Kafka内部的精确一次,外部系统(如写入MySQL)还需要业务层保证幂等。
【面试题】6. 消息积压了怎么处理?请给出一个完整的应急处理方案。
【标准答案】消息积压是线上高频问题,分三步走。第一步,紧急止血:先看消费端是否有异常(日志报错、线程阻塞、下游超时),有就先修复;如果消费能力就是不够,临时扩容Consumer实例。注意扩容Consumer时,分区数/队列数是上限——Kafka一个分区只能被一个Consumer消费,分区不够加Consumer没用,得先扩分区。第二步,加速消化:如果消息量实在太大,写一个临时Consumer程序,只做消息转发——从积压Topic快速消费,批量写入一个分区数更多的新Topic,然后用更多的Consumer并行消费新Topic。第三步,事后复盘:检查积压根因——是消费太慢(优化消费逻辑、批量处理、异步化下游调用)、还是生产突增(限流、削峰)、还是Consumer频繁Rebalance(调整session.timeout和max.poll.interval)。面试加分:监控告警要提前配置——消费延迟超过阈值就告警,别等到业务受损才发现。
【面试题】7. RabbitMQ的Exchange四种类型分别怎么路由消息?请各举一个典型使用场景。
【标准答案】①Direct Exchange——精确匹配Routing Key。队列绑定时指定Routing Key,消息的Routing Key完全一致才路由。场景:点对点任务分发,比如订单服务只接收routing_key="order.create"的消息。②Fanout Exchange——广播,忽略Routing Key,所有绑定队列都收到消息。场景:事件通知广播,比如用户注册后同时通知邮件服务、积分服务、日志服务。③Topic Exchange——模式匹配Routing Key,支持(一个单词)和#(零或多个单词)通配符。场景:多维度路由,比如日志系统用"app.error.db"格式的routing key,绑定".error.*"的队列收到所有错误日志,绑定"app.#"的队列收到app模块所有日志。④Headers Exchange——匹配消息头属性而非Routing Key,支持x-match=all(全部匹配)或any(任一匹配)。场景:多属性组合路由,比如根据消息头的region+department联合匹配,实际用得少,前三种覆盖90%场景。
【面试题】8. RocketMQ的事务消息原理是什么?和本地消息表方案相比有什么优劣?
【标准答案】事务消息原理分四步:①Producer发送半消息(Half Message)到Broker,半消息对Consumer不可见;②Broker存储半消息成功后返回确认;③Producer执行本地事务;④根据本地事务结果发送Commit或Rollback。关键机制——回查:如果步骤④的网络请求丢了,Broker长时间没收到二次确认,会定期回查Producer的本地事务状态(最多15次),Producer需要实现checkLocalTransaction接口返回事务状态。和本地消息表的对比:本地消息表是将消息写入业务库的本地表,利用本地事务保证业务操作和消息记录的原子性,再由定时任务扫描发送。优势:事务消息不依赖业务库,不侵入数据库schema,不需要定时扫描轮询;劣势:事务消息依赖Broker的半消息机制和回查逻辑,实现复杂度高,且回查对Producer有额外要求。面试总结:公司有RocketMQ就用事务消息,没有就用本地消息表,两者本质上都是最终一致性方案。
【面试题】9. Kafka零拷贝和RocketMQ mmap分别是怎么提升性能的?两者有何区别?
【标准答案】Kafka的零拷贝依赖操作系统的sendfile系统调用。传统流程:磁盘→内核缓冲区→用户缓冲区→Socket缓冲区→网卡,4次拷贝2次上下文切换。零拷贝流程:磁盘→内核缓冲区→网卡,2次拷贝0次用户态切换。Kafka的Broker处理Fetch请求时,直接用FileChannel.transferTo将日志文件数据发送到网络,数据不经过JVM堆内存。RocketMQ的mmap(内存映射文件)是将CommitLog文件映射到用户态虚拟内存地址空间,Java层用MappedByteBuffer操作。写入时直接写内存(操作系统异步刷盘),读取时通过内存地址直接访问文件内容,省去了read系统调用将数据从内核缓冲区拷贝到用户缓冲区的开销。核心区别:零拷贝是网络发送优化,mmap是读写优化。Kafka侧重Broker到Consumer的网络传输效率,RocketMQ侧重CommitLog的写入和随机读效率。实际中两者可以互补——RocketMQ高版本也支持transferTo加速网络发送。
【面试题】10. 如果让你设计一个百万级TPS的消息中间件架构,你会怎么做?请从存储、网络、架构三个层面说明。
【标准答案】存储层面:顺序写是基础——所有消息追加写入CommitLog,避免随机写;读端靠ConsumeQueue索引+PageCache加速;写完先写内存(异步刷盘),配合主从同步保证数据安全;文件预分配+mmap减少内存分配开销;高效存储格式,消息体支持压缩(LZ4/Zstd),减少磁盘IO。网络层面:Reactor线程模型——1个Acceptor线程接收连接,多个IO线程处理读写,业务线程池处理请求;批量发送(Producer端攒批)+批量压缩,减少网络RT;用零拷贝(sendfile)加速Broker到Consumer的数据传输;协议精简,避免冗余字段序列化开销。架构层面:分区(Partition/Queue)是并行度的基本单位——分区数决定最大并行度,百万TPS至少需要数百个分区分布到多台Broker;分区副本保证可靠性,ISR机制平衡可用性和一致性;Client端和Broker长连接+连接池复用,避免频繁建连;横向扩展靠加Broker+分区迁移,存储和计算分离(Broker只做消息转发和索引,存储层独立)。面试收尾:百万级不是单机撑的,本质是分区并行+顺序写+零拷贝+批量处理+水平扩展的综合结果。