高并发下的秒杀系统架构全景:从单体到云原生的演进之路
核心思路是架构跟着业务量迭代,不做过度设计
架构演进全阶段对比表
| 演进阶段 | 业务规模 / 峰值 QPS | 核心架构形态 | 核心痛点 | 关键解决技术 |
|---|---|---|---|---|
| 🟢 初创单体期 | 日活数千 / 峰值数百 | 单 Java 应用 + 单库单表 | DB 直接扛读写,超卖风险,单点故障 | 数据库乐观锁扣库存、JVM 本地缓存 |
| 🟡 成长分布式期 | 日活数万 / 峰值数千~万级 | 服务垂直拆分 + Redis 缓存 | DB 写压力大,服务耦合扩容难 | 库存预热 Redis 预扣减、Nginx 负载均衡、服务垂直拆分 |
| 🔴 爆发微服务期 | 大促场景 / 峰值十万级 | 微服务集群 + 全链路中间件 | 流量洪峰冲垮链路、缓存雪崩、服务雪崩 | 多级缓存体系、MQ 削峰、全链路限流降级、Lua 原子扣库存 |
| 🚀 成熟云原生期 | 平台级大促 / 峰值百万级 | K8s 容器化 + 云原生全家桶 | 资源利用率低、扩容不及时、容灾难度高 | HPA 弹性扩缩容、Serverless 峰值承接、异地多活、可观测体系 |
演进路线全景图
关于这个问题的底层原理和更多实战细节,我整理了一份《大厂面试手册》,包含大厂高频面试题、源码解析和性能调优案例。
关注公众号【Rain的Java大神之路】,回复"Java"即可免费领取,持续更新中。
各阶段核心方案拆解
🟢 1 初创单体期(冷启动阶段)
- 场景:业务刚上线,秒杀活动量级小,研发资源有限
- 核心问题:并发扣库存出现超卖,数据库直接扛所有请求容易宕机
- 落地优化 :
- 用
update goods set stock = stock - 1 where id = ? and stock > 0的乐观锁思路,从数据库层面杜绝超卖 - 热点商品数据放 JVM 本地缓存,挡掉大部分重复读请求
- 用
- 局限性:单点故障风险高,扩容只能整应用扩容,无法应对突发流量
单体架构 ------ "能用就行"的野蛮生长
这个阶段的正确打法
- 库存预热 :秒杀开始前把库存加载到 Redis,用
DECR原子扣减,杜绝超卖。 - 限流保护 :Nginx 层
limit_req_zone+ 应用层令牌桶(Guava RateLimiter),把无效流量挡在门外。 - 动静分离:页面静态化,CDN 扛住商品详情页。
⚠️ 但这个阶段本质还是单体,流量再大一点,Redis 热 Key、应用 CPU 瓶颈马上暴露。
🟡 2 成长分布式期(业务增长阶段)
- 场景:用户量上涨,秒杀活动频次增加,峰值到万级 QPS
- 核心问题:单体应用扩容成本高,数据库写压力成为瓶颈
- 落地优化 :
- 按业务垂直拆分商品、库存、订单、用户服务,单独扩容瓶颈模块
- 秒杀库存提前预热到 Redis,扣库存直接操作 Redis(预扣减),异步同步数据库,大幅降低 DB 压力
- 引入 Nginx 做负载均衡,静态资源前置拦截
单体变成"微服务 + 中间件集群",架构如下:
🔴 3 爆发微服务期(大促爆发阶段)
- 场景:平台级大促,百万用户同时参与,峰值十万级 QPS
- 核心问题:瞬时流量洪峰直接冲垮全链路,容易出现缓存雪崩、服务雪崩
核心请求链路图
核心技术点
- 多级缓存体系:CDN + Nginx 本地缓存 + Redis 分布式缓存 + JVM 热点缓存,99% 读请求在缓存层解决
- 流量削峰:下单请求先入消息队列,消费端平滑处理,把瞬时洪峰摊平
- 全链路限流降级:Sentinel 做网关层、服务层、热点参数限流;非核心功能(积分、推荐)直接降级,保核心下单链路
- 超卖兜底:Redis Lua 脚本原子扣库存 + 数据库乐观锁双重校验,订单唯一 ID 保证幂等
核心升级点
| 挑战 | 解法 | 关键技术点 |
|---|---|---|
| 超卖 | Redis 原子扣减 + 最终一致性校验 | Lua 脚本保证 DECR 后库存≥0 |
| 流量洪峰 | 消息队列异步削峰 | RocketMQ 事务消息,下游按数据库真实库存终裁 |
| 数据库压力 | 分库分表 + 读写分离 | ShardingSphere,按用户 ID 分片 |
| 热 Key | 多级缓存 + 本地缓存 | Redis Cluster + Caffeine,Tair 热点探测自动复制 |
| 读流量 | 商品详情多层缓存 | CDN → Nginx 本地 → Redis → DB |
重要细节
- 扣减链路:网关→秒杀服务→Lua 脚本扣 Redis→成功则发送 MQ 消息→订单服务异步建单→DB 库存兜底。
- 失败处理:MQ 消费端做幂等(唯一键),超时关单回补库存用"事务回查+状态机"。
🔧 这种架构能扛 十万级 QPS,但运维成本爆炸------集群、中间件、配置中心、链路追踪......人都麻了 😅。
🚀 4 成熟云原生期(平台级成熟阶段)
- 场景:多地域大促,峰值百万级 QPS,对成本、容灾要求极高
- 核心问题:秒杀流量波动极大,平时服务器闲置浪费,峰值扩容跟不上;多机房容灾和运维复杂度高
- 落地优化 :
- K8s 容器化 + HPA 弹性扩缩容:秒杀服务全容器化,基于 QPS、CPU 指标秒级自动扩缩容,峰值扩容、事后缩容降本
- Serverless 承接峰值:秒杀校验、排队等无状态逻辑用函数计算,按调用量付费,极致弹性不用预留资源
- 异地多活容灾:多地域部署,单地域故障流量快速切走,保证活动可用性
- 全链路可观测:Prometheus 监控 + Skywalking 链路追踪 + ELK 日志,秒级定位故障点
从"堆机器"变成"面向不可变基础设施、自动伸缩",架构升维:
为什么必须走云原生?
- 秒杀流量极度脉冲 ,K8s 的
KEDA甚至能做到基于消息队列积压数动态扩容消费者 Pod,比 HPA 更实时。 - 基础设施即代码(IaC)+ GitOps 让恢复时间从小时级变分钟级。
- 多集群 + 多云容灾:秒杀域名 GeoDNS 就近接入,故障自动切换。
💻 核心实现代码片段
1.Redis Lua 原子扣减库存(超卖核心解决方案)
秒杀场景最核心的无锁化扣库存实现,性能远超分布式锁方案
lua
-- 秒杀扣库存Lua脚本:利用Redis单线程特性,保证「库存查询+扣减」原子执行
-- KEYS[1] = 商品库存Key ARGV[1] = 扣减数量
local stock = tonumber(redis.call('get', KEYS[1]))
local deductNum = tonumber(ARGV[1])
-- 库存充足则扣减,返回1;库存不足返回0
if stock >= deductNum then
redis.call('decrby', KEYS[1], deductNum)
return 1
end
return 0
java
/**
* Java侧调用Lua脚本实现原子扣库存
*/
public boolean deductStock(Long skuId, Integer num) {
String stockKey = "seckill:stock:" + skuId;
// 单次网络往返完成全逻辑,避免多次Redis命令的并发问题
Long result = redisTemplate.execute(
new DefaultRedisScript<>(DEDUCT_STOCK_LUA, Long.class),
Collections.singletonList(stockKey),
num
);
return result != null && result == 1;
}
✅ 技术亮点:
- 无锁化实现并发安全,规避分布式锁的死锁、锁竞争性能损耗问题
- 单脚本原子执行,一次网络 IO 完成操作,QPS 可达单机数万级
- 缓存层直接拦截 99% 超卖请求,不会透传到数据库
2 RocketMQ 异步下单(削峰填谷核心实现)
把瞬时洪峰流量转化为平稳消费,彻底保护底层数据库
java
// 生产者:秒杀校验通过后仅发消息,立即返回「排队中」,核心链路耗时<10ms
public void sendOrderMsg(Long userId, Long skuId) {
String orderNo = generateUniqueOrderNo(userId, skuId);
SeckillOrderDTO orderDTO = SeckillOrderDTO.builder()
.orderNo(orderNo).userId(userId).skuId(skuId).build();
rocketMQTemplate.convertAndSend("seckill-order-topic", orderDTO);
}
// 消费者:平滑拉取消息,异步落库,消费速度可控
@Component
@RocketMQMessageListener(topic = "seckill-order-topic", consumerGroup = "seckill-order-group")
public class SeckillOrderConsumer implements RocketMQListener<SeckillOrderDTO> {
@Override
public void onMessage(SeckillOrderDTO orderDTO) {
// 幂等校验:订单号唯一索引兜底,避免重复消费生成多笔订单
if (orderService.isOrderExist(orderDTO.getOrderNo())) {
return;
}
// 数据库最终扣减库存 + 生成订单
orderService.createSeckillOrder(orderDTO);
}
}
✅ 技术亮点:
- 同步转异步削峰,把十万级瞬时 QPS 摊平为千级平稳消费,数据库压力降低 90% 以上
- 解耦库存预扣与订单生成,核心链路只做校验 + 发消息,大幅提升接口吞吐量
- 配合消息重试 + 死信队列,保证订单数据最终一致性
3 Sentinel 热点参数限流(流量精准防护)
细粒度到单个商品的限流,避免单个爆品打垮整个服务
java
/**
* 秒杀核心接口:针对商品ID做热点参数限流
*/
@SentinelResource(value = "seckill:doSeckill", blockHandler = "seckillBlockHandler")
public Result doSeckill(Long userId, Long skuId) {
// 秒杀核心业务逻辑
return Result.success("排队中,请稍后查询结果");
}
// 初始化热点限流规则:单个商品每秒最多放行1000个请求
@PostConstruct
public void initHotParamRule() {
ParamFlowRule rule = new ParamFlowRule("seckill:doSeckill")
.setParamIdx(1) // 对第2个参数skuId做限流
.setCount(1000) // 单商品每秒QPS阈值
.setGrade(RuleConstant.FLOW_GRADE_QPS);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
✅ 技术亮点:
- 细粒度限流,避免单个爆品占满全部服务资源,影响其他秒杀商品正常售卖
- 配合网关层全局限流,形成「入口总控 + 服务层精细化」的双层流量防护体系
- 支持动态调整阈值,大促期间无需重启服务即可灵活扩容
⚠️ 秒杀场景核心技术难点与解决方案汇总
| 技术难点 | 问题本质 | 落地方案 |
|---|---|---|
| 商品超卖 | 并发场景下「库存查询 + 扣减」非原子操作,导致库存扣为负数 | 1. 前置拦截:Redis Lua 脚本原子预扣库存,拦截 99% 超卖请求2. 兜底校验:数据库乐观锁 update ... where stock>0 做最终校验3. 双重保障实现零超卖 |
| 脉冲式流量洪峰打垮系统 | 秒杀流量是突发式的,几秒内几十万请求涌入,远超系统日常承载能力 | 1. 多层拦截:CDN→Nginx→网关→服务→DB,逐层过滤无效流量2. 削峰填谷:MQ 异步下单,把瞬时洪峰摊平为平稳消费3. 全链路限流:入口总限流 + 服务级限流 + 热点参数限流 |
| 缓存击穿 / 雪崩 | 热点商品缓存过期,瞬间大量请求直接穿透到数据库,导致 DB 宕机 | 1. 热点商品缓存永不过期,后台异步更新库存数值2. 互斥锁重构缓存,避免同时大量请求回源数据库3. 多级缓存体系:CDN + 本地缓存 + 分布式缓存,分散压力 |
| 服务级联雪崩 | 某一下游服务宕机,导致整条链路请求阻塞、线程耗尽,引发级联故障 | 1. 熔断降级:非核心服务(积分、短信、推荐)直接降级,保核心下单链路2. 线程池隔离:核心接口单独分配线程池,故障互不影响3. 超时控制:所有 RPC 调用设置合理超时,快速失败避免阻塞 |
| 库存数据一致性 | Redis 预扣库存与数据库最终库存不一致,出现少卖 / 超卖 | 1. 最终一致性:MQ 异步同步库存,失败重试 + 死信队列兜底2. 定时对账:定期校准 Redis 与 DB 库存,修复异常数据3. 库存回滚:下单超时 / 支付失败自动回补 Redis 库存 |
| 热点商品性能瓶颈 | 单个爆品流量集中,单 Redis 分片、单 DB 行锁成为性能瓶颈 | 1. 库存分片:单个商品库存拆分为多个 Redis Key,分散压力2. 分库分表:订单表按商品 ID 哈希拆分,分散数据库行锁压力3. JVM 本地缓存:热点商品数据放堆内缓存,减少 Redis 访问 |
| 接口幂等性失效 | 用户重复点击、网络重传、MQ 重复消费,导致生成多笔订单 | 1. 前端防控:秒杀按钮点击后置灰,防止重复提交2. 接口层:秒杀令牌机制,一个用户一个商品仅能获取一次有效令牌3. 落地兜底:订单号唯一索引,数据库层面强制幂等 |
总结
秒杀系统三大核心理念(贯穿全程):
- 快:尽量在内存完成,Redis/Lua 原子化,少网络调用。
- 稳:层层限流削峰(网关→应用→中间件),兜底降级方案(页面排队、验证码)。
- 准:库存一致性最终通过 DB 校验,宁可少卖不可超卖。
text
单体 → 分布式 → 云原生
"快糙猛" → "可扩展" → "弹性自愈极致成本"
- 超卖终极方案:Redis Lua 原子扣减做前置拦截,数据库乐观锁做最终兜底,双重校验零超卖
- 缓存击穿处理:热点商品设置永不过期 + 互斥锁重构缓存,避免热点 key 失效打穿 DB
- 削峰的本质:用 "排队" 换 "可用性",把同步瞬时流量转化为异步平滑流量,保护底层服务
- 云原生核心价值:解决秒杀 "平时资源闲置、峰值容量不足" 的矛盾,兼顾高可用和成本控制
如果本文对你有帮助,欢迎关注我的公众号【Rain的Java大神之路】。
专注 Java 面试、源码、高并发实战,回复"Java"领取《大厂面试手册》,持续更新。