后端与系统架构1452 字

商品秒杀巧用Redis与Lua预防库存超卖~

·全库 389 / 611

《商品秒杀巧用Redis与Lua预防库存超卖~》共 0.1 万字,通篇围绕 Redis、并发、场景、数据库 展开,归在「后端与系统架构」这一层。

覆盖Redis并发场景数据库上下文安全

适合给 AI 系统搭底座、要管得住的人

目录骨架(4 节)
  1. 1. 原理与执行方式
  2. 2. 性能与效率
  3. 3. 适用场景
  4. Lua脚本
目录 · 7 节

方案分析

在高并发秒杀场景下,库存超卖的主要原因是:

  1. 多个并发请求同时操作库存,导致库存变负或超卖。
  2. 数据库性能瓶颈无法快速响应大量请求。

Redis+Lua解决方案

Redis是单线程的,结合Lua脚本可以保证对库存的操作是原子的,避免并发问题。

  • Redis的优势:高并发、高性能、支持丰富的数据结构。
  • Lua的优势:运行在Redis中,可以在一个原子操作中完成多条命令。

核心思路

  • 商品秒杀前,将商品的库存预先加载到Redis中。
  • 使用Lua脚本对库存进行扣减操作,确保每个请求的库存操作是原子的。
  • Lua脚本校验库存是否足够,如果足够则扣减库存并返回秒杀成功;否则返回秒杀失败。

Lua脚本实现库存扣减

lua

-- Lua脚本:扣减库存
local stock_key = KEYS[1]  -- 商品库存Key
local user_key = KEYS[2]   -- 用户秒杀Key,防止重复秒杀
local stock = tonumber(redis.call('get', stock_key))

-- 判断库存是否足够
if stock <= 0 then
return -1  -- 库存不足
end

-- 判断用户是否已经秒杀过
if redis.call('sismember', user_key, ARGV[1]) == 1 then
return 0  -- 用户已经秒杀
end

-- 扣减库存
redis.call('decr', stock_key)

-- 记录用户已经秒杀
redis.call('sadd', user_key, ARGV[1])

return 1  -- 秒杀成功

Java代码调用Lua脚本

在Java中使用Jedis,Redis客户端调用上述Lua脚本。下面是一个基于Jedis的示例代码:

java

复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class SecKillService {
    private JedisPool jedisPool;

    public SecKillService(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public boolean doSecKill(String userId, String productId) {
        try (Jedis jedis = jedisPool.getResource()) {
            // Lua脚本
            String luaScript = "local stock_key = KEYS[1];" +
            "local user_key = KEYS[2];" +
            "local stock = tonumber(redis.call('get', stock_key));" +
            "if stock <= 0 then return -1 end;" +
            "if redis.call('sismember', user_key, ARGV[1]) == 1 then return 0 end;" +
            "redis.call('decr', stock_key);" +
            "redis.call('sadd', user_key, ARGV[1]);" +
            "return 1;";

            String stockKey = "product_stock_" + productId;
            String userKey = "user_seckill_" + productId;

            // 执行Lua脚本
            Object result = jedis.eval(luaScript, 2, stockKey, userKey, userId);

            if (result.equals(1L)) {
                System.out.println("秒杀成功");
                return true;
            } else if (result.equals(0L)) {
                System.out.println("你已经秒杀过了");
                return false;
            } else {
                System.out.println("库存不足");
                return false;
            }
        }
    }
}

使用场景示例

假设我们有一个热销商品的秒杀活动,活动开始时将商品的库存加载到Redis中:

java

public void preloadStock(String productId, int stock) {
    try (Jedis jedis = jedisPool.getResource()) {
        String stockKey = "product_stock_" + productId;
        jedis.set(stockKey, String.valueOf(stock));  // 设置库存
    }
}

在秒杀开始时,用户请求会通过Java代码调用Lua脚本进行库存的校验与扣减操作,保证每个操作是线程安全的。

秒杀并发防超卖架构图

image.png

资料里没有你的场景?

把你手上的数据和想解决的那个环节发上来,我们按具体情况回,不复述方法论。

发布需求