三个价值
| 价值 | 场景 |
|---|---|
| 异步 | 发邮件、生成报表,主流程不必等 |
| 削峰 | 秒杀洪峰先入队,按消费能力慢慢处理 |
| 解耦 | 服务间通过事件通信,互不直连 |
典型结构
生产者 → 队列(带持久化)→ 消费者。队列在中间当缓冲,生产者挂了也不丢,消费者慢也不压垮上游。
两个必须想清楚的坑
- 重复消费:网络重试会让同一条消息被处理多次,消费者要做幂等(去重表、状态机);
- 顺序:单队列内通常有序,分片后同一业务键要路由到同一分片才能保序。
和 RPC 的区别
RPC 是「现在就要结果」的同步调用;消息队列是「稍后处理」的异步通知。下单后扣库存可用 RPC,发通知邮件更适合队列。
实战案例:三个队列引发的线上问题
- 消息重复导致重复扣款:多数队列只保证“至少一次”,消费者必须幂等(业务唯一键或去重表)。
- 顺序错乱:为提升吞吐做了多分区并行消费,同一订单的“创建 / 支付”被乱序处理。需要顺序时,应把同一定序键路由到同一分区。
- 积压无人发现:队列长度没有监控,堆积数百万条直到磁盘告警。应监控积压量与消费延迟,并配置消费者自动扩缩。
常见问题(FAQ)
消息会丢吗?取决于配置:持久化、副本数与确认机制缺一不可,否则重启或故障时会丢。死信队列有什么用?存放多次重试仍失败的消息,避免阻塞主队列并便于人工排查。什么时候不该用队列?需要同步结果、强一致或极低延迟的场景,直接调用更合适。怎么保证“恰好一次”?端到端很难做到,工程上通常用“至少一次投递 + 消费端幂等”等效实现。
消息设计:幂等键与重试策略
- 消息要有幂等键:把业务唯一标识(订单号 + 动作)作为消费端去重依据,而不是依赖消息 ID,因为重复投递时消息 ID 可能不同;
- 消费端去重表:用唯一索引做“插入即去重”,比“先查再写”在并发下更可靠;
- 重试要分级:可重试错误(网络抖动、下游限流)按指数退避重试;不可重试错误(参数非法、业务拒绝)直接进死信队列,避免无谓重试拖垮系统;
- 超时与重试要收敛:超时应小于上游等待时间,重试次数要有限,否则一次下游抖动会被放大成雪崩;
- 顺序与并存的取舍:需要顺序时按业务键分区;不需要顺序时应并行消费以提升吞吐,不要为了“可能有用”而全局串行。
最后,队列本身也要有可观测性:积压量、消费延迟、重试率与死信增长都应进入看板与告警,否则问题往往在磁盘告警时才被发现。
与业务一致性的结合
- 本地事务与发送要一致:业务写入与消息发送若不在同一事务中,会出现“数据写了消息没发”或反之。常见方案是先写本地消息表再由投递任务发送,保证最终一定发出。
- 消费失败要能人工介入:死信队列之外,应提供查看、重放与丢弃的操作入口与权限控制,避免只能靠改数据库来救火。
- 关键链路要有兜底:若队列长时间不可用,应明确业务是拒绝请求还是降级为同步处理,而不是让请求一直挂起。
- 消息体保持精简:只传标识与必要字段,消费方按需查询最新状态,避免消息携带会过期的冗余数据导致消费方使用陈旧值。
- 版本兼容:消费方与生产方的发布节奏不同,消息结构变更应保持向后兼容,新增字段可选,删除字段分阶段进行。
队列把同步调用变成异步协作,也随之把一致性责任转移到了业务侧。把这些约定提前写清,才能既获得削峰解耦的收益,又不引入难以排查的数据问题。