redisson实现分布式锁
1、应用场景
在分布式系统中(多进程/多机器)如果有一个逻辑是希望串行执行,那么需要使用分布式锁来解决。
在单进程中如果希望有一个逻辑是串行执行,那么可以使用JUC的Lock来解决。
分布式锁可以理解为跨机器跨进程的JUC的Lock。
看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。
上述方案存在以下几个问题:
- 分布式锁如果没有释放,系统宕机,这个分布式锁可能就一直得不到释放,所以可以为这个锁增加一个过期时间,为了保证原子性,需要使用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到达,取决于哪个动作先到。
tryAcquire是核心实现。
leaseTime是个关键词,如果这个值是默认值-1,会启动watchDog机制进行锁的自动续期操作。
底层实现是lua脚本,这个脚本是这样的含义:如果这个锁lockName不存在,首先设置hset lockName 线程id 1,
延期这个锁lockName为internalLockLeaseTime时间,返回null
如果这个hash lockName 线程id 存在,那么将这个hash值加1,延长这个锁的生命周期(支持上面所谓的可重入逻辑),返回null
否则,返回这个key的剩余生命时长。
为了解决上面所说的死锁、业务时长超过锁的过期时间等问题,redisson引入了watchDog机制。
会启动一个定时任务,如果当前线程还持有锁,就回不断对锁进行续期。
这样的机制可以解决死锁问题:如果进程宕机,watchDog不工作,redis的锁本身是有生命周期的(30s),到期后会自动过期。
也可以解决业务时长超过锁的过期时间的问题,watchDog会不断续期,如果线程仍然持有的话。
当然,如果线程解锁了,watchDog是怎么失效的呢?关键在于EXPIRATION_RENEWAL_MAP这样一个map,
调用unlock会把对象的线程id的key remove掉。
这个环节是关键。
所以:
:::tips
不传releaseTime这个参数就会开启看门狗模式,锁也会有个默认的超时时间30s,如果线程没有执行完的话,watchDog会自动续期,如果进程挂掉的话,watchDog就不续期了,锁也不会变成死锁了
:::