[redis]Redis6的事务操作

Redis6的事务操作

Redis的事务定义

image-20220619152757114

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

组队的过程中可以通过discard来放弃组队。

image-20220619152848021

事务的错误处理

  • 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消

image-20220619152910457

  • 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

image-20220619152936595

事务冲突

例子

一个请求想给金额减8000

一个请求想给金额减5000

一个请求想给金额减1000

image-20220619164733463

两种锁的思想

image-20220619153547188

悲观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。

乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

WATCH key [key …]

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

unwatch

取消 WATCH 命令对所有 key 的监视。

如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

Redis事务三特性

Ø 单独的隔离操作

  • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Ø 没有隔离级别的概念

  • 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

Ø 不保证原子性

  • 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

秒杀案例

思路

image-20220619173036319

  1. 传入uid和prodid(并验证)
  2. 根据uid和prodid生成key
  3. 库存判断(null未开始,0结束),重复秒杀判断
  4. 用户秒杀,库存–
  5. 用户进入到秒杀成功清单

代码:

public static boolean doSecKill(String uid,String prodid) throws IOException {
   //1 uid和prodid非空判断
   if(uid == null || prodid == null) {
      return false;
   }

   //2 连接redis
   //Jedis jedis = new Jedis("192.168.44.168",6379);
   //通过连接池得到jedis对象
   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 || results.size()==0) {
      System.out.println("秒杀失败了....");
      jedis.close();
      return false;
   }

   //7.1 库存-1
   //jedis.decr(kcKey);
   //7.2 把秒杀成功用户添加清单里面
   //jedis.sadd(userKey,uid);

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

可以通过线程池解决链接超时问题

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

   private JedisPoolUtil() {
   }

   public static JedisPool getJedisPoolInstance() {
      if (null == jedisPool) {
         synchronized (JedisPoolUtil.class) {
            if (null == jedisPool) {
               JedisPoolConfig poolConfig = new JedisPoolConfig();
               poolConfig.setMaxTotal(200);
               poolConfig.setMaxIdle(32);
               poolConfig.setMaxWaitMillis(100*1000);
               poolConfig.setBlockWhenExhausted(true);
               poolConfig.setTestOnBorrow(true);  // ping  PONG
             
               jedisPool = new JedisPool(poolConfig, "192.168.44.168", 6379, 60000 );
            }
         }
      }
      return jedisPool;
   }

   public static void release(JedisPool jedisPool, Jedis jedis) {
      if (null != jedis) {
         jedisPool.returnResource(jedis);
      }
   }

}

可以使用lua来解决库存遗留问题

public class SecKill_redisByScript {
   
   private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

   public static void main(String[] args) {
      JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
 
      Jedis jedis=jedispool.getResource();
      System.out.println(jedis.ping());
      
      Set<HostAndPort> set=new HashSet<HostAndPort>();

   // doSecKill("201","sk: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 uid,String prodid) throws IOException {

      JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
      Jedis jedis=jedispool.getResource();

       //String sha1=  .secKillScript;
      String sha1=  jedis.scriptLoad(secKillScript);
      Object result= jedis.evalsha(sha1, 2, uid,prodid);

        String reString=String.valueOf(result);
      if ("0".equals( reString )  ) {
         System.err.println("已抢空!!");
      }else if("1".equals( reString )  )  {
         System.out.println("抢购成功!!!!");
      }else if("2".equals( reString )  )  {
         System.err.println("该用户已抢过!!");
      }else{
         System.err.println("抢购异常!!");
      }
      jedis.close();
      return true;
   }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值