《从 0 到 1:我如何用 Redis + Lua + Kafka + Sentinel实现高并发秒杀防超卖》
项目开源地址:https://github.com/songrongzhen/seckill.git
技术栈:Spring Boot 3.2 + Redis + Lua + Kafka + Sentinel
适用场景:电商秒杀、抢购、库存扣减等高并发场景
🔥 为什么秒杀系统需要防超卖?
在高并发场景下,比如“100 件商品,1 万人抢”,如果用传统数据库直接 UPDATE stock SET count = count - 1 WHERE id = ? AND count > 0,会面临:
-
超卖风险:多个请求同时读到库存为 1,都执行
count - 1,最终库存变成 -1。 - 性能瓶颈:数据库写锁竞争激烈,QPS 难以突破 1000。
- 重复下单:同一用户多次点击,生成多个订单。
解决思路:将库存判断与扣减操作移到 Redis,并用 Lua 脚本保证原子性。
🧠 设计思路:Redis + Lua 的天然优势
Redis 是单线程模型,所有命令天然串行执行。而 Lua 脚本在 Redis 中是原子执行的 —— 脚本执行期间,其他命令无法插入。
我们需要实现三个逻辑:
- 判断库存是否充足
- 判断用户是否已下单(防重复)
- 原子性扣减库存 + 记录用户
这三步必须在一个原子操作中完成,否则仍有超卖或重复风险。
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) <= 0 then
return 0
end
local isExist = redis.call('SISMEMBER', KEYS[2], ARGV[1])
if tonumber(isExist) == 1 then
return 2
end
redis.call('DECR', KEYS[1])
redis.call('SADD', KEYS[2], ARGV[1])
redis.call('EXPIRE', KEYS[2], 3600)
return 1
返回值说明:
-
0:库存不足 -
1:秒杀成功 -
2:请勿重复下单
⚙️ Spring Boot 集成 Lua 脚本
@Service
public class SeckillService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private KafkaTemplate<String, SeckillMessage> kafkaTemplate;
@SentinelResource(value = "seckill-jk", blockHandler = "handleSeckillBlock")
public String doSeckill(Long goodsId, Long userId) {
String stockKey = "stock:" + goodsId;
String userSetKey = "user:" + goodsId + ":" + userId;
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("stock_deduct.lua")));
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Arrays.asList(stockKey, userSetKey), userId.toString());
if (result == 0) {
return "库存不足";
} else if (result == 2) {
return "请勿重复下单";
}
kafkaTemplate.send("seckill-order-topic", new SeckillMessage(userId, goodsId));
return "秒杀成功,请等待订单生成";
}
// 限流降级方法
public String handleSeckillBlock(Long goodsId, Long userId, BlockException ex) {
return "请求过于频繁,请稍后再试";
}
}
✅ 关键点:
- 使用
RedisTemplate.execute()执行 Lua 脚本- 脚本执行成功后,才发送 Kafka 消息,避免无效请求打到下游
⚙️ Spring Boot 集成 Sentinel控制台
项目启动后在Sentinel控制台,设置限流qps值,当请求量达到阈值就触发限流,最终返回
请求过于频繁,请稍后再试


🧪 压测验证:0 超卖、0 重复
测试环境
- 阿里云 ECS:4核8G
- 商品库存:100
- 并发用户:10000
- QPS 限流:1000(Sentinel)
测试结果
|
指标 |
结果 |
|---|---|
|
总请求数 |
10000 |
|
成功扣库存 |
100(= 初始库存) |
|
超卖次数 |
0 |
|
重复下单 |
0 |
|
P99 延迟 |
138 ms |
🌟 为什么不用数据库 or Redis + Watch?
|
方案 |
是否原子 |
QPS |
超卖风险 |
说明 |
|---|---|---|---|---|
|
MySQL |
否(需事务) |
< 500 |
高 |
锁竞争严重 |
|
Redis + |
是(但复杂) |
~2000 |
中 |
失败需重试,逻辑复杂 |
|
Redis + Lua |
是 |
5000+ |
0 |
简单、高效、可靠 |
————————————
在项目的READM.md文件中有详细的部署流程,如对你有帮助,麻烦在GitHub给点点小🌟🌟
© 版权声明
文章版权归作者所有,未经允许请勿转载。