Redis 分布式鎖沒這么簡單,網(wǎng)上大多數(shù)都有 bug
Redis 分布式鎖這個(gè)話題似乎爛大街了,不管你是面試還是工作,隨處可見,「碼哥」為啥還要寫呢?
因?yàn)榫W(wǎng)上 99.99% 的文章都沒有真正的把分布式鎖說清楚,存在很多 bug。
今日,「碼哥」就跟大家深入分布式鎖的 G 點(diǎn),系統(tǒng)的做一個(gè)寫好代碼掌握技巧的真男人。
在進(jìn)入「高潮」之前,以下問題就當(dāng)做「前戲」去思考,你能回答多少?
- 什么時(shí)候需要分布式鎖?
- 加、解鎖的代碼位置有講究么?
- 如何避免出現(xiàn)死鎖
- 超時(shí)時(shí)間設(shè)置多少合適呢?
- 如何避免鎖被其他線程釋放
- 如何實(shí)現(xiàn)重入鎖?
- 主從架構(gòu)會帶來什么安全問題?
- 什么是 Redlock
- ……
為何需要分布式鎖?
碼哥,說個(gè)通俗的例子講解下什么時(shí)候需要分布式鎖呢?
精子噴射那一刻,億級流量沖向卵子,只有一個(gè)精子能獲得與卵子結(jié)合的幸運(yùn)。
造物主為了保證只有一個(gè)「精子」能獲得「卵子」的寵幸,當(dāng)有一個(gè)精子進(jìn)入后,卵子的外殼就會發(fā)生變化,將通道關(guān)閉把其余的精子阻擋在外。
億級別的精子就好比「并發(fā)」流量;
卵子就好比是共享資源;
卵子外殼只允許一個(gè)精子進(jìn)入的特殊蛋白就是一把鎖。
而多節(jié)點(diǎn)構(gòu)成的集群,就會有多個(gè) JVM 進(jìn)程,我們獲得同樣的效果就需要有一個(gè)中間人協(xié)調(diào),只允許一個(gè) JVM 中的一個(gè)線程獲得操作共享資源的資格。
分布式鎖就是用來控制同一時(shí)刻,只有一個(gè) JVM 進(jìn)程中的一個(gè)線程「精子」可以訪問被保護(hù)的資源「卵子」。
「每一個(gè)生命,都是億級選手中的佼佼者」,加油。
分布式鎖入門
分布式鎖應(yīng)該滿足哪些特性?
- 互斥:在任何給定時(shí)刻,只有一個(gè)客戶端可以持有鎖;
- 無死鎖:任何時(shí)刻都有可能獲得鎖,即使獲取鎖的客戶端崩潰;
- 容錯:只要大多數(shù) Redis的節(jié)點(diǎn)都已經(jīng)啟動,客戶端就可以獲取和釋放鎖。
碼哥,我可以使用 SETNX key value 命令是實(shí)現(xiàn)「互斥」特性。
這個(gè)命令來自于SET if Not eXists的縮寫,意思是:如果 key 不存在,則設(shè)置 value 給這個(gè)key,否則啥都不做。
命令的返回值:
- 1:設(shè)置成功;
- 0:key 沒有設(shè)置成功。
如下場景:
敲代碼一天累了,想去放松按摩下肩頸。
168 號技師最搶手,大家喜歡點(diǎn),所以并發(fā)量大,需要分布式鎖控制。
同一時(shí)刻只允許一個(gè)「客戶」預(yù)約 168 技師。
肖彩機(jī)申請 168 技師成功:
- > SETNX lock:168 1
- (integer) 1 # 獲取 168 技師成功
謝霸哥后面到,申請失?。?/p>
- > SETNX lock 2
- (integer) 0 # 客戶謝霸哥 2 獲取失敗
此刻,申請成功的客戶就可以享受 168 技師的肩頸放松服務(wù)「共享資源」。
享受結(jié)束后,要及時(shí)釋放鎖,給后來者享受 168 技師的服務(wù)機(jī)會。
肖彩機(jī),碼哥考考你如何釋放鎖呢?
很簡單,使用 DEL 刪除這個(gè) key 就行。
- > DEL lock:168
- (integer) 1
碼哥,你見過「龍」么?我見過,因?yàn)槲冶灰粭l龍服務(wù)過。
肖彩機(jī),事情可沒這么簡單。
這個(gè)方案存在一個(gè)存在造成「死鎖」的問題,造成該問題的場景如下:
在按摩過程中突然收到線上報(bào)警,提起褲子就跑去公司了,沒及時(shí)執(zhí)行 DEL 釋放鎖(客戶端處理業(yè)務(wù)異常,無法正確釋放鎖);
按摩過程中心肌梗塞嗝屁了,無法執(zhí)行 DEL指令。
這樣,這個(gè)鎖就會一直占用,其他客戶就「再也沒有」機(jī)會獲取 168 技師服務(wù)了。
如何避免死鎖
碼哥,我可以在獲取鎖成功的時(shí)候設(shè)置一個(gè)「超時(shí)時(shí)間」
比如設(shè)定按摩服務(wù)一次 60 分鐘,那么在給這個(gè) key 加鎖的時(shí)候設(shè)置 60 分鐘過期即可:
- > SETNX lock:168 1 // 獲取鎖
- (integer) 1
- > EXPIRE lock:168 60 // 60s 自動刪除
- (integer) 1
這樣,到點(diǎn)后鎖自動釋放,其他客戶就可以繼續(xù)享受 168 技師按摩服務(wù)了。
誰要這么寫,就糟透了。
「加鎖」、「設(shè)置超時(shí)」是兩個(gè)命令,他們不是原子操作。
如果出現(xiàn)只執(zhí)行了第一條,第二條沒機(jī)會執(zhí)行就會出現(xiàn)「超時(shí)時(shí)間」設(shè)置失敗,依然出現(xiàn)死鎖。
比如以下場景導(dǎo)致無法執(zhí)行第二條指令:
Redis 異常宕機(jī);
客戶端異常崩潰;
碼哥,那咋辦,我想被一條龍服務(wù),不能出現(xiàn)死鎖啊。
Redis 2.6.12 之后,拓展了 SET 命令的參數(shù),滿足了當(dāng) key 不存在則設(shè)置 value,同時(shí)設(shè)置超時(shí)時(shí)間的語義,并且滿足原子性。
- SET resource_name random_value NX PX 30000
NX:表示只有 resource_name 不存在的時(shí)候才能 SET 成功,從而保證只有一個(gè)客戶端可以獲得鎖;
PX 30000:表示這個(gè)鎖有一個(gè) 30 秒自動過期時(shí)間。
執(zhí)行時(shí)間超過鎖的過期時(shí)間
這樣我能穩(wěn)妥的享受一條龍服務(wù)了么?
No,還有一種場景會導(dǎo)致釋放別人的鎖:
- 客戶 1 獲取鎖成功并設(shè)置 30 秒超時(shí);
- 客戶 1 因?yàn)橐恍┰驅(qū)е聢?zhí)行很慢(網(wǎng)絡(luò)問題、發(fā)生 FullGC……),過了 30 秒依然沒執(zhí)行完,但是鎖過期「自動釋放了」;
- 客戶 2 申請加鎖成功;
- 客戶 1 執(zhí)行完成,執(zhí)行 DEL 釋放鎖指令,這個(gè)時(shí)候就把 客戶 2 的鎖給釋放了。
有兩個(gè)關(guān)鍵問題需要解決:
- 如何合理設(shè)置過期時(shí)間?
- 如何避免刪除別人持有的鎖。
正確設(shè)置鎖超時(shí)
鎖的超時(shí)時(shí)間怎么計(jì)算合適呢?
這個(gè)時(shí)間不能瞎寫,一般要根據(jù)在測試環(huán)境多次測試,然后壓測多輪之后,比如計(jì)算出平均執(zhí)行時(shí)間 200 ms。
那么鎖的超時(shí)時(shí)間就放大為平均執(zhí)行時(shí)間的 3~5 倍。
為啥要放大呢?
因?yàn)槿绻i的操作邏輯中有網(wǎng)絡(luò) IO 操作、JVM FullGC 等,線上的網(wǎng)絡(luò)不會總一帆風(fēng)順,我們要給網(wǎng)絡(luò)抖動留有緩沖時(shí)間。
那我設(shè)置更大一點(diǎn),比如設(shè)置 1 小時(shí)不是更安全?
不要鉆牛角,多大算大?
設(shè)置時(shí)間過長,一旦發(fā)生宕機(jī)重啟,就意味著 1 小時(shí)內(nèi),分布式鎖的服務(wù)全部節(jié)點(diǎn)不可用。
你要讓運(yùn)維手動刪除這個(gè)鎖么?
只要運(yùn)維真的不會打你。
有沒有完美的方案呢?不管時(shí)間怎么設(shè)置都不大合適。
我們可以讓獲得鎖的線程開啟一個(gè)守護(hù)線程,用來給快要過期的鎖「續(xù)航」。
加鎖的時(shí)候設(shè)置一個(gè)過期時(shí)間,同時(shí)客戶端開啟一個(gè)「守護(hù)線程」,定時(shí)去檢測這個(gè)鎖的失效時(shí)間。
如果快要過期,但是業(yè)務(wù)邏輯還沒執(zhí)行完成,自動對這個(gè)鎖進(jìn)行續(xù)期,重新設(shè)置過期時(shí)間。
這個(gè)道理行得通,可我寫不出。
別慌,已經(jīng)有一個(gè)庫把這些工作都封裝好了他叫Redisson。
Redisson 是一個(gè) Java 語言實(shí)現(xiàn)的 Redis SDK 客戶端,在使用分布式鎖時(shí),它就采用了「自動續(xù)期」的方案來避免鎖過期,這個(gè)守護(hù)線程我們一般也把它叫做「看門狗」線程。
關(guān)于 Redisson 的使用與原理分析由于篇幅有限,大家可關(guān)注「碼哥字節(jié)」且聽下回分解。
避免釋放別人的鎖
出現(xiàn)釋放別人鎖的關(guān)鍵在于「無腦執(zhí)行」DEL指令,所以我們要想辦法檢查下這個(gè)鎖是不是自己加的。
解鈴還須系鈴人
碼哥,我在加鎖的時(shí)候設(shè)置一個(gè)「唯一標(biāo)識」作為 value 代表加鎖的客戶端。
在釋放鎖的時(shí)候,客戶端將自己的「唯一標(biāo)識」與鎖上的「標(biāo)識」比較是否相等,匹配上則刪除,否則沒有權(quán)利釋放鎖。
偽代碼如下:
- // 比對 value 與 唯一標(biāo)識
- if (redis.get("lock:168").equals(uuid)){
- redis.del("lock:168"); //比對成功則刪除
- }
有沒有想過,這是 GET + DEL 指令組合而成的,這里又會涉及到原子性問題。
復(fù)現(xiàn)下情況:
- 客戶端 1 第一步對比成功后,第二步還沒來得及執(zhí)行,這時(shí)候鎖到期了。
- 客戶端 2 獲取鎖成功,將自己的 「uuid」設(shè)置進(jìn)去。
- 這時(shí)候客戶端 1 執(zhí)行第二步進(jìn)行釋放鎖,這肯定是錯誤的。
我們是追求極致的男人,所以這里通過 Lua 腳本來實(shí)現(xiàn),這樣判斷和刪除的過程就是原子操作了。
- if redis.call("get",KEYS[1]) == ARGV[1] then
- return redis.call("del",KEYS[1])
- else
- return 0
- end
一路優(yōu)化下來,方案似乎比較「嚴(yán)謹(jǐn)」了,抽象出對應(yīng)的模型如下。
通過 SET lock_resource_name $unique_id NX PX $expire_time,同時(shí)啟動守護(hù)線程為快要過期單還沒執(zhí)行完畢的客戶端的鎖續(xù)命;
客戶端執(zhí)行業(yè)務(wù)邏輯操作共享資源;
通過 Lua 腳本釋放鎖,先 get 判斷鎖是否是自己加的,再執(zhí)行 DEL。
加解鎖代碼位置有講究
根據(jù)前面的分析,我們已經(jīng)有了一個(gè)「相對嚴(yán)謹(jǐn)」的分布式鎖了。
于是「謝霸哥」就寫了如下代碼將分布式鎖運(yùn)用到項(xiàng)目中,以下是偽代碼邏輯:
- public void doSomething() {
- try {
- redisLock.lock(); // 上鎖
- // 處理業(yè)務(wù)
- redisLock.unlock(); // 釋放鎖
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
一旦執(zhí)行業(yè)務(wù)邏輯過程中拋出異常,程序就無法走下一步釋放鎖的流程。
所以釋放鎖的代碼一定要放在 finally{} 塊中。
加鎖的位置也有問題,如果執(zhí)行 redisLock.lock() 加鎖異常,那么就會執(zhí)行 finally{} 代碼塊指令執(zhí)行解鎖,這個(gè)時(shí)候鎖并沒有申請成功。
所以 redisLock.lock();應(yīng)該放在 try 外面。
綜上所述,正確代碼位置如下 :
- public void doSomething() {
- // 上鎖
- redisLock.lock();
- try {
- // 處理業(yè)務(wù)
- ...
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // 釋放鎖
- redisLock.unlock();
- }
- }
實(shí)現(xiàn)可重入鎖
可重入鎖要如何實(shí)現(xiàn)呢?重入之后,超時(shí)時(shí)間如何設(shè)置呢?
當(dāng)一個(gè)線程執(zhí)行一段代碼成功獲取鎖之后,繼續(xù)執(zhí)行時(shí),又遇到加鎖的代碼,可重入性就就保證線程能繼續(xù)執(zhí)行,而不可重入就是需要等待鎖釋放之后,再次獲取鎖成功,才能繼續(xù)往下執(zhí)行。
用一段代碼解釋可重入:
- public synchronized void a() {
- b();
- }
- public synchronized void b() {
- // pass
- }
假設(shè) X 線程在 a 方法獲取鎖之后,繼續(xù)執(zhí)行 b 方法,如果此時(shí)不可重入,線程就必須等待鎖釋放,再次爭搶鎖。
鎖明明是被 X 線程擁有,卻還需要等待自己釋放鎖,然后再去搶鎖,這看起來就很奇怪,我釋放我自己~
Redis Hash 可重入鎖
Redisson 類庫就是通過 Redis Hash 來實(shí)現(xiàn)可重入鎖,未來碼哥會專門寫一篇關(guān)于 Redisson 的使用與原理的文章……
當(dāng)線程擁有鎖之后,往后再遇到加鎖方法,直接將加鎖次數(shù)加 1,然后再執(zhí)行方法邏輯。
退出加鎖方法之后,加鎖次數(shù)再減 1,當(dāng)加鎖次數(shù)為 0 時(shí),鎖才被真正的釋放。
可以看到可重入鎖最大特性就是計(jì)數(shù),計(jì)算加鎖的次數(shù)。
所以當(dāng)可重入鎖需要在分布式環(huán)境實(shí)現(xiàn)時(shí),我們也就需要統(tǒng)計(jì)加鎖次數(shù)。
加鎖邏輯
我們可以使用 Redis hash 結(jié)構(gòu)實(shí)現(xiàn),key 表示被鎖的共享資源, hash 結(jié)構(gòu)的 fieldKey 的 value 則保存加鎖的次數(shù)。
通過 Lua 腳本實(shí)現(xiàn)原子性,假設(shè) KEYS1 = 「lock」, ARGV「1000,uuid」:
- ---- 1 代表 true
- ---- 0 代表 false
- if (redis.call('exists', KEYS[1]) == 0) then
- redis.call('hincrby', KEYS[1], ARGV[2], 1);
- redis.call('pexpire', KEYS[1], ARGV[1]);
- return 1;
- end ;
- if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
- redis.call('hincrby', KEYS[1], ARGV[2], 1);
- redis.call('pexpire', KEYS[1], ARGV[1]);
- return 1;
- end ;
- return 0;
加鎖代碼首先使用 Redis exists 命令判斷當(dāng)前 lock 這個(gè)鎖是否存在。
如果鎖不存在的話,直接使用 hincrby創(chuàng)建一個(gè)鍵為 lock hash 表,并且為 Hash 表中鍵為 uuid 初始化為 0,然后再次加 1,最后再設(shè)置過期時(shí)間。
如果當(dāng)前鎖存在,則使用 hexists判斷當(dāng)前 lock 對應(yīng)的 hash 表中是否存在 uuid 這個(gè)鍵,如果存在,再次使用 hincrby 加 1,最后再次設(shè)置過期時(shí)間。
最后如果上述兩個(gè)邏輯都不符合,直接返回。
解鎖邏輯
- -- 判斷 hash set 可重入 key 的值是否等于 0
- -- 如果為 0 代表 該可重入 key 不存在
- if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
- return nil;
- end ;
- -- 計(jì)算當(dāng)前可重入次數(shù)
- local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);
- -- 小于等于 0 代表可以解鎖
- if (counter > 0) then
- return 0;
- else
- redis.call('del', KEYS[1]);
- return 1;
- end ;
- return nil;
首先使用 hexists 判斷 Redis Hash 表是否存給定的域。
如果 lock 對應(yīng) Hash 表不存在,或者 Hash 表不存在 uuid 這個(gè) key,直接返回 nil。
若存在的情況下,代表當(dāng)前鎖被其持有,首先使用 hincrby使可重入次數(shù)減 1 ,然后判斷計(jì)算之后可重入次數(shù),若小于等于 0,則使用 del 刪除這把鎖。
解鎖代碼執(zhí)行方式與加鎖類似,只不過解鎖的執(zhí)行結(jié)果返回類型使用 Long。這里之所以沒有跟加鎖一樣使用 Boolean ,這是因?yàn)榻怄i lua 腳本中,三個(gè)返回值含義如下:
- 1 代表解鎖成功,鎖被釋放
- 0 代表可重入次數(shù)被減 1
- null 代表其他線程嘗試解鎖,解鎖失敗
主從架構(gòu)帶來的問題
碼哥,到這里分布式鎖「很完美了」吧,沒想到分布式鎖這么多門道。
路還很遠(yuǎn),之前分析的場景都是,鎖在「單個(gè)」Redis 實(shí)例中可能產(chǎn)生的問題,并沒有涉及到 Redis 的部署架構(gòu)細(xì)節(jié)。
我們通常使用「Cluster 集群」或者「哨兵集群」的模式部署保證高可用。
這兩個(gè)模式都是基于「主從架構(gòu)數(shù)據(jù)同步復(fù)制」實(shí)現(xiàn)的數(shù)據(jù)同步,而 Redis 的主從復(fù)制默認(rèn)是異步的。
我們試想下如下場景會發(fā)生什么問題:
如果客戶端 1 剛往 master 節(jié)點(diǎn)寫入一個(gè)分布式鎖,此時(shí)這個(gè)指令還沒來得及同步到 slave 節(jié)點(diǎn)。
此時(shí),master 節(jié)點(diǎn)宕機(jī),其中一個(gè) slave 被選舉為新 master,這時(shí)候新 master 是沒有客戶端 1 寫入的鎖,鎖丟失了。
此刻,客戶端 2 線程來獲取鎖,就成功了。
雖然這個(gè)概率極低,但是我們必須得承認(rèn)這個(gè)風(fēng)險(xiǎn)的存在。
Redis 的作者提出了一種解決方案,叫 Redlock(紅鎖)
Redis 的作者為了統(tǒng)一分布式鎖的標(biāo)準(zhǔn),搞了一個(gè) Redlock,算是 Redis 官方對于實(shí)現(xiàn)分布式鎖的指導(dǎo)規(guī)范,https://redis.io/topics/distlock,但是這個(gè) Redlock 也被國外的一些分布式專家給噴了。
因?yàn)樗膊煌昝?,?ldquo;漏洞”。
什么是 Redlock
紅鎖是不是這個(gè)?
泡面吃多了你,Redlock 紅鎖是為了解決主從架構(gòu)中鎖丟失而提出的一種算法。
Redlock 的方案基于 2 個(gè)前提:
- 不需要部署從庫和哨兵實(shí)例,只部署主庫
- 但主庫要部署多個(gè),官方推薦至少 5 個(gè)實(shí)例,這樣可以保證他們不會同時(shí)宕機(jī)。
也就是說,想用使用 Redlock,你至少要部署 5 個(gè) Redis 實(shí)例,而且都是主庫,它們之間沒有任何關(guān)系,都是一個(gè)個(gè)孤立的實(shí)例。
一個(gè)客戶端要獲取鎖有 5 個(gè)步驟:
- 客戶端獲取當(dāng)前時(shí)間 T1(毫秒級別);
- 使用相同的 key和 value順序嘗試從 N個(gè) Redis實(shí)例上獲取鎖。
- 每個(gè)請求都設(shè)置一個(gè)超時(shí)時(shí)間(毫秒級別),該超時(shí)時(shí)間要遠(yuǎn)小于鎖的有效時(shí)間,這樣便于快速嘗試與下一個(gè)實(shí)例發(fā)送請求。
- 比如鎖的自動釋放時(shí)間 10s,則請求的超時(shí)時(shí)間可以設(shè)置 5~50 毫秒內(nèi),這樣可以防止客戶端長時(shí)間阻塞。
- 客戶端獲取當(dāng)前時(shí)間 T2 并減去步驟 1 的 T1 來計(jì)算出獲取鎖所用的時(shí)間(T3 = T2 -T1)。當(dāng)且僅當(dāng)客戶端在大多數(shù)實(shí)例(N/2 + 1)獲取成功,且獲取鎖所用的總時(shí)間 T3 小于鎖的有效時(shí)間,才認(rèn)為加鎖成功,否則加鎖失敗。
- 如果第 3 步加鎖成功,則執(zhí)行業(yè)務(wù)邏輯操作共享資源,key 的真正有效時(shí)間等于有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟 3 計(jì)算的結(jié)果)。
- 如果因?yàn)槟承┰?,獲取鎖失敗(沒有在至少 N/2+1 個(gè) Redis 實(shí)例取到鎖或者取鎖時(shí)間已經(jīng)超過了有效時(shí)間),客戶端應(yīng)該在所有的 Redis 實(shí)例上進(jìn)行解鎖(即便某些 Redis 實(shí)例根本就沒有加鎖成功)。
為什么要部署多個(gè)實(shí)例并加鎖呢?
本質(zhì)是為了高可用和容錯,即使部分實(shí)例宕機(jī),大多數(shù)實(shí)例加鎖成功,整個(gè)分布式鎖服務(wù)依然可用。
為啥在第三步要計(jì)算加鎖的累計(jì)時(shí)間?
因?yàn)槎鄠€(gè)節(jié)點(diǎn)加鎖,耗時(shí)可能會比較長,網(wǎng)絡(luò)中可能存在丟包、超時(shí)等現(xiàn)象。
即使大多數(shù)節(jié)點(diǎn)獲取鎖成功,假如獲取鎖的總時(shí)間已經(jīng)超過鎖的有效時(shí)間,這個(gè)鎖已經(jīng)沒有意義了。
為什么釋放鎖要操作所有節(jié)點(diǎn),即使有的節(jié)點(diǎn)加鎖未成功?
因?yàn)橛锌赡芸蛻舳嗽?Redis 實(shí)例上加鎖成功,只是客戶端讀取響應(yīng)的時(shí)候失敗導(dǎo)致客戶端以為加鎖失敗。
為了安全的清理鎖,就需要向每個(gè)節(jié)點(diǎn)發(fā)送釋放鎖的請求。
Redlock 這么完美?那他解決了 Redis 主從架構(gòu)節(jié)點(diǎn)異常宕機(jī)導(dǎo)致鎖丟失的問題了么?
事情可沒這么簡單,Redis 作者把這個(gè)方案提出后,受到了業(yè)界著名的分布式系統(tǒng)專家的質(zhì)疑。
兩人好比神仙打架,兩人一來一回論據(jù)充足的對一個(gè)問題提出很多論斷……
由于篇幅原因,關(guān)于 兩人的爭論分析以及 Redssion 對分布式鎖的封裝以及 Redlock 的實(shí)現(xiàn)我們下期再見。
預(yù)知后事如何,且聽下回分解…
總結(jié)
完工,我建議你合上屏幕,自己在腦子里重新過一遍,每一步都在做什么,為什么要做,解決什么問題。
我們一起從頭到尾梳理了一遍 Redis 分布式鎖中的各種門道,其實(shí)很多點(diǎn)是不管用什么做分布式鎖都會存在的問題,重要的是思考的過程。
對于系統(tǒng)的設(shè)計(jì),每個(gè)人的出發(fā)點(diǎn)都不一樣,沒有完美的架構(gòu),沒有普適的架構(gòu),但是在完美和普適能平衡的很好的架構(gòu),就是好的架構(gòu)。
關(guān)于 Redlock 的爭論主要集中在如下幾點(diǎn):
- Redlock 效率太差、太重,對于提升效率的場景下,使用分布式鎖,允許鎖的偶爾失效,那么使用單 Redis 節(jié)點(diǎn)的鎖方案就足夠了,簡單而且效率高。
- 對于正確性要求高的場景下,它是依賴于時(shí)間的,不是一個(gè)足夠強(qiáng)的算法。Redlock 并沒有保住正確性。