如何设计高并发订单秒杀系统?从限流到最终一致性

129 阅读3分钟

如何设计高并发订单秒杀系统?从限流到最终一致性

一文带你从 0 到 1 构建一个高并发秒杀系统,掌握电商平台背后的技术精髓。


👀 业务场景分析

在传统电商或活动促销中,“秒杀”是一种非常常见的营销手段。它通常具有以下几个特点:

  • 突发高并发:秒杀开始瞬间,成千上万的用户同时请求。
  • 库存有限:商品数量极少,必须精准控制库存。
  • 严格限购:每人只能购买一次或一次限定数量。
  • 支付异步:订单创建与支付解耦,需确保最终一致性。

因此,一个健壮的秒杀系统需要同时满足:

  • 高并发处理能力
  • 高性能库存扣减机制
  • 数据一致性保障
  • 防刷、限流机制

🧠 技术选型与架构设计

技术选型

模块技术选型
接入层限流Nginx + Lua / Gateway + Redis Token Bucket
应用层限流Guava RateLimiter / Sentinel
消息队列RocketMQ / Kafka
缓存层Redis (Lua 脚本原子扣减)
数据库MySQL(行级锁、乐观锁)
一致性补偿机制本地消息表 / RocketMQ 事务消息
分布式锁Redisson / Zookeeper

架构图(简化版)

用户请求
   ↓
Nginx 限流
   ↓
应用网关限流 + 登录校验
   ↓
本地限流 + 商品缓存预热
   ↓
库存预扣减(Redis + Lua)
   ↓
消息队列异步下单(MQ)
   ↓
订单服务落库 + 最终一致性处理

🚀 核心模块实现

1. 接入层限流(Nginx + Lua)

-- nginx.conf 中配置
limit_req_zone $binary_remote_addr zone=limit:10m rate=10r/s;
server {
    location /seckill {
        limit_req zone=limit burst=5 nodelay;
        proxy_pass http://your_java_app;
    }
}

2. 本地限流(Guava RateLimiter)

RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100个请求

public String seckill(Long productId) {
    if (!rateLimiter.tryAcquire()) {
        return "抢购太火爆,请稍后再试";
    }
    // 继续执行业务逻辑
}

3. Redis + Lua 脚本实现库存原子扣减

-- seckill.lua
local stock = tonumber(redis.call('get', KEYS[1]))
if stock <= 0 then
    return -1
end
redis.call('decr', KEYS[1])
return 1
// Java 调用
String script = new String(Files.readAllBytes(Paths.get("seckill.lua")));
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);

Long result = redisTemplate.execute(redisScript, Collections.singletonList("product:stock:" + productId));
if (result == -1) {
    return "库存不足";
}

4. 消息队列异步下单

// 发送消息
OrderMessage orderMessage = new OrderMessage(userId, productId);
String msg = objectMapper.writeValueAsString(orderMessage);
rocketMQTemplate.convertAndSend("seckill-topic", msg);
// 消费消息
@RocketMQMessageListener(topic = "seckill-topic", consumerGroup = "order-group")
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        OrderMessage orderMsg = objectMapper.readValue(message, OrderMessage.class);
        orderService.createOrder(orderMsg);
    }
}

5. 数据库层保障最终一致性

  • 使用本地消息表 + 定时补偿任务
  • 或者使用 RocketMQ 的 事务消息机制
  • 订单状态:未支付 → 支付中 → 已支付 / 订单关闭

🔐 避免超卖的关键细节

  • Redis 的库存扣减必须是原子操作(Lua 脚本)
  • 避免重复下单:使用 Redis + Set 记录用户是否已参与
  • 限购校验:用户维度 + 商品维度唯一标识
String userKey = "seckill:uid:" + userId + ":pid:" + productId;
Boolean isFirst = redisTemplate.opsForValue().setIfAbsent(userKey, "1", 1, TimeUnit.HOURS);
if (!Boolean.TRUE.equals(isFirst)) {
    return "您已参与过该商品秒杀";
}

✅ 最终一致性策略

  • 异步下单:消息队列保障系统解耦与削峰
  • 支付超时关闭订单:定时任务 / 延迟队列
  • 支付成功回写状态:支付系统回调处理
  • 订单补偿机制:保障消息投递失败时订单状态一致

🔍 性能优化建议

  • Redis 热点 Key 拆分(库存分片)
  • 使用布隆过滤器防止缓存穿透
  • 使用 CDN + 静态页预热减少请求压力
  • 秒杀请求提前异步校验(如验证码、人脸识别等)

🧩 总结

构建一个稳定、高性能的秒杀系统,关键在于:

  • 前端限流、后端削峰:多级限流避免系统雪崩
  • 缓存优先、异步执行:合理利用 Redis 和 MQ
  • 库存原子操作、防止超卖:Lua 脚本是关键
  • 最终一致性保障:不要迷信强一致,能达到最终一致即可