《从 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 中是原子执行的 —— 脚本执行期间,其他命令无法插入。

我们需要实现三个逻辑:

  1. 判断库存是否充足
  2. 判断用户是否已下单(防重复)
  3. 原子性扣减库存 + 记录用户

这三步必须在一个原子操作中完成,否则仍有超卖或重复风险。

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 到 1:我如何用 Redis + Lua + Kafka + Sentinel实现高并发秒杀防超卖》

《从 0 到 1:我如何用 Redis + Lua + Kafka + Sentinel实现高并发秒杀防超卖》

🧪 压测验证:0 超卖、0 重复

测试环境

  • 阿里云 ECS:4核8G
  • 商品库存:100
  • 并发用户:10000
  • QPS 限流:1000(Sentinel)

测试结果

指标

结果

总请求数

10000

成功扣库存

100(= 初始库存)

超卖次数

0

重复下单

0

P99 延迟

138 ms

🌟 为什么不用数据库 or Redis + Watch?

方案

是否原子

QPS

超卖风险

说明

MySQL UPDATE

否(需事务)

< 500

锁竞争严重

Redis + WATCH

是(但复杂)

~2000

失败需重试,逻辑复杂

Redis + Lua

5000+

0

简单、高效、可靠

————————————
在项目的READM.md文件中有详细的部署流程,如对你有帮助,麻烦在GitHub给点点小🌟🌟

© 版权声明

相关文章