1. 程式人生 > >Redisson3.6.1版本的分散式鎖原始碼分析

Redisson3.6.1版本的分散式鎖原始碼分析

前言:

    stringRedisTemple好像沒有做到unlock的時候只解鎖當前執行緒的鎖,redisson看原始碼會獲取ThreadId,就想找找原始碼解讀,一直沒找到,google了一下才找到,轉載一下哈哈哈哈!

最近碰到的一個問題,Java程式碼中寫了一個定時器,分散式部署的時候,多臺同時執行的話就會出現重複的資料,為了避免這種情況,之前是通過在配置檔案裡寫上可以執行這段程式碼的IP,程式碼中判斷如果跟這個IP相等,則執行,否則不執行,想想也是一種比較簡單的方式吧,但是感覺很low很low,所以改用分散式鎖。

目前分散式鎖常用的三種方式:1.資料庫的鎖;2.基於Redis的分散式鎖;3.基於ZooKeeper的分散式鎖。其中資料庫中的鎖有共享鎖和排他鎖,這兩種都無法直接解決資料庫的單點和可重入的問題,所以,本章還是來講講基於Redis的分散式鎖,也可以用其他快取(Memcache、Tair等)來實現。

一、實現分散式鎖的要求

  1. 互斥性。在任何時候,當且僅有一個客戶端能夠持有鎖。
  2. 不能有死鎖。持有鎖的客戶端崩潰後,後續客戶端能夠加鎖。
  3. 容錯性。大部分Redis或者ZooKeeper節點能夠正常執行。
  4. 加鎖解鎖相同。加鎖的客戶端和解鎖的客戶端必須為同一個客戶端,不能讓其他的解鎖了。

二、Redis實現分散式鎖的常用命令

1.SETNX key val
當且僅當key不存在時,set一個key為val的字串,返回1;若key存在,則什麼都不做,返回0。
2.expire key timeout
為key設定一個超時時間,單位為second,超過這個時間鎖會自動釋放,避免死鎖。
3.delete key
刪除key,此處用來解鎖使用。
4.HEXISTS key field


當key 中儲存著field的時候返回1,如果key或者field至少有一個不存在返回0。
5.HINCRBY key field increment
將儲存在 key 中的雜湊(Hash)物件中的指定欄位 field 的值加上增量 increment。如果鍵 key 不存在,一個儲存了雜湊物件的新建將被建立。如果欄位 field 不存在,在進行當前操作前,其將被建立,且對應的值被置為 0。返回值是增量之後的值。

三、常見寫法

由上面三個命令,我們可以很快的寫一個分散式鎖出來:

  1. if(conn.setnx("lock","1").equals(1L)){
  2. //do something
  3. returntrue;
  4. }
  5. return
    false;

但是這樣也會存在問題,如果獲取該鎖的客戶端掛掉了怎麼辦?一般而言,我們可以通過設定expire的過期時間來防止客戶端掛掉所帶來的影響,可以降低應用掛掉所帶來的影響,不過當時間失效的時候,要保證其他客戶端只有一臺能夠獲取。

四、Redisson

Redisson在基於NIO的Netty框架上,充分的利用了Redis鍵值資料庫提供的一系列優勢,在Java實用工具包中常用介面的基礎上,為使用者提供了一系列具有分散式特性的常用工具類。使得原本作為協調單機多執行緒併發程式的工具包獲得了協調分散式多機多執行緒併發系統的能力,大大降低了設計和研發大規模分散式系統的難度。同時結合各富特色的分散式服務,更進一步簡化了分散式環境中程式相互之間的協作。——摘自百度百科

4.1 測試例子

先在pom引入Redssion

  1. <dependency>
  2. <groupId>org.redisson</groupId>
  3. <artifactId>redisson</artifactId>
  4. <version>3.6.1</version>
  5. </dependency>

起100個執行緒,同時對count進行操作,每次操作減1,加鎖的時候能夠保持順序輸出,不加的話為隨機。

  1. publicclassRedissonTestimplementsRunnable{
  2. privatestaticRedissonClient redisson;
  3. privatestaticint count =10000;
  4. privatestaticvoid init(){
  5. Config config =newConfig();
  6. config.useSingleServer()
  7. .setAddress("redis://119.23.46.71:6340")
  8. .setPassword("root")
  9. .setDatabase(10);
  10. redisson =Redisson.create(config);
  11. }
  12. @Override
  13. publicvoid run(){
  14. RLock lock = redisson.getLock("anyLock");
  15. lock.lock();
  16. count--;
  17. System.out.println(count);
  18. lock.unlock();
  19. }
  20. publicstaticvoid main(String[] args){
  21. init();
  22. for(int i =0; i <100; i++){
  23. newThread(newRedissonTest()).start();
  24. }
  25. }
  26. }

輸出結果(部分結果):

  1. ...
  2. 9930
  3. 9929
  4. 9928
  5. 9927
  6. 9926
  7. 9925
  8. 9924
  9. 9923
  10. 9922
  11. 9921
  12. ...

去掉lock.lock()和lock.unlock()之後(部分結果):

  1. ...
  2. 9930
  3. 9931
  4. 9933
  5. 9935
  6. 9938
  7. 9937
  8. 9940
  9. 9941
  10. 9942
  11. 9944
  12. 9947
  13. 9946
  14. 9914
  15. ...

五、RedissonLock原始碼分析

最新版的Redisson要求redis能夠支援eval的命令,否則無法實現,即Redis要求2.6版本以上。在lua指令碼中可以呼叫大部分的Redis命令,使用指令碼的好處如下:
(1)減少網路開銷:在Redis操作需求需要向Redis傳送5次請求,而使用指令碼功能完成同樣的操作只需要傳送一個請求即可,減少了網路往返時延。
(2)原子操作:Redis會將整個指令碼作為一個整體執行,中間不會被其他命令插入。換句話說在編寫指令碼的過程中無需擔心會出現競態條件,也就無需使用事務。事務可以完成的所有功能都可以用指令碼來實現。
(3)複用:客戶端傳送的指令碼會永久儲存在Redis中,這就意味著其他客戶端(可以是其他語言開發的專案)可以複用這一指令碼而不需要使用程式碼完成同樣的邏輯。

5.1 使用到的全域性變數

全域性變數:
expirationRenewalMap:儲存entryName和其過期時間,底層用的netty的PlatformDependent.newConcurrentHashMap()
internalLockLeaseTime:鎖預設釋放的時間:30 1000,即30秒
id:UUID,用作客戶端的唯一標識
PUBSUB:訂閱者模式,當釋放鎖的時候,其他客戶端能夠知道鎖已經被釋放的訊息,並讓佇列中的第一個消費者獲取鎖。使用PUB/SUB訊息機制的優點:減少申請鎖時的等待時間、安全、 鎖帶有超時時間、鎖的標識唯一,防止死鎖 鎖設計為可重入,避免死鎖。
*commandExecutor
:命令執行器,非同步執行器

5.2 加鎖

以lock.lock()為例,呼叫lock之後,底層使用的是lockInterruptibly,之後呼叫lockInterruptibly(-1, null);





(1)我們來看一下lockInterruptibly的原始碼,如果別的客戶端沒有加鎖,則當前客戶端進行加鎖並且訂閱,其他客戶端嘗試加鎖,並且獲取ttl,然後等待已經加了鎖的客戶端解鎖。

  1. //leaseTime預設為-1
  2. publicvoid lockInterruptibly(long leaseTime,TimeUnit unit)throwsInterruptedException{
  3. long threadId =Thread.currentThread().getId();//獲取當前執行緒ID
  4. Long ttl = tryAcquire(leaseTime, unit, threadId);//嘗試加鎖
  5. // 如果為空,當前執行緒獲取鎖成功,否則已經被其他客戶端加鎖
  6. if(ttl ==null){
  7. return;
  8. }
  9. //等待釋放,並訂閱鎖
  10. RFuture<RedissonLockEntry> future = subscribe(threadId);
  11. commandExecutor.syncSubscription(future);
  12. try{
  13. while(true){
  14. // 重新嘗試獲取鎖
  15. ttl = tryAcquire(leaseTime, unit, threadId);
  16. // 成功獲取鎖
  17. if(ttl ==null){
  18. break;
  19. }
  20. // 等待鎖釋放
  21. if(ttl >=0){
  22. getEntry(threadId).getLatch().tryAcquire(ttl,TimeUnit.MILLISECONDS);
  23. }else{
  24. getEntry(threadId).getLatch().acquire();
  25. }
  26. }
  27. }finally{
  28. // 取消訂閱
  29. unsubscribe(future, threadId);
  30. }
  31. }

(2)下面是tryAcquire的實現,呼叫的是tryAcquireAsync

  1. privateLong tryAcquire(long leaseTime,TimeUnit unit,long threadId){
  2. return get(tryAcquireAsync(leaseTime, unit, threadId));
  3. }

(3)下面是tryAcquireAsync的實現,非同步嘗試進行加鎖,嘗試加鎖的時候leaseTime為-1。通常如果客戶端沒有加鎖成功,則會進行阻塞,leaseTime為鎖釋放的時間。

  1. private<T>RFuture<Long> tryAcquireAsync(long leaseTime,TimeUnit unit,finallong threadId){
  2. if(leaseTime !=

    相關推薦

    Redisson3.6.1版本分散式原始碼分析

    前言:    stringRedisTemple好像沒有做到unlock的時候只解鎖當前執行緒的鎖,redisson看原始碼會獲取ThreadId,就想找找原始碼解讀,一直沒找到,google了一下才找到,轉載一下哈哈哈哈!最近碰到的一個問題,Java程式碼中寫了一個定時器,

    ZooKeeper框架Curator的分散式原始碼分析

    上一篇文章中,我們使用zookeeper的java api實現了分散式排他鎖。其實zookeeper有一個優秀的框架---Curator,提供了各種分散式協調的服務。Curator中有著更為標準、規範的分散式鎖實現。與其我們自己去實現,不如直接使用Curator。通過學習Cu

    Curator分散式原始碼分析

    Curator是Apache ZooKeeper的Java / JVM客戶端庫,官網有個圖很形象。 curator對於zookeeper來說就像Guava之餘java.我們知道Guava是谷歌開源的java類庫,該庫經過高度優化,運用得當可極大提高我們的程式碼效率和質量

    RedissonLock分散式原始碼分析

    最近碰到的一個問題,Java程式碼中寫了一個定時器,分散式部署的時候,多臺同時執行的話就會出現重複的資料,為了避免這種情況,之前是通過在配置檔案裡寫上可以執行這段程式碼的IP,程式碼中判斷如果跟這個IP相等,則執行,否則不執行,想想也是一種比較簡單的方式吧,但是感覺很low很

    分散式原始碼剖析(1) Redisson實現非公平分散式

    Redisson分散式鎖原始碼剖析(非公平鎖) maven配置檔案: <dependency> <groupId>org.redisson</groupId> <artifactId>redisso

    分散式】06-Zookeeper實現分散式:可重入原始碼分析

    前言 前面已經講解了Redis的客戶端Redission是怎麼實現分散式鎖的,大多都深入到原始碼級別。 在分散式系統中,常見的分散式鎖實現方案還有Zookeeper,接下來會深入研究Zookeeper是如何來實現分散式鎖的。 Zookeeper初識 檔案系統 Zookeeper維護一個類似檔案系統的資料結構

    MAC OS 下protobuf 2.6.1 版本編譯安裝及proto型別檔案編譯

    由於工程中proto語法採用的是2.0的,所以選擇安裝2.6.1版本的protobuf。(3.0和2.0語法不同,如果安裝3.0以上版本的protobuf,在編譯的時候應該需要增加啥~~~) 編譯安裝步驟: 1. 安裝 protobuf 依賴項, 其依賴於autoconf、 a

    Redisson 分散式實現分析(一)

      設計分散式鎖要注意的問題 互斥 分散式系統中執行著多個節點,必須確保在同一時刻只能有一個節點的一個執行緒獲得鎖,這是最基本的一點。 死鎖 分散式系統中,可能產生死鎖的情況要相對複雜一些。分散式系統是處在複雜網路環境中的,當一個節點獲取到鎖,如果它在釋放鎖之前掛掉了,

    分散式框架分析

    三大引擎分析 zookeeper引擎分析 優點: 鎖安全性高,zk可持久化,且能實時監聽獲取鎖的客戶端狀態。 zookeeper支援watcher機制,這樣實現阻塞鎖,可以watch鎖資料,等到資料被刪除,zookeeper會通知客戶端去重新競爭鎖。 zookeeper的資料可以支援臨時節點的概念,即客戶端

    Android7.1 [Camera] Camera Hal 原始碼分析(一)

    原始碼平臺:rk3399   命令列ls看下原始碼的結構 hardware/rockchip/camera/CameraHal: lib目錄 原始碼的檔案看起來有點多,我們看看Android.mk檔案, 這些檔案最終編譯成camera.rk30bo

    Android7.1 [Camera] CameraService啟動原始碼分析

    原始碼平臺:rk3399   摘要: 1.拷貝cameraserver.rc編譯拷貝到system/etc/init目錄 2.啟動cameraserver服務   摘要1:cameraserver.rc編譯拷貝到system/etc/init目錄 an

    分散式1分散式

    一、什麼是分散式鎖? 要介紹分散式鎖,首先要提到與分散式鎖相對應的是執行緒鎖、程序鎖。 執行緒鎖:主要用來給方法、程式碼塊加鎖。當某個方法或程式碼使用鎖,在同一時刻僅有一個執行緒執行該方法或該程式碼段。執行緒鎖只在同一JVM中有效果,因為執行緒鎖的實現在根本上是依靠執行緒之

    XXL-CONF v1.6.1分散式配置管理平臺

       Release Notes 1、在未設定accessToken情況下,非法請求惡意構造配置Key可遍歷讀取檔案漏洞修復;(From:360程式碼衛士團隊) 2、專案名正則校驗問題修復,專案名中劃線分隔,配置點分隔; 3、底層HTTP工具類優化; 4、RESTFU

    併發程式設計之——讀原始碼分析(解釋關於降級的爭議)

    1. 前言 在前面的文章 併發程式設計之——寫鎖原始碼分析中,我們分析了 1.8 JUC 中讀寫鎖中的寫鎖的獲取和釋放過程,今天來分析一下讀鎖的獲取和釋放過程,讀鎖相比較寫鎖要稍微複雜一點,其中還有一點有爭議的地方——鎖降級。 今天就來解開迷霧。 2. 獲取讀鎖 tryAcquireShar

    Jarslink1.6.1版本特性

    Jarslink 在4月初推出了新版本,增加支援Spring註解和模組多版本特性。歡迎參與開源專案,成為我們的Commiter。 註解的使用 新版本加入了註解的支援,使用者只需要在構建ModuleConfig的時候呼叫ModuleConfig.addScanPackage(String)方法即

    分散式原始碼剖析(4) zookeeper實現分散式

    zookeeper分散式鎖(curator) maven配置檔案: <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes<

    分散式原始碼剖析(3) Redisson的MultiLock和RedLock

    MultiLock MultiLock:將多個鎖合併為一個大鎖,對一個大鎖進行統一的申請加鎖以及釋放鎖,一次性鎖定多個資源,再去處理一些事情,然後事後一次性釋放所有的資源對應的鎖。 maven配置檔案: <dependency> <group

    分散式原始碼剖析(2) Redisson實現公平分散式

    Redisson分散式鎖原始碼剖析(公平鎖) maven配置檔案: <dependency> <groupId>org.redisson</groupId> <artifactId>redisson

    HBase 1.1.3 balance相關原始碼分析

    HMaster類中與balance相關部分1、初始化//balancer作為HMaster的一個成員變數 LoadBalancer balancer; //ClusterStatusChore 這個會定時去執行balancer private ClusterStatus

    終於在pycharm下(Python3.6.1版本)安裝完成機器學習相關庫檔案(sklearn scikit-learn gensim xgboost tensorflow nltk )

    安裝scikit-learn請參考本人的相應部落格,這個是最為難安裝的。其他的大部分能夠直接通過pycharm的settings安裝。 由於安裝部分庫檔案時,會附帶安裝其他庫檔案,因此在整體安裝完成後,庫檔案還是挺多的。安裝後的整體庫包括如圖所示