Redis事务_秒杀案例

一、秒杀代码
//秒杀过程
    @PostMapping("/doSecKill/{uid}/{prodid}")
    public boolean doSecKill(@PathVariable("uid") String uid,@PathVariable("prodid") String prodid){
        //1 uid和prodid非空判断
          if( uid == null || prodid == null ){
              return false;
          }
        //2 连接redis
        Jedis jedis = new Jedis("192.168.xxx.163", 6379);
        //3 拼接key
        //3.1 库存key
        String kcKey = "sk:"+prodid+":qt";
        //3.2 秒杀成功用户key
        String userKey = "sk:"+uid+":user";
        //4 获取库存,如果库存为null,秒杀还没有开始
        String kc = jedis.get(kcKey);
        if( kc == null ){
            System.out.println("秒杀还没开始,请等待");
            jedis.close();
            return false;
        }
        //5 判断用户是否重复秒杀操作
        if(jedis.sismember(userKey,uid)){
            System.out.println("已经秒杀成功了,不能重复秒杀");
            jedis.close();
            return false;
        }
        //6 判断如果商品数量,库存数量小于1,秒杀结束
        if(Integer.parseInt(kc) <= 0){
            System.out.println("秒杀已经结束了");
            jedis.close();
            return false;
        }
        //7 秒杀过程
        //7.1 库存-1
        jedis.decr(kcKey);
        //7.1 把秒杀成功的用户添加到清单里面
        jedis.sadd(userKey,uid);
        System.out.println("秒杀成功了。。。");
        jedis.close();
        return true;
    }
二、Redis事务--秒杀并发模拟
1、使用工具ab模拟测试,

            centos6默认安装,centos7需要手动安装, 联网:yum install httpd-tools

       2、测试
ab -n 1000 -c 100 -p /home/du/postfile -T application/x-www-form-urlencoded http://192.168.xx.1:9000/redisTest/doSecKill
       3、结果

出现了超卖

127.0.0.1:6379> get sk:0101:qt
"-4"
三、超卖问题和超时问题解决
      1、超时问题

  使用连接池代替客户端连接

public class JedisPoolUtil {
   private static volatile JedisPool jedisPool = null;

   private JedisPoolUtil(){

   }

   public static JedisPool getJedisPoolInstance(){
       if(jedisPool == null){
           synchronized (JedisPoolUtil.class){
               if(jedisPool == null){
                   JedisPoolConfig poolConfig = new JedisPoolConfig();
                   poolConfig.setMaxTotal(300);
                   poolConfig.setMaxIdle(32);
                   poolConfig.setMaxWaitMillis(100*100);
                   poolConfig.setBlockWhenExhausted(true);
                   poolConfig.setTestOnBorrow(true);

                   jedisPool = new JedisPool(poolConfig, "192.168.xxx.166", 6379, 60000);
               }
           }
       }
       return jedisPool;
   }
    public static void release(JedisPool jedispool,Jedis jedis) {
        if(null!=jedis) {
            jedis.close();
        }
    }
}
//2 连接redis
        //Jedis jedis = new Jedis("192.168.xxx.166", 6379);
        //使用连接池
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();
      2、超卖问题

使用Redis乐观锁+Redis事务

    //秒杀过程
    @PostMapping("/doSecKill")
    public boolean doSecKill(@RequestParam(value = "uid",required = false) String uid,@RequestParam("prodid") String prodid){
        uid = String.valueOf(new Random().nextInt(10000));
        //1 uid和prodid非空判断
          if( uid == null || prodid == null ){
              return false;
          }
        //2 连接redis
        //Jedis jedis = new Jedis("192.168.xxx.166", 6379);
        //使用连接池
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();
        //3 拼接key
        //3.1 库存key
        String kcKey = "sk:"+prodid+":qt";
        //3.2 秒杀成功用户key
        String userKey = "sk:"+prodid+":user";
        //监视库存
        jedis.watch(kcKey);

        //4 获取库存,如果库存为null,秒杀还没有开始
        String kc = jedis.get(kcKey);
        if( kc == null ){
            System.out.println("秒杀还没开始,请等待");
            jedis.close();
            return false;
        }
        //5 判断用户是否重复秒杀操作
        if(jedis.sismember(userKey,uid)){
            System.out.println("已经秒杀成功了,不能重复秒杀");
            jedis.close();
            return false;
        }
        //6 判断如果商品数量,库存数量小于1,秒杀结束
        if(Integer.parseInt(kc) <= 0){
            System.out.println("秒杀已经结束了");
            jedis.close();
            return false;
        }
        //7 秒杀过程
        //创建事务
        Transaction multi = jedis.multi();
        //添加组队
        multi.decr(kcKey);
        multi.sadd(userKey,uid);
        //执行
        List<Object> results = multi.exec();
        //判断结果
        if(results == null){
            System.out.println("秒杀失败了。。。");
            jedis.close();
            return true;
        }
        7.1 库存-1
        //jedis.decr(kcKey);
        7.1 把秒杀成功的用户添加到清单里面
        //jedis.sadd(userKey,uid);

        System.out.println("秒杀成功了。。。");
        jedis.close();
        return true;
    }

结果

127.0.0.1:6379> get sk:0101:qt
"0"
四、库存遗留问题
1、原因

      乐观锁版本不一致,导致购买失败

2、解决

     使用lua嵌入式脚本(2.6以上版本)

     lua脚本优势:将复杂或者多步的Redis操作,写为一个脚本,一次提交给Redis执行,减少反复连接redis次数,提升性能;(有一定原子性,不会被其他命令插队,可以完成一些redis事务性的操作)

    

public class SecKill_redisByScript {

    public static void main(String[] args) {
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();
        System.out.println(jedis.ping());
        HashSet<HostAndPort> set = new HashSet<>();
        doSecKill("201","0101");

    }
    static 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;";

    static String secKillScript2 = "local userExists = redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n"
            +"return 1";

    public static boolean doSecKill(String userid,String prodid){
        Jedis jedis = null;
        JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
        try{
            jedis = jedisPool.getResource();
        }catch (RuntimeException e){
            if(jedis != null){
                JedisPoolUtil.release(jedisPool,jedis);
            }
        }

        String sha1 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1,2,userid,prodid);
        System.out.println(result);
        String reString = String.valueOf(result);

        if("0".equals(reString)){
            System.out.println("已抢空!!");
        }else if("1".equals(reString)){
            System.out.println("抢购成功!!!!!");
        }else if("2".equals(reString)){
            System.out.println("该用户已抢过");
        }else{
            System.out.println("抢购异常!!");
        }
        JedisPoolUtil.release(jedisPool,jedis);
        return true;
    }
}
  3、测试
ab -n 2000 -c 300 -p /home/du/postfile -T application/x-www-form-urlencoded http://192.168.xxx.1:9000/redisTest/doSecKill
 4、结果
127.0.0.1:6379> get sk:0101:qt
"500"
127.0.0.1:6379> get sk:0101:qt
"0"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值