<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
2023的金三銀四來的沒想象中那麼激烈,一個朋友前段時間投了幾十家,多數石沉大海,好不容易等來面試機會,就恰好被問道專案中關於分散式鎖的應用,後涉及Redisson實現分散式鎖的原理,答不上來。
我們都知道,Java中synchronized和lock都支援可重入,synchronized的鎖關聯一個執行緒持有者和一個計數器。當一個執行緒請求成功後,JVM會記下持有鎖的執行緒,並將計數器計為1。此時其他執行緒請求該鎖,則必須等待。而該持有鎖的執行緒如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增。當執行緒退出一個synchronized方法/塊時,計數器會遞減,如果計數器為0則釋放該鎖;在ReentrantLock中,底層的 AQS 對應的state 同步狀態值表示執行緒獲取該鎖的可重入次數,通過CAS方式進行設定,在預設情況下,state的值為0 表示當前鎖沒有被任何執行緒持有,原理類似。所以如果想要實現可重入性,可能須有一個計數器來控制重入次數,實際Redisson確實是這麼做的。
好的我們通過Redisson使用者端進行設定,並回圈3次,模擬鎖重入:000
for(int i = 0; i < 3; i++) { RedissonLockUtil.tryLock("distributed:lock:distribute_key", TimeUnit.SECONDS, 20, 100); }
連線Redis使用者端進行檢視:
可以看到,我們設定的分散式鎖是存在一個hash結構中,value看起來是迴圈的次數3,key就不怎麼認識了,那這個key是怎麼設定進去的呢,另外為什麼要設定成為Hash型別呢?
我們先來看看普通的分散式鎖的上鎖流程:
說明:
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 = this.tryAcquire(waitTime, leaseTime, unit, threadId); if (ttl == null) { return true; } else { // 訂閱分散式Key對應的訊息,監聽其它鎖持有者釋放,鎖沒有釋放的時候則會等待,直到鎖釋放的時候會執行下面的while迴圈 CompletableFuture subscribeFuture = this.subscribe(threadId); subscribeFuture.get(time, TimeUnit.MILLISECONDS); try { do { // 嘗試獲取鎖 ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId); // 競爭獲取鎖成功,退出迴圈,不再競爭。 if (ttl == null) { return true; } // 利用號誌機制阻塞當前執行緒相應時間,之後再重新獲取鎖 if (ttl >= 0L && ttl < time) { ((RedissonLockEntry)this.commandExecutor.getNow(subscribeFuture)).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { ((RedissonLockEntry)this.commandExecutor.getNow(subscribeFuture)).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; } while(time > 0L); } finally { // 競爭鎖成功後,取消訂閱該執行緒Id事件 this.unsubscribe((RedissonLockEntry)this.commandExecutor.getNow(subscribeFuture), threadId); } } } }
RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) { // 如果設定了持有鎖的時長,直接進行嘗試加鎖操作 if (leaseTime != -1L) { return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } else { // 未設定加鎖時長,在加鎖成功後,啟動續期任務,初始預設持有鎖時間是30s RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); ttlRemainingFuture.addListener(new FutureListener<Long>() { public void operationComplete(Future<Long> future) throws Exception { if (future.isSuccess()) { Long ttlRemaining = (Long)future.getNow(); if (ttlRemaining == null) { RedissonLock.this.scheduleExpirationRenewal(threadId); } } } }); return ttlRemainingFuture; } }
我們都知道Redis執行Lua指令碼具有原子性,所以在嘗試加鎖的下層,Redis主要執行了一段複雜的lua指令碼:
-- 不存在該key時 if (redis.call('exists', KEYS[1]) == 0) then -- 新增該鎖並且hash中該執行緒id對應的count置1 redis.call('hincrby', KEYS[1], ARGV[2], 1); -- 設定過期時間 redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; -- 存在該key 並且 hash中執行緒id的key也存在 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]);
引數說明:
KEYS[1]:對應我們設定的分散式key,即:distributed:lock:distribute_key
ARGV[1]:業務自定義的加鎖時長或者預設的30s;
ARGV[2]: 具體的使用者端初始化連線UUID+執行緒ID: 9d8f0907-1165-47d2-8983-1e130b07ad0c:1
我們從上面的指令碼中可以看出核心邏輯其實不難:
接下來看看scheduleExpirationRenewal續命是怎麼做的呢?
private void scheduleExpirationRenewal(final long threadId) { if (!expirationRenewalMap.containsKey(this.getEntryName())) { Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() { public void run(Timeout timeout) throws Exception { // 執行續命操作 RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId); future.addListener(new FutureListener<Boolean>() { public void operationComplete(Future<Boolean> future) throws Exception { RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName()); ... // 續命成功,繼續 if ((Boolean)future.getNow()) { RedissonLock.this.scheduleExpirationRenewal(threadId); } } }); } }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS); } }
Tip小知識點:
而在上面的renewExpirationAsync中續命操作的執行核心Lua指令碼要做的事情也非常的簡單,就是給這個Key的過期時間重新設定為指定的30s.
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;
釋放鎖主要是除了解鎖本省,另外還要考慮到如果存在續期的情況,要將續期任務刪除:
public RFuture<Void> unlockAsync(long threadId) { // 解鎖 RFuture<Boolean> future = this.unlockInnerAsync(threadId); CompletionStage<Void> f = future.handle((opStatus, e) -> { // 解除續期 this.cancelExpirationRenewal(threadId); ... }); return new CompletableFutureWrapper(f); }
在unlockInnerAsync內部,Redisson釋放鎖其實核心也是執行了如下一段核心Lua指令碼:
// 校驗是否存在 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; // 刪除Key else redis.call('del', KEYS[1]); // 通知阻塞的使用者端可以搶鎖啦 redis.call('publish', KEYS[2], ARGV[1]); return 1; end; return nil;
其中:
KEYS[1]: 分散式鎖
KEYS[2]: redisson_lock_channel:{分散式鎖} 釋出訂閱訊息的管道名稱
ARGV[1]: 釋出的訊息內容
ARGV[2]: 鎖的過期時間
ARGV[3]: 執行緒ID標識名稱
其它問題
以上就是Redisson分散式鎖第一彈-加解鎖的詳細內容,更多關於Redisson分散式鎖加解鎖的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45