当前位置:   article > 正文

Redis实现分布式锁_jedis实现分布式锁

jedis实现分布式锁

一、分布式锁和普通锁的区别

1.普通的锁

        用来解决一个进程的多个线程同时操作同一资源的问题,如果是在分布式系统或者集群中,普通的锁是锁不住的。如图1:

图1

2.分布式锁

        用来解决多个进程同时操作同一资源的问题。他的原理就是所有的获取到同一把锁,无论有多少个服务,只有一个服务可以获取到锁,其他没有获得到的锁的服务则进行等待或者自旋等,直到锁释放,其他的服务才可以尝试获取到锁,如图2:

图2

二、Redis实现分布式锁

1.使用setnx加锁和释放锁

        Redis有一个setnx可以实现分布式锁,sexnx在指定的key不存在时,会为key设置指定的值,若给定的key已存在,setnx不做任何操作。

  1. if(jedis.setnx(lock_stock,1) == 1){ //获取锁
  2. try {
  3. //需要执行的业务代码
  4. } finally {
  5. //释放锁
  6. jedis.del(lock_stock)
  7. }

2.为锁设置过期时间

        使用setnx的命令进行加锁和释放锁,我们可以发现可能会存在死锁的现象,当我们在锁释放之前,服务宕机了,就会导致锁无法释放,那么其他的服务也就无法获取到锁,就会造成死锁,因此,我们在加锁时还应该为锁设置一个过期的时间。

  1. if(jedis.setnx(lock_stock,1) == 1){ //获取锁
  2. //设置锁超时
  3. expire(lock_stock,2
  4. try {
  5. //需要执行的业务代码
  6. } finally {
  7. //释放锁
  8. jedis.del(lock_stock)
  9. }
  10. }

        但是使用了expire设置了过期时间,仍会存在一种极值的现象,就是若在加锁和设置锁超时之间,服务发生了宕机,依然会发生死锁的现象,因此我们需要去保证加锁和释放的原子性。Redis中提供了一个set命令可以解决这个问题,相当于是加锁的设置过期时间的一个组合命令。

  1. if(set(lock_stock,1,"NX","EX",2) == 1){    //获取锁并设置超时
  2.     try {
  3.         //需要执行的业务代码
  4.     } finally {
  5.   //释放锁
  6.         del(lock_stock)                   
  7.     }
  8. }

        - EX : 将键的过期时间设置为秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。
        - PX : 将键的过期时间设置为毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。
        - NX : 只在键不存在时,才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。
        - XX : 只在键已经存在时, 才对键进行设置操作。

3.删除锁之前判断锁

        使用set命令进行加锁和释放锁,虽然保证了原子性,但是又出现了新的问题,如果我们需要执行的业务代码时间超过了锁设置的过期时间,那么在锁达到过期时间时会将这个锁释放,而在执行完业务后又进行了一次锁的删除,就会导致第二次删除的锁不是执行该业务时加的锁,造成锁的一个误删除现象。为解决这一问题,我们需要在删除锁的时候进行锁的判断,判断当前删除的锁是不是自己的锁。

  1. String uuid = UUID.randomUUID().toString();
  2. if(jedis.set(lock_stock,uuid,"NX","EX",5) == 1){    //获取锁并设置超时
  3.     try {
  4.         //执行自己的业务代码
  5.     } finally {
  6. //获取锁的值
  7.         String lockValue = jedis.get(lock_stock);    
  8. //判断是不是自己的锁
  9.         if(lockValue.equals(uuid)){            
  10. //释放锁
  11.             jedis.del(lock_stock)                   
  12.         }
  13.     }
  14. }

        除了要判断锁,我们还应该去保证判断锁和删除锁的一个原子性,否则依然会有极值的问题,导致锁的误删除,因此这里使用Redis+Lua脚本来解决一致性的问题。

  1. String script = "if redis.call('get', KEYS[1]) == ARGV[1]
  2. then return redis.call('del', KEYS[1]) else return 0 end";

        上述的这段Lua脚本,可以保证多个命令的原子性,其中

  •  redis.call(‘get’, KEYS[1]) :是调用redis的get命令,key可以通过参数传入
  •  == ARGV[1] :意思是是否和 某个值相等,这里的值也可以参数传入
  •  then return redis.call(‘del’, KEYS[1]) :如果相等就执行 redis.call('del', KEYS[1]) 删除操作
  •  else return 0 end :否则就返回

        修改上述的代码为:

  1. String uuid = UUID.randomUUID().toString();
  2. if(jedis.set(lock_stock,uuid,"NX","EX",2) == 1){ //获取锁并设置超时
  3. try {
  4. //需要执行的业务代码
  5. } finally {
  6. //lua脚本
  7. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  8. //执行脚本
  9. jedis.eval(script, Collections.singletonList("lock_stock"),Collections.singletonList(uuid));
  10. }
  11. }

        到此一个基本的redis实现分布式锁就完成了,但是考虑到实际的业务,未获得锁的线程可以做重入,等待一会儿,再次尝试获取锁。

  1. public void method(){
  2.     String uuid = UUID.randomUUID().toString();
  3.     if(jedis.set(lock_stock,uuid,"NX","EX",5) == 1){    //获取锁并设置超时
  4.         try {
  5.             //需要执行的业务代码
  6.         } finally {
  7.             //lua脚本
  8.             String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  9.             //执行脚本
  10.             jedis.eval(script, Collections.singletonList("lock_stock"),Collections.singletonList(uuid));
  11.         }
  12.     }else{
  13.         //休眠之后重入方法,尝试获取锁,休眠的时间可根据自己的时间调整
  14.         Thread.sleep(100);
  15. //自旋,重新进入方法
  16.         method();    
  17.     }
  18. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/696708
推荐阅读
相关标签
  

闽ICP备14008679号