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,队列中的第一个元素通过消息事件,尝试去获取锁。