Java面试要点:MQ异步解耦、重试、死信与消费幂等
前言
消息驱动是企业里最常见的架构手段之一。面试官通常会问:
- 为什么要用 MQ。
- 什么时候适合同步,什么时候适合异步。
- 消费失败怎么重试。
- 怎么保证消息不重复处理。
- 顺序消息怎么保证顺序。
- 延迟消息怎么做订单超时关闭。
这类题目本质上在考你对“异步化、最终一致性、削峰、解耦、补偿”的理解。
为什么要用 MQ
MQ 常见价值:
- 异步解耦。
- 削峰填谷。
- 事件驱动。
- 失败重试。
- 顺序处理。
- 延迟任务。
flowchart TD
A[同步调用链] --> B[接口变长]
A --> C[耦合变强]
D[MQ 事件驱动] --> E[生产者只负责发消息]
E --> F[消费者按自己的节奏处理]
F --> G[系统解耦和削峰]
适合用 MQ 的场景:
- 下单后发短信、发积分、发优惠券。
- 审批通过后触发多系统联动。
- 日志、埋点、统计收集。
- 大促流量削峰。
- 延迟关闭订单。
不适合的场景:
- 强同步返回且链路很短。
- 结果必须立即给出且不能接受最终一致。
- 数据量很小但系统复杂度会被 MQ 放大。
常见 MQ 选型
| MQ | 优点 | 典型优势 | 代价 |
|---|---|---|---|
| Kafka | 吞吐高,生态强 | 日志、埋点、流处理 | 事务语义和顺序控制更偏工程化 |
| RocketMQ | 事务消息、延迟消息、业务场景丰富 | 电商交易、订单、支付 | 运维复杂度中等 |
| RabbitMQ | 协议成熟,路由灵活 | 业务路由、确认机制细 | 吞吐不如 Kafka,集群扩展性相对弱 |
面试回答时不要只说“哪个更快”,要说它们在业务里的定位差异。
异步解耦怎么理解
同步链路:
- 发起方必须等待所有下游完成。
异步链路:
- 发起方只负责发起事件。
- 下游按自己的节奏消费。
例如订单支付成功后:
- 订单服务更新状态。
- 积分服务加积分。
- 营销服务发优惠券。
- 通知服务发短信。
这些动作不应该全部绑在支付回调接口里,否则一个下游慢就会拖垮整个支付链路。
削峰填谷
大促、秒杀、活动抽奖时常见流量模型是“短时间暴增”。
常见方案:
- 网关限流。
- MQ 缓冲。
- 消费端按能力处理。
- 失败后重试和补偿。
flowchart LR
A[流量高峰] --> B[网关限流]
B --> C[消息队列缓冲]
C --> D[消费者按能力处理]
D --> E[库存/订单/通知]
面试回答:
MQ 不是把流量消失了,而是把瞬时流量转换成可处理的消息积压,再通过消费者能力慢慢消化。
消息可靠性
消息系统的核心问题通常不是“能不能发出去”,而是:
- 发出去了,消费没消费到。
- 消费到了,业务没处理成功。
- 业务处理成功了,消息确认失败。
至少一次、至多一次、恰好一次
| 语义 | 含义 | 实际特点 |
|---|---|---|
| 至少一次 | 消息可能重复 | 最常见,依赖幂等 |
| 至多一次 | 消息可能丢失 | 风险大,不常用 |
| 恰好一次 | 理论理想态 | 实际实现成本高 |
企业系统里常见的是“至少一次 + 幂等处理”。
重试与死信
消费失败后常见处理:
- 本地重试。
- MQ 重试。
- 进入死信队列。
- 人工补偿。
sequenceDiagram
participant P as Producer
participant Q as MQ
participant C as Consumer
participant D as Dead Letter
P->>Q: 发送消息
Q->>C: 投递消息
C->>C: 处理失败
C->>Q: 触发重试
Q->>C: 再次投递
C->>D: 多次失败后进入死信
面试重点:
- 重试不能无限打。
- 死信不是垃圾桶,而是补偿入口。
- 必须有监控和告警。
消费幂等怎么做
消息重复是常态,不是异常。
常见幂等方案:
- 业务唯一键去重。
- 消费表记录消息 ID。
- Redis 去重。
- 数据库唯一约束。
- 状态机条件更新。
例子:
1 | update order_event |
如果更新行数为 0,说明已经处理过或状态不匹配。
顺序消息
顺序消息适合:
- 同一订单的状态变更。
- 同一用户的关键动作。
- 同一设备的指令流。
保证顺序的关键是:
- 同一个业务 key 路由到同一个分区或队列。
- 单分区单线程消费。
但要注意:
- 全局顺序代价太高。
- 只要能接受局部顺序,就不要追求全局顺序。
事务消息
事务消息用来解决“本地事务和发消息”之间的一致性问题。
常见思路:
- 先发半消息。
- 执行本地事务。
- 成功则提交消息,失败则回滚。
- MQ 通过回查确认最终状态。
适合:
- 订单创建后发出业务事件。
- 资金变化后通知下游系统。
不适合:
- 超高频、极低延迟、极简链路。
延迟消息
延迟消息常用于:
- 订单超时关闭。
- 未支付自动取消。
- 发送提醒。
方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 延迟消息 | 简单、与事件驱动统一 | 延迟精度受 MQ 能力影响 |
| 定时任务 | 可控、易补偿 | 轮询压力大 |
| 时间轮/调度中心 | 高性能 | 实现复杂 |
高频面试题
为什么消费端一定要做幂等?
因为 MQ 语义通常是至少一次,重复投递和重复消费都可能发生。消费端不幂等就会产生重复扣款、重复发货、重复发券等严重问题。
死信队列有什么用?
死信队列用于承接反复失败的消息,作为人工介入或补偿处理的入口。
Kafka、RocketMQ、RabbitMQ 怎么选?
Kafka 偏吞吐和日志流;RocketMQ 更偏业务消息和交易场景;RabbitMQ 路由灵活,适合中小规模业务编排。
发送消息成功就代表业务成功吗?
不代表。业务成功要结合本地事务、消息确认和下游消费结果综合判断。
顺序消息能解决所有顺序问题吗?
不能。它只解决“同一 key 的局部顺序”,而且会牺牲吞吐和并行度。
总结
消息驱动场景回答可以围绕这四件事:
- 为什么用 MQ:异步、解耦、削峰、事件驱动。
- 怎么保证不丢:确认、重试、事务消息、补偿。
- 怎么保证不重:幂等、去重、状态机、唯一约束。
- 怎么保证顺:局部 key 路由、分区、单线程消费。
企业里 MQ 的价值不是“中间插一个消息系统”,而是把高耦合同步链路变成可观测、可重试、可补偿的事件流。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Junly博客!
评论


