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都会去获取一遍锁,每个锁都获取到了才叫获取到锁。

相关推荐
Query*2 小时前
Java 设计模式——工厂模式:从原理到实战的系统指南
java·python·设计模式
哼?~3 小时前
C++11标准 上 (万字解析)
开发语言·c++
VB.Net3 小时前
C#循序渐进
开发语言·c#
楼田莉子3 小时前
C++学习:C++11扩展:constexpr特性
开发语言·c++·学习
懒羊羊不懒@3 小时前
Java基础语法—最小单位、及注释
java·c语言·开发语言·数据结构·学习·算法
ss2733 小时前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
eurotruck3 小时前
c++贪吃蛇V1.0
开发语言·c++·贪吃蛇
DokiDoki之父3 小时前
MyBatis—增删查改操作
java·spring boot·mybatis
兩尛4 小时前
Spring面试
java·spring·面试