开篇词:6000万会员规模下,我们是怎么做秒杀系统的

第一次自由卡促销活动,把核心交易系统打挂了。

自由卡是一种虚拟预付卡,用户先充值,再用卡内余额购买商品。配合半价或大力度折扣时,对用户的吸引力非常强。活动一开始,瞬时并发请求直接把核心系统的数据库连接池打满,整个交易链路不可用。不光秒杀用户受影响,正常的下单、支付也全部阻塞。

那次事故之后,CTO拍板:做一套独立的秒杀系统,和核心交易链路完全隔离。秒杀流量不管多大,不能影响正常交易。

后端组建了两个技术小组:会员用户组和订单支付组,加起来十几位同学参与。前端有独立团队配合。纯功能开发周期用了一个半月,加上专职测试、系统联调和多轮压测,从立项到正式扛住第一波自由卡活动,前前后后差不多两个半月。

上线之后,这套系统扛住了无数次自由卡活动。每次活动期间,DBA、运维、技术经理、核心开发都在现场值班。在上线前的两个多月里,团队专门做了多轮全链路压测和演练:先用模拟数据把网关、秒杀核心域、订单域、自由卡域一整条链路压到接近生产峰值;再在预生产环境按真实活动时间窗口做演练,包括限流开关、降级开关、队列堆积处置预案。确保每个环节在出问题时都有可落地的应急手册,不是线上临时想对策。

这个专栏,就是把这套秒杀系统从架构设计到关键代码,完整地拆解给你看。

这不是玩具级Demo。这是一套在6000万会员规模下扛过真实流量的秒杀系统,你能看到完整链路、关键代码,以及背后每个设计决策的取舍。

和市面上的秒杀课有什么不一样

市面上讲秒杀的内容不少。大多停留在原理层面:讲讲限流、讲讲缓存、讲讲消息队列,然后写个单体应用的Demo跑一跑。用来理解概念可以,但离真实生产环境差得远。

拿扣库存来说。Demo里用Redis做个DECR,扣完了事。真实的生产环境要面对的问题多得多:Lua脚本怎么保证校验和扣减的原子性,库存什么时候从数据库预热到Redis,活动还没开始就有请求进来怎么办,缓存里的库存和数据库里的库存出现不一致怎么补偿。这些问题在Demo里永远碰不到。

再比如支付。Demo里通常不涉及支付环节,或者用一个Mock接口模拟一下。实际的秒杀系统,从微信支付的JSAPI下单到异步回调,从支付超时的订单关闭到用户重复支付的退款处理,每个环节都有坑。这些都是在线上真实遇到过、处理过的问题。

还有分库分表。秒杀订单量大,单表撑不住。按什么字段分片、分几张表、跨片查询怎么处理、分片键选错了数据倾斜怎么办,这些在架构设计阶段就要想清楚,上线之后再改代价极大。

这个专栏给你的,是一套完整的、经过生产环境验证的微服务秒杀系统。8个服务模块,从网关到核心域到支付到风控,每个模块的设计思路、关键代码和踩过的坑都会展开讲。

你能学到什么

整个专栏围绕真实的秒杀系统展开。下面列出一些你在其他课程里大概率看不到的设计细节。

入口防刷:不是加个验证码就完了

网关层的QPS限流只能控制总流量,挡不住针对性的脚本攻击。我们在第一次自由卡活动时就吃过亏:活动开始不到几分钟,有人用脚本刷掉了三分之一的卡,真正想买的用户反而没抢到。这件事直接推动了后面整套防刷体系的建设。

这套系统在网关限流之上,还做了三层入口防护:

  • 机审校验。前端调用预检接口拿到一个随机串和时间戳,根据时间戳用约定算法计算出一个变形串,在正式抢购请求时带回后端比对。这个串存在Redis里,30秒有效,用完即删。脚本如果不逆向前端代码,拿到原始串也过不了校验
  • 用户级限流。用Redisson的RRateLimiter给每个用户单独设限。即使某个账号写了脚本死命刷,也只能在自己的速率配额内制造压力,不会把整条链路拖垮
  • 小黑屋机制。活动还没开始就有请求反复探路的用户,会被自动标记为风险用户。活动开始后这批账号的请求直接过滤掉,不进入后续的库存扣减流程

入口层的防刷只是第一关。消费端在真正扣库存之前,还会拿最新的黑名单再做一次兜底过滤。为什么要在消费端再过滤一次?因为入口层用的黑名单可能有几秒的延迟,而消费端可以拿到最新数据,在最值钱的库存操作之前把漏网的风险用户拦住。

库存扣减:限购计数和库存的操作顺序有讲究

库存和限购都放在Redis + Lua脚本里做原子操作,不走MySQL行锁。几千并发下MySQL的行锁争用会让RT飙高,后面的业务全被拖慢,把冲突仲裁放到Redis里做是唯一合理的选择。

操作顺序是:先用Lua脚本原子递增用户的限购计数,通过了再扣库存。为什么不能反过来?如果先扣库存再查限购,用户可能出现库存已经扣了,却被限购规则拦住的情况。你可以告诉用户没抢到,但很难跟用户解释为什么系统让他下了10单却只允许买2份。限购规则前置,对体验和风控都更友好。

如果库存扣减成功但后续创建订单失败了怎么办?这里有stackRelease兜底:把库存和限购计数一起释放回去,避免库存被占住但订单没生成的隐性损耗。

三重幂等:消息重复消费不会产生重复订单

秒杀订单从秒杀域同步到订单域走的是MQ,消息重复投递是必须面对的问题。这套系统用了三层防线:

  • Redis Set做O(1)快速判重,大部分重复消息在这一层就被拦掉
  • 分布式锁防并发写入,避免两条相同消息同时通过Redis Set检查后并发落库
  • 强制走主库查询,防止主从延迟导致Redis Set和从库都没查到已有记录的极端情况

另外,入口服务返回给用户的traceId是一次性消费的:消费端处理消息时会校验traceId的有效性,校验通过后立即从Redis删除,同一个traceId无法被二次使用,从源头防止请求重放。

预支付视图全走Redis:轮询和回调不碰数据库

用户抢到单之后,前端会轮询查支付状态,支付渠道也会异步回调通知。当时我们踩过一个坑:活动开始几分钟后,不是下单那一刻打垮数据库,而是支付回调和轮询查询的请求一起涌上来,把DB搞到雪崩。

后来的方案是把订单状态、支付参数、轮询映射全部放在Redis里。前端轮询通过traceId查Redis拿订单号和状态,支付回调先更新Redis里的缓存,真正的订单状态流转由秒杀订单中心在数据库事务里完成,频次远低于轮询。

配合延迟支付检查消息,如果用户长时间不支付或者支付渠道回调丢失,消费端会在延迟到期后扫描Redis里的预支付记录,超时就取消订单、释放库存和限购名额,避免库存被长期占住。

两阶段下单:秒杀订单和正式订单是两回事

第一阶段在秒杀系统里完成:抢到名额、扣库存、生成秒杀订单、准备预支付信息。第二阶段在支付成功后触发:通过MQ把已支付的秒杀订单推送到普通订单域,落地为一张正式的用户订单。

这样做的目的是让秒杀流量和普通订单的读写完全隔离。秒杀订单在独立的数据库分片里写入,即便被打到极限,普通订单和其他交易路径也不受影响。

订单同步链路:物理隔离之后怎么融合回来

秒杀订单同步到订单域这条链路,有几个容易被忽视但非常关键的设计:

  • 消息即数据源。MQ消息体里包含了订单的所有字段,订单域消费消息后直接落库,不需要反向调用秒杀系统的接口查订单详情,系统间的耦合降到最低
  • 定时对账 + 失败重推。秒杀系统在推送MQ时会把订单号存入Redis List,定时任务定期检查这些订单是否已在订单域落地成功,没有就重新推送。这保证了即使MQ消费失败或服务短暂宕机,订单最终一定会同步过去
  • 异常消息落库。消费端如果处理异常,会把原始消息写入异常记录表,方便人工补单和问题排查

同步到订单域之后,也不走普通订单的submit → 预支付 → 支付回调这条链路,而是直接写入已支付状态的订单。这段逻辑需要单独实现,和普通订单创建流程完全不同。

策略模式区分不同商品的下单逻辑

自由卡和实物商品的下单流程差异很大。消费端通过策略模式把不同商品类型的下单逻辑拆开,每种策略只关心拿到库存之后怎么落订单、怎么记账,不需要关心前面的排队和防刷细节。

分库分表、MQ削峰、全链路压测、应急预案

这些在其他秒杀课程里也会讲到的知识点,这个专栏里同样完整覆盖,包括基于ShardingSphere按用户ID分片的订单分表设计、RocketMQ异步削峰的队列拆分策略、多级缓存的更新时机和失效处理、限流降级的多层防线配置、全链路压测的数据准备和瓶颈定位、以及每次活动前必须过一遍的应急预案手册。区别在于你看到的是在6000万会员规模下经过多轮压测验证的完整落地方案,不是概念层面的原理介绍。

活动值班工具体系:上线只是开始

每次秒杀活动,技术经理、核心开发、DBA、运维十几个人在现场值班。这套值班工具是在一次次活动中迭代出来的,哪些操作需要一键触发、哪些参数需要运行时可调、哪些数据需要实时可查,全部来自真实的值班经验。

  • 一键关停活动。管理后台提供紧急关停接口,活动状态从进行中直接切到已停止,所有新请求立即被拦截,不需要重启服务、不需要改配置
  • 渠道级入口开关。微信小程序、支付宝、APP、抖音四个渠道各自独立控制。某个渠道流量异常时可以单独关闭,不影响其他入口
  • 运行时动态调参。通过Nacos配置中心,十几个关键参数支持运行时修改、秒级生效:机审校验开关、用户限流比例、黑名单触发阈值、黑名单标记的起止时间和过期时间、会员等级限制、风控检查开关。活动进行中发现某个参数不合适,改完配置几秒后所有节点自动生效
  • 手动干预接口。批量取消订单、手动触发对账、手动释放库存、手动创建消费队列。正常流程中用不到,异常情况下是值班人员的兜底手段

10个定时任务覆盖活动全生命周期

秒杀系统跑了10个XXL-Job定时任务,按活动的三个阶段划分。

活动前:把SKU信息预热到Redis;为每个商品创建独立的RabbitMQ队列,队列的最大长度设为该商品的库存数,超出的消息直接拒绝,用队列容量做天然限流;构建多级缓存。

活动中:定时刷新活动和商品缓存,自动检查并更新活动状态(到了开始时间自动开启,到了结束时间自动关闭)。

活动后:扫描超时未支付的订单释放库存(超时阈值310秒),对账检查秒杀订单是否已同步到订单域,处理两个失败队列中的遗留消息(取消失败的订单和释放失败的库存)。

服务重启时,CommandLineRunner会自动从Redis读取队列名称列表,重新注册所有消息监听器,不需要人工干预。

值班统计:每个SKU卖了多少、还剩多少、多少人没付款

活动进行中,值班人员需要实时掌握数据。系统准备了一组统计查询:按活动维度查总订单数和已支付金额,按SKU维度查每个商品的已售数量、剩余库存和待支付订单数。订单表做了分库分表(4张订单表、5张订单明细表),这些统计SQL需要跨分片聚合,DBA在值班时可以随时跑,快速判断当前活动的整体状况。

以上这些设计细节,在专栏里都会结合具体的业务场景和关键代码来展开。你看到的每一个技术选择,都能追溯到背后的业务需求和踩过的坑。

秒杀系统技术架构

整套秒杀系统拆成了8个微服务模块,各自职责明确:

模块 职责
seckill-gateway C端网关,流量入口。负责请求鉴权、流量限流、路由转发
seckill-service 秒杀核心聚合服务。处理用户参与秒杀、排队查询、下单等核心流程
seckill-base 秒杀基础域。管理活动、商品、库存等核心业务数据
seckill-support 支撑域。包含会员、订单、支付、账户(自由卡)、风控五个子域
seckill-processor 异步消息处理。消费MQ消息,执行Lua脚本扣库存并创建订单
seckill-job 定时任务服务。处理库存释放、订单超时关闭、缓存刷新等周期性任务
seckill-admin 管理后台。运营人员配置活动、管理商品、查看运营数据
seckill-common 基础框架。统一异常、统一响应、分布式锁、ID生成、链路追踪等公共组件

一条完整的秒杀请求链路是这样的:

用户发起秒杀请求 → seckill-gateway做鉴权和限流 → seckill-service校验活动状态、用户资格和库存 → 将请求发送到RocketMQ → seckill-processor消费消息,执行Lua脚本原子扣库存,扣成功则创建订单 → seckill-base将订单持久化到分片数据库 → 前端轮询排队结果 → 用户拿到订单后发起支付 → seckill-support处理支付下单和异步回调 → 订单状态更新为已支付。

底层基础设施:

  • Redis:承担缓存、库存扣减、分布式锁、限流计数等职责,是整个系统中访问频率最高的组件
  • MySQL(分库分表) :活动数据、商品数据、订单数据的持久化存储,订单表按用户ID水平拆分
  • RocketMQ:秒杀请求的异步削峰通道,将瞬时流量转化为可控的消费速率
  • Nacos:服务注册发现和动态配置管理
  • XXL-Job:分布式任务调度,管理所有周期性任务的触发和执行

技术栈

列一下整个项目用到的技术组件和版本,方便你提前了解和准备环境。

类别 技术 版本
语言 Java 17
核心框架 Spring Boot 2.7.17
微服务 Spring Cloud 2021.0.8
微服务 Spring Cloud Alibaba 2021.0.1.0
RPC Dubbo 3.2.0
注册中心 / 配置中心 Nacos 随Spring Cloud Alibaba版本
限流熔断 Sentinel 随Spring Cloud Alibaba版本
ORM MyBatis Plus 3.5.1
分库分表 ShardingSphere 5.3.2
数据库 MySQL 8.0
缓存 / 分布式锁 Redis + Redisson Redisson 3.17.4
本地缓存 Caffeine 2.9.3
消息队列 RocketMQ Spring Boot Starter 2.2.3
定时任务 XXL-Job 2.4.0
对象映射 MapStruct 1.5.5.Final
JWT JJWT 0.11.2
日志 Log4j2 + Disruptor Log4j2 2.17.2
工具 Lombok 1.18.30

Spring Boot 2.7对应的是Spring Framework 5.3.x。项目整体基于Spring Cloud微服务体系,服务间通过Dubbo做RPC调用,Nacos同时承担注册中心和配置中心的角色。

专栏目录

整个专栏分为六个模块:

模块一:需求与方案设计

从业务需求出发,分析秒杀场景下的产品需求和技术挑战,完成整体技术方案设计。然后搭建基础框架:统一异常处理、统一响应体、Preconditions参数校验、分布式ID生成器、链路追踪、MyBatis Plus自动配置等公共组件。这些基础设施是后续所有业务代码的底座。

  • 秒杀需求分析
  • 技术方案设计
  • 基础框架搭建

模块二:流量入口

设计C端网关,作为所有秒杀请求的统一入口。覆盖路由规则配置、JWT鉴权方案、网关层限流策略,以及请求合法性校验。

  • C端网关设计与实现

模块三:秒杀核心域

这是整个专栏最核心的部分。从入口防刷、机审校验、小黑屋机制,到Lua脚本原子扣库存、限购策略、消息队列异步下单,再到订单创建、预支付缓存、幂等设计,完整覆盖秒杀链路的每一个关键环节。

  • 入口防刷:机审校验、用户级限流与小黑屋
  • 活动管理与多级缓存设计
  • 库存预热与Lua脚本原子扣库存
  • 限购策略、排队机制与stackRelease兜底
  • RocketMQ异步下单与消费端二次校验
  • 订单创建与策略模式
  • 预支付缓存与两阶段下单
  • 三重幂等设计与traceId一次性消费

模块四:支付与订单同步

对接微信支付和支付宝,处理支付下单、异步回调、超时关闭、退款等完整支付流程。讲解自由卡账户体系,以及秒杀订单到普通订单域的异步同步、定时对账和异常补偿方案。

  • 自由卡账户体系
  • 多渠道支付对接
  • 支付回调与延迟支付检查
  • 秒杀订单同步到订单域
  • 定时对账、失败重推与异常补偿

模块五:风控与稳定性

两层风控过滤机制,入口层防刷和消费端兜底的配合方式。分库分表的选型和落地。限流降级的多层防线设计。全链路压测从方案到执行的完整流程,以及每次活动前必须过一遍的应急预案。

  • 风控体系:入口过滤与消费端兜底
  • 分库分表实践
  • 限流降级方案
  • 全链路压测与应急演练

模块六:运维工具与活动保障

秒杀活动不是上线就结束了,每次活动都有十几人现场值班。这个模块覆盖管理后台的活动生命周期管理、一键关停和渠道级入口开关、Nacos动态调参、10个定时任务的编排逻辑(活动前预热/活动中刷新/活动后清理)、值班统计SQL、手动干预接口,以及服务重启后的队列自恢复机制。

  • 管理后台与活动生命周期
  • 一键关停、渠道开关与动态调参
  • 10个定时任务编排
  • 值班统计与手动干预工具
  • 监控告警与应急响应

每周更新两篇。

本专栏在知乎里,且我也开了星球,有兴趣的可以订阅和加入,一起交流。

  • 知乎账号:SamDeepThinking
  • 星球:老码头的技术浮生录

小结

做了十几年开发,接触过不少系统,也处理过各种线上事故。有一个感受越来越强烈:真正能让技术能力产生质变的,不是学了多少新框架,而是在真实业务压力下做过多少次技术决策。

秒杀系统就是这样一个典型场景。它把高并发、高可用、数据一致性这些平时停留在概念层面的东西,全部压缩到一个具体的业务场景里。你必须在有限的时间窗口内,把每一个技术选择想清楚、做到位。扣库存用什么方案、消息队列怎么配、限流阈值设多少、降级策略怎么触发,每一个决策都直接影响线上结果。这种压力下做出的判断,和纸上谈兵的认知深度完全不同。

一个技术方案好不好,不是看它的PPT写得多漂亮,而是看它在生产环境里能不能扛住真实流量。

这个专栏的价值,不在于教你某个框架怎么用,而在于让你看到一个真实的秒杀系统从0到1的完整过程,看到每个技术选择背后的原因和代价。当你自己面对类似场景时,能做出更准确的判断。

相关推荐
GOWIN革文品牌咨询2 小时前
国际B2B品牌官网架构方法:如何把资料库重构成“认知中枢”
架构
程序员书虫2 小时前
Spring 依赖注入一次讲透:`@Autowired`、`@Resource`、`@Qualifier`、`@Primary` 到底怎么选
java·后端·面试
Rsun045512 小时前
16、Java 迭代器模式从入门到实战
java·开发语言·迭代器模式
quan26312 小时前
20260416,日常开发-再记一次内存溢出
java·内存溢出·jprofile
布吉岛的石头2 小时前
线上服务凌晨OOM:一次因「无超时设置」引发的内存雪崩复盘
java
SamDeepThinking2 小时前
Spring Bean作用域的设计与使用
java·后端·面试
Flittly2 小时前
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
java·安全·spring·springboot·安全架构
前端一课2 小时前
《NestJS 从入门到资深》书稿(Markdown)
后端
Memory_荒年2 小时前
Java + FFmpeg:从“玩具”到“工业级”的音视频实战
后端