秒杀、抢购、定时开奖这类场景有个共同特点:请求不是均匀来的,而是几秒钟内涌进几十倍于平时的流量。让请求直接打到数据库,结果通常是连接池被打满、慢查询堆积、最后整条链路雪崩。
消息队列是这类问题最常见的一层缓冲。但它不是"接进来就能削峰"——流量怎么进来、消费者用哪种、重试怎么配、堆积了怎么处理,每一环都决定这套方案是救命还是添乱。这篇用一个秒杀下单场景,把 RocketMQ 的完整链路拆开讲。
一、先界定:削峰填谷解决的是哪一段
订单链路通常是「网关 → 订单服务 → 数据库」,秒杀时瓶颈几乎总在最后一段。假设单库稳定写入 2000 TPS,瞬时请求却到 5 万 TPS,差了 25 倍,硬扛就是连接数暴涨、锁等待变长、超时报错。
引入队列后变成:
用户请求 → 网关 → 订单服务(校验 + 发消息)→ [RocketMQ] → 消费组(落库)
↑
按数据库能承受的速率消费
两个点必须说清楚:
一,队列把"同步写库"变成"异步排队"。 请求通过前置校验(库存预占、限购、风控)后,发一条消息即可返回"排队中",真正落库由消费者按自己节奏做。
二,队列吸收的是"峰值差",不是"无限流量"。 入口流量持续高于消费能力时,消息只会堆到存储上限,最终仍失败。前置校验必须挡住绝大部分无效请求——这一步不能指望队列来做。
| 场景要素 | 适合削峰 | 不适合削峰 |
|---|---|---|
| 流量特征 | 短时脉冲,峰后有低谷 | 持续高水位,无低谷 |
| 用户预期 | 可接受"排队中/稍后出结果" | 必须同步返回最终结果 |
| 业务可拆 | 能拆成"收单"与"处理"两段 | 全链路强一致 |
| 失败容忍 | 可延迟、可补偿 | 一步错即整体回滚 |
秒杀下单属于典型的"可拆、可延迟、可补偿",这也是它成为标准演示场景的原因。
二、消息类型怎么选
选错类型,后面付双倍代价:
| 类型 | 语义 | 用在本场景 | 代价 |
|---|---|---|---|
| 普通消息 | 并发投递,不保序 | 下单请求入队、库存通知 | 不保证消费顺序 |
| 顺序消息 | 按 ShardingKey 分区内 FIFO | 同一订单的多步状态流转 | 并发度受分区数限制 |
| 事务消息 | 本地事务与发消息最终一致 | 扣库存与下单记录的一致性 | 须实现回查逻辑 |
| 定时/延时 | 指定时刻或延迟后投递 | 15 分钟未支付自动关单 | 时长有上限,精度有误差 |
几个容易踩的边界:
- 顺序消息只保证分区内有序。 官方定义是消息按 ShardingKey 分区、同一分区 FIFO、不同分区不保证。所以 Key 必须选订单号这类能唯一标识业务实体的字段。用用户 ID 做 Key 会把同一用户的多笔订单塞进同一分区,反而拖慢整体消费。
- 事务消息解决"本地事务和消息发送不能两头空"。 先在本地事务里扣预占库存、写订单记录,再提交消息;若提交后消息发送失败,服务端回查本地事务状态决定是否投递。这要求你实现可幂等的回查接口,不是配个开关就完事。
- 定时消息有精度误差和时长上限。 5.0 系列定时精度存在 1~2 秒误差;定时时长上限按实例类型区分,标准版与 Serverless 标准版/专业版最大 7 天,专业版、铂金版最大 40 天。买标准版却想压 15 天后的关单任务,会直接发送失败。以官方页面实时展示为准。
三、容量怎么估:五步推到位
这是最容易被跳过、也最容易翻车的一步。
第一步,算消息量。 5 万请求,前置校验放行 2000 单,每单 1 条下单消息 + 1 条库存通知 = 4000 条。
第二步,换算成计费的"收发次数"。 关键规则:收发次数 = 接收次数 + 投递次数。发一条算 1 次接收,服务端投递给消费者算 1 次投递,无论是否消费成功、是否返回 ACK 都计。正常走完是 2 次。
第三步,乘消息类型倍数。 顺序发送/消费、事务发送、定时/延时发送按 5 倍计算,轻量消息按 3 倍。若 4000 条里 2000 条走事务消息:
普通消息 2000 条 × 2 次 = 4000 次
事务消息 2000 条 × 2 次 × 5 倍 = 20000 次
合计 24000 次
第四步,乘负载倍数。 按 4 KB 向上取整,18 KB 的消息按 ⌈18/4⌉ = 5 次计。订单消息带商品、地址、优惠,很容易超 4 KB,这项经常被漏算。
第五步,落到单价。 Serverless 实例按阶梯计价,公共云地域(华东1、华东2、华南1、华北2 等)第一阶梯(0,10] 亿次:普通消息 2 元/百万次,定时/延时与事务消息 10 元/百万次,超阶梯后单价逐级下降。另有实例费 0.3 元/个/小时(约 216 元/月),以及超出免费额度的连接数、资源费(Topic 与 Group 总数)。
按上例算下来是几毛钱级别——削峰场景真正的成本压力不在消息费,而在前置校验和消费者机器上。 价格以官方页面实时展示为准。
流量稳定可预测时可选"预留 + 弹性":预留容量共享部署第一阶梯 0.0009 元/TPS/小时,超出部分按弹性 TPS 计费(共享 0.00012 元/TPS/小时)。官方选型参考是:小时级并发约 10 万用户、QPS 2000~3000 的电商场景,预留 2000 TPS 基本够用,不必为极端峰预留。
四、消费者选型:Push 还是 Simple
5.0 系列两类消费者的差别不只是 API 风格:
| 维度 | PushConsumer | SimpleConsumer |
|---|---|---|
| 消息拉取 | SDK 托管 | 应用主动拉取 |
| 重试状态机 | 有 WaitingRetry 状态 | 无 WaitingRetry,靠不可见时间控制 |
| 重试间隔 | 无序消息阶梯退避(10s→30s→1min…→2h) | 重试间隔 = 不可见时间 − 实际处理时长 |
| 单次处理超时 | 默认 230 分钟,不支持修改 | 最大 12 小时,最小 10 秒 |
| 适合 | 常规业务消费、开发快 | 需精细控制速率与超时的削峰消费 |
秒杀落库这个场景推荐 SimpleConsumer:不可见时间就是"处理预算",设成 30 秒这类业务可接受的上限,处理不完自动重试,不必关心 SDK 内部状态流转;也天然适合按数据库能力限速消费——拉多少、批多大全在自己手里。
PushConsumer 更省事,但单次消费超时固定 230 分钟且不可改。这个数字放在秒杀场景偏长:一次卡住的消费要挂很久才触发重试,堆积看起来不明显,实际处理已经停滞。
无论哪种,消费逻辑必须做三件事:
- 幂等兜底。 至少一次投递意味着消息可能重复,落库用唯一键(订单号 + 业务动作)做幂等,或先查后写加分布式锁。
- 区分可重试与不可重试。 参数格式错误、商品已下架这类业务性失败重试一万次也不会成功,应直接落库为失败态并告警;只有网络抖动、下游超时这类才返回失败。
- 不要用"消费失败"做限流。 官方使用建议明确写了:限流应延迟获取消息,而不是让消息进重试链路——重试是多倍计费的,还会污染重试队列。
五、堆积了怎么办:可执行的排查顺序
告警响了,最忌讳直接扩消费者。合理顺序是:
一,看是不是同一批消息在打转。 若是同一条消息反复重试造成的"假堆积",扩机器毫无意义。5.0 系列 PushConsumer 默认最大重试 16 次(上限可配到 1000),间隔阶梯递增:10 秒、30 秒、1/2/3/4/5/6/7/8/9/10 分钟,第 13~16 次为 20 分钟、30 分钟、1 小时、2 小时,超过 16 次后每次都是 2 小时。一条坏消息能在队列里折腾 10 小时以上。所以上线第一件事是打开"保存死信消息"开关——它默认关闭,关闭状态下超次数消息会被直接丢弃,既不进死信 Topic,也无迹可查。
二,看消费者是否真在消费。 连接数、线程池、下游数据库连接池,任何一环打满,表现都是"堆积但消费者不报错"。
三,区分能力不足还是逻辑变慢。 同样机器昨天 3000 TPS 今天只有 300,多半是下游变慢(比如新加了一次远程调用)。
四,才扩容或降级。 顺序消息的并发上限受分区数限制,扩消费者不一定提吞吐;必要时先降级非核心链路(如通知类消息延后处理)。
五,沉淀结论并确认保留时长。 5.0 系列消息保留最短 24 小时、最长 720 小时(30 天)。堆积时间超过配置的保留时长,超期消息会被滚动删除,这部分只能从业务侧补偿。
六、上线前必须确认的五件事
- 实例版本与定时上限对齐业务预期。 标准版 7 天、专业版/铂金版 40 天,别做到一半才发现关单任务压不进去。
- 重试策略确认到客户端协议。 官方注明:重试策略(阶梯退避、固定间隔)仅对 gRPC 协议客户端生效;Remoting 协议需自己控制
maxReconsumeTimes,最大重试次数以客户端设置为准。 - 死信开关打开,并给死信 Topic 配消费与告警。 死信没人看,等于没开。
- 访问控制别用默认配置。 5.0 实例二选一:智能身份识别(内网 VPC 自动识别,公网需填用户名密码)或 ACL 身份识别。生产建议配 IP 白名单,生产端与消费端拆成不同 ACL 用户按最小权限授权。消息体也守在 4 MB 以内、属性不超 16 KB,大对象走 OSS。
- 监控与压测都要打穿消费端。 客户端看 TPS、失败率、处理耗时;服务端看堆积量、重试量、死信量,只盯"发送成功率"看不到堆积。压测只压生产者没意义,要验证"下游慢一半时能否在可接受时间内排空堆积"。
七、一个容易忽略的认知
消息队列削峰的本质是用"时间"换"稳定性":把 3 秒内的峰值摊到 30 分钟平滑处理。它不提高系统总处理能力,也不会让数据库变快。
所以真正该问的是:业务能不能接受这个延迟? 如果用户点完"立即购买"必须在 200 毫秒内看到"下单成功",链路只能做成"同步返回排队号 + 异步出结果",页面文案、重试逻辑、客服话术都得跟着改。技术方案决定能力上限,产品设计决定用户感受——这两件事必须一起定,否则上线后第一个投诉就能推翻整套架构。
如果还在选型阶段:RocketMQ 偏业务消息与交易链路,Kafka 偏大数据与日志流,轻量消息队列适合云产品间通知,MQTT 面向物联网设备。关键不是"哪个更强",而是"消息是什么形态、消费方是谁、要不要顺序和事务"。产品能力与计费细节可参考 云消息队列 RocketMQ 版官方页面,价格与规格以官方页面实时展示为准。