中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Redis中怎么實現分布式鎖

發布時間:2021-10-20 11:44:09 來源:億速云 閱讀:132 作者:iii 欄目:關系型數據庫

本篇內容介紹了“Redis中怎么實現分布式鎖”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

Redis中怎么實現分布式鎖

為什么需要分布式鎖

為什么需要分布式鎖

使用分布式鎖的目的,無外乎就是保證同一時間只有一個客戶端可以對共享資源進行操作。

我們在分布式應用進行邏輯處理時經常會遇到并發問題。【相關推薦:Redis視頻教程】

比如一個操作要修改用戶的狀態,修改狀態需要先讀出用戶的狀態,在內存里進行修改,改完了再存回去。如果這樣的操作同時進行了,就會出現并發問題,因為讀取和保存狀態這兩個操作不是原子的。

這個時候就要使用到分布式鎖來限制程序的并發執行。redis作為一個緩存中間件系統,就能提供這種分布式鎖機制,

其本質就是在redis里面占一個坑,當別的進程也要來占坑時,發現已經被占領了,就只要等待稍后再嘗試

一般來說,生產環境可用的分布式鎖需要滿足以下幾點:

  • 互斥性,互斥是鎖的基本特征,同一時刻只能有一個線程持有鎖,執行臨界操作;

  • 超時釋放,超時釋放是鎖的另一個必備特性,可以對比 MySQL InnoDB 引擎中的 innodb_lock_wait_timeout配置,通過超時釋放,防止不必要的線程等待和資源浪費;

  • 可重入性,在分布式環境下,同一個節點上的同一個線程如果獲取了鎖之后,再次請求還是可以成功;

實現方式

使用SETNX實現

SETNX的使用方式為:SETNX key value,只在鍵key不存在的情況下,將鍵key的值設置為value,若鍵key存在,則SETNX不做任何動作。

boolean result = jedis.setnx("lock-key",true)== 1L;
if  (result) {
    try {
        // do something
    } finally {
        jedis.del("lock-key");
    }
 }

這種方案有一個致命問題,就是某個線程在獲取鎖之后由于某些異常因素(比如宕機)而不能正常的執行解鎖操作,那么這個鎖就永遠釋放不掉了。

為此,我們可以為這個鎖加上一個超時時間

執行 SET key value EX seconds 的效果等同于執行 SETEX key seconds value

執行 SET key value PX milliseconds 的效果等同于執行 PSETEX key milliseconds value

String result = jedis.set("lock-key",true, 5);
if ("OK".equals(result)) {
    try {
        // do something
    } finally {
        jedis.del("lock-key");
    }
}

方案看上去很完美,但實際上還是會有問題

試想一下,某線程A獲取了鎖并且設置了過期時間為10s,然后在執行業務邏輯的時候耗費了15s,此時線程A獲取的鎖早已被Redis的過期機制自動釋放了

在線程A獲取鎖并經過10s之后,改鎖可能已經被其它線程獲取到了。當線程A執行完業務邏輯準備解鎖(DEL key)的時候,有可能刪除掉的是其它線程已經獲取到的鎖。

所以最好的方式是在解鎖時判斷鎖是否是自己的,我們可以在設置key的時候將value設置為一個唯一值uniqueValue(可以是隨機值、UUID、或者機器號+線程號的組合、簽名等)。

當解鎖時,也就是刪除key的時候先判斷一下key對應的value是否等于先前設置的值,如果相等才能刪除key

String velue= String.valueOf(System.currentTimeMillis())
String result = jedis.set("lock-key",velue, 5);
if ("OK".equals(result)) {
    try {
        // do something
    } finally {
      	//非原子操作
	      if(jedis.get("lock-key")==value){
		        jedis.del("lock-key");
        }    
    }
}

這里我們一眼就可以看出問題來:GETDEL是兩個分開的操作,在GET執行之后且在DEL執行之前的間隙是可能會發生異常的。

如果我們只要保證解鎖的代碼是原子性的就能解決問題了

這里我們引入了一種新的方式,就是Lua腳本,示例如下:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

其中ARGV[1]表示設置key時指定的唯一值。

由于Lua腳本的原子性,在Redis執行該腳本的過程中,其他客戶端的命令都需要等待該Lua腳本執行完才能執行。

確保過期時間大于業務執行時間

為了防止多個線程同時執行業務代碼,需要確保過期時間大于業務執行時間

增加一個boolean類型的屬性isOpenExpirationRenewal,用來標識是否開啟定時刷新過期時間

在增加一個scheduleExpirationRenewal方法用于開啟刷新過期時間的線程

加鎖代碼在獲取鎖成功后將isOpenExpirationRenewal置為true,并且調用scheduleExpirationRenewal方法,開啟刷新過期時間的線程

解鎖代碼增加一行代碼,將isOpenExpirationRenewal屬性置為false,停止刷新過期時間的線程輪詢

Redisson實現

獲取鎖成功就會開啟一個定時任務,定時任務會定期檢查去續期

該定時調度每次調用的時間差是internalLockLeaseTime / 3,也就10秒

默認情況下,加鎖的時間是30秒.如果加鎖的業務沒有執行完,那么到 30-10 = 20秒的時候,就會進行一次續期,把鎖重置成30秒

RedLock

在集群中,主節點掛掉時,從節點會取而代之,客戶端上卻并沒有明顯感知。原先第一個客戶端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。然后從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客戶端過來請求加鎖時,立即就批準了。這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生

Redlock算法就是為了解決這個問題

使用 Redlock,需要提供多個 Redis 實例,這些實例之前相互獨立沒有主從關系。同很多分布式算法一樣,redlock 也使用大多數機制

加鎖時,它會向過半節點發送 set指令,只要過半節點 set 成功,那就認為加鎖成功。釋放鎖時,需要向所有節點發送 del 指令。不過 Redlock 算法還需要考慮出錯重試、時鐘漂移等很多細節問題,同時因為 Redlock 需要向多個節點進行讀寫,意味著相比單實例 Redis 性能會下降一些

Redlock 算法是在單 Redis 節點基礎上引入的高可用模式,Redlock 基于 N 個完全獨立的 Redis 節點,一般是大于 3 的奇數個(通常情況下 N 可以設置為 5),可以基本保證集群內各個節點不會同時宕機。

假設當前集群有 5 個節點,運行 Redlock 算法的客戶端依次執行下面各個步驟,來完成獲取鎖的操作

  • 客戶端記錄當前系統時間,以毫秒為單位;

  • 依次嘗試從 5 個 Redis 實例中,使用相同的 key 獲取鎖,當向 Redis 請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,超時時間應該小于鎖的失效時間,避免因為網絡故障出現的問題;

  • 客戶端使用當前時間減去開始獲取鎖時間就得到了獲取鎖使用的時間,當且僅當從半數以上的 Redis 節點獲取到鎖,并且當使用的時間小于鎖失效時間時,鎖才算獲取成功;

  • 如果獲取到了鎖,key 的真正有效時間等于有效時間減去獲取鎖所使用的時間,減少超時的幾率;

  • 如果獲取鎖失敗,客戶端應該在所有的 Redis 實例上進行解鎖,即使是上一步操作請求失敗的節點,防止因為服務端響應消息丟失,但是實際數據添加成功導致的不一致。

也就是說,假設鎖30秒過期,三個節點加鎖花了31秒,自然是加鎖失敗了

在 Redis 官方推薦的 Java 客戶端 Redisson 中,內置了對 RedLock 的實現

RedLock問題:

RedLock 只是保證了鎖的高可用性,并沒有保證鎖的正確性

RedLock 是一個嚴重依賴系統時鐘的分布式系統

Martin 對 RedLock 的批評:

  • 對于提升效率的場景下,RedLock 太重。

  • 對于對正確性要求極高的場景下,RedLock 并不能保證正確性。

“Redis中怎么實現分布式鎖”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

崇仁县| 宁安市| 沽源县| 会泽县| 墨脱县| 万安县| 漳浦县| 昭苏县| 奉节县| 如皋市| 称多县| 清流县| 裕民县| 辛集市| 望城县| 长阳| 辽阳市| 乐至县| 大化| 睢宁县| 抚松县| 广饶县| 文昌市| 永仁县| 柳州市| 绥阳县| 潜江市| 慈利县| 青田县| 广平县| 镇宁| 清新县| 寿宁县| 兴隆县| 迭部县| 灌阳县| 集安市| 凤冈县| 二连浩特市| 汶上县| 玛曲县|