redisson实现分布式锁

1、应用场景

在分布式系统中(多进程/多机器)如果有一个逻辑是希望串行执行,那么需要使用分布式锁来解决。
在单进程中如果希望有一个逻辑是串行执行,那么可以使用JUC的Lock来解决。
分布式锁可以理解为跨机器跨进程的JUC的Lock。
image.png
看Redisson的分布式锁也是声明为实现JUC的Lock的子类,继承了JUC的Lock的行为。

2、使用范例

RLock lock = null;
boolean res = false;
try{
    lock = RedisUtils.getRedissonClient().getLock(LOCK_NAME);
    //最大等待30s
    res = lock.tryLock(30, TimeUnit.SECONDS);
    if(res){
       //执行分布式动作
       log.info("加锁成功");
       doSomething();
    }else{
         log.info("加锁失败");
     }
}catch (Exception e){
      log.error("加锁异常", e);
}finally{
    if(res){
       try{
           lock.unlock();  
       }catch(Exception e){
           log.error("解锁异常:", e);     
       }    
    } 
}  

3、传统的实现方案

使用redis的setNx(set if not exists),设置成功返回1,否则返回0。
image.png
上述方案存在以下几个问题:

  • 分布式锁如果没有释放,系统宕机,这个分布式锁可能就一直得不到释放,所以可以为这个锁增加一个过期时间,为了保证原子性,需要使用lua脚本处理。
  • 但是增加一个过期时间又产生了新的问题,如果业务时长超过过期时间,这个分布式锁的串行又出现了问题。
  • 不支持可重入,意思是一个线程在持有锁后不能多次获取这个锁。

4、redisson怎么解决这个问题


    /**
     * Returns <code>true</code> as soon as the lock is acquired.
     * If the lock is currently held by another thread in this or any
     * other process in the distributed system this method keeps trying
     * to acquire the lock for up to <code>waitTime</code> before
     * giving up and returning <code>false</code>. If the lock is acquired,
     * it is held until <code>unlock</code> is invoked, or until <code>leaseTime</code>
     * have passed since the lock was granted - whichever comes first.
     *
     * @param waitTime the maximum time to aquire the lock
     * @param leaseTime lease time
     * @param unit time unit
     * @return <code>true</code> if lock has been successfully acquired
     * @throws InterruptedException - if the thread is interrupted before or during this method.
     */
    boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;

翻译如下:
这个锁一旦获取会立刻返回true。如果这个锁被分布式系统中的这个或其他进程中的其他线程持有,这个方法会尝试获取这个锁直到到达waitTime的时间。这个锁被获取,它将被持有到unlock方法调用或者leaseTime到达,取决于哪个动作先到。
image.png
tryAcquire是核心实现。
image.png
leaseTime是个关键词,如果这个值是默认值-1,会启动watchDog机制进行锁的自动续期操作。
image.png
底层实现是lua脚本,这个脚本是这样的含义:如果这个锁lockName不存在,首先设置hset lockName 线程id 1,
延期这个锁lockName为internalLockLeaseTime时间,返回null
如果这个hash lockName 线程id 存在,那么将这个hash值加1,延长这个锁的生命周期(支持上面所谓的可重入逻辑),返回null
否则,返回这个key的剩余生命时长。

为了解决上面所说的死锁、业务时长超过锁的过期时间等问题,redisson引入了watchDog机制。
image.png
image.png
会启动一个定时任务,如果当前线程还持有锁,就回不断对锁进行续期。
这样的机制可以解决死锁问题:如果进程宕机,watchDog不工作,redis的锁本身是有生命周期的(30s),到期后会自动过期。
也可以解决业务时长超过锁的过期时间的问题,watchDog会不断续期,如果线程仍然持有的话。
当然,如果线程解锁了,watchDog是怎么失效的呢?关键在于EXPIRATION_RENEWAL_MAP这样一个map,
调用unlock会把对象的线程id的key remove掉。
image.png
这个环节是关键。
所以:
:::tips
不传releaseTime这个参数就会开启看门狗模式,锁也会有个默认的超时时间30s,如果线程没有执行完的话,watchDog会自动续期,如果进程挂掉的话,watchDog就不续期了,锁也不会变成死锁了
:::

posted on 2022-10-13 19:55  张小泽的小号  阅读(628)  评论(0编辑  收藏  举报

导航