Redis中是如何實現分佈式鎖的?

分佈式鎖常見的三種實現方式:html

  1. 數據庫樂觀鎖;java

  2. 基於Redis的分佈式鎖;git

  3. 基於ZooKeeper的分佈式鎖。github

本地面試考點是,你對Redis使用熟悉嗎?Redis中是如何實現分佈式鎖的。面試

要點

Redis要實現分佈式鎖,如下條件應該獲得知足redis

互斥性算法

  • 在任意時刻,只有一個客戶端能持有鎖。數據庫

不能死鎖併發

  • 客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其餘客戶端能加鎖。dom

容錯性

  • 只要大部分的Redis節點正常運行,客戶端就能夠加鎖和解鎖。

實現

能夠直接經過 set key value px milliseconds nx 命令實現加鎖, 經過Lua腳本實現解鎖。

//獲取鎖(unique_value能夠是UUID等)
SET resource_name unique_value NX PX  30000

//釋放鎖(lua腳本中,必定要比較value,防止誤解鎖)
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else
    return 0 end

 

代碼解釋

  • set 命令要用 set key value px milliseconds nx,替代 setnx + expire 須要分兩次執行命令的方式,保證了原子性,

  • value 要具備惟一性,可使用UUID.randomUUID().toString()方法生成,用來標識這把鎖是屬於哪一個請求加的,在解鎖的時候就能夠有依據;

  • 釋放鎖時要驗證 value 值,防止誤解鎖;

  • 經過 Lua 腳原本避免 Check And Set 模型的併發問題,由於在釋放鎖的時候由於涉及到多個Redis操做 (利用了eval命令執行Lua腳本的原子性);

加鎖代碼分析

首先,set()加入了NX參數,能夠保證若是已有key存在,則函數不會調用成功,也就是隻有一個客戶端能持有鎖,知足互斥性。其次,因爲咱們對鎖設置了過時時間,即便鎖的持有者後續發生崩潰而沒有解鎖,鎖也會由於到了過時時間而自動解鎖(即key被刪除),不會發生死鎖。最後,由於咱們將value賦值爲requestId,用來標識這把鎖是屬於哪一個請求加的,那麼在客戶端在解鎖的時候就能夠進行校驗是不是同一個客戶端。

解鎖代碼分析

將Lua代碼傳到jedis.eval()方法裏,並使參數KEYS[1]賦值爲lockKey,ARGV[1]賦值爲requestId。在執行的時候,首先會獲取鎖對應的value值,檢查是否與requestId相等,若是相等則解鎖(刪除key)。

存在的風險

若是存儲鎖對應key的那個節點掛了的話,就可能存在丟失鎖的風險,致使出現多個客戶端持有鎖的狀況,這樣就不能實現資源的獨享了。

  1. 客戶端A從master獲取到鎖

  2. 在master將鎖同步到slave以前,master宕掉了(Redis的主從同步一般是異步的)。
    主從切換,slave節點被晉級爲master節點

  3. 客戶端B取得了同一個資源被客戶端A已經獲取到的另一個鎖。致使存在同一時刻存不止一個線程獲取到鎖的狀況。

redlock算法出現

這個場景是假設有一個 redis cluster,有 5 個 redis master 實例。而後執行以下步驟獲取一把鎖:

  1. 獲取當前時間戳,單位是毫秒;

  2. 跟上面相似,輪流嘗試在每一個 master 節點上建立鎖,過時時間較短,通常就幾十毫秒;

  3. 嘗試在大多數節點上創建一個鎖,好比 5 個節點就要求是 3 個節點 n / 2 + 1;

  4. 客戶端計算創建好鎖的時間,若是創建鎖的時間小於超時時間,就算創建成功了;

  5. 要是鎖創建失敗了,那麼就依次以前創建過的鎖刪除;

  6. 只要別人創建了一把分佈式鎖,你就得不斷輪詢去嘗試獲取鎖。

 

【07期】Redis中是如何實現分佈式鎖的?

Redis 官方給出了以上兩種基於 Redis 實現分佈式鎖的方法,詳細說明能夠查看:

https://redis.io/topics/distlock 。

Redisson實現

Redisson是一個在Redis的基礎上實現的Java駐內存數據網格(In-Memory Data Grid)。它不只提供了一系列的分佈式的Java經常使用對象,還實現了可重入鎖(Reentrant Lock)、公平鎖(Fair Lock、聯鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)等,還提供了許多分佈式服務。

Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進使用者對Redis的關注分離(Separation of Concern),從而讓使用者可以將精力更集中地放在處理業務邏輯上。

Redisson 分佈式重入鎖用法

Redisson 支持單點模式、主從模式、哨兵模式、集羣模式,這裏以單點模式爲例:

// 1.構造redisson實現分佈式鎖必要的Config
Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0); // 2.構造RedissonClient
RedissonClient redissonClient = Redisson.create(config); // 3.獲取鎖對象實例(沒法保證是按線程的順序獲取到)
RLock rLock = redissonClient.getLock(lockKey); try { /** * 4.嘗試獲取鎖 * waitTimeout 嘗試獲取鎖的最大等待時間,超過這個值,則認爲獲取鎖失敗 * leaseTime 鎖的持有時間,超過這個時間鎖會自動失效(值應設置爲大於業務處理的時間,確保在鎖有效期內業務能處理完) */
    boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS); if (res) { //成功得到鎖,在這裏處理業務
 } } catch (Exception e) { throw new RuntimeException("aquire lock fail"); }finally{ //不管如何, 最後都要解鎖
 rLock.unlock(); }

 

加鎖流程圖

【07期】Redis中是如何實現分佈式鎖的?

解鎖流程圖

【07期】Redis中是如何實現分佈式鎖的?

咱們能夠看到,RedissonLock是可重入的,而且考慮了失敗重試,能夠設置鎖的最大等待時間, 在實現上也作了一些優化,減小了無效的鎖申請,提高了資源的利用率。

須要特別注意的是,RedissonLock 一樣沒有解決 節點掛掉的時候,存在丟失鎖的風險的問題。而現實狀況是有一些場景沒法容忍的,因此 Redisson 提供了實現了redlock算法的 RedissonRedLock,RedissonRedLock 真正解決了單點失敗的問題,代價是須要額外的爲 RedissonRedLock 搭建Redis環境。

因此,若是業務場景能夠容忍這種小几率的錯誤,則推薦使用 RedissonLock, 若是沒法容忍,則推薦使用 RedissonRedLock。

參考

https://github.com/javazhiyin/advanced-java/
https://crazyfzw.github.io/2019/04/15/distributed-locks-with-redis/

最近三期

【04期】分庫分表以後,id 主鍵如何處理?

【05期】消息隊列中,如何保證消息的順序性?

【06期】單例模式有幾種寫法?

原文出處:https://www.cnblogs.com/javazhiyin/p/11737403.html

相關文章
相關標籤/搜索