
秒杀异步下单架构从同步到三阶段演进本文是「10Wqps 秒杀架构」系列的第二篇。在上一篇中我们分析了秒杀的业务特征和流量模型明确了 Tomcat 和 MySQL 是链路瓶颈。本篇聚焦如何通过异步架构突破这两个瓶颈。一、开篇在秒杀系统中一个下单请求到达服务端后至少需要经历 8 个处理步骤验证码校验、活动有效性检查、黑名单检查、库存校验、库存扣减、价格计算、订单创建、真实库存更新。如果所有步骤都在一个线程中同步执行单个请求的处理延迟合计可能达到 500ms。这意味着一个部署了 200 个 Tomcat 线程的服务实例在最理想的情况下也只能支撑200 / 0.5 400 qps。面对 10Wqps 的秒杀峰值这种架构毫无招架之力。异步架构是破解这个困局的核心手段。二、问题拆解同步模式为什么不行2.1 同步下单的 8 步流程MySQLRedis商城服务用户MySQLRedis商城服务用户发起秒杀请求① 校验验证码② 判断活动是否结束③ 检查黑名单④ 查询库存⑤ 扣减缓存库存⑥ 计算秒杀价格⑦ 创建订单⑧ 扣减真实库存返回秒杀结果这 8 个步骤在同步模式下是串行执行的。每个步骤都会阻塞当前线程直到该步骤完成。2.2 性能瓶颈分析步骤耗时参考值操作类型校验验证码~10msRPC 调用验证码服务判断活动结束~5msRedis 查询黑名单检查~10msRedis 查询查询库存~5msRedis 查询扣减缓存库存~10msRedis 写操作计算秒杀价格~5ms本地计算创建订单~200msMySQL 写操作最重扣减真实库存~50msMySQL 写操作合计~500ms—可以看到MySQL 的两次写操作占总耗时的 50% 以上。同步模式下长事务会长时间占用数据库连接和 Tomcat 线程峰值 QPS 时连接池很快耗尽。2.3 同步架构的坍塌过程当并发量超出线程池容量时Tomcat 开始排队请求排队的请求占用越来越多的内存和 CPU数据库连接池也在同时饱和最终——请求超时服务假死雪崩发生。这就是为什么同步架构在秒杀场景中不可行的根本原因线程是所有请求共享的有限资源不能被任何一个慢操作长期霸占。三、核心方案异步架构的演进3.1 两阶段异步将快慢操作解耦两阶段异步的核心思想是把轻量操作放在同步阶段第一阶段把重量操作放在异步阶段第二阶段。MySQL下单Worker消息队列Redis商城服务用户MySQL下单Worker消息队列Redis商城服务用户第一阶段预下单同步轻量第二阶段正式下单异步重量发起秒杀请求① 校验验证码② 判断活动是否结束③ 检查黑名单④ 查询库存⑤ 扣减缓存库存Lua 原子操作发送下单消息返回排队中消费下单消息⑥ 计算秒杀价格⑦ 创建订单⑧ 扣减真实库存第一阶段预下单包含步骤 ① 到 ⑤全部是 Redis 操作或本地判断耗时约 30-50ms。第二阶段正式下单包含步骤 ⑥ 到 ⑧通过 MQ 异步触发不再阻塞用户请求的线程。两阶段的关键收益Tomcat 线程从被 500ms 的请求占满变为被 50ms 的请求占满单实例 QPS 从 400 提升到约200 / 0.05 4000 qps提升 10 倍。不过这里存在一个问题第一阶段失败时比如库存不足不应该发送下单消息第二阶段不触发。反过来第一阶段成功后库存预扣成功必须保证消息发送成功——这属于消息 0 丢失的范畴将在 MQ 架构篇详细展开。3.2 三阶段异步进一步释放 IO 线程两阶段方案中服务端在发送 MQ 消息后仍然需要等待一个同步的确认即使这个过程比同步下单快很多。三阶段方案将 MQ 发送也完全异步化响应立即返回给前端MQ 发送在后台进行前端通过轮询获取结果。MySQL下单WorkerRocketMQRedis商城服务用户前端MySQL下单WorkerRocketMQRedis商城服务用户前端alt[库存不足][预扣成功]前端开始短轮询每 2 秒一次loop[轮询直到出结果]POST /seckill/submitRedis Lua 原子扣减库存{code: 0, msg: 已抢完}异步发送下单消息{code: 1, msg: 排队中, orderId: xxx}GET /seckill/result?orderIdxxx查询订单状态{status: processing | success | failed}消费下单消息创建订单 扣减库存更新订单状态为 success三阶段方案的关键设计第一阶段同步Redis Lua 原子扣减库存成功则返回orderId失败则直接返回已抢完。耗时 30ms。第二阶段异步发送MQ 消息发送与 HTTP 响应解耦——先响应前端再确保消息发送成功。第三阶段轮询获取结果前端每 2 秒轮询一次订单状态直到返回success或failed。相比同步模式不长时间占用 HTTP 连接。3.3 短轮询设计细节前端轮询看似简单但在秒杀场景下有几个设计要点轮询间隔2-3 秒。太短500ms会给服务端造成额外的查询压力太长10s用户体验差。轮询上限设置最大轮询次数如 30 次即 60 秒超时后提示用户请到订单中心查看结果。轮询接口只做 Redis 查询O(1)不走 MySQL确保查询本身不成为瓶颈。接口限流轮询接口也需要限流。按用户 ID 限制同一用户 2 秒内只能查询一次同一订单。3.4 同步 vs 两阶段 vs 三阶段性能对比维度同步模式两阶段异步三阶段异步用户请求耗时~500ms~100ms同步部分~30ms同步部分Tomcat 单实例 QPS~400~2,000~6,000HTTP 连接占用长时间占用短时间占用极短时间占用下单延迟即时秒级MQ 消费秒级MQ 消费 轮询感知实现复杂度低中中高前端改动无小增加排队提示中增加轮询逻辑对于 10Wqps 级别的秒杀场景三阶段异步是唯一可行的选择。同步部分只做 Redis Lua 原子扣减耗时 30ms所有重操作通过 MQ 异步完成HTTP 连接被快速释放整体吞吐量提升 15 倍以上。四、边界与异常4.1 第一阶段成功但消息发送失败这是两阶段/三阶段架构中最危险的异常——库存已经预扣了但下单消息没发出去用户得不到订单。这属于掉单问题解决方案将在 MQ 异步架构篇 详细展开核心手段包括 RocketMQ 事务消息、本地消息表 定时扫描。4.2 MQ 消费延迟过大在大促峰值时MQ 可能积压数十万条消息用户轮询 60 秒仍查不到结果。应对监控消息积压量超过阈值如 10000 条时自动扩容消费者实例同时前端在轮询超时后提示用户请到订单中心查看而非秒杀失败。4.3 轮询接口的缓存一致性订单状态更新后如果 Redis 主从存在延迟轮询接口可能读到旧状态。解决订单创建后 1 分钟内用户查询走 Redis 主节点1 分钟后切换为从节点查询。4.4 库存预扣后用户放弃支付三阶段库存管理中的回退环节将在 库存扣减与数据一致性篇 详细讨论。五、总结同步模式的天花板是 Tomcat 线程数。当一个请求耗时 500ms 且大量线程被 MySQL 操作阻塞时单实例只能撑 400 qps远远不够。两阶段异步将请求拆分为轻量同步 重量异步同步阶段只做 Redis 操作~50ms性能提升 10 倍。三阶段异步进一步将 MQ 发送与 HTTP 响应解耦同步部分耗时降至 ~30ms单实例 QPS 提升至 6000是秒杀场景的推荐方案。短轮询是实现三阶段方案的前端标配——不长时间占用 HTTP 连接后端只做 Redis 查询实现成本低。异步化引入了新的复杂度——消息可靠性、幂等性、库存回退——这些将在后续文章中逐一攻克。