微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用Redis事务与乐观锁、Lua脚本解决秒杀系统问题

该栏目会系统的介绍 Redis 的知识体系,共分为相关概念、操作指令、主从复制等模块


文章目录


事务简介

1、概述

  • :redis 事务是一个单独的隔离操作,就是用来串联多个命令防止别的命令插队

2、特性

  • 单独的隔离操作
  • 没有隔离级别
  • 不保证原子性

3、相关命令

功能指令
组队multi
执行exec
取消组队discard
监视watch
取消监视unwatch

秒杀案例

  • 连接超时问题:使用连接池解决
  • 超卖问题:使用事务和乐观锁
  • 遗留库存问题:使用Lua脚本
/**
 * 1、使用连接池解决超时问题
 * 2、使用事务与乐观锁解决超卖问题,但引入遗留库存问题,用Lua脚本解决
 *
 * @param productId 商品Id
 * @param userId    用户Id
 * @return 操作结果
 */
@Test
public boolean secKill(String productId, String userId) {
    if (productId == null || userId == null) {
        return false;
    }

    // 定义key
    String stockKey = "sk:" + productId + ":stock";
    String userKey = "sk:" + productId + ":user";

    // 判断秒杀是否开始
    final String stock = (String) stringOps.get(stockKey);
    if (stock == null) {
        System.out.println("秒杀活动还没开始,请等待");
        return false;
    }
    // 判断用户是否已经秒杀过
    final Boolean result = Optional.ofNullable(setops.isMember(userKey, userId)).get();
    if (result) {
        System.out.println("用户已经参加过秒杀了");
        return false;
    }
    // 判断秒杀是否结束
    if (Integer.parseInt(stock) <= 0) {
        System.out.println("秒杀结束...");
        return false;
    }

    // 监视库存,使用乐观锁机制
    redistemplate.watch(stockKey);

    // 开启事务
    redistemplate.multi();
    stringOps.decrement(stockKey);
    setops.add(userKey, userId);
    final List<Object> results = redistemplate.exec();
    if (results.size() == 0) {
        System.out.println("秒杀失败");
        return false;
    }

    System.out.println("秒杀成功");
    return true;
}

/**
 * LUA脚本概述:将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接
 * redis次数。提升性能。LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完
 * 成一些redis事务性的操作
 * 
 * 使用Lua脚本解决遗留库存问题
 *
 * @param productId 商品Id
 * @param userId    用户Id
 * @return 操作结果
 */
@Test
public boolean secKillScript(String productId, String userId) {
    String secKillScript = "local userid=KEYS[1];\r\n" +
        "local prodid=KEYS[2];\r\n" +
        "local qtkey='sk:'..prodid..\":qt\";\r\n" +
        "local usersKey='sk:'..prodid..\":usr\";\r\n" +
        "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
        "if tonumber(userExists)==1 then \r\n" +
        "   return 2;\r\n" +
        "end\r\n" +
        "local num= redis.call(\"get\" ,qtkey);\r\n" +
        "if tonumber(num)<=0 then \r\n" +
        "   return 0;\r\n" +
        "else \r\n" +
        "   redis.call(\"decr\",qtkey);\r\n" +
        "   redis.call(\"sadd\",usersKey,userid);\r\n" +
        "end\r\n" +
        "return 1";

    // 定义key
    String stockKey = "sk:" + productId + ":stock";
    String userKey = "sk:" + userId + ":user";

    // 执行Lua脚本
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    redisScript.setScriptText(secKillScript);
    redisScript.setResultType(Long.class);
    final long result = Optional.ofNullable(redistemplate.execute(
    			redisScript, Arrays.asList(userId, productId))).get();
    if (0 == result) {
        System.out.println("秒杀活动结束");
        return false;
    } else if (1 == result) {
        System.out.println("秒杀成功");
        return true;
    } else if (2 == result) {
        System.out.println("用户已经参加过秒杀了");
        return false;
    } else {
        System.out.println("秒杀异常!");
        return false;
    }
    
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐