尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redisson MultiLock 原理

Redisson MultiLock 原理 1. 什么是 MultiLockRedisson 的 MultiLock多重锁是一种分布式锁机制它允许将多个独立的RLock实例组合成一个逻辑上的锁。当用户需要对多个资源同时加锁时MultiLock 可以保证这些资源要么全部被锁定要么全部不被锁定从而避免部分锁定导致的死锁或数据不一致问题。2. 核心机制MultiLock 本身并不是一把新的 Redis 锁而是一个对多个子锁RLock的统一管理器。其加锁过程如下顺序加锁按传入顺序依次尝试获取每一个子锁。全量成功只有所有子锁都获取成功MultiLock 才认为加锁成功。失败回滚如果在获取某个子锁时失败会立即释放已经获取的所有子锁恢复到加锁前的状态。异步非阻塞底层使用 Redisson 的异步非阻塞模型通过 Lua 脚本在 Redis 服务端保证原子性操作。3. MultiLock 的特性原子性操作通过 Lua 脚本确保加锁和解锁的原子性避免部分成功或失败。可重入性支持同一个线程多次加锁每次解锁只减少持有计数计数归零时才真正释放锁。超时机制支持设置锁的自动释放时间防止死锁。公平锁与非公平锁根据底层RLock的配置可以支持公平或非公平锁模式。4. 代码示例以下是一个使用 MultiLock 的典型 Java 示例RLock lock1 redisson.getLock(lock1); RLock lock2 redisson.getLock(lock2); RLock lock3 redisson.getLock(lock3); // 创建 MultiLock RLock multiLock redisson.getMultiLock(lock1, lock2, lock3); // 尝试加锁设置等待时间和锁自动释放时间 boolean isLocked multiLock.tryLock(10, 30, TimeUnit.SECONDS); if (isLocked) { try { // 执行业务逻辑 } finally { multiLock.unlock(); } }5. 底层 Lua 脚本加锁和解锁的核心逻辑由 Lua 脚本在 Redis 服务端执行。以下是加锁的简化逻辑-- 参数KEYS[1..n] 为锁的 keyARGV[1] 为超时时间ARGV[2] 为线程标识 local failedLocks {} for i, key in ipairs(KEYS) do if redis.call(exists, key) 0 then redis.call(hset, key, ARGV[2], 1) redis.call(pexpire, key, ARGV[1]) else table.insert(failedLocks, key) end end if #failedLocks 0 then -- 释放已获取的锁进行回滚 for i, key in ipairs(KEYS) do if not table.contains(failedLocks, key) then redis.call(del, key) end end return 0 end return 1进一步刨析里面的trylock()源码代码来源于https://cyborg2077.github.io/2022/10/22/RedisPractice/#Redisson%E9%94%81%E7%9A%84MutiLock%E5%8E%9F%E7%90%86public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long newLeaseTime -1L; //如果传入了释放时间 if (leaseTime ! -1L) { //再判断一下是否有等待时间 if (waitTime -1L) { //如果没传等待时间不重试则只获得一次 newLeaseTime unit.toMillis(leaseTime); } else { //想要重试耗时较久万一释放时间小于等待时间则会有问题所以这里将等待时间乘以二 newLeaseTime unit.toMillis(waitTime) * 2L; } } //获取当前时间 long time System.currentTimeMillis(); //剩余等待时间 long remainTime -1L; if (waitTime ! -1L) { remainTime unit.toMillis(waitTime); } //锁等待时间与剩余等待时间一样 long lockWaitTime this.calcLockWaitTime(remainTime); //锁失败的限制源码返回是的0 int failedLocksLimit this.failedLocksLimit(); //已经获取成功的锁 ListRLock acquiredLocks new ArrayList(this.locks.size()); //迭代器用于遍历 ListIteratorRLock iterator this.locks.listIterator(); while(iterator.hasNext()) { RLock lock (RLock)iterator.next(); boolean lockAcquired; try { //没有等待时间和释放时间调用空参的tryLock if (waitTime -1L leaseTime -1L) { lockAcquired lock.tryLock(); } else { //否则调用带参的tryLock long awaitTime Math.min(lockWaitTime, remainTime); lockAcquired lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS); } } catch (RedisResponseTimeoutException var21) { this.unlockInner(Arrays.asList(lock)); lockAcquired false; } catch (Exception var22) { lockAcquired false; } //判断获取锁是否成功 if (lockAcquired) { //成功则将锁放入成功锁的集合 acquiredLocks.add(lock); } else { //如果获取锁失败 //判断当前锁的数量减去成功获取锁的数量如果为0则所有锁都成功获取跳出循环 if (this.locks.size() - acquiredLocks.size() this.failedLocksLimit()) { break; } //否则将拿到的锁都释放掉 if (failedLocksLimit 0) { this.unlockInner(acquiredLocks); //如果等待时间为-1则不想重试直接返回false if (waitTime -1L) { return false; } failedLocksLimit this.failedLocksLimit(); //将已经拿到的锁都清空 acquiredLocks.clear(); //将迭代器往前迭代相当于重置指针放到第一个然后重试获取锁 while(iterator.hasPrevious()) { iterator.previous(); } } else { --failedLocksLimit; } } //如果剩余时间不为-1很充足 if (remainTime ! -1L) { //计算现在剩余时间 remainTime - System.currentTimeMillis() - time; time System.currentTimeMillis(); //如果剩余时间为负数则获取锁超时了 if (remainTime 0L) { //将之前已经获取到的锁释放掉并返回false this.unlockInner(acquiredLocks); //联锁成功的条件是每一把锁都必须成功获取一把锁失败则都失败 return false; } } } //如果设置了锁的有效期因为没把锁的获取时间都不一样所以剩余过期时间不一样这里就给时间重新赋值 if (leaseTime ! -1L) { ListRFutureBoolean futures new ArrayList(acquiredLocks.size()); //迭代器用于遍历已经获取成功的锁 Iterator var24 acquiredLocks.iterator(); while(var24.hasNext()) { RLock rLock (RLock)var24.next(); //设置每一把锁的有效期 RFutureBoolean future ((RedissonLock)rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS); futures.add(future); } var24 futures.iterator(); while(var24.hasNext()) { RFutureBoolean rFuture (RFuture)var24.next(); rFuture.syncUninterruptibly(); } } //但如果没设置有效期则会触发WatchDog机制自动帮我们设置有效期所以大多数情况下我们不需要自己设置有效期 return true; }6. 适用场景多资源事务需要同时锁定多个资源以保证事务的原子性。跨服务调用在分布式系统中多个服务需要协同完成某个操作时。避免死锁通过统一的加锁和解锁机制减少死锁风险。7. 总结Redisson MultiLock 通过组合多个独立的RLock实例配合 Lua 脚本实现的原子性加锁回滚机制为分布式系统提供了可靠的多资源锁定能力。理解其原理有助于在需要同时锁定多个资源时设计出更健壮、更安全的并发控制方案。
返回列表