秒杀系统是对后端技术架构的终极考验,它需要在瞬时万级QPS的冲击下,精准地平衡库存安全、用户体验与成本控制。本文将分享一套经过实战验证的秒杀系统设计方案,涵盖分层架构、核心模块与工程化实践。
一、业务特性与核心挑战分析
1. 秒杀业务的三大核心特性
- 流量突增:日常流量可能仅为100QPS,而秒杀瞬间可达10万+ QPS,呈现上千倍的突发性。
- 库存有限:单个商品库存通常极少(≤1000件),库存扣减必须绝对精准,避免超卖或少卖。
- 短事务性:核心流程“库存校验→扣减→订单生成”要求极高响应速度,RT(响应时间)通常需控制在50ms以内。
2. 技术实现的五大痛点

二、全链路架构分层设计
1. 七层防护架构
构建从用户端到数据层的多层防护体系,逐层过滤与缓冲请求:
- 前端层 → 接入层:按钮防重复点击(防用户重复提交)
- 网关层 → 接入层:令牌桶限流(流量控制)
- 接入层 → 应用层:人机校验(防御自动化攻击)
- 应用层 → 缓存层:队列削峰(应对流量高峰)
- 缓存层 → 数据库层:库存预热(预加载热点数据)
- 数据库层 → 存储层:行锁优化(并发控制)
- 存储层 → 日志系统:异步落盘(提升IO性能)
2. 关键分层设计解析
1)前端层:流量第一道防线
- 按钮置灰:点击后禁用3秒,可拦截大量重复请求。
- 动态令牌:调用秒杀接口前需先获取Redis令牌(
seckill:token:{userId})。
- 浏览器缓存:缓存秒杀倒计时等静态信息,减少无效API调用。
2)网关层:流量清洗中心
在网关层进行全局限流是控制入口流量的关键。例如,使用Spring Cloud Gateway进行配置:
// 基于Spring Cloud Gateway的限流配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("seckill", r -> r.path("/seckill/**")
.filters(f -> f.requestRateLimiter(config ->
config.setRedisRateLimiter(
new RedisRateLimiter(10, 20) // 每秒10请求,突发容量20
)
))
.uri("lb://seckill-service"))
.build();
}
3)应用层:业务逻辑核心
- 独立域名隔离:秒杀业务使用独立域名(如
seckill.example.com),避免影响主站服务。
- 线程池隔离:为秒杀业务配置独立的线程池(例如
corePoolSize=200, maxPoolSize=500),实现资源隔离。
4)缓存层:库存前置处理
- 双写一致性:Redis库存与数据库库存通过异步消息队列保持最终一致性。
- 热点分片:按商品ID哈希分片存储(如
seckill:stock:{商品ID%分片数}),分散Redis集群压力。
三、核心模块实现详解
1. 库存预热模块(核心代码)
库存操作是整个秒杀的核心,应最大限度在缓存中进行。以下是基于Java和Redis Lua脚本实现的无锁化库存服务示例:
public class StockPreheatService {
private final JedisCluster jedisCluster;
private final SeckillGoodsMapper goodsMapper;
// 预热库存到Redis(活动开始前10分钟执行)
public void preheatStock(Long goodsId, Integer stock) {
// 初始化库存(使用Lua脚本保证原子性)
String luaScript = "if redis.call('exists', KEYS[1]) == 0 then " +
"redis.call('set', KEYS[1], ARGV[1]) " +
"redis.call('set', KEYS[2], ARGV[2]) end";
jedisCluster.eval(luaScript, 2,
"seckill:stock:" + goodsId, // 库存键
"seckill:version:" + goodsId, // 版本号键
String.valueOf(stock),
"1"); // 初始版本号
}
// 扣减库存(无锁化设计)
public boolean deductStock(Long goodsId) {
String luaScript = "local stock = tonumber(redis.call('get', KEYS[1])) " +
"if stock > 0 then " +
"redis.call('decr', KEYS[1]) " +
"redis.call('incr', KEYS[2]) " +
"return 1 " +
"else " +
"return 0 " +
"end";
Long result = (Long) jedisCluster.eval(luaScript, 2,
"seckill:stock:" + goodsId,
"seckill:version:" + goodsId);
return result == 1;
}
}
2. 分布式令牌生成(防刷机制)
public class TokenService {
private final RedisTemplate<String, Integer> redisTemplate;
// 生成秒杀令牌(每个用户限领1个)
public boolean generateToken(Long userId, Long goodsId) {
String key = "seckill:token:" + userId + ":" + goodsId;
return redisTemplate.opsForValue().setIfAbsent(key, 1, 60, TimeUnit.SECONDS);
}
// 校验令牌并删除(防止重复使用)
public boolean validateToken(Long userId, Long goodsId) {
String key = "seckill:token:" + userId + ":" + goodsId;
return redisTemplate.delete(key);
}
}
3. 异步队列削峰(Kafka 实现)
使用消息队列将瞬时高峰流量平滑为异步处理,是保证系统弹性的重要手段。
public class SeckillProducer {
private final KafkaTemplate<String, SeckillRequest> kafkaTemplate;
// 发送秒杀请求到队列(削峰填谷)
public void sendSeckillRequest(SeckillRequest request) {
kafkaTemplate.send("seckill_topic", request.getGoodsId().toString(), request);
}
}
@Service
public class SeckillConsumer {
private final StockPreheatService stockService;
private final OrderService orderService;
@KafkaListener(topics = "seckill_topic", groupId = "seckill_group")
public void processSeckillRequest(SeckillRequest request) {
// 1. 库存扣减
if (stockService.deductStock(request.getGoodsId())) {
// 2. 生成订单(数据库事务)
createOrder(request);
}
}
private void createOrder(SeckillRequest request) {
OrderEntity order = new OrderEntity();
order.setGoodsId(request.getGoodsId());
order.setUserId(request.getUserId());
order.setCreateTime(LocalDateTime.now());
orderService.save(order);
}
}
四、数据库层防超卖设计
1. 库存扣减的三级校验
- Redis预扣:通过Lua脚本保证原子性扣减,完成内存级高速校验。
- 数据库行锁:异步处理落库时,使用
SELECT ... FOR UPDATE锁定库存记录。
- 版本号校验:通过
UPDATE ... WHERE id=? AND version=?防止ABA问题。
2. 数据库表结构优化
CREATE TABLE seckill_goods (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
goods_id BIGINT NOT NULL COMMENT '商品ID',
stock INT NOT NULL COMMENT '库存',
version INT DEFAULT 0 COMMENT '乐观锁版本号',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 扣减库存SQL(带版本号校验)
UPDATE seckill_goods SET stock = stock - 1, version = version + 1
WHERE goods_id = ? AND stock > 0 AND version = ?
五、工程化最佳实践
1. 压测与容量规划
- 基准测试:使用JMeter等工具模拟高并发,确定单节点处理能力(如单节点QPS阈值)。
- 弹性扩展:根据压测结果规划服务器数量,并设计弹性扩缩容策略,例如在Kubernetes中配置HPA。
- 应急预案:准备熔断降级组件(如Sentinel),当依赖服务(如Redis)响应超时时自动熔断。
2. 监控与报警体系
- 核心指标监控:
- Redis命中率(目标>99%)
- 消息队列堆积量(设置阈值告警)
- 数据库连接池使用率
- 报警机制:通过Prometheus+Grafana实现实时监控,异常时触发多渠道报警。
3. 流量调度策略
- 预热期(活动前):逐步增加CDN缓存节点,同步预热Redis集群数据。
- 高峰期(活动开始):启用Nginx限流模块,果断拒绝超过系统处理能力的请求。
- 降温期(库存售罄后):返回静态售罄页面,并逐步关闭非核心服务线程。
六、避坑指南与经验总结
1. 三大核心坑点解决方案

2. 实战经验总结
- 缓存优先:绝大部分性能瓶颈可通过将热点数据(如库存)预加载至Redis解决。
- 慎用分布式锁:优先考虑无锁化设计(如原子操作、Lua脚本),必须用时采用细粒度分片锁。
- 限流重于处理:在前端、网关层做好流量控制,可以有效拦截超过80%的无效或恶意请求。
- 权衡一致性:确保库存扣减的强一致性,而订单生成等后续流程可采用最终一致性异步处理。