redis事务和redis分布式锁(悲观锁和乐观锁)

1.redis事务-mutil/exec

redis中mutil是事务的开始,exec是事务的结束

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set hello 1
QUEUED
127.0.0.1:6379> set hello 2
QUEUED
127.0.0.1:6379> set hello 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> get hello
"3"

如图:执行mutil命令时,表示事务开始,然后进入命令1,命令2,命令3,依次进入队列,等到exec时,所有队列中的命令按顺序执行。中间一旦出现问题,就返回错误信息。(但是redis并没有提供回滚机制,也就是如果上面的 set hello 2执行错误,依然执行set hello 3,最后get hello的返回值也是3),但是java程序可以通过返回的错信息来执行相关操作。

2.redis事务-watch(watch的作用类似于乐观锁)

单线程中mutil/exec就可以保证事务,但是多线程中,下面的代码并不能保证事务(下面是实现incr mykey的代码),当两个线程同时进入val = val + 1这一行代码,依然可能出现原本自加一次的代码,出现自加两次。

mutil
val = GET mykey
val = val + 1
SET mykey $val
exec

于是出现了watch命令,WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务队列中保存的命令就不会执行,监控一直持续到EXEC命令。(事务队列中的命令是在EXEC才执行的) 这样就可保证,如果在exec之前出现错误,就会返回错误信息。事务队列中的命令全部取消执行。

下面代码中,watch提供了乐观锁的机制,先在执行事务之前比较mykey与期望值是否相同,如果mykey没有被其他修改就执行下面的操作,否则取消执行SET mykey $val。

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

3. redis的分布式锁实现–悲观锁

tryLock本质就是给某个key使用SETNX命令。
具体可以见:https://blog.csdn.net/qq_35688140/article/details/100923747

while (true) {
        if (Integer.parseInt(jedis.get(key)) <= 0) {
            break;//缓存中没有商品,跳出循环,消费者线程执行完毕
        }
        //缓存中还有商品,取锁,商品数目减一
         System.out.println("顾客:" + clientName + "开始抢商品");  
         if (redisBasedDistributedLock.tryLock(3,TimeUnit.SECONDS)) {//等待3秒获取锁,否则返回false(悲观锁:每次拿数据都上锁)
            int prdNum = Integer.parseInt(jedis.get(key));//再次取得商品缓存数目
            if (prdNum > 0) {
                jedis.decr(key);//商品数减一
                jedis.sadd(clientList, clientName);//将抢购到商品的顾客记录一下
                System.out.println("恭喜,顾客:" + clientName + "抢到商品");  
            } else {  
                System.out.println("抱歉,库存为0,顾客:" + clientName + "没有抢到商品");  
            }
            redisBasedDistributedLock.unlock0();//操作完成释放锁
            break;
        }
    }
    //释放资源
    redisBasedDistributedLock = null;
    RedisUtil.returnResource(jedis);
}

4.redis的分布式锁实现–乐观锁

利用watch,mutil和exec实现分布式乐观锁。

while(true){
     System.out.println("顾客:" + clientName + "开始抢购商品");
     jedis = RedisUtil.getInstance().getJedis();
     try {
         jedis.watch(key);//监视商品键值对,作用时如果事务提交exec时发现监视的键值对发生变化,事务将被取消
         int prdNum = Integer.parseInt(jedis.get(key));//当前商品个数
         if (prdNum > 0) {
             Transaction transaction = (Transaction) jedis.multi();//开启redis事务
             ((Jedis) transaction).set(key,String.valueOf(prdNum - 1));//商品数量减一
             List<Object> result = ((redis.clients.jedis.Transaction) transaction).exec();//提交事务(乐观锁:提交事务的时候才会去检查key有没有被修改)
             if (result == null || result.isEmpty()) {
                 System.out.println("很抱歉,顾客:" + clientName + "没有抢到商品");// 可能是watch-key被外部修改,或者是数据操作被驳回
             }else {
                 jedis.sadd(clientList, clientName);//抢到商品的话记录一下
                 System.out.println("恭喜,顾客:" + clientName + "抢到商品");  
                 break; 
             }
         }else {
              System.out.println("很抱歉,库存为0,顾客:" + clientName + "没有抢到商品");  
              break; 
         }
     } catch (Exception e) {
         // TODO: handle exception
     }finally{
         jedis.unwatch();
         RedisUtil.returnResource(jedis);
     }
 }

参考文献:
https://www.lizenghai.com/archives/21845.html
https://www.jianshu.com/p/06f1bce98451
https://blog.csdn.net/Evankaka/article/details/70570200
https://blog.csdn.net/qw463800202/article/details/53287139
https://blog.csdn.net/wanderlustlee/article/details/81082506
https://www.jianshu.com/p/361cb9cd13d5
https://blog.csdn.net/wjq008/article/details/81207240

你可能感兴趣的:(并发,秒杀,redis)