亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

如何用Redis分布式鎖才能確保萬無一失

發布時間:2021-10-22 09:51:04 來源:億速云 閱讀:107 作者:iii 欄目:數據庫

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

一、背景

我們日常在電商網站購物時經常會遇到一些高并發的場景,例如電商 App  上經常出現的秒殺活動、限量優惠券搶購,還有我們去哪兒網的火車票搶票系統等,這些場景有一個共同特點就是訪問量激增,雖然在系統設計時會通過限流、異步、排隊等方式優化,但整體的并發還是平時的數倍以上,為了避免并發問題,防止庫存超賣,給用戶提供一個良好的購物體驗,這些系統中都會用到鎖的機制。

對于單進程的并發場景,可以使用編程語言及相應的類庫提供的鎖,如 Java 中的 synchronized 語法以及 ReentrantLock 類等,避免并發問題。

如何用Redis分布式鎖才能確保萬無一失

如果在分布式場景中,實現不同客戶端的線程對代碼和資源的同步訪問,保證在多線程下處理共享數據的安全性,就需要用到分布式鎖技術。

如何用Redis分布式鎖才能確保萬無一失

那么何為分布式鎖呢?分布式鎖是控制分布式系統或不同系統之間共同訪問共享資源的一種鎖實現,如果不同的系統或同一個系統的不同主機之間共享了某個資源時,往往需要互斥來防止彼此干擾保證一致性。

一個相對安全的分布式鎖,一般需要具備以下特征:

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

  • 超時釋放。通過超時釋放,可以避免死鎖,防止不必要的線程等待和資源浪費,類似于 MySQL 的 InnoDB 引擎中的 innodblockwait_timeout 參數配置。

  • 可重入性。一個線程在持有鎖的情況可以對其再次請求加鎖,防止鎖在線程執行完臨界區操作之前釋放。

  • 高性能和高可用。加鎖和釋放鎖的過程性能開銷要盡可能的低,同時也要保證高可用,防止分布式鎖意外失效。

可以看出實現分布式鎖,并不是鎖住資源就可以了,還需要滿足一些額外的特征,避免出現死鎖、鎖失效等問題。

二、分布式鎖的實現方式

目前實現分布式鎖的方式有很多,常見的主要有:

  • Memcached 分布式鎖

利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情況下,才能 add 成功,也就意味著線程得到了鎖。

  • Zookeeper 分布式鎖

利用 Zookeeper 的順序臨時節點,來實現分布式鎖和等待隊列。ZooKeeper  作為一個專門為分布式應用提供方案的框架,它提供了一些非常好的特性,如 ephemeral 類型的 znode 自動刪除的功能,同時  ZooKeeper 還提供 watch 機制,可以讓分布式鎖在客戶端用起來就像一個本地的鎖一樣:加鎖失敗就阻塞住,直到獲取到鎖為止。

  • Chubby

Google 公司實現的粗粒度分布式鎖服務,有點類似于 ZooKeeper,但也存在很多差異。Chubby 通過 sequencer 機制解決了請求延遲造成的鎖失效的問題。

  • Redis 分布式鎖

基于 Redis 單機實現的分布式鎖,其方式和 Memcached 的實現方式類似,利用 Redis 的 SETNX  命令,此命令同樣是原子性操作,只有在 key 不存在的情況下,才能 set 成功。而基于 Redis 多機實現的分布式鎖Redlock,是  Redis 的作者 antirez 為了規范 Redis 分布式鎖的實現,提出的一個更安全有效的實現機制。

本文主要討論分析基于Redis的分布式鎖的幾種實現方式以及存在的問題。

三、Redis分布式鎖

使用 Redis 作為分布式鎖,本質上要實現的目標就是一個進程在 Redis 里面占據了僅有的一個“茅坑”,當別的進程也想來占坑時,發現已經有人蹲在那里了,就只好放棄或者等待稍后再試。

目前基于 Redis 實現分布式鎖主要有兩大類,一類是基于單機,另一類是基于 Redis 多機,不管是哪種實現方式,均需要實現加鎖、解鎖、鎖超時這三個分布式鎖的核心要素。

1、基于Redis單機實現的分布式鎖

1)使用 SETNX 指令

最簡單的加鎖方式就是直接使用 Redis 的 SETNX 指令,該指令只在 key 不存在的情況下,將 key 的值設置為 value,若 key 已經存在,則 SETNX 命令不做任何動作。key 是鎖的唯一標識,可以按照業務需要鎖定的資源來命名。

比如在某商城的秒殺活動中對某一商品加鎖,那么 key 可以設置為 lock_resource_id ,value 可以設置為任意值,在資源使用完成后,使用 DEL 刪除該 key 對鎖進行釋放,整個過程如下:

如何用Redis分布式鎖才能確保萬無一失

很顯然,這種獲取鎖的方式很簡單,但也存在一個問題,就是我們上面提到的分布式鎖三個核心要素之一的鎖超時問題,即如果獲得鎖的進程在業務邏輯處理過程中出現了異常,可能會導致 DEL 指令一直無法執行,導致鎖無法釋放,該資源將會永遠被鎖住。

如何用Redis分布式鎖才能確保萬無一失

所以,在使用 SETNX 拿到鎖以后,必須給 key 設置一個過期時間,以保證即使沒有被顯式釋放,在獲取鎖達到一定時間后也要自動釋放,防止資源被長時間獨占。由于 SETNX 不支持設置過期時間,所以需要額外的 EXPIRE 指令,整個過程如下:

如何用Redis分布式鎖才能確保萬無一失

這樣實現的分布式鎖仍然存在一個嚴重的問題,由于 SETNX 和 EXPIRE 這兩個操作是非原子性的, 如果進程在執行 SETNX 和  EXPIRE 之間發生異常,SETNX 執行成功,但 EXPIRE  沒有執行,導致這把鎖變得“長生不老”,這種情況就可能出現前文提到的鎖超時問題,其他進程無法正常獲取鎖。

如何用Redis分布式鎖才能確保萬無一失

2)使用 SET 擴展指令

為了解決 SETNX 和 EXPIRE 兩個操作非原子性的問題,可以使用 Redis 的 SET 指令的擴展參數,使得 SETNX 和 EXPIRE 這兩個操作可以原子執行,整個過程如下:

如何用Redis分布式鎖才能確保萬無一失

在這個 SET 指令中:

  • NX 表示只有當 lock_resource_id 對應的 key 值不存在的時候才能 SET 成功。保證了只有第一個請求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖。

  • EX 10 表示這個鎖10秒鐘后會自動過期,業務可以根據實際情況設置這個時間的大小。

但是這種方式仍然不能徹底解決分布式鎖超時問題:

  • 鎖被提前釋放。假如線程 A 在加鎖和釋放鎖之間的邏輯執行的時間過長(或者線程 A  執行過程中被堵塞),以至于超出了鎖的過期時間后進行了釋放,但線程 A 在臨界區的邏輯還沒有執行完,那么這時候線程 B  就可以提前重新獲取這把鎖,導致臨界區代碼不能嚴格的串行執行。

  • 鎖被誤刪。假如以上情形中的線程A執行完后,它并不知道此時的鎖持有者是線程 B,線程A會繼續執行 DEL 指令來釋放鎖,如果線程 B 在臨界區的邏輯還沒有執行完,線程 A 實際上釋放了線程 B 的鎖。

為了避免以上情況,建議不要在執行時間過長的場景中使用 Redis 分布式鎖,同時一個比較安全的做法是在執行 DEL 釋放鎖之前對鎖進行判斷,驗證當前鎖的持有者是否是自己。

具體實現就是在加鎖時將 value 設置為一個唯一的隨機數(或者線程 ID ),釋放鎖時先判斷隨機數是否一致,然后再執行釋放操作,確保不會錯誤地釋放其它線程持有的鎖,除非是鎖過期了被服務器自動釋放,整個過程如下:

如何用Redis分布式鎖才能確保萬無一失

但判斷 value 和刪除 key 是兩個獨立的操作,并不是原子性的,所以這個地方需要使用 Lua 腳本進行處理,因為 Lua 腳本可以保證連續多個指令的原子性執行。

如何用Redis分布式鎖才能確保萬無一失

如何用Redis分布式鎖才能確保萬無一失

基于 Redis 單節點的分布式鎖基本完成了,但是這并不是一個完美的方案,只是相對完全一點,因為它并沒有完全解決當前線程執行超時鎖被提前釋放后,其它線程乘虛而入的問題。

3)使用 Redisson 的分布式鎖

怎么能解決鎖被提前釋放這個問題呢?

可以利用鎖的可重入特性,讓獲得鎖的線程開啟一個定時器的守護線程,每 expireTime/3 執行一次,去檢查該線程的鎖是否存在,如果存在則對鎖的過期時間重新設置為 expireTime,即利用守護線程對鎖進行“續命”,防止鎖由于過期提前釋放。

當然業務要實現這個守護進程的邏輯還是比較復雜的,可能還會出現一些未知的問題。

目前互聯網公司在生產環境用的比較廣泛的開源框架 Redisson 很好地解決了這個問題,非常的簡便易用,且支持 Redis 單實例、Redis M-S、Redis Sentinel、Redis Cluster 等多種部署架構。

感興趣的朋友可以查閱下官方文檔或者源碼:

https://github.com/redisson/redisson/wiki

其實現原理如圖所示(圖中以 Redis 集群為例):

如何用Redis分布式鎖才能確保萬無一失

2、基于Redis多機實現的分布式鎖Redlock

以上幾種基于 Redis 單機實現的分布式鎖其實都存在一個問題,就是加鎖時只作用在一個 Redis 節點上,即使 Redis 通過  Sentinel 保證了高可用,但由于 Redis 的復制是異步的,Master  節點獲取到鎖后在未完成數據同步的情況下發生故障轉移,此時其他客戶端上的線程依然可以獲取到鎖,因此會喪失鎖的安全性。

整個過程如下:

  • 客戶端 A 從 Master 節點獲取鎖。

  • Master 節點出現故障,主從復制過程中,鎖對應的 key 沒有同步到 Slave 節點。

  • Slave升 級為 Master 節點,但此時的 Master 中沒有鎖數據。

  • 客戶端 B 請求新的 Master 節點,并獲取到了對應同一個資源的鎖。

  • 出現多個客戶端同時持有同一個資源的鎖,不滿足鎖的互斥性。

正因為如此,在 Redis 的分布式環境中,Redis 的作者 antirez 提供了 RedLock 的算法來實現一個分布式鎖,該算法大概是這樣的:

假設有 N(N>=5)個 Redis 節點,這些節點完全互相獨立,不存在主從復制或者其他集群協調機制,確保在這N個節點上使用與在 Redis 單實例下相同的方法獲取和釋放鎖。

獲取鎖的過程,客戶端應執行如下操作:

  • 獲取當前 Unix 時間,以毫秒為單位。

  • 按順序依次嘗試從5個實例使用相同的 key 和具有唯一性的 value(例如 UUID)獲取鎖。當向 Redis  請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小于鎖的失效時間。例如鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端  Redis 已經掛掉的情況下,客戶端還在一直等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該盡快嘗試去另外一個 Redis  實例請求獲取鎖。

  • 客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(N/2+1,這里是3個節點)的 Redis 節點都取到鎖,并且使用的時間小于鎖失效時間時,鎖才算獲取成功。

  • 如果取到了鎖,key 的真正有效時間等于有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。

  • 如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個 Redis 實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的 Redis 實例上進行解鎖(使用 Redis Lua 腳本)。

釋放鎖的過程相對比較簡單:客戶端向所有 Redis 節點發起釋放鎖的操作,包括加鎖失敗的節點,也需要執行釋放鎖的操作,antirez 在算法描述中特別強調這一點,這是為什么呢?

原因是可能存在某個節點加鎖成功后返回客戶端的響應包丟失了,這種情況在異步通信模型中是有可能發生的:客戶端向服務器通信是正常的,但反方向卻是有問題的。雖然對客戶端而言,由于響應超時導致加鎖失敗,但是對  Redis節點而言,SET 指令執行成功,意味著加鎖成功。因此,釋放鎖的時候,客戶端也應該對當時獲取鎖失敗的那些 Redis  節點同樣發起請求。

除此之外,為了避免 Redis 節點發生崩潰重啟后造成鎖丟失,從而影響鎖的安全性,antirez 還提出了延時重啟的概念,即一個節點崩潰后不要立即重啟,而是等待一段時間后再進行重啟,這段時間應該大于鎖的有效時間。

關于 Redlock 的更深層次的學習,感興趣的朋友可以查閱下官方文檔:https://redis.io/topics/distlock

四、總結

如何用Redis分布式鎖才能確保萬無一失

分布式系統設計是實現復雜性和收益的平衡,既要盡可能地安全可靠,也要避免過度設計。Redlock  確實能夠提供更安全的分布式鎖,但也是有代價的,需要更多的 Redis 節點。在實際業務中,一般使用基于單點的 Redis  實現分布式鎖就可以滿足絕大部分的需求,偶爾出現數據不一致的情況,可通過人工介入回補數據進行解決,正所謂“技術不夠,人工來湊”!。

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

向AI問一下細節

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

AI

阿克苏市| 四平市| 田阳县| 香河县| 青龙| 惠来县| 响水县| 武定县| 资阳市| 大同市| 平顶山市| 山阳县| 林西县| 老河口市| 大姚县| 南华县| 金沙县| 昭苏县| 五峰| 商洛市| 德钦县| 芒康县| 会理县| 山丹县| 六盘水市| 德江县| 灵宝市| 东台市| 镇巴县| 长泰县| 宜川县| 洞口县| 油尖旺区| 左贡县| 宣化县| 汨罗市| 台中市| 巴林右旗| 南城县| 陆川县| 攀枝花市|