一、看源碼前的一些說(shuō)明
1. 這篇文章的源碼筆記是基于之前的筆記,所以,這里的當(dāng)前時(shí)間假設(shè),客戶端A的過(guò)期時(shí)間,都需要結(jié)合之前的筆記
2. 通過(guò)前幾篇的筆記,現(xiàn)在有客戶端A,B,C三個(gè),假設(shè)此時(shí)時(shí)間已經(jīng)到達(dá)了10:00:36,客戶端C來(lái)進(jìn)行的重新嘗試進(jìn)行加鎖,此時(shí)客戶端B他其實(shí)在這之前不知道可能因?yàn)榫W(wǎng)絡(luò)原因或者是別的什么原因,可能他就是沒(méi)有嘗試過(guò)重新加鎖
3. 參數(shù)說(shuō)明,這些參數(shù)都是Lua腳本中的參數(shù),在閱讀源碼的時(shí)候,方便隨時(shí)回頭過(guò)來(lái)看
KEYS = Arrays.asList(getName(), threadsQueueName, timeoutSetName)KEYS[1] = getName() = 鎖的名字,“anyLock”KEYS[2] = threadsQueueName = redisson_lock_queue:{anyLock},基于redis的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的一個(gè)隊(duì)列KEYS[3] = timeoutSetName = redisson_lock_timeout:{anyLock},基于redis的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的一個(gè)Set數(shù)據(jù)集合,有序集合,可以自動(dòng)按照你給每個(gè)數(shù)據(jù)指定的一個(gè)分?jǐn)?shù)(score)來(lái)進(jìn)行排序ARGV = internalLockLeaseTime, getLockName(threadId), currentTime + threadWaitTime, currentTimeARGV[1] = 30000毫秒ARGV[2] = UUID:threadIdARGV[3] = 當(dāng)前時(shí)間(10:00:00) + 5000毫秒 = 10:00:05ARGV[4] = 當(dāng)前時(shí)間(10:00:00)
二、代碼剖析
代碼片段一、
?RedissonFairLock
源碼解析中的1、2、3是程序進(jìn)來(lái)的順序,比如1走完后,2才會(huì)走
@Override RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) { internalLockLeaseTime = unit.toMillis(leaseTime); long currentTime = System.currentTimeMillis(); if (command == RedisCommands.EVAL_NULL_BOOLEAN) { 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[3]) 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; " + "return 1;", Arrays.asList(getName(), threadsQueueName, timeoutSetName), internalLockLeaseTime, getLockName(threadId), currentTime); } if (command == RedisCommands.EVAL_LONG) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, // remove stale threads //1. 此時(shí)客戶端C會(huì)進(jìn)入到一個(gè)死循環(huán) "while true do “ //1. 從redisson_lock_queue:{anyLock} 中獲取第一個(gè)元素,應(yīng)該是客戶端B,UUID_02:threadId_02 // 2. 此時(shí)隊(duì)列中就只剩下C了,那么此時(shí)firstThreadId2 = UUID_02:threadId_03 + "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);" + "if firstThreadId2 == false then " + "break;" + "end; “ //1. zscore redisson_lock_timeout:{anyLock} UUID_02:threadId_02 獲取客戶端B的超時(shí)時(shí)間 // 2. zscore redisson_lock_timeout:{anyLock} UUID_03:threadId_03,獲取客戶端C的超時(shí)時(shí)間 + "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));” //1. 10:00:30(參考前幾篇筆記中的客戶端B的超時(shí)時(shí)間) <= 10:00:36(假設(shè)的當(dāng)前時(shí)間)?條件是成立的 //2. 10:00:35(參考之前筆記客戶端B的超時(shí)時(shí)間) <= 10:00:36(假設(shè)的當(dāng)前時(shí)間) ? ,條件是成立的 + "if timeout <= tonumber(ARGV[4]) then “ // 1. zrem redisson_lcok_timeout:{anyLock} UUID_02:threadId_02 其實(shí)就是將客戶端B在有序集合中的元素進(jìn)行移除 // 2.zrem redisson_lock_timeout:{anyLock} UUID_03:threadId_03 ,將客戶端C在有序集合中移除 + "redis.call('zrem', KEYS[3], firstThreadId2); // 1. Lpop redisson_lock_queue:{anyLock} 將客戶端B在隊(duì)列中進(jìn)行移除 // 1. 此時(shí)隊(duì)列中就只剩下客戶端C了,然后繼續(xù)進(jìn)行死循環(huán)當(dāng)中 // 2. lpop redisson_lock_queue:{anyLock} 將客戶端C從隊(duì)列中移除 // 2. 此時(shí)隊(duì)列中和有序集合中的的元素都為空,跳出死循環(huán) + "redis.call('lpop', KEYS[2]); " + "else " + "break;" + "end; " + "end;" // 3. 這里的條件是不成立的,因?yàn)榭蛻舳薃還持有這把鎖的 + "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; “ + //3.hexists anyLock UUID_03:threadId_03 = 1 條件是不成立的,因?yàn)榇藭r(shí)隊(duì)列時(shí)空的 "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; " + // 3.lindex redisson_lock_timeout:{anyLock} 0 取出隊(duì)列的第一個(gè)元素,肯定是空的 "local firstThreadId = redis.call('lindex', KEYS[2], 0); " + "local ttl; “ + // if 條件不滿足 "if firstThreadId ~= false and firstThreadId ~= ARGV[2] then " + "ttl = tonumber(redis.call('zscore', KEYS[3], firstThreadId)) - tonumber(ARGV[4]);" + "else “ // ttl此時(shí)是anyLock的剩余生存時(shí)間,假設(shè)剩余23000毫秒 + "ttl = redis.call('pttl', KEYS[1]);" + "end; " + // timeout = 23000 + 10:00:36 + 5000 = 10:01:04 "local timeout = ttl + tonumber(ARGV[3]);” + // zadd redisson_lock_timeout:{anyLock} 10:01:04 UUID_03:threadId_03 將客戶端C保存到有序集合中 "if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 then “ + // rpush redisson_lock_queue UUID_03:threadid_03 將客戶端C放入到隊(duì)列中 "redis.call('rpush', KEYS[2], ARGV[2]);" + "end; " + "return ttl;", Arrays.asList(getName(), threadsQueueName, timeoutSetName), internalLockLeaseTime, getLockName(threadId), currentTime + threadWaitTime, currentTime); } throw new IllegalArgumentException();}
三、總結(jié):
我們可以看到,在一個(gè)客戶端剛剛加鎖之后,其他的客戶端來(lái)爭(zhēng)搶這把鎖,剛開(kāi)始在一定時(shí)間范圍之內(nèi),時(shí)間不要過(guò)長(zhǎng),各個(gè)客戶端是可以按照公平的節(jié)奏,在隊(duì)列和有序集合里面進(jìn)行排序
在一定時(shí)間范圍內(nèi),時(shí)間不要過(guò)長(zhǎng),其實(shí)隊(duì)列里的元素順序是不會(huì)改變的,各個(gè)客戶端重新嘗試加鎖,只不過(guò)是刷新有序集合中的分?jǐn)?shù)(timeout),各個(gè)客戶端的timeout不斷加長(zhǎng),但是整體順序大致還是保持一致的
但是如果客戶端A持有的鎖的時(shí)間過(guò)長(zhǎng),timeout,這個(gè)所謂的排隊(duì)是有timeout,可能會(huì)在while true死循環(huán)中將一些等待時(shí)間過(guò)長(zhǎng)的客戶端從隊(duì)列和有序集合中刪除,一旦刪除過(guò)后,就會(huì)發(fā)生各個(gè)客戶端隨著自己重新嘗試加鎖的時(shí)間次序,重新進(jìn)行一個(gè)隊(duì)列中的重排,也就是排隊(duì)的順序可能會(huì)發(fā)生變化
客戶端跟redis通信的網(wǎng)絡(luò)的一個(gè)問(wèn)題,延遲,各種情況都可能會(huì)發(fā)生
客戶端釋放鎖,釋放鎖之后隊(duì)列中的排隊(duì)的客戶端是如何依次獲取這把鎖的,是按照隊(duì)列里的順序去獲取鎖的
四、加鎖流程圖
四、寄語(yǔ):
================
如果手機(jī)觀看代碼比較難受,可以看網(wǎng)頁(yè)版本的
https://www.jianshu.com/p/ba52ce206986
================
總結(jié)
以上是生活随笔為你收集整理的公平锁非公平锁的实际使用_3. redisson源码公平锁之队列重排序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。