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

相关推荐
zhangyao94033027 分钟前
关于js导入Excel时,Excel的(年/月/日)日期是五位数字的问题。以及对Excel日期存在的错误的分析和处理。
开发语言·javascript·excel
熙客33 分钟前
TiDB:分布式关系型数据库
java·数据库·分布式·tidb
骑驴看星星a33 分钟前
【Three.js--manual script】4.光照
android·开发语言·javascript
你想考研啊2 小时前
linux安装jdk和tomcat和并自启动
java·linux·tomcat
星释2 小时前
Rust 练习册 :Leap与日期计算
开发语言·后端·rust
悟能不能悟3 小时前
java的java.sql.Date和java.util.Date的区别,应该怎么使用
java·开发语言
循环过三天4 小时前
3.4、Python-集合
开发语言·笔记·python·学习·算法
高山上有一只小老虎4 小时前
java 正则表达式大全
java·正则表达式
_院长大人_5 小时前
设计模式-工厂模式
java·开发语言·设计模式
MATLAB代码顾问5 小时前
MATLAB实现决策树数值预测
开发语言·决策树·matlab