28.redisson源码分析分布式锁

复制代码
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;

waitTime表示获取锁的最大等待时长,在第一次获取锁失败后不会立即返回,而是在等待时间内不断的去尝试获取锁。如果超过最大等待时长都没有成功获取到锁就会返回false。所以传入waitTime的值就变成了一个可重试的锁了。

leaseTime表示锁自动失效,释放锁的一个时间。不传值,默认值为-1。

unit是时间单位。

锁重试机制

必须要传入参数waitTime

复制代码
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
复制代码
@Override
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
    return tryLock(waitTime, -1, unit);
}
复制代码
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime,
                                        commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
                                        TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
复制代码
private long lockWatchdogTimeout = 30 * 1000;

这个方法中,参数time就是上面介绍的waitTime。

当leaseTime锁过期时间不传时,默认值为-1,lockWatchdogTimeout看门狗的超时时间为30秒。

复制代码
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return evalWriteAsync(getName(), 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(getName()), internalLockLeaseTime, getLockName(threadId));
}

上面的lua脚本

1.当判断锁不存在说明是第一次获取锁,则添加锁并计数加一,并记录锁标识,hash结构,获取锁成功,返回null。

2.当判断锁存在,并且是同一个线程获取锁,则直接计数加一,体现锁的可重入性,获取锁成功,返回null。

如果获取锁失败则返回锁的剩余有效期。

复制代码
 @Override
    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 = tryAcquire(waitTime, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return true;
        }
        
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
        
        current = System.currentTimeMillis();
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
            if (!subscribeFuture.cancel(false)) {
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            acquireFailed(waitTime, unit, threadId);
            return false;
        }

        try {
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                    subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }
            }
        } finally {
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

time -= System.currentTimeMillis() - current;

剩余锁的等待时间 = 锁的最大等待时长 - 获取锁过程中消耗的时间

如果刚才获取锁失败,不会立即又去尝试获取锁,而是去订阅其他线程释放锁的信号。如果其他线程释放了锁,会发一个信号,这个信号就会被接收到。

使用到了信号量和消息订阅机制。

在unlock释放锁的源码中的lua脚本:

复制代码
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                    "return nil;" +
                    "end; " +
                    "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                    "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                    "else " +
                    "redis.call('del', KEYS[1]); " +
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; " +
                    "end; " +
                    "return nil;",
            Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}

看出锁被释放的时候,会发布一条消息出去。

等待释放锁的信号,等待的时间 < 剩余锁的等待时间

如果超时,则取消消息订阅,获取锁失败。

只有不设置锁的过期时间,也就默认值为-1才会走看门狗的逻辑,默认锁超时时间是30秒的情况下,等到超时时间三分之一的时候,也就是10秒钟的时候会重新去刷新锁的有效期。当锁释放的时候,会去取消刷新锁的有效期。

redis主从同步延迟引起的锁失效问题

解决办法:

不存在主从节点,都是同级节点。

联锁

配置多个独立的redis, 获取锁的时候,每个redis都会去获取一遍锁,每个锁都获取到了才叫获取到锁。

相关推荐
液态不合群7 小时前
查找算法详解
java·数据结构·算法
郝学胜-神的一滴7 小时前
Linux C++ 守护进程开发指南
linux·运维·服务器·开发语言·c++·程序人生·性能优化
雨中飘荡的记忆7 小时前
观察者模式:从理论到生产实践
java·设计模式
北城以北88887 小时前
SpringBoot--Redis基础知识
java·spring boot·redis·后端·intellij-idea
_dindong7 小时前
笔试强训:Week -8
开发语言·c++·算法
AI_56787 小时前
Jupyter交互式数据分析的效率革命
开发语言·python
superman超哥7 小时前
仓颉语言中并发集合的实现深度剖析与高性能实践
开发语言·后端·python·c#·仓颉
superman超哥7 小时前
仓颉语言中原子操作的封装深度剖析与无锁编程实践
c语言·开发语言·后端·python·仓颉
wniuniu_7 小时前
ceph中的rbd的稀疏写入
java·服务器·数据库
云泽8088 小时前
C++ list容器模拟实现:迭代器、构造与STL风格编程
开发语言·c++·list