教你一招:基於Redis實現一個分散式鎖
導讀 | 與分散式鎖相對應的是「單機鎖」,我們在寫多執行緒程式時,避免同時操作一個共享變數產生資料問題,通常會使用一把鎖來「互斥」,以保證共享變數的正確性,其使用範圍是在「同一個程式」中。 |
在開始講分散式鎖之前,有必要簡單介紹一下,為什麼需要分散式鎖?
與分散式鎖相對應的是「單機鎖」,我們在寫多執行緒程式時,避免同時操作一個共享變數產生資料問題,通常會使用一把鎖來「互斥」,以保證共享變數的正確性,其使用範圍是在「同一個程式」中。
如果換做是多個程式,需要同時操作一個共享資源,如何互斥呢?
例如,現在的業務應用通常都是微服務架構,這也意味著一個應用會部署多個程式,那這多個程式如果需要修改 MySQL 中的同一行記錄時,為了避免操作亂序導致資料錯誤,此時,我們就需要引入「分散式鎖」來解決這個問題了。
想要實現分散式鎖,必須藉助一個外部系統,所有程式都去這個系統上申請「加鎖」。
而這個外部系統,必須要實現「互斥」的能力,即兩個請求同時進來,只會給一個程式返回成功,另一個返回失敗(或等待)。
這個外部系統,可以是 MySQL,也可以是 Redis 或 Zookeeper。但為了追求更好的效能,我們通常會選擇使用 Redis 或 Zookeeper 來做。
下面我就以 Redis 為主線,由淺入深,帶你深度剖析一下,分散式鎖的各種「安全性」問題,幫你徹底理解分散式鎖。
我們從最簡單的開始講起。
想要實現分散式鎖,必須要求 Redis 有「互斥」的能力,我們可以使用 SETNX ,這個 表示SET if Not eXists,即如果 key 不存在,才會設定它的值,否則什麼也不做。
兩個客戶端程式可以執行這個命令,達到互斥,就可以實現一個分散式鎖。
客戶端 1 申請加鎖,加鎖成功:
127.0.0.1:6379> SETNX lock 1 (integer) 1 // 客戶端1,加鎖成功
客戶端 2 申請加鎖,因為後到達,加鎖失敗:
127.0.0.1:6379> SETNX lock 1 (integer) 0 // 客戶端2,加鎖失敗
此時,加鎖成功的客戶端,就可以去操作「共享資源」,例如,修改 MySQL 的某一行資料,或者呼叫一個 API 請求。
操作完成後,還要及時釋放鎖,給後來者讓出操作共享資源的機會。如何釋放鎖呢?
也很簡單,直接使用 DEL 命令刪除這個 key 即可:
127.0.0.1:6379> DEL lock // 釋放鎖 (integer) 1
這個邏輯非常簡單,整體的路程就是這樣:
但是,它存在一個很大的問題,當客戶端 1 拿到鎖後,如果發生下面的場景,就會造成「死鎖」:
程式處理業務邏輯異常,沒及時釋放鎖
程式掛了,沒機會釋放鎖
這時,這個客戶端就會一直佔用這個鎖,而其它客戶端就「永遠」拿不到這把鎖了。
怎麼解決這個問題呢?
我們很容易想到的方案是,在申請鎖時,給這把鎖設定一個「租期」。
在 Redis 中實現時,就是給這個 key 設定一個「過期時間」。這裡我們假設,操作共享資源的時間不會超過 10s,那麼在加鎖時,給這個 key 設定 10s 過期即可:
127.0.0.1:6379> SETNX lock 1 // 加鎖 (integer) 1 127.0.0.1:6379> EXPIRE lock 10 // 10s後自動過期 (integer) 1
這樣一來,無論客戶端是否異常,這個鎖都可以在 10s 後被「自動釋放」,其它客戶端依舊可以拿到鎖。
疑問臉,但這樣真的沒問題嗎?
還是有問題。
現在的操作,加鎖、設定過期是 2 條命令,有沒有可能只執行了第一條,第二條卻「來不及」執行的情況發生呢?例如:
- SETNX 執行成功,執行 EXPIRE 時由於網路問題,執行失敗
- SETNX 執行成功,Redis 異常當機,EXPIRE 沒有機會執行
- SETNX 執行成功,客戶端異常崩潰,EXPIRE 也沒有機會執行
總之,這兩條命令不能保證是原子操作(一起成功),就有潛在的風險導致過期時間設定失敗,依舊發生「死鎖」問題。
在 Redis 2.6.12 版本之前,我們需要想盡辦法,保證 SETNX 和 EXPIRE 原子性執行,還要考慮各種異常情況如何處理。
但在 Redis 2.6.12 之後,Redis 擴充套件了 SET 命令的引數,用這一條命令就可以了:
// 一條命令保證原子性執行 127.0.0.1:6379> SET lock 1 EX 10 NX OK
這樣就解決了死鎖問題,也比較簡單。
本文原創地址:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2842164/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 基於Redis實現一個分散式鎖Redis分散式
- 手把手教你實現一個基於Redis的分散式鎖Redis分散式
- 基於redis實現分散式鎖Redis分散式
- 分散式鎖與實現(一)基於Redis實現!分散式Redis
- 基於redis的分散式鎖實現Redis分散式
- 實現一個 Redis 分散式鎖Redis分散式
- 基於redis分散式鎖實現“秒殺”Redis分散式
- 實現一個redis的分散式鎖Redis分散式
- 基於 Redis 實現簡單的分散式鎖Redis分散式
- 分散式鎖實現(一):Redis分散式Redis
- 基於 Redis 分散式鎖Redis分散式
- java 實現開箱即用基於 redis 的分散式鎖JavaRedis分散式
- 基於redis和zookeeper的分散式鎖實現方式Redis分散式
- 基於Redis的分散式鎖的簡單實現Redis分散式
- 基於 Redis 的分散式鎖Redis分散式
- 基於redis的分散式鎖Redis分散式
- 基於redis做分散式鎖Redis分散式
- Golang 基於單節點 Redis 實現的分散式鎖GolangRedis分散式
- 基於ZK實現分散式鎖分散式
- 分散式鎖----Redis實現分散式Redis
- Redis實現分散式鎖Redis分散式
- 一種redis命令搞定基於redis的分散式鎖Redis分散式
- 【Redis】利用 Redis 實現分散式鎖Redis分散式
- 【分散式架構】(10)---基於Redis元件的特性,實現一個分散式限流分散式架構Redis元件
- 基於AOP和Redis實現的簡易版分散式鎖Redis分散式
- 基於資料庫、redis和zookeeper實現的分散式鎖資料庫Redis分散式
- 基於Redis實現分散式鎖,Redisson使用及原始碼分析Redis分散式原始碼
- 基於 Zookeeper 的分散式鎖實現分散式
- Redis之分散式鎖實現Redis分散式
- 分散式鎖之Redis實現分散式Redis
- redis分散式鎖-java實現Redis分散式Java
- Redis如何實現分散式鎖Redis分散式
- 利用Redis實現分散式鎖Redis分散式
- redis分散式鎖的實現Redis分散式
- redis分散式鎖-SETNX實現Redis分散式
- 使用 Redis 實現分散式鎖Redis分散式
- Redis面試系列:Redis實現分散式鎖Redis面試分散式
- Java程式猿筆記——基於redis分散式鎖實現“秒殺”Java筆記Redis分散式