Redis分布式鎖安全性問題,分布式系統(tǒng)專家Martin Kleppmann和Redis的作者antirez之間就發(fā)生過一場爭論。過程:為規(guī)范各家對基于Redis的分布式鎖實現(xiàn),Redis作者提出更安全實現(xiàn)Redlock,Martin Kleppmann分析Redlock安全性問題。Redis作者立即反駁
redis鎖存在問題:
1、原子性問題(加過期時間、get? del順序)? ?2、加隨機值,保證釋放的是同一個鎖? 3、單節(jié)點
一、Redlock算法
防止了 單節(jié)點故障造成整個服務(wù)停止運行的情況;
借助Redis實現(xiàn)分布式鎖(Distributed Lock),思路相近,細節(jié)不同。1)Redlock前,基于單個Redis節(jié)點。2)Redlock基于多個Redis節(jié)點(都是Master)的一種實現(xiàn)。
1、基于單Redis節(jié)點的分布式鎖
(1)Redis客戶端獲取鎖,向Redis節(jié)點發(fā)送:SET resource_name? ?my_random_value? NX? PX? ?30000
? ??my_random_value是由客戶端生成的一個隨機字符串,它要保證在足夠長的一段時間內(nèi)在所有客戶端的所有獲取鎖的請求中都是唯一的。
? ??NX表示只有當resource_name對應(yīng)的key值不存在的時候才能SET成功。這保證了只有第一個請求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖。
????PX 30000表示這個鎖有一個30秒的自動過期時間。當然,這里30秒只是一個例子,客戶端可以選擇合適的過期時間。
(2)釋放鎖:執(zhí)行Redis Lua腳本來

執(zhí)行時候要把my_random_value為ARGV[1]、resource_name為KEYS[1]值傳進去。
問題1,鎖要設(shè)置過期時間。避免崩潰一直持有
問題2,獲取鎖實現(xiàn)成兩個Redis命令:SETNX resource_name my_random_value? ? ?EXPIRE resource_name30? ? ?不是原子。SETNX后崩潰,沒有機會EXPIRE了,一直持有。解決:Multi/Exec包起確保請求的原子性
問題3,my_random_value必要,保證釋放是持有那個鎖。如獲鎖SET不是隨機字符串,而是固定值,那么可能會發(fā)生下面的執(zhí)行序列:
1獲取鎖。阻塞。過期釋放
2獲取同鎖。1恢復(fù),釋放2持有的鎖。2沒鎖
問題4,釋放鎖三步:'GET'、判斷、'DEL',Lua腳本來保證原子性。
問題5,failover引起,基于單Redis節(jié)點的分布式鎖無法解決。催生Redlock
Redis節(jié)點宕機,所有客戶端無法獲鎖,服務(wù)不可用。掛一個Slave解決,但Redis主從復(fù)制異步,導(dǎo)致failover中喪失鎖的安全性??紤]下面的執(zhí)行序列:
????客戶端1從Master獲鎖。Master宕,存鎖的key沒同步到Slave上。
????Slave升級為Master。客戶端2從新Master獲同一資源鎖。1和2持同一鎖。安全性打破。
2、分布式鎖Redlock
基于N個完全獨立的Redis節(jié)點(通常N=5)獲取鎖操作:
1)獲取當前時間(毫秒數(shù))
2)按順序向N個Redis節(jié)點獲取鎖,超時時間遠<有效時間(幾十毫秒量級)。獲取鎖失敗(如節(jié)點不可用,或已經(jīng)被其它持有),試下個節(jié)點。
3)計算獲鎖總時長(減去第1步記錄時間)。如大多數(shù)節(jié)點(>= N/2+1)成功獲鎖,且獲鎖總時長 > 鎖有效時間,才認為最終獲鎖成功
4)如成功,重新計算鎖有效時間=最初鎖有效時間 - 第3步獲鎖消耗時間。
5)如失敗,向所有Redis節(jié)點發(fā)起釋放鎖操作(即前面介紹的Redis Lua腳本)。不管這些節(jié)點當時在獲取鎖的時候成功與否
6)大多數(shù)節(jié)點正常工作,Redlock就正常工作,如節(jié)點崩潰重啟,對鎖安全性有影響,具體影響程度跟Redis對數(shù)據(jù)的持久化程度有關(guān):
3、宕機后,同時存在多個鎖:
假設(shè)5個Redis節(jié)點:A, B, C, D, E。事件序列:
1、客戶端1成功鎖住A, B, C,獲取鎖成功(但D和E沒有鎖?。?。
2、C崩潰重啟,C的鎖沒有持久化,丟失了。
3、C重啟后,客戶端2鎖住C, D, E,獲取鎖成功。1和2同時獲鎖
默認,AOF每秒寫一次磁盤(即fsync),最壞丟1秒。設(shè)置成每次修都fsync,但仍可能丟數(shù)據(jù)(決于系統(tǒng),不是Redis的實現(xiàn))。解決辦法:延遲重啟(delayed restarts)。崩潰后,不立即重啟,等待一會時間(大于鎖的有效時間)。不對現(xiàn)有鎖造成影響
ps:為什么沒獲鎖客戶端也要釋放?如果節(jié)點收到獲取鎖請求,成功執(zhí)行SET,但返回給客戶端響應(yīng)包丟了。超時失敗,Redis會認為加鎖已經(jīng)成功了。
鏈接:https://juejin.im/post/6844903465181773831