Redisson的锁重试和看门狗机制
先看一下自己利用setnx写的简易分布式锁,有四个问题,
1,不可重入
2,不可重试
3,超时自动释放(如果业务阻塞了,就算代码没执行完也会释放锁)
4,集群模式下怎么办?
@Override public boolean tryLock(long timeoutSec) { // 获取线程标示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 获取锁 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); }
Redisson都能解决!!
锁重入:
利用hash结构记录线程id和重入次数。
锁重试:
redisson在尝试获取锁的时候,如果传了时间参数,就不会在获取锁失败时立即返回失败,而是会进行重试。
三个参数:最大重试时间,锁释放时间(默认为-1,会触发看门狗机制),时间单位
上部分源码
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();
//尝试获取锁,返回的ttl为null,表示获取成功, Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
//获取成功 if (ttl == null) { return true;
//获取失败 } else {
//计算剩余时间 time -= System.currentTimeMillis() - current;
//剩余时间小于等于零,不再重试,返回失败 if (time <= 0L) { this.acquireFailed(waitTime, unit, threadId); return false; } else {
//剩余时间大于零,subscribe订阅拿到锁的线程,该线程释放锁后会发布通知,其余等待的线程可以继续争抢。 current = System.currentTimeMillis(); RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
看门狗机制:
在获取锁成功以后,开启一个定时任务,每隔一段时间就会去重置锁的超时时间,以确保锁是在程序执行完unlock手动释放的,不会发生因为业务阻塞,key超时而自动释放的情况。
下面源码
private void renewExpiration() { RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName()); if (ee != null) {
//Timeout定时任务,或者叫周期任务 Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() { public void run(Timeout timeout) throws Exception { RedissonLock.ExpirationEntry ent = (RedissonLock.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(); } } }); } } }
//刷新周期, this.internalLockLeaseTime / 3L, 默认释放时间是30秒,除以3就是每10秒更新一次
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS); ee.setTimeout(task); } }
可以配合下面流程图来看源码
集群问题:
实际生产过程中,redis都是主从或者集群配置的,如果只在主节点上加锁,之后主节点宕机了,而且锁还没被同步到从节点上,就会出现安全问题。
需要使用multilock()方法。必须在所有的节点都获取锁成功,才算成功。 缺点是运维成本高,实现复杂。
@Resource private RedissonClient redissonClient; @Resource private RedissonClient2 redissonClient2; @Resource private RedissonClient3 redissonClient3; RLock lock = redissonClient.getMultilock(lock1,lock2,lock3)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)