前言

消息驱动是企业里最常见的架构手段之一。面试官通常会问:

  • 为什么要用 MQ。
  • 什么时候适合同步,什么时候适合异步。
  • 消费失败怎么重试。
  • 怎么保证消息不重复处理。
  • 顺序消息怎么保证顺序。
  • 延迟消息怎么做订单超时关闭。

这类题目本质上在考你对“异步化、最终一致性、削峰、解耦、补偿”的理解。

为什么要用 MQ

MQ 常见价值:

  • 异步解耦。
  • 削峰填谷。
  • 事件驱动。
  • 失败重试。
  • 顺序处理。
  • 延迟任务。
flowchart TD
    A[同步调用链] --> B[接口变长]
    A --> C[耦合变强]
    D[MQ 事件驱动] --> E[生产者只负责发消息]
    E --> F[消费者按自己的节奏处理]
    F --> G[系统解耦和削峰]

适合用 MQ 的场景:

  • 下单后发短信、发积分、发优惠券。
  • 审批通过后触发多系统联动。
  • 日志、埋点、统计收集。
  • 大促流量削峰。
  • 延迟关闭订单。

不适合的场景:

  • 强同步返回且链路很短。
  • 结果必须立即给出且不能接受最终一致。
  • 数据量很小但系统复杂度会被 MQ 放大。

常见 MQ 选型

MQ 优点 典型优势 代价
Kafka 吞吐高,生态强 日志、埋点、流处理 事务语义和顺序控制更偏工程化
RocketMQ 事务消息、延迟消息、业务场景丰富 电商交易、订单、支付 运维复杂度中等
RabbitMQ 协议成熟,路由灵活 业务路由、确认机制细 吞吐不如 Kafka,集群扩展性相对弱

面试回答时不要只说“哪个更快”,要说它们在业务里的定位差异。

异步解耦怎么理解

同步链路:

  • 发起方必须等待所有下游完成。

异步链路:

  • 发起方只负责发起事件。
  • 下游按自己的节奏消费。

例如订单支付成功后:

  • 订单服务更新状态。
  • 积分服务加积分。
  • 营销服务发优惠券。
  • 通知服务发短信。

这些动作不应该全部绑在支付回调接口里,否则一个下游慢就会拖垮整个支付链路。

削峰填谷

大促、秒杀、活动抽奖时常见流量模型是“短时间暴增”。

常见方案:

  1. 网关限流。
  2. MQ 缓冲。
  3. 消费端按能力处理。
  4. 失败后重试和补偿。
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: 多次失败后进入死信

面试重点:

  • 重试不能无限打。
  • 死信不是垃圾桶,而是补偿入口。
  • 必须有监控和告警。

消费幂等怎么做

消息重复是常态,不是异常。

常见幂等方案:

  1. 业务唯一键去重。
  2. 消费表记录消息 ID。
  3. Redis 去重。
  4. 数据库唯一约束。
  5. 状态机条件更新。

例子:

1
2
3
update order_event
set status = 'DONE'
where event_id = ? and status = 'NEW';

如果更新行数为 0,说明已经处理过或状态不匹配。

顺序消息

顺序消息适合:

  • 同一订单的状态变更。
  • 同一用户的关键动作。
  • 同一设备的指令流。

保证顺序的关键是:

  • 同一个业务 key 路由到同一个分区或队列。
  • 单分区单线程消费。

但要注意:

  • 全局顺序代价太高。
  • 只要能接受局部顺序,就不要追求全局顺序。

事务消息

事务消息用来解决“本地事务和发消息”之间的一致性问题。

常见思路:

  1. 先发半消息。
  2. 执行本地事务。
  3. 成功则提交消息,失败则回滚。
  4. MQ 通过回查确认最终状态。

适合:

  • 订单创建后发出业务事件。
  • 资金变化后通知下游系统。

不适合:

  • 超高频、极低延迟、极简链路。

延迟消息

延迟消息常用于:

  • 订单超时关闭。
  • 未支付自动取消。
  • 发送提醒。

方案对比:

方案 优点 缺点
延迟消息 简单、与事件驱动统一 延迟精度受 MQ 能力影响
定时任务 可控、易补偿 轮询压力大
时间轮/调度中心 高性能 实现复杂

高频面试题

为什么消费端一定要做幂等?

因为 MQ 语义通常是至少一次,重复投递和重复消费都可能发生。消费端不幂等就会产生重复扣款、重复发货、重复发券等严重问题。

死信队列有什么用?

死信队列用于承接反复失败的消息,作为人工介入或补偿处理的入口。

Kafka、RocketMQ、RabbitMQ 怎么选?

Kafka 偏吞吐和日志流;RocketMQ 更偏业务消息和交易场景;RabbitMQ 路由灵活,适合中小规模业务编排。

发送消息成功就代表业务成功吗?

不代表。业务成功要结合本地事务、消息确认和下游消费结果综合判断。

顺序消息能解决所有顺序问题吗?

不能。它只解决“同一 key 的局部顺序”,而且会牺牲吞吐和并行度。

总结

消息驱动场景回答可以围绕这四件事:

  1. 为什么用 MQ:异步、解耦、削峰、事件驱动。
  2. 怎么保证不丢:确认、重试、事务消息、补偿。
  3. 怎么保证不重:幂等、去重、状态机、唯一约束。
  4. 怎么保证顺:局部 key 路由、分区、单线程消费。

企业里 MQ 的价值不是“中间插一个消息系统”,而是把高耦合同步链路变成可观测、可重试、可补偿的事件流。