在很多場(chǎng)景中铅碍,不同的進(jìn)程必須以排他的方式操作一些共享資源轴咱,這時(shí)分布式鎖就是一個(gè)非常有用的原語。
有很多庫和博客都描述了如何使用Redis實(shí)現(xiàn)分布式鎖管理器(Distributed Lock Manager, DLM),但是每個(gè)庫的實(shí)現(xiàn)方式都不太一樣份殿,并且其中很多使用的都是一種簡(jiǎn)單的方式黎炉,但是這降低了可靠性保障枝秤,而有的也使用了稍微復(fù)雜的設(shè)計(jì)。
本文嘗試提供一種更加典型的算法來實(shí)現(xiàn)Redis分布式鎖慷嗜。我們提出了一種被稱為Redlock的算法來實(shí)現(xiàn)DLM淀弹,我們相信它比普通的單實(shí)例方式更加安全。我們希望Redis社區(qū)可以對(duì)它進(jìn)行分析庆械、提供反饋薇溃,并使用它作為更復(fù)雜的設(shè)計(jì)或替代方案的起點(diǎn)。
實(shí)現(xiàn)細(xì)節(jié)
在開始描述該算法之前干奢,這里有一些已經(jīng)可用的實(shí)現(xiàn)痊焊,以供參考。
Redlock-rb (Ruby 實(shí)現(xiàn)). There is also a fork of Redlock-rb that adds a gem for easy distribution and perhaps more.
Redlock-py (Python 實(shí)現(xiàn)).
Aioredlock (Asyncio Python 實(shí)現(xiàn)).
Redlock-php (PHP 實(shí)現(xiàn)).
PHPRedisMutex (further PHP 實(shí)現(xiàn))
cheprasov/php-redis-lock (PHP library for locks)
Redsync.go (Go 實(shí)現(xiàn)).
Redisson (Java 實(shí)現(xiàn)).
Redis::DistLock (Perl 實(shí)現(xiàn)).
Redlock-cpp (C++ 實(shí)現(xiàn)).
Redlock-cs (C#/.NET 實(shí)現(xiàn)).
RedLock.net (C#/.NET 實(shí)現(xiàn)). 包含了對(duì)異步和擴(kuò)展鎖的支持忿峻。
ScarletLock (C# .NET 實(shí)現(xiàn)薄啥,使用了可配置存儲(chǔ) )
node-redlock (NodeJS 實(shí)現(xiàn)). 包含了對(duì)擴(kuò)展鎖的支持。
安全性和活躍性保證
我們將使用3個(gè)特性來開始我們的設(shè)計(jì)逛尚,從我們的觀點(diǎn)來看垄惧,這3個(gè)特性是以一種有效方式實(shí)現(xiàn)分布式鎖的最低保障。
安全性:互斥性绰寞。在任意時(shí)刻到逊,只有一個(gè)客戶端可以持有鎖铣口。
活躍性A:無死鎖。即使持有鎖的客戶端崩潰了或是
被分割觉壶,其他客戶端最終依然可以獲取鎖脑题。活躍性B:容錯(cuò)性。只要大部分Redis節(jié)點(diǎn)是可用的铜靶,客戶端就可以獲取鎖和釋放鎖叔遂。
為什么基于故障恢復(fù)的實(shí)現(xiàn)還不夠
為了理解我們想要改進(jìn)什么,讓我們先來分析一下目前大多數(shù)基于Redis的分布式鎖的狀態(tài)争剿。
最簡(jiǎn)單的使用Redis來鎖住資源的方式是在客戶端實(shí)例里創(chuàng)建一個(gè)鍵已艰。通常創(chuàng)建這個(gè)鍵時(shí)會(huì)利用Redis的過期特性使之帶上存活時(shí)間,所以該鎖最終一定會(huì)被釋放蚕苇。當(dāng)客戶端需要釋放鎖時(shí)哩掺,它會(huì)刪除這個(gè)鍵。
從表面上看上述鎖可以工作得很好涩笤,但是還是有一個(gè)問題:在我們的架構(gòu)中有單點(diǎn)故障的隱患嚼吞。如果Redis主機(jī)宕機(jī)了怎么辦?你可能會(huì)說蹬碧,我們可以加上一個(gè)從機(jī)誊薄!當(dāng)主機(jī)宕機(jī)了我們就可以使用從機(jī)。然而不幸的是這種方式并不可行锰茉。因?yàn)槭褂眠@種方式我們不能保證安全性中的互斥性呢蔫,因?yàn)镽edis的復(fù)制過程是異步的。
很顯然在這個(gè)模型中有一個(gè)明顯的競(jìng)爭(zhēng)條件:
客戶端A在主機(jī)中獲取了鎖飒筑。
在把鍵值寫入從機(jī)之前片吊,主機(jī)掛掉了。
從機(jī)變?yōu)橹鳈C(jī)协屡。
客戶端B獲取了客戶端A對(duì)同一資源已經(jīng)獲得的鎖俏脊。
安全性被違反了!
在特定場(chǎng)景下肤晓,上述模型可以運(yùn)行得很好爷贫,例如當(dāng)主機(jī)宕機(jī)時(shí),多個(gè)客戶端同時(shí)持有了鎖补憾,而你又可以接受這種情況漫萄。此時(shí)你就可以使用基于復(fù)制的解決方案。否則我們建議你使用本文將要介紹的實(shí)現(xiàn)方案盈匾。
在單實(shí)例場(chǎng)景中的正確實(shí)現(xiàn)
在試圖克服前文描述的單實(shí)例限制之前腾务,我們先來看看在這種簡(jiǎn)單場(chǎng)景中如何正確地實(shí)現(xiàn)分布式鎖。實(shí)際上偶爾發(fā)生競(jìng)爭(zhēng)條件是可行的解決方案削饵,并且單實(shí)例加鎖方法也是我們?cè)谶@里描述的分布式算法的基礎(chǔ)岩瘦。
為了獲取鎖未巫,我們的實(shí)現(xiàn)方式是這樣的:
SET resource_name my_random_value NX PX 30000
這個(gè)命令只有在該鍵不存在的時(shí)候才會(huì)為之賦值,并且?guī)в幸粋€(gè)30秒的過期時(shí)間启昧。這個(gè)鍵的值將會(huì)被設(shè)置為"my_random_value"叙凡,這個(gè)值必須在所有的客戶端和所有的鎖請(qǐng)求中都是唯一的。
基本上這個(gè)隨機(jī)值被用來以安全的方式釋放鎖密末,它使用一個(gè)腳本來告訴Redis:只有這個(gè)鍵存在且該鍵上存儲(chǔ)的值就是我鎖期望的那一個(gè)是才可以刪除該鍵狭姨。這是通過以下Lua腳本來實(shí)現(xiàn)的。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
為了避免刪除一個(gè)被其他客戶端創(chuàng)建的鎖苏遥,這一點(diǎn)是非常重要的。舉個(gè)栗子:一個(gè)客戶端獲取了鎖赡模,但是由于某些原因被阻塞的時(shí)間大于鎖的可用時(shí)間(即鎖的過期時(shí)間)田炭,隨后刪除了該鎖,而這個(gè)所此時(shí)已經(jīng)被其他客戶端所持有漓柑。僅僅使用DEL命令是不安全的教硫,因?yàn)檫@可能會(huì)刪除其他客戶端的鎖。而使用上面的腳本辆布,每個(gè)鎖都有一個(gè)隨機(jī)的簽名瞬矩,因此當(dāng)客戶端試圖刪除它時(shí),只有這把鎖仍然是該客戶端所持有的才可以刪除锋玲。
那么這個(gè)隨機(jī)的字符串應(yīng)該是什么樣的呢景用?我設(shè)想它是從/dev/urandom產(chǎn)生的一個(gè)20字節(jié)的字符串,但是你可以找到一種更廉價(jià)的方式來確保它的唯一性惭蹂。舉個(gè)栗子伞插,一種安全的方式是使用/dev/urandom產(chǎn)生的隨機(jī)數(shù)作為RC4的種子,然后產(chǎn)生一個(gè)偽隨機(jī)流盾碗。一種更加簡(jiǎn)單的解決方案是使用客戶端id與unix的毫秒時(shí)間相結(jié)合媚污,它并不足夠安全,但是可以滿足大多數(shù)場(chǎng)景的要求廷雅。
我們使用的鍵的存活時(shí)間耗美,被稱為"鎖有效時(shí)間"。它既是自動(dòng)釋放鎖的時(shí)間航缀,也是一個(gè)客戶端在其它客戶端可能重新獲取這把鎖之前必須完成所要求的操作的截止時(shí)間商架,而在技術(shù)上又不違反互斥性,也就是說客戶端持有鎖的時(shí)間被限制在從獲取鎖那一刻開始的一個(gè)指定的時(shí)間窗芥玉。
現(xiàn)在我們有了一種很好的方式來獲取鎖和釋放鎖甸私。這個(gè)系統(tǒng)是由一個(gè)單一的、一直可用的實(shí)例構(gòu)成的非分布式系統(tǒng)飞傀,理論上來說皇型,該系統(tǒng)是安全的∥芘耄現(xiàn)在,讓我們將這個(gè)觀念擴(kuò)展至分布式系統(tǒng)弃鸦,并且這個(gè)分布式系統(tǒng)并不能保證是一直可用的绞吁。
Redlock算法
在分布式版本的算法中,我們假設(shè)有N臺(tái)Redis主機(jī)唬格。這些節(jié)點(diǎn)是完全獨(dú)立的家破,所以我們并不需要使用主從復(fù)制或者其他隱式的一致性機(jī)制。我們已經(jīng)描述過在單實(shí)例系統(tǒng)中如何安全地獲取鎖和釋放鎖购岗。我們想當(dāng)然地認(rèn)為該算法將采用這種方式在單個(gè)實(shí)例中獲取鎖和釋放鎖汰聋。在我們的例子中,我們假設(shè)N=5喊积,這是一個(gè)合理的假設(shè)烹困,所以我們需要5個(gè)Redis實(shí)例運(yùn)行在不同的電腦或者虛擬機(jī)中,并且確保他們?cè)诖蠖鄶?shù)時(shí)候的宕機(jī)都是獨(dú)立的乾吻。
為了獲取鎖髓梅,客戶端會(huì)執(zhí)行以下操作:
獲取當(dāng)前時(shí)間的毫秒數(shù)
嘗試使用相同的鍵和隨機(jī)的值依次從所有實(shí)例中獲取鎖。在步驟2中绎签,在每個(gè)實(shí)例中設(shè)置鎖時(shí)枯饿,為了獲取鎖,客戶端會(huì)設(shè)置一個(gè)比鎖的自動(dòng)釋放時(shí)間要小的超時(shí)時(shí)間诡必。舉個(gè)栗子奢方,如果鎖的自動(dòng)釋放時(shí)間是10秒,那么這個(gè)超時(shí)時(shí)間可以在5~50毫秒之間爸舒。這樣可以防止在Redis節(jié)點(diǎn)宕機(jī)的情況下袱巨,客戶端仍然長時(shí)間地持有鎖來等待響應(yīng)。如果一個(gè)實(shí)例不可用碳抄,我們就應(yīng)該盡快嘗試與另外一個(gè)實(shí)例建立連接愉老。
客戶端會(huì)用當(dāng)前時(shí)間減去在步驟1中獲取的時(shí)間來計(jì)算獲取鎖所消耗的時(shí)間。當(dāng)且僅當(dāng)客戶端在在大多數(shù)(至少3個(gè))實(shí)例中獲取了鎖剖效,并且獲取鎖所消耗的時(shí)間小于鎖鎖有效時(shí)間嫉入,我們才認(rèn)為該客戶端成功獲取了鎖。
如果客戶端成功獲取了鎖璧尸,它的真正有效時(shí)間就是最開始的有效時(shí)間減去在步驟3中計(jì)算得到的消耗時(shí)間咒林。
如果由于某些原因,客戶端獲取鎖失敗了(無論是因?yàn)闊o法至少在N/2+1個(gè)實(shí)例中獲取鎖爷光,還是因?yàn)楂@取鎖時(shí)間超過了鎖有效時(shí)間)垫竞,它將會(huì)嘗試在所有實(shí)例中釋放鎖(即使它在某個(gè)實(shí)例中并沒有獲取鎖)。
這個(gè)算法是異步的嗎?
這個(gè)算法依賴于這樣一個(gè)假設(shè):即當(dāng)進(jìn)程之間沒有時(shí)鐘同步時(shí)欢瞪,每個(gè)進(jìn)程的本地時(shí)間都近似以相同的速度流逝活烙,其誤差小于鎖的自動(dòng)釋放時(shí)間。這個(gè)假設(shè)與現(xiàn)實(shí)世界的計(jì)算機(jī)非常相似:每個(gè)電腦都有一個(gè)本地時(shí)鐘遣鼓,我們可以容忍不同電腦之間有非常微小的時(shí)間偏移啸盏。
在這一點(diǎn)上,我們需要再次強(qiáng)調(diào)我們的互斥規(guī)則:只有在客戶端完成任務(wù)的時(shí)間小于鎖有效時(shí)間(在步驟3中計(jì)算得到的時(shí)間)減去一些時(shí)間(通常是幾毫秒骑祟,主要是為了補(bǔ)償不同進(jìn)程之間的時(shí)鐘漂移)回懦,安全性才能得到保證。
想要了解更多關(guān)于需要綁定時(shí)鐘漂移系統(tǒng)的信息次企,這篇文章是一個(gè)有意思的參考:Leases: an efficient fault-tolerant mechanism for distributed file cache consistency.
重試機(jī)制
當(dāng)客戶端無法獲取鎖時(shí)怯晕,它應(yīng)該在延遲一個(gè)隨機(jī)的時(shí)間后進(jìn)行重試,以使多個(gè)客戶端得到同步缸棵,同時(shí)嘗試獲取同一個(gè)資源的鎖(這可能導(dǎo)致腦裂的發(fā)生舟茶,在這種情況下沒有贏家)。同樣蛉谜,客戶端獲取大多數(shù)實(shí)例鎖的時(shí)間越短,腦裂出現(xiàn)的概率也就越低(同時(shí)需要重試的可能也就越小)崇堵,所以理想狀態(tài)是客戶端應(yīng)該同時(shí)向N個(gè)實(shí)例發(fā)送SET命令型诚。
有一點(diǎn)值得強(qiáng)調(diào)的是:當(dāng)客戶端沒能獲取大多數(shù)鎖時(shí),盡快釋放已獲得的鎖是多么的重要鸳劳。這樣一來就沒有必要在重新獲取鎖時(shí)需要先等待鍵過期(然而如果由于網(wǎng)絡(luò)分區(qū)的發(fā)生狰贯,客戶端不再能夠與Redis實(shí)例進(jìn)行通信,那么此時(shí)就必須等待鍵過期了赏廓。)涵紊。
釋放鎖
釋放鎖是很簡(jiǎn)單的,我們只需要在所有實(shí)例中釋放鎖即可幔摸,而不用關(guān)心客戶端在該實(shí)例中是否獲得了鎖摸柄。
安全性爭(zhēng)議
這個(gè)算法是安全的碼?我們可以嘗試去了解一下在不同場(chǎng)景中到底發(fā)生了什么既忆。
一開始讓我們先假設(shè)一個(gè)客戶端能夠獲取大多數(shù)實(shí)例的鎖驱负。所有的實(shí)例都會(huì)包含一個(gè)帶有相同存活時(shí)間的鍵。然而患雇,不同實(shí)例的鍵是在不同時(shí)間設(shè)置的跃脊,所以所有鍵也會(huì)在不同的時(shí)間過期。但是如果第一個(gè)鍵在最糟糕的時(shí)間T1(在我們與第一臺(tái)服務(wù)器建立通信之前的時(shí)間采樣)被設(shè)置苛吱,而最后一個(gè)鍵也在最糟糕的時(shí)間T2(我們從最后一臺(tái)服務(wù)器得到回復(fù)的時(shí)間)被設(shè)置酪术,那么我們可以確定的是第一個(gè)過期的鍵將至少存活MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT
這么長的時(shí)間。其它的鍵將會(huì)過期得更晚翠储,所以我們可以確定所有的鍵至少可以存活這么長的時(shí)間绘雁。
在大多數(shù)鍵的存活時(shí)間內(nèi)橡疼,其它客戶端將無法獲取鎖,因?yàn)槿绻鸑/2+1個(gè)鍵已經(jīng)存在的話咧七,將沒有N/2+1個(gè)SETNX命令可以執(zhí)行成功衰齐。所以如果已經(jīng)獲得了鎖,同一時(shí)刻將無法再次獲取鎖(如果這樣就將違反互斥性)继阻。
然而我們同樣想確定當(dāng)多個(gè)客戶端嘗試在同一時(shí)間獲取鎖時(shí)將無法同時(shí)成功耻涛。
如果一個(gè)客戶端獲取大多數(shù)實(shí)例鎖的時(shí)間使用的時(shí)間與鎖最大有效時(shí)間(基本上是使用SET命令是的存活時(shí)間)很相近或者甚至更大,那么這把鎖會(huì)被認(rèn)為是無效的瘟檩,并且會(huì)釋放所有的實(shí)例抹缕。所以我們只需要考慮客戶端獲取大多數(shù)實(shí)例鎖消耗的時(shí)間比有效時(shí)間小的這種場(chǎng)景。在這種情況下墨辛,關(guān)于前文討論的爭(zhēng)議卓研,在MIN_VALIDITY時(shí)間內(nèi)將沒有客戶端可以重新獲取鎖。所以只有在獲取大多數(shù)實(shí)例鎖的時(shí)間大于TTL時(shí)間的情況下睹簇,才有可能出現(xiàn)多個(gè)客戶端可以同時(shí)鎖住N/2+1個(gè)實(shí)例奏赘,但這種情況下獲得的鎖也是無效的。
對(duì)于現(xiàn)有的類似算法太惠,你能給出正式的安全性證明或者發(fā)現(xiàn)bug嗎磨淌?如果可以,我們將不勝感激凿渊。
活躍性爭(zhēng)議
系統(tǒng)的活躍性建立在下面3個(gè)主要的特征之上:
鎖可以自動(dòng)被釋放(因?yàn)殒I會(huì)過期):最終鍵可以被重新獲得梁只。
事實(shí)上,當(dāng)鎖沒有被獲得時(shí)埃脏,或者鎖已經(jīng)被獲取但是工作也已經(jīng)完成搪锣,客戶端會(huì)主動(dòng)將鎖釋放,這樣一來我們就不需要等待鍵過期以便再次獲取鎖彩掐。
當(dāng)客戶端嘗試重新獲取鎖時(shí)构舟,為了是降低在資源爭(zhēng)奪期間出現(xiàn)腦裂的概率,它等待的時(shí)間會(huì)大于需要獲取大多數(shù)鎖的時(shí)間堵幽。
然而旁壮,當(dāng)出現(xiàn)網(wǎng)絡(luò)分區(qū)不可用時(shí),我們還是會(huì)付出等待TTL時(shí)間的代價(jià)谐檀。所以如果網(wǎng)絡(luò)持續(xù)的有問題抡谐,我們將一直處于等待狀態(tài)。每次當(dāng)客戶端獲取了鎖桐猬,但是在它釋放鎖之前出現(xiàn)網(wǎng)絡(luò)分區(qū)后麦撵,這種情況都會(huì)發(fā)生。
所以如果網(wǎng)絡(luò)分區(qū)一直不可用,那么系統(tǒng)也將一直不可用免胃。
性能音五、崩潰恢復(fù)和同步
許多用戶將Redis作為需要高性能處理延遲獲取鎖和釋放鎖的鎖服務(wù)器,并且每秒可以處理很多次獲取/釋放的操作羔沙。為了滿足這一要求躺涝,我們減少與N臺(tái)Redis服務(wù)器通信延時(shí)的策略肯定是復(fù)用(或者性能較差的復(fù)用,即使用非阻塞模式扼雏,發(fā)送所有命令坚嗜,然后稍后讀取所有命令的回復(fù),假設(shè)客戶端和每個(gè)實(shí)例的RTT都是相近的)诗充。
然而苍蔬,如果目標(biāo)是建立一個(gè)崩潰恢復(fù)的系統(tǒng)模型,還需要考慮的一點(diǎn)是持久化蝴蜓。
讓我們來看一下這個(gè)問題碟绑,假設(shè)我們配置Redis時(shí)完全沒有考慮持久化。一個(gè)客戶端在5個(gè)實(shí)例中獲取了3個(gè)的鎖茎匠,而在客戶端獲取到鎖的實(shí)例中有一個(gè)重啟了格仲,這時(shí)又有3個(gè)實(shí)例可供我們對(duì)統(tǒng)一資源再次獲取鎖,另外一個(gè)客戶端就可以重新鎖住它诵冒,而這就違反了互斥性凯肋。
如果我們開啟了AOF持久化選項(xiàng),情況就會(huì)有所好轉(zhuǎn)造烁。舉個(gè)栗子否过,我們可以通過發(fā)送SHUTDOWN命令來重啟或升級(jí)一臺(tái)服務(wù)器午笛。因?yàn)镽edis的過期命令是從語義上實(shí)現(xiàn)的惭蟋,所以即使服務(wù)器關(guān)機(jī)了,時(shí)間依然在流逝药磺,我們所有的要求都是滿足的告组。雖然只要它是一個(gè)干凈的關(guān)閉,所有事情都是ok的癌佩,那么如果是停電呢木缝?默認(rèn)情況下,Redis被配置為每秒執(zhí)行一次fsync围辙,這樣在重啟后我們的鍵就可能會(huì)丟失我碟。理論上,如果我們想確保在任意類型的實(shí)例重啟時(shí)鎖的安全性姚建,我們就需要在持久化配置中開啟fsync=always選項(xiàng)矫俺。而這將會(huì)完全破壞用來以安全方式實(shí)現(xiàn)分布式鎖的CP系統(tǒng)的性能。
然而事情往往比第一眼看到的情況要好一些±逋校基本上算法的安全性是可以得到保證的友雳,只要一個(gè)實(shí)例在崩潰重啟后不再參與到任何當(dāng)前活動(dòng)的鎖,所以當(dāng)實(shí)例重啟時(shí)铅匹,當(dāng)前活動(dòng)的鎖依然可以通過鎖住該實(shí)例來保持押赊,而不會(huì)有新的鎖重新加入系統(tǒng)。
為了使這一點(diǎn)得到保證包斑,我們只需要使實(shí)例在發(fā)生崩潰后不可用的時(shí)間比我們的最大TTL略長一點(diǎn)點(diǎn)即可流礁,在這個(gè)時(shí)間內(nèi),當(dāng)實(shí)例崩潰時(shí)舰始,所有已經(jīng)被鎖住的鍵都變得不可用崇棠,并且會(huì)被自動(dòng)釋放。
使用延遲重啟可以在沒有任何Redis持久化機(jī)制的條件下保證安全性丸卷,然而需要注意到的是枕稀,這可能會(huì)導(dǎo)致系統(tǒng)不可用。例如當(dāng)大多數(shù)實(shí)例都崩潰后谜嫉,整個(gè)系統(tǒng)將在TTL時(shí)間內(nèi)完全不可用(這里完全意味著在這段時(shí)間內(nèi)將沒有資源可以被鎖住)萎坷。
使算法更加可靠:鎖的擴(kuò)展
如果客戶端的工作由很多小步驟組成,那么默認(rèn)情況下就有可能使用更小的鎖有效時(shí)間沐兰,并實(shí)現(xiàn)一個(gè)擴(kuò)展鎖機(jī)制來擴(kuò)展該算法哆档。如果客戶端正在進(jìn)行計(jì)算而鎖有效時(shí)間所剩無幾,這時(shí)可以通過向所有實(shí)例發(fā)送一個(gè)Lua腳本來延長TTL從而擴(kuò)展鎖住闯,如果鎖的鍵依然存在并且鍵值與剛開始獲取鎖時(shí)分配的隨機(jī)值相等的話瓜浸。
只有可以在有效時(shí)間內(nèi)在大多數(shù)實(shí)例上擴(kuò)展鎖成功的條件下,客戶端擴(kuò)展鎖才算成功(基本上該算法和獲取鎖的算法非常相似)比原。
然而這從技術(shù)上并沒有改變?cè)撍惴ú宸穑栽跀U(kuò)展鎖的過程中必須對(duì)最大嘗試此書進(jìn)行限制,否則就會(huì)違反其中一條活躍性量窘。
需要幫助雇寇?
如果你對(duì)分布式系統(tǒng)很感興趣,那么你的意見/分析將非常重要蚌铜。其他語言的分布式鎖實(shí)現(xiàn)算法同樣非常重要锨侯。
提前感謝各位!
Redlock分析
Martin Kleppmann在這篇文章(http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html)中分析了Redlock冬殃,但是我并不贊同他的觀點(diǎn)囚痴,我的回復(fù)在這里(http://antirez.com/news/101)。
2017-09-11