redis分布式锁详解Redisson(RedissonClient)
作者:码上走人
Redisson提供了多种锁和实用方法,实现了对数据的增删改查等操作,RedissonClient接口的实现类中,重点介绍了重入锁、公平锁和联锁的实现方式,在实际应用中,设置定时过期的分布式锁需要考虑服务宕机或重启的问题,可以通过记录锁的Set来解决
RedissonClient中提供了好多种锁,还有其它很多实用的方法。
Redisson是Redis官方推荐的Java版的Redis客户端。实现了对数据的增删改查等操作。
Redisson实现了RedissonClient的接口。这里只介绍其中的锁。
依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.7</version>
</dependency>重入锁 RedissonLock
重入锁可以通过Redisson的getLock方法获取
@Override
public RLock getLock(String name) {
return new RedissonLock(connectionManager.getCommandExecutor(), name);
}/**
* 获取锁-同一个线程可重入
* @param lockKey 锁的名称
* @param waitTime 获取锁的等待时间
* @param leaseTime 锁的持续时间
* @param unit 时间的单位
* @return 获取锁的结果
*/
public Boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 1. 最常见的使用方法
//lock.lock();
// 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
//lock.lock(10, TimeUnit.SECONDS);
boolean locked = lock.tryLock(waitTime, leaseTime, unit);
if (locked) lockKeys.add(lockKey);
return locked;
} catch (InterruptedException e) {
System.out.println(String.format("尝试获取锁%s失败", lockKey));
e.printStackTrace();
}
return Boolean.FALSE;
}
/**
* 解锁 - 重入的方式,所以同一个线程加了几次锁就要释放几次锁
* @param lockKey 锁的值
*/
public boolean unLock(String lockKey) {
try {
RLock lock = redissonClient.getLock(lockKey);
if (null != lock && lock.isHeldByCurrentThread()) { //判断锁是否存在,和是否当前线程加的锁。
lock.unlock();
return lockKeys.remove(lockKey);
}
} catch (Exception e) {
System.out.println(String.format("解锁锁%s失败", lockKey));
e.printStackTrace();
}
return false;
}重入锁的异步执行方式
/**
* 异步获取锁-同一个线程可重入
* @param lockKey 锁的名称
* @param waitTime 获取锁的等待时间
* @param leaseTime 锁的持续时间
* @param unit 时间的单位
* @return 获取锁的结果
*/
public Boolean tryLockAsync(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 1. 最常见的使用方法
//lock.lockAsync();
// 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
//lock.lockAsync(10, TimeUnit.SECONDS);
RFuture<Boolean> locked = lock.tryLockAsync(waitTime, leaseTime, unit);
if (locked.get()) lockKeys.add(lockKey);
return locked.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println(String.format("尝试获取锁%s失败", lockKey));
e.printStackTrace();
}
return Boolean.FALSE;
}
/**
* 解锁 - 重入的方式,所以同一个线程加了几次锁就要释放几次锁
* @param lockKey 锁的值
*/
public boolean unAsyncLock(String lockKey) {
try {
RLock lock = redissonClient.getLock(lockKey);
if (null != lock && lock.isHeldByCurrentThread()) { //判断锁是否存在,和是否当前线程加的锁。
RFuture<Void> future = lock.unlockAsync();
if(future.await(5 * 1000) && future.isSuccess()) {
return lockKeys.remove(lockKey);
}
}
} catch (Exception e) {
System.out.println(String.format("解锁%s失败", lockKey));
e.printStackTrace();
}
return false;
}公平锁
改公平锁是可重入的,在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。同时也提供了异步的方式。
实现方式参照上一个锁的实现。
@Override
public RLock getFairLock(String name) {
return new RedissonFairLock(connectionManager.getCommandExecutor(), name);
}/**
* 公平锁
*
* @param lockKey 锁的名称
* @param waitTime 获取锁的等待时间
* @param leaseTime 锁的持续时间
* @param unit 时间的单位
*
* @return 获取锁的结果
*/
public Boolean tryFairLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getFairLock(lockKey);
try {
// 1. 最常见的使用方法
//lock.tryLock();
// 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
//lock.tryLock(10, TimeUnit.SECONDS);
boolean locked = lock.tryLock(waitTime, leaseTime, unit);
if (locked)
lockKeys.add(lockKey);
return locked;
/* 异步实现方式
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
RFuture<Boolean> locked = lock.tryLockAsync(waitTime, leaseTime, unit);
if (locked.get()) lockKeys.add(lockKey);
return locked.get();*/
} catch (InterruptedException e) {
System.out.println(String.format("尝试获取锁%s失败", lockKey));
e.printStackTrace();
}
return Boolean.FALSE;
}
/**
* 解锁 - 重入的方式,所以同一个线程加了几次锁就要释放几次锁
*
* @param lockKey 锁的值
*/
public boolean unFairLock(String lockKey) {
try {
RLock lock = redissonClient.getFairLock(lockKey);
if (null != lock && lock.isHeldByCurrentThread()) { //判断锁是否存在,和是否当前线程加的锁。
lock.unlock();
return lockKeys.remove(lockKey);
//异步方式删除锁
/*RFuture<Void> future = lock.unlockAsync();
if (future.await(5 * 1000) && future.isSuccess()) {
return lockKeys.remove(lockKey);
}*/
}
} catch (Exception e) {
System.out.println(String.format("解锁%s失败", lockKey));
e.printStackTrace();
}
return false;
}联锁(MultiLock)
Redisson的RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
需要注意的是,在锁的释放时,可以单独释放Redisson添加的锁,其他锁不会释放依旧存在。
/**
* 连锁-只有所有的RedissonClient都锁成功才算成功
*
* @param waitTime 获取锁的等待时间
* @param leaseTime 锁的持续时间
* @param unit 时间的单位
*
* @return 获取锁的结果
*/
public Boolean tryMultiLock(RedissonClient redisson1,RedissonClient redisson2, long waitTime, long leaseTime, TimeUnit unit){
RLock lock1 = redisson1.getLock("zhong:test:lock1");
RLock lock2 = redisson2.getLock("zhong:test:lock2");
RLock lock = redissonClient.getMultiLock(lock1, lock2);
try {
// 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。
lock.lock();
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(waitTime, unit);
return res;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return false;
}
/**
* 连锁 - 需要遵循谁加的锁设计释放锁,可以单独释放自己加的锁
*
*/
public boolean unMultiLock(RedissonClient client ,RedissonClient client1) {
try {
List<RLock> locks = new ArrayList<>();
locks.add(client.getLock("zhong:test:lock1"));
locks.add(client1.getLock("zhong:test:lock2"));
RedissonMultiLock lock = new RedissonMultiLock(locks.toArray(new RLock[0]));
lock.unlock();
//异步方式删除锁
/*RFuture<Void> future = lock.unlockAsync();
if (future.await(5 * 1000) && future.isSuccess()) {
return lockKeys.remove(lockKey);
}*/
} catch (Exception e) {
System.out.println(String.format("解锁失败"));
e.printStackTrace();
return false;
}
return true;
}RedissonClient还提供了红锁,读写锁等。
在实际应用中我们最常用的分布式锁一般都是设置定时过期的。
这样的锁在实际应用中存在一个问题就是服务宕机或重启这个锁在redis上是一直存在的。一旦重启就可能会导致所有线程无法获取到锁。
解决办法就是在加锁的时候将锁记录到Set里面。释放锁的时候将记录Set中的锁删除,在服务停止之前就就根据set记录里面的锁先将欧锁释放。
这样就能保证重启后能获取到锁。实现方式参考DisposableBean的实现方式。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
