基本使用
1、引入依赖
<!-- redisson依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
2、配置redisson客户端
// 将RedissonClient对象注入IOC容器
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
// 配置单节点redis地址
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
@Resource
RedissonClient redissonClient;
// ----
// 获取可重入锁
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();
// (无参)尝试获取锁
boolean isLock = lock.tryLock();
// 重载方法1 tryLock(Long maxWaitTime,Long timeoutRelease,TimeUnit.SECONDS)
// 参数1:锁的最大等待时间(期间会重试)
// 参数2: 锁超时释放时间
// 参数3:时间单位
boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);
// 重载方法2 tryLock(Long maxWaitTime,TimeUnit.SECONDS)
// 参数1:锁的最大等待时间(期间会重试)
// 参数2:时间单位
boolean isLock = lock.tryLock(1,TimeUnit.SECONDS);
// 释放锁
lock.unlock();
tryLock
trylock具有返回值,true或者false,表示是否成功获取锁。tryLock前期获取锁逻辑基本与lock一致,主要是后续获取锁失败的处理逻辑与lock不一致。
trylock方法
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
// 获取剩余过期时间
Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
// 为null,则获取锁成功
if (ttl == null) {
return true;
} else {
time -= System.currentTimeMillis() - current;
// 获取锁失败后,中途tryLock会一直判断中间操作耗时是否已经消耗锁的过期时间,如果消耗完则返回false
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
return false;
} else {
current = System.currentTimeMillis();
// 此处为订阅锁释放事件,
// 如果当前线程通过 Redis 的 channel 订阅锁的释放事件获取得知已经被释放
// 则会发消息通知待等待的线程进行竞争.
RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
// 等待释放锁,若超过剩余等待时间,则取消订阅,
// 并返回false,获取锁失败
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}
});
}
this.acquireFailed(waitTime, unit, threadId);
return false;
} else {
try {
// 减去订阅等待的时间,如果消耗完则返回false
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
boolean var20 = false;
return var20;
} else {
boolean var16;
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
// 尝试使用信号量的方式获取锁
// 如果ttl < time ;则使用ttl的时间尝试获取锁
if (ttl >= 0L && ttl < time) {
((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
// 如果ttl > time ;则使用time 的时间尝试获取锁
} else {
((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
// time时间大于0,继续尝试获取锁
} while(time > 0L);
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
} finally {
this.unsubscribe(subscribeFuture, threadId);
}
}
}
}
}
tryLock的lua脚本
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,
// KEYS[1] :锁的key
// ARGV[1] :设置锁的过期时间
// ARGV[2] :threadId
// 判断锁是否存在,若不存在
"if (redis.call('exists', KEYS[1]) == 0) then\n" +
// 创建hash表并使field属性次数加一
"redis.call('hincrby', KEYS[1], ARGV[2], 1);\n" +
// 设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]);\n" +
// 返回结果
"return nil;\n" +
"end;\n" +
// 锁存在,判断hash的指定字段是否存在,若存在
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then" +
// hash的field属性次数加一
"redis.call('hincrby', KEYS[1], ARGV[2], 1);\n" +
// 设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]);\n" +
// 返回结果
"return nil;\n" +
"end;\n" +
// 若代码走到这里,说明锁不是当前线程的,获取锁失败并返回锁的剩余过期时间(ms)
"return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
}
hash结构
上述lua脚本的流程图
可重试
超时续约
看门狗机制开始条件 leaseTime = -1
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
} else {
RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
// 获取锁成功,锁的过期时间续约
if (ttlRemaining) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
scheduleExpirationRenewal方法
private void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
// 如果entry不存在,才添加,返回null
// 如果entry不存在,不添加进去,返回oldEntry
// 这样使得锁重入
ExpirationEntry oldEntry = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
// 第一次来,添加更新有效期定时任务
this.renewExpiration();
}
}
renewExpiration方法
private void renewExpiration() {
ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (ee != null) {
// 定时任务
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = (ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
} else {
if (res) {
// 递归
RedissonLock.this.renewExpiration();
}
}
});
}
}
}
//internalLockLeaseTime = 30 * 1000
// 10s后执行该任务
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}
renewExpirationAsync方法
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// 判断这个锁是否是当前线程的
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
// 重置锁的有效期
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end;" +
"return 0;", Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
}
unlock
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise();
RFuture<Boolean> future = this.unlockInnerAsync(threadId);
future.onComplete((opStatus, e) -> {
// 取消定时任务
this.cancelExpirationRenewal(threadId);
if (e != null) {
result.tryFailure(e);
} else if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
result.tryFailure(cause);
} else {
result.trySuccess((Object)null);
}
});
return result;
}
cancelExpirationRenewal方法
void cancelExpirationRenewal(Long threadId) {
// 从map取出Entry
ExpirationEntry task = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (task != null) {
if (threadId != null) {
// 删除threadId
task.removeThreadId(threadId);
}
if (threadId == null || task.hasNoThreads()) {
Timeout timeout = task.getTimeout();
if (timeout != null) {
// 取消定时任务
timeout.cancel();
}
//从map中删除entry
EXPIRATION_RENEWAL_MAP.remove(this.getEntryName());
}
}
}
unlock的lua脚本
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
/** KEYS[1] :锁的key
* KEYS[2] :锁的key
* ARGV[1]
* ARGV[2] :锁的有效期
* ARGV[3] :threadId
*/
// 判断锁是否是当前线程的,若等于0,说明锁不存在,直接结束
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end;" +
// 锁存在,hash的field属性次数减一
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);" +
// 判断field值是否大于0,
"if (counter > 0) then" +
// >0 重置有效期
"redis.call('pexpire', KEYS[1], ARGV[2]);" +
"return 0;" +
// <=0 删除锁
"else" +
"redis.call('del', KEYS[1]);" +
// 消息通知等待的线程进行竞争
"redis.call('publish', KEYS[2], ARGV[1]);" +
"return 1;" +
"end;" +
"return nil;"
, Arrays.asList(this.getName(), this.getChannelName()), LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId));
}
trylock和unlock流程图
总结
redisson分布式锁原理:
-
可重入:使用hash结构记录线程id和重入次数
获取锁
1、判断锁是否存在,不存在,获取锁成功
2、若锁存在,判断field是不是当前线程的,若是,value++;若不是,获取锁失败;
释放锁
1、判断field是不是当前线程的,若是,value–,直到value为0,删除锁; -
可重试:利用信号量和redis的pubsub功能实现等待、唤醒、获取锁失败的重试机制(不是无限制的等待,有等待时间)
-
超时续约: watchDog机制,每隔一段时间(this.internalLockLeaseTime / 3L),重置过期时间
ps:internalLockLeaseTime = 30 * 1000 ms
本文的图片来源:https://www.bilibili.com/video/BV1cr4y1671t?p=67&spm_id_from=pageDriver&vd_source=3635fc6ba1824ef476d69a1c58b3e2ec