如何设计高并发订单秒杀系统?从限流到最终一致性
一文带你从 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 脚本是关键
- 最终一致性保障:不要迷信强一致,能达到最终一致即可