RabbitMQ 面试问答 1. 为什么要消息队列很多业务不是一个接口干到底下单之后还要发短信、推仓储、写搜索索引——如果全同步串行一个环节慢或挂了整条链路都受影响。消息队列解决三类问题异步主流程只负责发消息下游慢慢处理用户不用干等解耦订单服务不用直接依赖短信、仓储各改各的接口不变削峰秒杀瞬间流量先堆在队列里消费者按自己的处理能力慢慢消费代价是系统变复杂重复消费、顺序、延迟、运维都要额外考虑。MQ 换的是可用性和扩展性不是加了就更快。2. 什么场景用 RabbitMQ适合服务之间发业务事件、任务分发、需要灵活路由不同消息进不同队列、吞吐量在万级/秒量级。不太适合调用方必须立刻拿到下游结果才能继续要回放全量历史日志做流式计算更适合 Kafka团队没人运维却上了要精细调参的集群。和 Redis 队列比Redis List / Stream 适合量小、能丢几条RabbitMQ 有确认、持久化、死信适合业务消息要可靠投递的场景。3. 消息怎么从生产者到消费者RabbitMQ 走 AMQP 模型路径是生产者 - Exchange交换机- Queue队列- 消费者Exchange 不存消息只负责路由——看 routing key 和 binding 规则决定消息进哪个 Queue。Queue 才存消息等消费者来取。很多人误以为生产者直接发 Queue其实中间必须经过 Exchange除非用默认 Exchangerouting key 必须等于 queue 名那是 direct 的特例。Binding 是 Queue 和 Exchange 之间的绑定关系声明什么样的 routing key 能进我这个队列。4. Exchange 四种类型类型怎么路由典型用法directrouting key 和 binding key完全相等按业务类型分发支付走支付队列取消走取消队列fanout不看 routing key广播到所有绑定的队列配置刷新、缓存失效通知所有节点topicrouting key 按.分段*匹配一个词#匹配零个或多个日志分级order.*收所有订单日志order.payment.#只收支付相关headers按消息头匹配几乎不用知道有就行日常 direct 和 topic 最多。fanout 做广播。5. 怎么保证消息不丢可靠性要保三段缺一段都不行① 生产者 → Broker发完不能默认成功。开Publisher ConfirmBroker 收到并路由后会回 ack没有队列能接收时回 nack 或 return需开 mandatory。别用事务模式性能差。② Broker 存盘Broker 重启消息还在需要三个开关同时开Exchange 声明durable trueQueue 声明durable true消息delivery mode 2persistent持久化是写磁盘不是魔法仍受刷盘策略影响且会降低吞吐。③ Broker → 消费者生产环境用manual ack业务处理成功再确认。auto ack是一推给消费者就确认了处理到一半进程挂了消息就丢了。Ack处理成功告诉 Broker 这条可以删了NackNegative Acknowledgment处理失败告诉 Broker 这条没消费成功requeue: true放回队列稍后重试requeue: false不再重投配合死信交换机进 DLQ6. Prefetch 是什么basicQos(prefetchCount: n)限制单个 Channel 上还没 ack 的消息最多几条。不是把总数平均分给所有消费者。每个消费者各自有一个 Channel各自有自己的 prefetch 上限。Broker 推消息时某消费者 unacked 条数已达上限就不再给它推转给其他还没满的消费者。prefetch 1是常见配置每个消费者同一时间最多拿 1 条ack 后才拿下一条。多消费者共抢一个队列时快慢消费者之间分配较均匀慢消费者也不会一口气占很多消息不 ack。代价是吞吐略低往返次数多消息小、处理快时可调到 1050。不设 prefetch 时Broker 可能一次性推几百条给某个消费者慢消费者内存压力大其他消费者反而空闲。prefetch 是 per-channel 的每个消费线程用自己的 Channel各自配置。7. 重复消费与投递语义第 5 节讲的是消息不丢。这一节讲另一个问题同一条消息消费者可能收到不止一次。语义含义RabbitMQ 能不能做到最多一次可能丢但不会重复auto ack 就是这样不推荐至少一次不会丢但可能重复Confirm 持久化 manual ack生产常见恰好一次不丢也不重复Broker 端到端做不到靠业务幂等兜底RabbitMQ 配好了可靠性实际就是至少一次消息一定能送到但同一条可能被消费两次甚至更多。MQ 不负责去重消费端要自己扛。重复常见来源有三类。生产者重试发完没收到 Confirm以为失败又发一遍队列里两条内容一样的消息。消费者 ack 超时或忘了 ack业务已跑完DB 已扣库存但 Ack 没发出去或处理超时Broker 认为还没消费成功会重新投递。网络抖动Ack 包丢了Broker 没收到确认同样重投。本质都是 Broker 不知道处理完了所以再发一次。幂等指同一个操作执行多次结果和执行一次一样。支付消息来两次库存只能扣一次。消费端不能假设消息只来一次重复只能在消费端挡用orderId eventType做幂等表插过就跳过或对业务键加数据库唯一约束或用状态机订单已是 Paid 就不再扣款。8. 死信队列DLX正常队列里的消息消费者取出来处理、Ack 掉。有些消息在正常流程里走不下去了——消费不了、过期了、队列满了——既不能一直占着业务队列重试也不能直接丢掉这类叫死信Dead Letter。**死信队列DLQ**专门收这些消息相当于业务队列旁边的隔离区。配置上业务队列声明x-dead-letter-exchange指向死信交换机DLXDLX 再绑定 DLQ消息一旦变成死信Broker 自动转过去。变成死信的情况Nack(requeue: false)不再重试消息或队列 TTL 过期队列超过max-length被挤出。DLQ 三个常见用途。隔离毒消息每次消费都抛异常的消息若一直Nack(requeue: true)会无限循环本地重试 N 次仍失败就Nack(requeue: false)进 DLQ业务队列恢复。人工排查补偿查看 DLQ 内容、修数据后重新发回或脚本补偿。延迟投递消息设 TTL过期变死信进 DLQ消费者监听 DLQ 相当于延迟到了才处理见第 10 节。DLQ 和业务队列分开建用于告警、排查、补偿不混在主流程里。9. 消息顺序能保证吗单队列 单消费者基本有序。同一队列多个消费者并发处理顺序没法保证。要大体有序按业务键 hash 到固定队列比如orderId % 4进 4 个队列之一每个队列只开一个消费者或者业务层容忍乱序用版本号做合并。别说 MQ 保证顺序。10. 延迟消息订单 30 分钟未支付自动关单需要延迟投递。常见做法TTL DLX消息设 TTL过期后进死信队列相当于延迟到了才消费delayed-message-exchange 插件最直接业务层延迟表 定时扫描不依赖 MQ 特性TTL DLX 要注意同队列里不同 TTL 的消息队头没过期会堵住后面的叫队头阻塞。11. 和 Kafka 怎么选RabbitMQKafka定位消息代理智能路由分布式日志高吞吐流消费后通常 ack 后从队列删除消息保留可回溯重放吞吐量万级/秒百万级/秒典型场景任务分发、业务事件、服务解耦日志采集、流计算、事件溯源订单支付通知仓储、发短信——RabbitMQ 合适。用户行为日志进数仓、Flink 实时聚合——Kafka 合适。不是谁更好是问题类型不同。12. 本地事务和发 MQ 怎么一致用户下单同一个操作里既要写数据库插订单又要发 MQ 通知仓储备货。至少不能出现库里有订单但仓储没收到消息或仓储收到消息但订单没建成。数据库和 RabbitMQ 是两个独立系统先写 DB 再发 MQ 是两步任何一步都可能单独失败。DB 成功、发 MQ 前程序崩溃——仓储永远不知道。MQ 发出去了、DB 事务回滚——仓储收到备货通知订单不存在。在 DB 事务里直接BasicPublish也解决不了MQ 提交不归 DB 管DB 回滚带不走已发出的消息。Outbox的做法发 MQ 这件事先记到 DB 里跟业务数据进同一个本地事务。1. 开启 DB 事务插入订单表插入 outbox 表payload、statuspending 2. 提交事务 —— 订单和 outbox 要么都在要么都不在 3. 后台 Worker 扫 outbox发到 RabbitMQ成功则标记 sentWorker 发完还没标记就挂了可能重复发靠消费者幂等第 7 节。还没发就挂了outbox 仍是 pending下次重试。最终至少会发出去一次。消费端同理DB 事务处理业务并提交成功再 Ack失败 NackAck 前做好幂等。DB 和 MQ 的一致性靠 Outbox 消费者幂等做最终一致。13. 消息堆积怎么排查先看队列深度分ready等着被消费和unacked已推给消费者还没 ackunacked 高消费者慢、卡住了、忘了 ack、prefetch 设太大ready 高、消费者少扩容消费者或优化单条处理逻辑没有消费者订阅丢了、连接断了、vhost 权限不对单条处理太慢瓶颈在下游 DB 或 API加消费者没用先分清是生产太快还是消费太慢再动刀。内存/磁盘告警时 RabbitMQ 会流控阻塞生产者常见原因也是消费者停了或消息不 ack 堆着。14. 高可用与集群RabbitMQ 的高可用分两层多台 Broker组集群以及队列多副本消息不随单节点挂掉而丢。集群是多台节点连成一组共享 Exchange、Queue、Binding 等元数据定义。客户端连任意节点都能发收消息节点之间同步路由、互相转发。三台组了集群不等于三条消息分别存三台——集群同步的是定义普通队列里的消息内容默认只放在声明这个队列的那台节点上。集群有 A、B、C 三台应用在 A 上声明了order.queueA 节点存着 order.queue 里的 1000 条消息 B、C 节点知道 order.queue 存在可转发但本地没有这 1000 条连 B 发消息B 转给 A 存连 B 消费B 从 A 取。A 挂了这个普通队列暂时不可用。集群管的是组网、路由、接入拓扑全集群一致连接可分散到任意节点Quorum Queue 等多副本方案也得以集群为前提。消息本身要不丢还得靠下面的队列类型。类型说明普通队列数据在单个节点节点挂了就完了经典镜像队列老方案主从同步复制有脑裂等问题老项目可能还在用Quorum Queue3.8 推荐Raft 共识多副本主节点挂了自动切换生产新部署优先 Quorum Queue集群至少 3 节点副本数通常 3。和 Redis Cluster 不同Redis 按 key分片每个 key 只在一台RabbitMQ 集群是组网 队列选副本策略一条队列可通过 Quorum 在多个节点有副本。RabbitMQ 不是消息追踪系统消息 ack 后从队列删除没法按 ID 查历史追踪靠业务层日志或 outbox 状态表。