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

相关推荐
温轻舟5 分钟前
Python自动办公工具06-设置Word文档中表格的格式
开发语言·python·word·自动化工具·温轻舟
p***c9495 分钟前
PHP在电商中的电商系统
开发语言·php
Z***258013 分钟前
JavaScript在Node.js中的Deno
开发语言·javascript·node.js
ss27317 分钟前
019:深入解析可重入互斥锁:原理、实现与线程安全实践
java·数据库·redis
luyun02020223 分钟前
牛批了,某音录播神器
java·windows·figma
高级程序源26 分钟前
springboot社区医疗中心预约挂号平台app-计算机毕业设计源码16750
java·vue.js·spring boot·mysql·spring·maven·mybatis
a***56061 小时前
Windows上安装Go并配置环境变量(图文步骤)
开发语言·windows·golang
San30.1 小时前
ES6+ 新特性解析:让 JavaScript 开发更优雅高效
开发语言·javascript·es6
y***61311 小时前
SpringBoot集成Flowable
java·spring boot·后端
烤麻辣烫1 小时前
黑马程序员苍穹外卖(新手)DAY6
java·开发语言·学习·spring·intellij-idea