又是美好的一天呀~
个人博客地址: huanghong.top
本文预估阅读时长为20分钟左右~
Redisson分布式锁执行逻辑
执行逻辑图

获取锁代码
本地代码
package com.huang.component;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @Time 2023-02-27 16:25
* Created by Huang
* className: RedisComponent
* Description:
*/
@Slf4j
@Component
public class RedisComponent {
@Autowired
private RedissonClient redissonClient;
private RLock lock;
@PostConstruct
public void setup(){
lock = redissonClient.getLock("distribute-lock");
}
public void method(){
boolean acquireLockFlag = lock.tryLock();
if(acquireLockFlag){
log.error("获取锁失败!");
return ;
}
try {
log.info("获取锁成功!");
Thread.sleep(3000);
log.info("执行业务逻辑...");
} catch (Exception e) {
log.error("业务逻辑执行异常: {}",e.toString());
} finally {
log.info("准备释放锁!");
lock.unlock();
log.info("释放锁成功!");
}
}
}
org.redisson.RedissonLock#tryAcquireOnceAsync
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
// leaseTime 就是租约时间,就是Redis key 的过期时间
// 无参的tryLock 这里必定为-1 不会进入这个判断
if (leaseTime != -1) {
return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
//commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout() 获取到的值
//private long lockWatchdogTimeout = 30 * 1000; 这个是获取的默认的超时时间30s
RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(waitTime,
commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
//等待获取加锁的结果
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
// 存在异常 直接返回
if (e != null) {
return;
}
// 加锁成功,下面代码实现锁超时重试,也就是看门狗的逻辑
// lock acquired
if (ttlRemaining) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
org.redisson.RedissonLock#tryLockInnerAsync
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
// 将超时时间保存在RedissonLock的internalLockLeaseTime变量中,用来解决锁超时问题 watchDog机制
// 真实的获取锁的过程
// getName() distribute-lock
// Collections.singletonList(getName()) 获取锁的key也就是当前锁的名称,传入lua 脚本中的key对应KEYS[1]
// internalLockLeaseTime 超时时间 传入lua脚本的参数[1] 对应ARGV[1]
// getLockName(threadId) 线程唯一标识,对应hash结构的field hash结构的value对应重入次数
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}
对应lua内容
--- KEYS[1] 是锁的key
--- ARGV[1] 是超时时间
--- ARGV[2] 是锁线程名称
local key = KEYS[1] --锁的key
local releaseTime = ARGV[1] -- 锁超时时间
local threadId = ARGV[2] -- 线程唯一标识
--- 所不存在执行的流程
if (redis.call("exists", key) == 0) then
-- 不存在获取锁
redis.call("hset", key, threadId, '1');
-- 设置有效期
redis.call("pexpire", key, releaseTime);
return nil;
end
--- 锁存在执行的流程
if (redis.call("hexists", key, threadId) == 1) then
-- 锁重入 当前线程对应的value增加一
redis.call("hincrby",key,threadId,'1');
-- 重置超时时间
redis.call("pexpire", key, releaseTime);
return nil; --返回结果
end
--- 当key不存在时,返回-2 。 当key存在但没有设置剩余生存时间时,返回-1 。 否则,以毫秒为单位,返回key 的剩余生存时间。
return redis.call('pttl', KEYS[1])
org.redisson.RedissonBaseLock#scheduleExpirationRenewal
protected void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
//判断map中是否存在当前线程对象的实体,如果存在则返回实体,如果不存在则创建新的返回null
ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
try {
// 看门狗实现,创建新的实体需要增加看门狗的逻辑
renewExpiration();
} finally {
if (Thread.currentThread().isInterrupted()) {
cancelExpirationRenewal(threadId);
}
}
}
}
org.redisson.RedissonBaseLock#renewExpiration
private void renewExpiration() {
// 从map中获取ExpirationEntry如果为null则直接返回
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
// 如果不为空则创建一个延时任务 task 与Timer返回的TimerTask关联的句柄。
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
//在重新设置超时时间
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock " + getRawName() + " expiration", e);
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
return;
}
if (res) {
// reschedule itself
renewExpiration();
} else {
cancelExpirationRenewal(null);
}
});
}
//internalLockLeaseTime 就是我们之前获取到的leaseTime不传默认30秒
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
// 将延时任务放入到ExpirationEntry中
ee.setTimeout(task);
}
org.redisson.RedissonBaseLock#renewExpirationAsync
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
// 重新设置超时时间的代码
return evalWriteAsync(getRawName(), 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(getRawName()),
internalLockLeaseTime, getLockName(threadId));
}
问题点
- 手动释放锁失败会导致watchdog线程会一直执行,如果大量线程释放锁均失败,会导致OOM
- redis主从同步未同步redis-lock,主节点就宕机,从节点升级为主节点后重新接受客户端请求,重新设置redis-lock,造成脏数据
感谢阅读完本篇文章!!!
个人博客地址: huanghong.top