分布式锁源码剖析(2) Redisson实现公平分布式锁

Redisson分布式锁源码剖析(公平锁)

maven配置文件:

 <dependency>
         <groupId>org.redisson</groupId>
         <artifactId>redisson</artifactId>
         <version>3.8.1</version>
</dependency>

代码示例:

Config config = new Config();
config.useClusterServers()
    .addNodeAddress("redis://192.168.31.114:7001")
    .addNodeAddress("redis://192.168.31.184:7002");

RedissonClient redisson = Redisson.create(config);

RLock lock = redisson.getFairLock("anyLock");
lock.lock();
lock.unlock();

lock()方法源码剖析

基本逻辑和可重入锁类似,最大区别是加锁的逻辑。

核心源码:

lock()->RedisssonLock.lock()->lockInterruptibly()->tryAcquire()->tryAcquireAsync()->tryLockInnerAsync()(RedissonFairLock类中的方法)

 if (command == RedisCommands.EVAL_LONG) {
            return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                    // remove stale threads
                    "while true do "
                    + "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
                    + "if firstThreadId2 == false then "
                        + "break;"
                    + "end; "
                    + "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
                    + "if timeout <= tonumber(ARGV[4]) then "
                        + "redis.call('zrem', KEYS[3], firstThreadId2); "
                        + "redis.call('lpop', KEYS[2]); "
                    + "else "
                        + "break;"
                    + "end; "
                  + "end;"
                    
                      + "if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) "
                            + "or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then " +
                            "redis.call('lpop', KEYS[2]); " +
                            "redis.call('zrem', KEYS[3], ARGV[2]); " +
                            "redis.call('hset', 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; " +
                            
                        "local firstThreadId = redis.call('lindex', KEYS[2], 0); " +
                        "local ttl; " + 
                        "if firstThreadId ~= false and firstThreadId ~= ARGV[2] then " + 
                            "ttl = tonumber(redis.call('zscore', KEYS[3], firstThreadId)) - tonumber(ARGV[4]);" + 
                        "else "
                          + "ttl = redis.call('pttl', KEYS[1]);" + 
                        "end; " + 
                            
                        "local timeout = ttl + tonumber(ARGV[3]);" + 
                        "if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 then " +
                            "redis.call('rpush', KEYS[2], ARGV[2]);" +
                        "end; " +
                        "return ttl;", 
                        Arrays.<Object>asList(getName(), threadsQueueName, timeoutSetName), 
                                    internalLockLeaseTime, getLockName(threadId), currentTime + threadWaitTime, currentTime);
        }

第一段逻辑:死循环,弹出队列中的第一个元素,如果队列是空,直接跳出循环。timeout = set集合里面线程对应的过期时间。如果timeout <= 当前时间,清空集合,清空队列。

第二段逻辑:如果anyLock锁不存在并且队列为空或者队列的第一个元素是当前线程,弹出队列的第一个元素,从有序set集合中删除当前线程对应的元素,然后给当前线程加锁,设置这个key的有效时间是30秒。判断anyLock中,是否有当前线程:1,如果有,那累加1,即当前线程:2,。(重入锁)

第三段逻辑:得到队列中的第一个元素,如果队列中的第一个元素存在并且线程id不等于当前线程,ttl = 队列第一个元素过期时间 - 当前时间。如果队列是空,ttl = anyLock的存活时间。

第四段逻辑:timeout= ttl + 当前时间 + 50秒(等待时间),往set集合插入一个元素,过期时间为timeout,并把它放入队列中。最终返回ttl。

unlock()方法源码剖析

unlock()->RedisssonLock.unlock()->unlockAsync()->unlockInnerAsync()(RedissonFairLock类中的方法)

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                // remove stale threads
                "while true do "
                + "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
                + "if firstThreadId2 == false then "
                    + "break;"
                + "end; "
                + "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
                + "if timeout <= tonumber(ARGV[4]) then "
                    + "redis.call('zrem', KEYS[3], firstThreadId2); "
                    + "redis.call('lpop', KEYS[2]); "
                + "else "
                    + "break;"
                + "end; "
              + "end;"
                
              + "if (redis.call('exists', KEYS[1]) == 0) then " + 
                    "local nextThreadId = redis.call('lindex', KEYS[2], 0); " + 
                    "if nextThreadId ~= false then " +
                        "redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
                    "end; " +
                    "return 1; " +
                "end;" +
                "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; " +
                "end; " +
                    
                "redis.call('del', KEYS[1]); " +
                "local nextThreadId = redis.call('lindex', KEYS[2], 0); " + 
                "if nextThreadId ~= false then " +
                    "redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
                "end; " +
                "return 1; ",
                Arrays.<Object>asList(getName(), threadsQueueName, timeoutSetName, getChannelName()), 
                LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId), System.currentTimeMillis());
    }

第一段逻辑:获取队列中的第一个元素,如果队列为空,跳出循环。timeout = set集合中线程对应的元素存活时间,如果timeout <=当前时间,清空set集合和队列。

第二段逻辑:如果锁不存在,取出队列中的第一个元素,发布一个解锁消息事件,如果anylock的map结构中没有任何线程,则直接返回null。递减anyLock的threadId:1。(因为锁的可重入性)

第三段逻辑:删除anyLock这个key,队列中的第一个元素通过消息事件,尝试去获取锁。

Redisson 公平锁的原理图

Redisson分布式锁原理(公平锁)

猜你喜欢

转载自blog.csdn.net/qq40988670/article/details/85276530