玖叶教程网

前端编程开发入门

Redis实现秒杀

思路

  • 如果没有库存,则说明活动还未开始
  • 如果库存数量小于1,则说明活动已结束
  • 如果秒杀成功列表中包含当前用户,则说明该用户已成功参与,不允许多次参与

步骤

  • 拼接库存和秒杀成功的key
  • 判断当前用户是否已经参与
  • 获取库存数量,判断活动是否开始,或者是已结束
  • 进行减库存和添加用户操作

Java 功能代码

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public boolean doSecondKill(String uid, String goodsId) {

        // 1. 参数校验
        if( StringUtils.isEmpty(uid) || StringUtils.isEmpty(goodsId) ) {
            return false;
        }

        // 2. 获取Redis操作对象
        ValueOperations<String, Object> strOps =redisTemplate.opsForValue();
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();

        // 3. 拼接key
        // 3.1 库存key
        String stockKey = "SK:"+goodsId+":nums";

        // 3.2 秒杀用户成功key
        String userKey = "SK:" + goodsId + ":users";

        // 4. 获取库存,如果库存为空,秒杀还未开始
        Object stock = strOps.get(stockKey);
        if(StringUtils.isEmpty(stock)) {
            System.out.println("秒杀还未开始,请等待……");
            return false;
        }

        // 5. 判断用户是否重复秒杀操作
        if(setOps.isMember(userKey, uid)) {
            System.out.println("已参与");
            return false;
        }

        // 6. 判断商品数量,如果商品数量小于1,秒杀结束
        if(Integer.parseInt(stock.toString()) < 1) {
            System.out.println("秒杀已结束");
            return false;
        }

        // 7. 秒杀
        // 7.1 库存减一
        strOps.decrement(stockKey);

        // 7.2 秒杀成功的用户添加清单里面
        setOps.add(userKey, uid);

        return true;
    }

存在问题

  • 超卖问题

解决超卖问题

  • Redis事务

    public boolean doSecondKill(String uid, String goodsId) {

        // 1. 参数校验
        if( StringUtils.isEmpty(uid) || StringUtils.isEmpty(goodsId) ) {
            return false;
        }

        // 2. 获取Redis操作对象
        ValueOperations<String, Object> strOps =redisTemplate.opsForValue();
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();

        // 3. 拼接key

        // 3.1 库存key
        String stockKey = "SK:"+goodsId+":nums";

        // 3.2 秒杀用户成功key
        String userKey = "SK:" + goodsId + ":users";

        redisTemplate.watch(stockKey);

        // 4. 获取库存,如果库存为空,秒杀还未开始
        Object stock = strOps.get(stockKey);
        if(StringUtils.isEmpty(stock)) {
            System.out.println("秒杀还未开始,请等待……");
            return false;
        }

        // 5. 判断用户是否重复秒杀操作
        if(setOps.isMember(userKey, uid)) {
            System.out.println("已参与");
            return false;
        }

        // 6. 判断商品数量,如果商品数量小于1,秒杀结束
        if(Integer.parseInt(stock.toString()) < 1) {
            System.out.println("秒杀已结束");
            return false;
        }

        // 7. 秒杀
        redisTemplate.multi();

        // 7.1 库存减一
        strOps.decrement(stockKey);

        // 7.2 秒杀成功的用户添加清单里面
        setOps.add(userKey, uid);

        List<Object> execs = redisTemplate.exec();
        if(null == execs || execs.isEmpty()) {
            System.out.println("秒杀失败");
            return false;
        }

        return true;
    }

存在问题

  • 库存遗留问题

解决库存遗留问题

  • 锁机制 + Lua
private void lock(String lockKey, String lockValue, String uid) {

        Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, Duration.ofSeconds(30));
        System.out.println(uid + "->" + "加锁状态:" + nativeLock);
        if(nativeLock) {    // 加锁成功

            try {
                // 业务代码
                // ... 此处省略
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 解锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) else return 0 end";
                Integer ret = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList(lockKey), lockValue);
                System.out.println(uid + "->" + "解锁:" + "\t\t" + ret);
            }
        } else {    // 自旋锁
            System.out.println(uid + "->" + "加锁失败,睡眠100ms ");
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock(lockKey, lockValue, uid);
        }
    }

存在问题

  • 超时

解决超时问题

  • redisson + Lua
local stockKey = KEYS[1];
local userKey = KEYS[2];

local userId = ARGV[1];

-- 判断用户是否已参与
local userExists=redis.call("sismember", userKey, userId);
if type(userExists) == "number" and tonumber(userExists)==1 then
    -- 已参与
    return 2;
end

-- 库存判断 & 减库存操作
local num = redis.call("get", stockKey);
if type(num) == "boolean" then -- 还未开始
    return -1;
elseif type(num) == "number" and tonumber(num) <= 0 then -- 秒杀结束
    return 0;
else
    redis.call("decr", stockKey);
    redis.call("sadd", userKey, userId);
end

-- 秒杀成功
return 1;
private Boolean redissonLock(String lockName, String uid, String goodsId) {
        RLock lock = redissonClient.getLock(lockName);
        lock.lock();

        try {

            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/sk.lua")));
            redisScript.setResultType(Long.class);

            String stockKey = "SK:" + goodsId + ":stocks";
            String userKey = "SK:" + goodsId + ":users";

            // System.out.println(stockKey);
            // System.out.println(userKey);

            Long ret = redisTemplate.execute(redisScript, Arrays.asList(stockKey, userKey), uid);
            System.out.println(ret);
            return ret == 1;
        } finally {
            lock.unlock();
        }

    }



发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言