秒杀系统设计
2026/1/15大约 2 分钟
秒杀系统设计
场景分析
需求:10万人抢购100件商品,要求不超卖、不少卖、高性能。
挑战:
- 瞬时高并发(QPS 可能达到百万)
- 库存准确性
- 防止恶意请求
架构设计
┌─────────────────────────────────────────────────────────────────┐
│ 秒杀系统架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户 → CDN → Nginx → 网关 → 秒杀服务 → Redis → MQ → 订单服务 │
│ ↓ ↓ │
│ 静态化 限流/验证 │
│ │
└─────────────────────────────────────────────────────────────────┘核心方案
1. 流量削峰
// 前端:按钮置灰 + 倒计时
// 验证码/答题:拉长请求时间
// 限流:令牌桶/漏桶算法
@SentinelResource(value = "seckill", blockHandler = "seckillBlock")
public Result seckill(Long userId, Long productId) {
// 业务逻辑
}
public Result seckillBlock(Long userId, Long productId, BlockException e) {
return Result.fail("系统繁忙,请稍后重试");
}2. 库存预热
// 活动开始前,将库存加载到 Redis
@PostConstruct
public void initStock() {
List<SeckillProduct> products = productService.getSeckillProducts();
for (SeckillProduct p : products) {
redisTemplate.opsForValue().set("stock:" + p.getId(), p.getStock());
}
}3. Redis 扣减库存
// Lua 脚本保证原子性
String script =
"local stock = redis.call('get', KEYS[1]) " +
"if stock and tonumber(stock) > 0 then " +
" redis.call('decr', KEYS[1]) " +
" return 1 " +
"end " +
"return 0";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList("stock:" + productId)
);
if (result == 1) {
// 扣减成功,发送 MQ 创建订单
mqTemplate.send("seckill-order", new OrderMessage(userId, productId));
}4. 异步下单
@RocketMQMessageListener(topic = "seckill-order", consumerGroup = "order-consumer")
public class OrderConsumer implements RocketMQListener<OrderMessage> {
@Override
public void onMessage(OrderMessage msg) {
// 创建订单
Order order = orderService.createOrder(msg.getUserId(), msg.getProductId());
// 扣减数据库库存
productService.decreaseStock(msg.getProductId());
}
}5. 防重复购买
// 用户维度限制
String userKey = "seckill:user:" + userId + ":" + productId;
Boolean success = redisTemplate.opsForValue().setIfAbsent(userKey, "1", 1, TimeUnit.HOURS);
if (!success) {
return Result.fail("您已参与过此活动");
}防刷策略
// 1. 接口限流
@RateLimiter(value = 100, timeout = 1) // 每秒100次
// 2. 用户限流
String userLimitKey = "limit:user:" + userId;
Long count = redisTemplate.opsForValue().increment(userLimitKey);
if (count == 1) {
redisTemplate.expire(userLimitKey, 1, TimeUnit.SECONDS);
}
if (count > 5) {
return Result.fail("请求过于频繁");
}
// 3. IP 限流
// 4. 黑名单
// 5. 验证码/滑块库存一致性
// 最终一致性方案
// 1. Redis 预扣减
// 2. MQ 异步扣减数据库
// 3. 定时任务对账
@Scheduled(cron = "0 */5 * * * ?")
public void checkStock() {
// 对比 Redis 和数据库库存
// 不一致则告警或修复
}高可用
- Redis 集群
- MQ 集群
- 服务多实例
- 熔断降级
- 兜底页面