Redis實現分布式鎖

Redis實現分布式鎖

一、Redis單節點實現

(一) 獲取鎖

使用 Redis 客戶端獲取鎖,向Redis發出下面的命令:

set key random_value NX PX 1000

上面的 SET 命令中:

  • random_value 是客戶端隨機產生的字符串,需要保證唯一性,用于防止誤刪鎖的情況

  • NX 表示 key 不存在的時候,SET 操作才成功,這樣保證了只有一個客戶端活動鎖

  • PX 1000 表示這個鎖在 1000毫秒以后過期

(二) 鎖釋放

當獲取鎖的客戶端完成了操作,需要執行下面的命令釋放鎖:

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

釋放鎖時,要先比較 key 的 value 與 客戶端的 random_value 是否相等,如果相等,就是釋放鎖,否則失敗,這里的比較操作和刪除操作需要保證原子性,所以使用 lua 腳本實現

這里記錄一下在執行 lua 腳本時遇到的坑

#錯誤
eval "if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end" 1 name heyong

#正確
eval 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end' 1 name heyong

上面兩行腳本唯一的不同點在于 ‘ 和 “, 在 redis-cli 執行腳本的時候一定要注意

將執行的 lua 腳本寫到文件中

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

執行 lua 腳本文件

#錯誤,逗號兩邊少了空格
redis-cli --eval del.lua name,heyong

#正確
redis-cli --eval del.lua name , heyong

調用lua 腳本的語法如下:

調用Lua腳本的語法:
$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] 

KEYS[1],KEYS[2] : 代表要操作的鍵
ARGV[1] ARGV[2] : 參數,在lua腳本中通過 ARGV[1] ARGV[2] 獲得

注意 : KEYS和ARGV中間的 ',' 兩邊的空格,不能省略

二、單機Redis分布式鎖相關問題

(一) 為什么需要設置過期時間

Redis實現分布式鎖必須要設置過期時間,否則某個客戶端獲取鎖以后,客戶端宕機獲取由于網絡問題無法與Redis節點通信,那么該客戶端就一直持有鎖,其他客戶端就無法獲得鎖。鎖的過期時間要根據自己的業務場景來,但是也不要設置的過長或者過短。

(二) 為什么鎖的獲取需要保證原子性

如果獲取鎖的操作是使用下面的命令會有說明問題

SETNX key random_value
EXPIRE key 10

在理想情況下,通過上面的命令也能夠獲得鎖,但是由于缺少原子操作,在執行完第一條命令以后,客戶端重啟或者崩潰,那么鎖就一直沒有辦法釋放

(三) random_value的必要性

設置 random_value 保證了一個客戶端釋放的鎖必須是自己持有的鎖,如果不能保證random_value值的唯一性,就可能出現下面的情況:

  1. 客戶端A獲得鎖
  2. 客戶端在某個操作上阻塞
  3. key 過期,鎖自動釋放
  4. 客戶端B獲得鎖
  5. 客戶端A完成業務操作,釋放掉客戶端B持有的鎖

由于客戶端B的鎖被釋放,那么就會有其他客戶端來獲取鎖,多個客戶端同時操作共享資源,導致臟數據

(四) 為什么鎖釋放需要保證原子性

在釋放鎖的時候, random_value是否等于key的value 和 key的刪除操作需要保證原子性,如果沒有保證原子性,就可能出現下面的情況:

  1. 客戶端A獲得鎖
  2. 客戶端A執行業務操作
  3. 客戶端執行GET命令獲取key的value,并與random_value相等
  4. 客戶端A發出del命令,但是由于網絡問題,導致請求時間過長
  5. key過期,鎖自動釋放
  6. 客戶端B獲得鎖
  7. 客戶端A的請求到達Redis服務器,執行del操作,客戶端B的鎖被釋放

客戶端B的鎖被釋放,就不能保護共享資源

(五) Redis主從對鎖的影響

在生產環境中,Redis部署至少會實現主從架構,并通過各種容災機制,在主節點宕機的時候將從節點升級為主節點,在這種架構下,分布式鎖也可能出現問題

  1. 客戶端A從Master節點獲取到鎖
  2. Master節點宕機,并且key沒有及時同步到slave節點
  3. Slave節點升級為主節點
  4. 客戶端B獲得鎖

這樣也出現了共享資源被多個客戶端操作的情況

三、分布式鎖Redlock

上面提出提出了單機Redis分布式存在鎖安全的問題,于是Redis的作者Antirez提出的Redlock方案。
有關Redlock可以參考:
1、https://github.com/antirez/redis-doc/blob/master/topics/distlock.md
2、http://ifeve.com/redis-lock/

四、使用單機Redis分布式鎖還是Redlock

通過上面的分析,如果Redis主節點宕機,可能會喪失鎖的安全性,但是是否項目使用單機Redis分布式鎖需要結合自己的業務場景考慮,下面舉一些場景作為參考

  1. 在網上商城中,商品的信息很少發生變化,所以會將商品數據緩存到Redis中,可能會出現并發請求一個熱點商品數據的情況,如果當前熱點商品緩存過期,那么大量的請求就會打到 DB上,為了解決這種情況通常使用分布式鎖,讓獲取到鎖的線程去數據庫獲取商品數據,在這里使用單機Redis實現分布式鎖,如果主節點宕機也不會影響數據的正確性,只是在短時間類可能出現多個請求打到DB,獲取相同的商品數據。

  2. 如果涉及到對某個共享資源的修改操作,需要保證數據的安全性,建議使用Redlock

五、擴展

(一) SET 命令參數

在redis2.6以后,提供了相關的參數來設置 SET 命令的行為

  • EX second : 設置鍵的過期時間,過期時間以秒為單位
  • PX millisecond : 設置鍵的過期時間,過期時間以毫秒為單位
  • NX : 鍵不存在時才操作成功
  • XX : 鍵存在時才操作成功

(二) 其他分布式實現方案

  1. 基于數據庫實現:在數據庫創建一張表,加鎖的機制就是在數據庫里面通過插入和刪除記錄實現,當需要加鎖的時候,創建一條數據庫記錄,釋放鎖的時候刪除記錄

  2. 基于Zookeeper實現:ZK提供了臨時節點,如果客戶端與ZK斷開連接,那么客戶端就會自動刪除改臨時節點。同時ZK提供了 watcher 機制,如果節點發生了變更,會通知監聽改節點的客戶端。

根據zookeeper的這些特性,我們來看看如何利用這些特性來實現分布式鎖:

  • 創建一個鎖目錄lock

  • 線程A獲取鎖會在lock目錄下,創建臨時順序節點

  • 獲取鎖目錄下所有的子節點,然后獲取比自己小的兄弟節點,如果不存在,則說明當前線程順序號最小,獲得鎖

  • 線程B創建臨時節點并獲取所有兄弟節點,判斷自己不是最小節點,設置監聽(watcher)比自己次小的節點(只關注比自己次小的節點是為了防止發生“羊群效應”)

  • 線程A處理完,刪除自己的節點,線程B監聽到變更事件,判斷自己是最小的節點,獲得鎖

上面的分布式鎖是基于ZK的臨時節點和watch機制實現的,該方案也存在問題,如果出現網絡抖動問題,導致client和ZK集群斷開連接,那么臨時節點就會被自動刪除,那么其他客戶端也可以獲取鎖。

可以使用 Apache 開源的curator 開實現 Zookeeper 分布式鎖。

實現方式 優點 缺點 使用場景
redis 性能高 實現復雜 安全性低 高并發的分布式鎖實現
zookeeper 有現成的框架,實現簡單, 同時ZK提供了臨時節點和watch機制,鎖的安全性相對較高 添加和刪除節點性能較低 并發量小,安全性要求較高的業務場景
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容