从头开始学Redisson--------分布式锁
一、立即获取锁
Redisson提供了一种非常便捷的分布式锁,个人认为堪称分布式锁中好用之最。
//获取锁
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();
//释放锁
lock.unlock();
我部署了两个节点,分别是8081端口与8082端口。访问其中的任意一个节点,我们按照上述文档的代码,获取锁。方法上接收的参数是锁的名字。
首先请求8081端口的方法,URL是 localhost:8081/lock?lockName=myLock,同步锁名称传 'myLock'。业务代码如下,模拟执行业务20秒。
再从8082端口请求相同的URL,localhost:8082/lock?lockName=myLock。因为lockName的值是相同的,所以在8082节点拿到的是同一把锁。8082在调用tryLock()方法的时候,如果8081端口的20秒业务没有处理完的话,那么tryLock ()方法将会返回false。如果lock.tryLock()返回false,说明8081端口的业务没有处理完,8082就会直接跳过执行业务代码。如果lock.tryLock()返回true,说明8081的业务处理完毕,8082则可以获取到同步锁,因此可以执行规定的业务代码。
@Autowired
private RedissonClient redisson;
@GetMapping(value = "/lock")
public void addLock(String lockName) {
//从Redisson获取分布式锁
RLock lock = redisson.getLock(lockName);
//尝试获取锁
if (lock.tryLock()) {
try {
LOGGER.info("执行业务20秒");
Thread.sleep(20000);
} catch (Exception e) {
LOGGER.error("an exception was occurred ,
caused by :{}", e.getMessage());
} finally {
lock.unlock();
}
}
System.out.println("执行结束");
}
二、可重入特性
如果有两个同步方法,它们的同步锁对象是相同的。我们假设这两个方法分别叫A方法与B方法。如果A方法的内部执行了B方法,或者B方法的内部执行了A方法,那么它们的调用是畅通无阻的。说的比较高端一点: 在已经获得锁的同步方法或同步代码块内部可以调用锁定对象的其他同步方法。
我写一段比较直观的代码。addLock()方法里又执行了reMehod()方法。reMethod()方法的业务里面的需要获取到同步锁才能执行。因为addLock()方法会把锁的名称传到reMethod()方法里,所以reMethod()方法与addLock()方法获取到的是同一把锁,因此reMethod()方法的lock.tryLock()会返回true。所以reMethod()方法的业务得以立即被执行。
@Autowired
private RedissonClient redisson;
@GetMapping(value = "/lock")
public void addLock(String lockName) {
RLock lock = redisson.getLock(lockName);
if (lock.tryLock()) {
try {
LOGGER.info("执行业务20秒");
//可重入性测试
this.reMethod(lockName);
Thread.sleep(20000);
} catch (Exception e) {
LOGGER.error("an exception was occurred , caused by :{}",
e.getMessage());
} finally {
lock.unlock();
}
}
System.out.println("执行结束");
}
private void reMethod(String lockName) {
RLock lock = redisson.getLock(lockName);
if (lock.tryLock()) {
try {
LOGGER.info("可重入任务");
} catch (Exception e) {
LOGGER.error("an exception was occurred , caused by :{}", e.getMessage());
} finally {
lock.unlock();
}
}
}
三、等待放弃获取锁机制
如果某个请求需要同步锁,但是同步锁此刻被其他的线程占用了,但是当前线程又不想立即放弃操作。尝试在指定时间内获取同步锁,如果在指定时间内没有获取到锁,才最终放弃获取锁。
java.util.concurrent.locks.Lock#tryLock(long time,TimeUnit unit);这个接口是无参tryLock()的重载方法。
Redisson的RLock类实现了Lock接口,因此就具备在指定时间内放弃锁的功能。
@GetMapping(value = "/lock")
public void addLock(String lockName) throws InterruptedException {
RLock lock = redisson.getLock(lockName);
//4秒内如果没有拿到同步锁,返回false
if (lock.tryLock(4, TimeUnit.SECONDS)) {
try {
LOGGER.info("执行业务20秒");
Thread.sleep(20000);
} catch (Exception e) {
} finally {
lock.unlock();
}
}
System.out.println("执行结束");
}
四、自动释放锁
另外Redisson还通过加锁的方法提供了leaseTime
的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
五、宕机情况
我做了8081端口与8082端口两台实例的简单分布式情况。我在8081端口尝试获取到锁以后,在共享代码里面打了断点,模拟宕机情况,让8081一直不调用lock.unlock()方法释放锁。同时,也向8082端口发送请求。预期结果是,8082节点将会一直阻塞,因为同步锁被8081端口霸占。可以好像每次过了几十秒,8082端口就自动通了。这与我之前的经验所违背。难道是Redisson的分布式锁失效了?
几经查阅资料,原来Redisson就是专门这样设计的。Redisson就怕某个节点拿到了锁但是宕机了,那么其它节点就会一直拿不到锁。贴一下Redisson 官网的解释:
大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
原来,Redisson为每个RLock提供了看门狗进行30秒的倒计时。如果想修改这个倒计时时间,在SpringBoot中像我这样配置就好了。看来Redisson还挺人性化的嘛。
@Bean
public RedissonClient redissonClient() {
LOGGER.error(url);
Config config = new Config();
//设置看门狗时间为60秒
config.setLockWatchdogTimeout(60 * 1000);
config.useSingleServer().setAddress(url).setPassword(password);
RedissonClient redisson = Redisson.create(config);
return redisson;
}
如果本文对你有帮助,不妨请我喝瓶可乐吧!
你的打赏是对我最好的支持!