【飛哥薦讀】基于Redis的分布式鎖到底安全嗎?

飛哥薦讀

本文從redis分布式鎖的官方實現(xiàn)右核,討論了分布式鎖需要的考慮的問題申屹,并分析了RedLock、zookeeper撵割、chubby分布式鎖可能存在的安全問題。引用Martin的觀點給出了分布式鎖的技術(shù)選型建議:從效率和正確性兩方便去考慮辙芍。
文中引用鏈接的文章也值得關(guān)注分布式的同學(xué)一讀啡彬。
非常不錯的一篇分布式鎖的整理文章,推薦閱讀故硅。

網(wǎng)上有關(guān)Redis分布式鎖的文章可謂多如牛毛了庶灿,不信的話你可以拿關(guān)鍵詞“Redis 分布式鎖”隨便到哪個搜索引擎上去搜索一下就知道了。這些文章的思路大體相近吃衅,給出的實現(xiàn)算法也看似合乎邏輯往踢,但當(dāng)我們著手去實現(xiàn)它們的時候,卻發(fā)現(xiàn)如果你越是仔細(xì)推敲徘层,疑慮也就越來越多峻呕。

實際上,大概在一年以前惑灵,關(guān)于Redis分布式鎖的安全性問題山上,在分布式系統(tǒng)專家Martin Kleppmann和Redis的作者antirez之間就發(fā)生過一場爭論。由于對這個問題一直以來比較關(guān)注英支,所以我前些日子仔細(xì)閱讀了與這場爭論相關(guān)的資料佩憾。這場爭論的大概過程是這樣的:為了規(guī)范各家對基于Redis的分布式鎖的實現(xiàn),Redis的作者提出了一個更安全的實現(xiàn)干花,叫做Redlock妄帘。有一天,Martin Kleppmann寫了一篇blog池凄,分析了Redlock在安全性上存在的一些問題抡驼。然后Redis的作者立即寫了一篇blog來反駁Martin的分析。但Martin表示仍然堅持原來的觀點肿仑。隨后致盟,這個問題在Twitter和Hacker News上引發(fā)了激烈的討論碎税,很多分布式系統(tǒng)的專家都參與其中。

對于那些對分布式系統(tǒng)感興趣的人來說馏锡,這個事件非常值得關(guān)注雷蹂。不管你是剛接觸分布式系統(tǒng)的新手,還是有著多年分布式開發(fā)經(jīng)驗的老手杯道,讀完這些分析和評論之后匪煌,大概都會有所收獲。要知道党巾,親手實現(xiàn)過Redis Cluster這樣一個復(fù)雜系統(tǒng)的antirez萎庭,足以算得上分布式領(lǐng)域的一名專家了。但對于由分布式鎖引發(fā)的一系列問題的分析中齿拂,不同的專家卻能得出迥異的結(jié)論驳规,從中我們可以窺見分布式系統(tǒng)相關(guān)的問題具有何等的復(fù)雜性。實際上创肥,在分布式系統(tǒng)的設(shè)計中經(jīng)常發(fā)生的事情是:許多想法初看起來毫無破綻达舒,而一旦詳加考量,卻發(fā)現(xiàn)不是那么天衣無縫叹侄。

下面巩搏,我們就從頭至尾把這場爭論過程中各方的觀點進(jìn)行一下回顧和分析。在這個過程中趾代,我們把影響分布式鎖的安全性的那些技術(shù)細(xì)節(jié)展開進(jìn)行討論贯底,這將是一件很有意思的事情。這也是一個比較長的故事撒强。當(dāng)然禽捆,其中也免不了包含一些小“八卦”。

Redlock算法

就像本文開頭所講的飘哨,借助Redis來實現(xiàn)一個分布式鎖(Distributed Lock)的做法胚想,已經(jīng)有很多人嘗試過。人們構(gòu)建這樣的分布式鎖的目的芽隆,是為了對一些共享資源進(jìn)行互斥訪問浊服。

但是,這些實現(xiàn)雖然思路大體相近胚吁,但實現(xiàn)細(xì)節(jié)上各不相同牙躺,它們能提供的安全性和可用性也不盡相同。所以腕扶,Redis的作者antirez給出了一個更好的實現(xiàn)孽拷,稱為Redlock,算是Redis官方對于實現(xiàn)分布式鎖的指導(dǎo)規(guī)范半抱。Redlock的算法描述就放在Redis的官網(wǎng)上:

在Redlock之前脓恕,很多人對于分布式鎖的實現(xiàn)都是基于單個Redis節(jié)點的膜宋。而Redlock是基于多個Redis節(jié)點(都是Master)的一種實現(xiàn)。為了能理解Redlock炼幔,我們首先需要把簡單的基于單Redis節(jié)點的算法描述清楚激蹲,因為它是Redlock的基礎(chǔ)。

基于單Redis節(jié)點的分布式鎖

首先江掩,Redis客戶端為了獲取鎖,向Redis節(jié)點發(fā)送如下命令:

SET resource_name my_random_value NX PX 30000

上面的命令如果執(zhí)行成功乘瓤,則客戶端成功獲取到了鎖环形,接下來就可以訪問共享資源了;而如果上面的命令執(zhí)行失敗衙傀,則說明獲取鎖失敗抬吟。

注意,在上面的SET命令中:

  • my_random_value是由客戶端生成的一個隨機(jī)字符串统抬,它要保證在足夠長的一段時間內(nèi)在所有客戶端的所有獲取鎖的請求中都是唯一的火本。
  • NX表示只有當(dāng)resource_name對應(yīng)的key值不存在的時候才能SET成功。這保證了只有第一個請求的客戶端才能獲得鎖聪建,而其它客戶端在鎖被釋放之前都無法獲得鎖钙畔。
  • PX 30000表示這個鎖有一個30秒的自動過期時間。當(dāng)然金麸,這里30秒只是一個例子擎析,客戶端可以選擇合適的過期時間。

最后挥下,當(dāng)客戶端完成了對共享資源的操作之后揍魂,執(zhí)行下面的Redis Lua腳本來釋放鎖:

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

這段Lua腳本在執(zhí)行的時候要把前面的my_random_value作為ARGV[1]的值傳進(jìn)去,把resource_name作為KEYS[1]的值傳進(jìn)去棚瘟。

至此现斋,基于單Redis節(jié)點的分布式鎖的算法就描述完了。這里面有好幾個問題需要重點分析一下偎蘸。

首先第一個問題庄蹋,這個鎖必須要設(shè)置一個過期時間。否則的話禀苦,當(dāng)一個客戶端獲取鎖成功之后蔓肯,假如它崩潰了,或者由于發(fā)生了網(wǎng)絡(luò)分割(network partition)導(dǎo)致它再也無法和Redis節(jié)點通信了振乏,那么它就會一直持有這個鎖蔗包,而其它客戶端永遠(yuǎn)無法獲得鎖了。antirez在后面的分析中也特別強(qiáng)調(diào)了這一點慧邮,而且把這個過期時間稱為鎖的有效時間(lock validity time)调限。獲得鎖的客戶端必須在這個時間之內(nèi)完成對共享資源的訪問舟陆。

第二個問題,第一步獲取鎖的操作耻矮,網(wǎng)上不少文章把它實現(xiàn)成了兩個Redis命令:

SETNX resource_name my_random_value
EXPIRE resource_name 30

雖然這兩個命令和前面算法描述中的一個SET命令執(zhí)行效果相同秦躯,但卻不是原子的。如果客戶端在執(zhí)行完SETNX后崩潰了裆装,那么就沒有機(jī)會執(zhí)行EXPIRE了踱承,導(dǎo)致它一直持有這個鎖。

第三個問題哨免,也是antirez指出的茎活,設(shè)置一個隨機(jī)字符串my_random_value是很有必要的,它保證了一個客戶端釋放的鎖必須是自己持有的那個鎖琢唾。假如獲取鎖時SET的不是一個隨機(jī)字符串载荔,而是一個固定值,那么可能會發(fā)生下面的執(zhí)行序列:

  1. 客戶端1獲取鎖成功采桃。
  2. 客戶端1在某個操作上阻塞了很長時間懒熙。
  3. 過期時間到了,鎖自動釋放了普办。
  4. 客戶端2獲取到了對應(yīng)同一個資源的鎖工扎。
  5. 客戶端1從阻塞中恢復(fù)過來,釋放掉了客戶端2持有的鎖泌豆。

之后定庵,客戶端2在訪問共享資源的時候,就沒有鎖為它提供保護(hù)了踪危。

第四個問題蔬浙,釋放鎖的操作必須使用Lua腳本來實現(xiàn)。釋放鎖其實包含三步操作:’GET’贞远、判斷和’DEL’畴博,用Lua腳本來實現(xiàn)能保證這三步的原子性。否則蓝仲,如果把這三步操作放到客戶端邏輯中去執(zhí)行的話俱病,就有可能發(fā)生與前面第三個問題類似的執(zhí)行序列:

  1. 客戶端1獲取鎖成功。
  2. 客戶端1訪問共享資源袱结。
  3. 客戶端1為了釋放鎖亮隙,先執(zhí)行’GET’操作獲取隨機(jī)字符串的值。
  4. 客戶端1判斷隨機(jī)字符串的值垢夹,與預(yù)期的值相等溢吻。
  5. 客戶端1由于某個原因阻塞住了很長時間。
  6. 過期時間到了,鎖自動釋放了促王。
  7. 客戶端2獲取到了對應(yīng)同一個資源的鎖犀盟。
  8. 客戶端1從阻塞中恢復(fù)過來,執(zhí)行DEL操縱蝇狼,釋放掉了客戶端2持有的鎖阅畴。

實際上,在上述第三個問題和第四個問題的分析中迅耘,如果不是客戶端阻塞住了贱枣,而是出現(xiàn)了大的網(wǎng)絡(luò)延遲,也有可能導(dǎo)致類似的執(zhí)行序列發(fā)生颤专。

前面的四個問題冯事,只要實現(xiàn)分布式鎖的時候加以注意,就都能夠被正確處理血公。但除此之外,antirez還指出了一個問題缓熟,是由failover引起的累魔,卻是基于單Redis節(jié)點的分布式鎖無法解決的。正是這個問題催生了Redlock的出現(xiàn)够滑。

這個問題是這樣的垦写。假如Redis節(jié)點宕機(jī)了,那么所有客戶端就都無法獲得鎖了彰触,服務(wù)變得不可用梯投。為了提高可用性,我們可以給這個Redis節(jié)點掛一個Slave况毅,當(dāng)Master節(jié)點不可用的時候分蓖,系統(tǒng)自動切到Slave上(failover)。但由于Redis的主從復(fù)制(replication)是異步的尔许,這可能導(dǎo)致在failover過程中喪失鎖的安全性么鹤。考慮下面的執(zhí)行序列:

  1. 客戶端1從Master獲取了鎖味廊。
  2. Master宕機(jī)了蒸甜,存儲鎖的key還沒有來得及同步到Slave上。
  3. Slave升級為Master余佛。
  4. 客戶端2從新的Master獲取到了對應(yīng)同一個資源的鎖柠新。

于是,客戶端1和客戶端2同時持有了同一個資源的鎖辉巡。鎖的安全性被打破恨憎。針對這個問題,antirez設(shè)計了Redlock算法红氯,我們接下來會討論框咙。

【其它疑問】

前面這個算法中出現(xiàn)的鎖的有效時間(lock validity time)咕痛,設(shè)置成多少合適呢?如果設(shè)置太短的話喇嘱,鎖就有可能在客戶端完成對于共享資源的訪問之前過期茉贡,從而失去保護(hù);如果設(shè)置太長的話者铜,一旦某個持有鎖的客戶端釋放鎖失敗腔丧,那么就會導(dǎo)致所有其它客戶端都無法獲取鎖,從而長時間內(nèi)無法正常工作作烟∮湓粒看來真是個兩難的問題。

而且拿撩,在前面對于隨機(jī)字符串my_random_value的分析中衣厘,antirez也在文章中承認(rèn)的確應(yīng)該考慮客戶端長期阻塞導(dǎo)致鎖過期的情況。如果真的發(fā)生了這種情況压恒,那么共享資源是不是已經(jīng)失去了保護(hù)呢影暴?antirez重新設(shè)計的Redlock是否能解決這些問題呢?

分布式鎖Redlock

由于前面介紹的基于單Redis節(jié)點的分布式鎖在failover的時候會產(chǎn)生解決不了的安全性問題探赫,因此antirez提出了新的分布式鎖的算法Redlock型宙,它基于N個完全獨立的Redis節(jié)點(通常情況下N可以設(shè)置成5)。

運行Redlock算法的客戶端依次執(zhí)行下面各個步驟伦吠,來完成獲取鎖的操作:

  1. 獲取當(dāng)前時間(毫秒數(shù))妆兑。
  2. 按順序依次向N個Redis節(jié)點執(zhí)行獲取鎖的操作。這個獲取操作跟前面基于單Redis節(jié)點的獲取鎖的過程相同毛仪,包含隨機(jī)字符串my_random_value搁嗓,也包含過期時間(比如PX 30000,即鎖的有效時間)箱靴。為了保證在某個Redis節(jié)點不可用的時候算法能夠繼續(xù)運行谱姓,這個獲取鎖的操作還有一個超時時間(time out),它要遠(yuǎn)小于鎖的有效時間(幾十毫秒量級)刨晴√肜矗客戶端在向某個Redis節(jié)點獲取鎖失敗以后,應(yīng)該立即嘗試下一個Redis節(jié)點狈癞。這里的失敗茄靠,應(yīng)該包含任何類型的失敗,比如該Redis節(jié)點不可用蝶桶,或者該Redis節(jié)點上的鎖已經(jīng)被其它客戶端持有(注:Redlock原文中這里只提到了Redis節(jié)點不可用的情況慨绳,但也應(yīng)該包含其它的失敗情況)。
  3. 計算整個獲取鎖的過程總共消耗了多長時間,計算方法是用當(dāng)前時間減去第1步記錄的時間脐雪。如果客戶端從大多數(shù)Redis節(jié)點(>= N/2+1)成功獲取到了鎖厌小,并且獲取鎖總共消耗的時間沒有超過鎖的有效時間(lock validity time),那么這時客戶端才認(rèn)為最終獲取鎖成功战秋;否則璧亚,認(rèn)為最終獲取鎖失敗。
  4. 如果最終獲取鎖成功了脂信,那么這個鎖的有效時間應(yīng)該重新計算癣蟋,它等于最初的鎖的有效時間減去第3步計算出來的獲取鎖消耗的時間。
  5. 如果最終獲取鎖失敗了(可能由于獲取到鎖的Redis節(jié)點個數(shù)少于N/2+1狰闪,或者整個獲取鎖的過程消耗的時間超過了鎖的最初有效時間)疯搅,那么客戶端應(yīng)該立即向所有Redis節(jié)點發(fā)起釋放鎖的操作(即前面介紹的Redis Lua腳本)。

當(dāng)然埋泵,上面描述的只是獲取鎖的過程幔欧,而釋放鎖的過程比較簡單:客戶端向所有Redis節(jié)點發(fā)起釋放鎖的操作,不管這些節(jié)點當(dāng)時在獲取鎖的時候成功與否丽声。

由于N個Redis節(jié)點中的大多數(shù)能正常工作就能保證Redlock正常工作琐馆,因此理論上它的可用性更高。我們前面討論的單Redis節(jié)點的分布式鎖在failover的時候鎖失效的問題恒序,在Redlock中不存在了,但如果有節(jié)點發(fā)生崩潰重啟谁撼,還是會對鎖的安全性有影響的歧胁。具體的影響程度跟Redis對數(shù)據(jù)的持久化程度有關(guān)。

假設(shè)一共有5個Redis節(jié)點:A, B, C, D, E厉碟。設(shè)想發(fā)生了如下的事件序列:

  1. 客戶端1成功鎖住了A, B, C喊巍,獲取鎖成功(但D和E沒有鎖住)箍鼓。
  2. 節(jié)點C崩潰重啟了崭参,但客戶端1在C上加的鎖沒有持久化下來,丟失了款咖。
  3. 節(jié)點C重啟后何暮,客戶端2鎖住了C, D, E,獲取鎖成功铐殃。

這樣海洼,客戶端1和客戶端2同時獲得了鎖(針對同一資源)。

在默認(rèn)情況下富腊,Redis的AOF持久化方式是每秒寫一次磁盤(即執(zhí)行fsync)坏逢,因此最壞情況下可能丟失1秒的數(shù)據(jù)。為了盡可能不丟數(shù)據(jù),Redis允許設(shè)置成每次修改數(shù)據(jù)都進(jìn)行fsync是整,但這會降低性能肖揣。當(dāng)然,即使執(zhí)行了fsync也仍然有可能丟失數(shù)據(jù)(這取決于系統(tǒng)而不是Redis的實現(xiàn))浮入。所以龙优,上面分析的由于節(jié)點重啟引發(fā)的鎖失效問題,總是有可能出現(xiàn)的舵盈。為了應(yīng)對這一問題陋率,antirez又提出了延遲重啟(delayed restarts)的概念。也就是說秽晚,一個節(jié)點崩潰后瓦糟,先不立即重啟它,而是等待一段時間再重啟赴蝇,這段時間應(yīng)該大于鎖的有效時間(lock validity time)菩浙。這樣的話,這個節(jié)點在重啟前所參與的鎖都會過期,它在重啟后就不會對現(xiàn)有的鎖造成影響屁使。

關(guān)于Redlock還有一點細(xì)節(jié)值得拿出來分析一下:在最后釋放鎖的時候毛俏,antirez在算法描述中特別強(qiáng)調(diào),客戶端應(yīng)該向所有Redis節(jié)點發(fā)起釋放鎖的操作先嬉。也就是說,即使當(dāng)時向某個節(jié)點獲取鎖沒有成功楚堤,在釋放鎖的時候也不應(yīng)該漏掉這個節(jié)點疫蔓。這是為什么呢?設(shè)想這樣一種情況身冬,客戶端發(fā)給某個Redis節(jié)點的獲取鎖的請求成功到達(dá)了該Redis節(jié)點衅胀,這個節(jié)點也成功執(zhí)行了SET操作,但是它返回給客戶端的響應(yīng)包卻丟失了酥筝。這在客戶端看來滚躯,獲取鎖的請求由于超時而失敗了,但在Redis這邊看來嘿歌,加鎖已經(jīng)成功了掸掏。因此,釋放鎖的時候宙帝,客戶端也應(yīng)該對當(dāng)時獲取鎖失敗的那些Redis節(jié)點同樣發(fā)起請求阅束。實際上,這種情況在異步通信模型中是有可能發(fā)生的:客戶端向服務(wù)器通信是正常的茄唐,但反方向卻是有問題的息裸。

【其它疑問】

前面在討論單Redis節(jié)點的分布式鎖的時候蝇更,最后我們提出了一個疑問,如果客戶端長期阻塞導(dǎo)致鎖過期呼盆,那么它接下來訪問共享資源就不安全了(沒有了鎖的保護(hù))年扩。這個問題在Redlock中是否有所改善呢?顯然访圃,這樣的問題在Redlock中是依然存在的厨幻。

另外,在算法第4步成功獲取了鎖之后腿时,如果由于獲取鎖的過程消耗了較長時間况脆,重新計算出來的剩余的鎖有效時間很短了,那么我們還來得及去完成共享資源訪問嗎批糟?如果我們認(rèn)為太短格了,是不是應(yīng)該立即進(jìn)行鎖的釋放操作?那到底多短才算呢徽鼎?又是一個選擇難題盛末。

Martin的分析

Martin Kleppmann在2016-02-08這一天發(fā)表了一篇blog,名字叫”How to do distributed locking “否淤,地址如下:

Martin在這篇文章中談及了分布式系統(tǒng)的很多基礎(chǔ)性的問題(特別是分布式計算的異步模型)悄但,對分布式系統(tǒng)的從業(yè)者來說非常值得一讀。這篇文章大體可以分為兩大部分:

  • 前半部分石抡,與Redlock無關(guān)檐嚣。Martin指出,即使我們擁有一個完美實現(xiàn)的分布式鎖(帶自動過期功能)啰扛,在沒有共享資源參與進(jìn)來提供某種fencing機(jī)制的前提下嚎京,我們?nèi)匀徊豢赡塬@得足夠的安全性。
  • 后半部分侠讯,是對Redlock本身的批評。Martin指出暑刃,由于Redlock本質(zhì)上是建立在一個同步模型之上厢漩,對系統(tǒng)的記時假設(shè)(timing assumption)有很強(qiáng)的要求,因此本身的安全性是不夠的岩臣。

首先我們討論一下前半部分的關(guān)鍵點溜嗜。Martin給出了下面這樣一份時序圖:

在上面的時序圖中,假設(shè)鎖服務(wù)本身是沒有問題的架谎,它總是能保證任一時刻最多只有一個客戶端獲得鎖炸宵。上圖中出現(xiàn)的lease這個詞可以暫且認(rèn)為就等同于一個帶有自動過期功能的鎖」瓤郏客戶端1在獲得鎖之后發(fā)生了很長時間的GC pause土全,在此期間捎琐,它獲得的鎖過期了,而客戶端2獲得了鎖裹匙。當(dāng)客戶端1從GC pause中恢復(fù)過來的時候瑞凑,它不知道自己持有的鎖已經(jīng)過期了,它依然向共享資源(上圖中是一個存儲服務(wù))發(fā)起了寫數(shù)據(jù)請求概页,而這時鎖實際上被客戶端2持有籽御,因此兩個客戶端的寫請求就有可能沖突(鎖的互斥作用失效了)。

初看上去惰匙,有人可能會說技掏,既然客戶端1從GC pause中恢復(fù)過來以后不知道自己持有的鎖已經(jīng)過期了,那么它可以在訪問共享資源之前先判斷一下鎖是否過期项鬼。但仔細(xì)想想哑梳,這絲毫也沒有幫助。因為GC pause可能發(fā)生在任意時刻秃臣,也許恰好在判斷完之后涧衙。

也有人會說,如果客戶端使用沒有GC的語言來實現(xiàn)奥此,是不是就沒有這個問題呢弧哎?Martin指出,系統(tǒng)環(huán)境太復(fù)雜稚虎,仍然有很多原因?qū)е逻M(jìn)程的pause撤嫩,比如虛存造成的缺頁故障(page fault),再比如CPU資源的競爭蠢终。即使不考慮進(jìn)程pause的情況序攘,網(wǎng)絡(luò)延遲也仍然會造成類似的結(jié)果。

總結(jié)起來就是說寻拂,即使鎖服務(wù)本身是沒有問題的程奠,而僅僅是客戶端有長時間的pause或網(wǎng)絡(luò)延遲,仍然會造成兩個客戶端同時訪問共享資源的沖突情況發(fā)生祭钉。而這種情況其實就是我們在前面已經(jīng)提出來的“客戶端長期阻塞導(dǎo)致鎖過期”的那個疑問瞄沙。

那怎么解決這個問題呢?Martin給出了一種方法慌核,稱為fencing token距境。fencing token是一個單調(diào)遞增的數(shù)字,當(dāng)客戶端成功獲取鎖的時候它隨同鎖一起返回給客戶端垮卓。而客戶端訪問共享資源的時候帶著這個fencing token垫桂,這樣提供共享資源的服務(wù)就能根據(jù)它進(jìn)行檢查,拒絕掉延遲到來的訪問請求(避免了沖突)粟按。如下圖:

在上圖中诬滩,客戶端1先獲取到的鎖霹粥,因此有一個較小的fencing token,等于33碱呼,而客戶端2后獲取到的鎖蒙挑,有一個較大的fencing token,等于34愚臀∫涫矗客戶端1從GC pause中恢復(fù)過來之后,依然是向存儲服務(wù)發(fā)送訪問請求姑裂,但是帶了fencing token = 33馋袜。存儲服務(wù)發(fā)現(xiàn)它之前已經(jīng)處理過34的請求,所以會拒絕掉這次33的請求舶斧。這樣就避免了沖突欣鳖。

現(xiàn)在我們再討論一下Martin的文章的后半部分。

Martin在文中構(gòu)造了一些事件序列茴厉,能夠讓Redlock失效(兩個客戶端同時持有鎖)泽台。為了說明Redlock對系統(tǒng)記時(timing)的過分依賴,他首先給出了下面的一個例子(還是假設(shè)有5個Redis節(jié)點A, B, C, D, E):

  1. 客戶端1從Redis節(jié)點A, B, C成功獲取了鎖(多數(shù)節(jié)點)矾缓。由于網(wǎng)絡(luò)問題怀酷,與D和E通信失敗。
  2. 節(jié)點C上的時鐘發(fā)生了向前跳躍嗜闻,導(dǎo)致它上面維護(hù)的鎖快速過期蜕依。
  3. 客戶端2從Redis節(jié)點C, D, E成功獲取了同一個資源的鎖(多數(shù)節(jié)點)琉雳。
  4. 客戶端1和客戶端2現(xiàn)在都認(rèn)為自己持有了鎖。

上面這種情況之所以有可能發(fā)生檐束,本質(zhì)上是因為Redlock的安全性(safety property)對系統(tǒng)的時鐘有比較強(qiáng)的依賴被丧,一旦系統(tǒng)的時鐘變得不準(zhǔn)確肌幽,算法的安全性也就保證不了了晚碾。Martin在這里其實是要指出分布式算法研究中的一些基礎(chǔ)性問題抓半,或者說一些常識問題喂急,即好的分布式算法應(yīng)該基于異步模型(asynchronous model),算法的安全性不應(yīng)該依賴于任何記時假設(shè)(timing assumption)笛求。在異步模型中:進(jìn)程可能pause任意長的時間廊移,消息可能在網(wǎng)絡(luò)中延遲任意長的時間糕簿,甚至丟失,系統(tǒng)時鐘也可能以任意方式出錯狡孔。一個好的分布式算法懂诗,這些因素不應(yīng)該影響它的安全性(safety property),只可能影響到它的活性(liveness property)苗膝,也就是說殃恒,即使在非常極端的情況下(比如系統(tǒng)時鐘嚴(yán)重錯誤),算法頂多是不能在有限的時間內(nèi)給出結(jié)果而已辱揭,而不應(yīng)該給出錯誤的結(jié)果离唐。這樣的算法在現(xiàn)實中是存在的,像比較著名的Paxos问窃,或Raft亥鬓。但顯然按這個標(biāo)準(zhǔn)的話,Redlock的安全性級別是達(dá)不到的熟呛。

隨后,Martin覺得前面這個時鐘跳躍的例子還不夠偿短,又給出了一個由客戶端GC pause引發(fā)Redlock失效的例子。如下:

  1. 客戶端1向Redis節(jié)點A, B, C, D, E發(fā)起鎖請求勾怒。
  2. 各個Redis節(jié)點已經(jīng)把請求結(jié)果返回給了客戶端1,但客戶端1在收到請求結(jié)果之前進(jìn)入了長時間的GC pause。
  3. 在所有的Redis節(jié)點上坪创,鎖過期了柠掂。
  4. 客戶端2在A, B, C, D, E上獲取到了鎖涯贞。
  5. 客戶端1從GC pause從恢復(fù),收到了前面第2步來自各個Redis節(jié)點的請求結(jié)果∩蟠牛客戶端1認(rèn)為自己成功獲取到了鎖。
  6. 客戶端1和客戶端2現(xiàn)在都認(rèn)為自己持有了鎖钾恢。

Martin給出的這個例子其實有點小問題。在Redlock算法中疹瘦,客戶端在完成向各個Redis節(jié)點的獲取鎖的請求之后,會計算這個過程消耗的時間险胰,然后檢查是不是超過了鎖的有效時間(lock validity time)。也就是上面的例子中第5步,客戶端1從GC pause中恢復(fù)過來以后奖年,它會通過這個檢查發(fā)現(xiàn)鎖已經(jīng)過期了,不會再認(rèn)為自己成功獲取到鎖了水评。隨后antirez在他的反駁文章中就指出來了這個問題,但Martin認(rèn)為這個細(xì)節(jié)對Redlock整體的安全性沒有本質(zhì)的影響。

拋開這個細(xì)節(jié)咱扣,我們可以分析一下Martin舉這個例子的意圖在哪。初看起來偏瓤,這個例子跟文章前半部分分析通用的分布式鎖時給出的GC pause的時序圖是基本一樣的,只不過那里的GC pause發(fā)生在客戶端1獲得了鎖之后已骇,而這里的GC pause發(fā)生在客戶端1獲得鎖之前。但兩個例子的側(cè)重點不太一樣鲤竹。Martin構(gòu)造這里的這個例子,是為了強(qiáng)調(diào)在一個分布式的異步環(huán)境下痘拆,長時間的GC pause或消息延遲(上面這個例子中,把GC pause換成Redis節(jié)點和客戶端1之間的消息延遲桥氏,邏輯不變),會讓客戶端獲得一個已經(jīng)過期的鎖祥款。從客戶端1的角度看,Redlock的安全性被打破了桨昙,因為客戶端1收到鎖的時候,這個鎖已經(jīng)失效了桂塞,而Redlock同時還把這個鎖分配給了客戶端2。換句話說狂打,Redis服務(wù)器在把鎖分發(fā)給客戶端的途中,鎖就過期了蒿涎,但又沒有有效的機(jī)制讓客戶端明確知道這個問題。而在之前的那個例子中,客戶端1收到鎖的時候鎖還是有效的市怎,鎖服務(wù)本身的安全性可以認(rèn)為沒有被打破,后面雖然也出了問題驰弄,但問題是出在客戶端1和共享資源服務(wù)器之間的交互上。

在Martin的這篇文章中,還有一個很有見地的觀點乱灵,就是對鎖的用途的區(qū)分。他把鎖的用途分為兩種:

  • 為了效率(efficiency)蝉稳,協(xié)調(diào)各個客戶端避免做重復(fù)的工作。即使鎖偶爾失效了毕莱,只是可能把某些操作多做一遍而已蛹稍,不會產(chǎn)生其它的不良后果。比如重復(fù)發(fā)送了一封同樣的email奉芦。
  • 為了正確性(correctness)宠叼。在任何情況下都不允許鎖失效的情況發(fā)生伸蚯,因為一旦發(fā)生,就可能意味著數(shù)據(jù)不一致(inconsistency),數(shù)據(jù)丟失瑞眼,文件損壞,或者其它嚴(yán)重的問題。

最后锯蛀,Martin得出了如下的結(jié)論:

  • 如果是為了效率(efficiency)而使用分布式鎖翔曲,允許鎖的偶爾失效菌羽,那么使用單Redis節(jié)點的鎖方案就足夠了猾蒂,簡單而且效率高。Redlock則是個過重的實現(xiàn)(heavyweight)案糙。
  • 如果是為了正確性(correctness)在很嚴(yán)肅的場合使用分布式鎖炉抒,那么不要使用Redlock。它不是建立在異步模型上的一個足夠強(qiáng)的算法塞茅,它對于系統(tǒng)模型的假設(shè)中包含很多危險的成分(對于timing)飒泻。而且泞遗,它沒有一個機(jī)制能夠提供fencing token惰许。那應(yīng)該使用什么技術(shù)呢?Martin認(rèn)為史辙,應(yīng)該考慮類似Zookeeper的方案汹买,或者支持事務(wù)的數(shù)據(jù)庫佩伤。

Martin對Redlock算法的形容是:

neither fish nor fowl (非驢非馬)

【其它疑問】

  • Martin提出的fencing token的方案结序,需要對提供共享資源的服務(wù)進(jìn)行修改,這在現(xiàn)實中可行嗎霹肝?
  • 根據(jù)Martin的說法冷尉,看起來,如果資源服務(wù)器實現(xiàn)了fencing token宏榕,它在分布式鎖失效的情況下也仍然能保持資源的互斥訪問叉抡。這是不是意味著分布式鎖根本沒有存在的意義了?
  • 資源服務(wù)器需要檢查fencing token的大小尺碰,如果提供資源訪問的服務(wù)也是包含多個節(jié)點的(分布式的),那么這里怎么檢查才能保證fencing token在多個節(jié)點上是遞增的呢?
  • Martin對于fencing token的舉例中挥等,兩個fencing token到達(dá)資源服務(wù)器的順序顛倒了(小的fencing token后到了)切威,這時資源服務(wù)器檢查出了這一問題刺彩。如果客戶端1和客戶端2都發(fā)生了GC pause知押,兩個fencing token都延遲了袖外,它們幾乎同時到達(dá)了資源服務(wù)器,但保持了順序雳刺,那么資源服務(wù)器是不是就檢查不出問題了巡蘸?這時對于資源的訪問是不是就發(fā)生沖突了?
  • 分布式鎖+fencing的方案是絕對正確的嗎默伍?能證明嗎训措?

===========下半部===========

自從我寫完這個話題的上半部分之后叉寂,就感覺頭腦中出現(xiàn)了許多細(xì)小的聲音鸭丛,久久揮之不去章母。它們就像是在為了一些雞毛蒜皮的小事而相互爭吵個不停。的確,有關(guān)分布式的話題就是這樣蚪缀,瑣碎異常秫逝,而且每個人說的話聽起來似乎都有道理。

今天询枚,我們就繼續(xù)探討這個話題的后半部分违帆。本文中,我們將從antirez反駁Martin Kleppmann的觀點開始講起金蜀,然后會涉及到Hacker News上出現(xiàn)的一些討論內(nèi)容刷后,接下來我們還會討論到基于Zookeeper和Chubby的分布式鎖是怎樣的,并和Redlock進(jìn)行一些對比渊抄。最后尝胆,我們會提到Martin對于這一事件的總結(jié)。

還沒有看過上半部分的同學(xué)护桦,請先閱讀:

antirez的反駁

Martin在發(fā)表了那篇分析分布式鎖的blog (How to do distributed locking)之后含衔,該文章在Twitter和Hacker News上引發(fā)了廣泛的討論。但人們更想聽到的是Redlock的作者antirez對此會發(fā)表什么樣的看法二庵。

Martin的那篇文章是在2016-02-08這一天發(fā)表的贪染,但據(jù)Martin說,他在公開發(fā)表文章的一星期之前就把草稿發(fā)給了antirez進(jìn)行review催享,而且他們之間通過email進(jìn)行了討論杭隙。不知道Martin有沒有意料到,antirez對于此事的反應(yīng)很快因妙,就在Martin的文章發(fā)表出來的第二天寺渗,antirez就在他的博客上貼出了他對于此事的反駁文章,名字叫”Is Redlock safe?”兰迫,地址如下:

這是高手之間的過招信殊。antirez這篇文章也條例非常清晰,并且中間涉及到大量的細(xì)節(jié)汁果。antirez認(rèn)為涡拘,Martin的文章對于Redlock的批評可以概括為兩個方面(與Martin文章的前后兩部分對應(yīng)):

  • 帶有自動過期功能的分布式鎖,必須提供某種fencing機(jī)制來保證對共享資源的真正的互斥保護(hù)据德。Redlock提供不了這樣一種機(jī)制鳄乏。
  • Redlock構(gòu)建在一個不夠安全的系統(tǒng)模型之上。它對于系統(tǒng)的記時假設(shè)(timing assumption)有比較強(qiáng)的要求棘利,而這些要求在現(xiàn)實的系統(tǒng)中是無法保證的橱野。

antirez對這兩方面分別進(jìn)行了反駁。

首先善玫,關(guān)于fencing機(jī)制水援。antirez對于Martin的這種論證方式提出了質(zhì)疑:既然在鎖失效的情況下已經(jīng)存在一種fencing機(jī)制能繼續(xù)保持資源的互斥訪問了,那為什么還要使用一個分布式鎖并且還要求它提供那么強(qiáng)的安全性保證呢?即使退一步講蜗元,Redlock雖然提供不了Martin所講的遞增的fencing token或渤,但利用Redlock產(chǎn)生的隨機(jī)字符串(my_random_value)可以達(dá)到同樣的效果。這個隨機(jī)字符串雖然不是遞增的奕扣,但卻是唯一的薪鹦,可以稱之為unique token。antirez舉了個例子惯豆,比如池磁,你可以用它來實現(xiàn)“Check and Set”操作,原話是:

When starting to work with a shared resource, we set its state to “<token>”, then we operate the read-modify-write only if the token is still the same when we write.
(譯文:當(dāng)開始和共享資源交互的時候楷兽,我們將它的狀態(tài)設(shè)置成“<token>”地熄,然后僅在token沒改變的情況下我們才執(zhí)行“讀取-修改-寫回”操作。)

第一遍看到這個描述的時候拄养,我個人是感覺沒太看懂的离斩。“Check and Set”應(yīng)該就是我們平常聽到過的CAS操作了瘪匿,但它如何在這個場景下工作跛梗,antirez并沒有展開說(在后面講到Hacker News上的討論的時候,我們還會提到)棋弥。

然后核偿,antirez的反駁就集中在第二個方面上:關(guān)于算法在記時(timing)方面的模型假設(shè)。在我們前面分析Martin的文章時也提到過顽染,Martin認(rèn)為Redlock會失效的情況主要有三種:

  • 時鐘發(fā)生跳躍漾岳。
  • 長時間的GC pause。
  • 長時間的網(wǎng)絡(luò)延遲粉寞。

antirez肯定意識到了這三種情況對Redlock最致命的其實是第一點:時鐘發(fā)生跳躍尼荆。這種情況一旦發(fā)生,Redlock是沒法正常工作的唧垦。而對于后兩種情況來說捅儒,Redlock在當(dāng)初設(shè)計的時候已經(jīng)考慮到了,對它們引起的后果有一定的免疫力振亮。所以巧还,antirez接下來集中精力來說明通過恰當(dāng)?shù)倪\維,完全可以避免時鐘發(fā)生大的跳動坊秸,而Redlock對于時鐘的要求在現(xiàn)實系統(tǒng)中是完全可以滿足的麸祷。

Martin在提到時鐘跳躍的時候,舉了兩個可能造成時鐘跳躍的具體例子:

  • 系統(tǒng)管理員手動修改了時鐘褒搔。
  • 從NTP服務(wù)收到了一個大的時鐘更新事件阶牍。

antirez反駁說:

  • 手動修改時鐘這種人為原因喷面,不要那么做就是了。否則的話荸恕,如果有人手動修改Raft協(xié)議的持久化日志乖酬,那么就算是Raft協(xié)議它也沒法正常工作了死相。
  • 使用一個不會進(jìn)行“跳躍”式調(diào)整系統(tǒng)時鐘的ntpd程序(可能是通過恰當(dāng)?shù)呐渲茫┤谇螅瑢τ跁r鐘的修改通過多次微小的調(diào)整來完成。

而Redlock對時鐘的要求算撮,并不需要完全精確生宛,它只需要時鐘差不多精確就可以了。比如肮柜,要記時5秒陷舅,但可能實際記了4.5秒,然后又記了5.5秒审洞,有一定的誤差莱睁。不過只要誤差不超過一定范圍,這對Redlock不會產(chǎn)生影響芒澜。antirez認(rèn)為呢仰剿,像這樣對時鐘精度并不是很高的要求,在實際環(huán)境中是完全合理的痴晦。

好了南吮,到此為止,如果你相信antirez這里關(guān)于時鐘的論斷誊酌,那么接下來antirez的分析就基本上順理成章了部凑。

關(guān)于Martin提到的能使Redlock失效的后兩種情況,Martin在分析的時候恰好犯了一個錯誤(在本文上半部分已經(jīng)提到過)碧浊。在Martin給出的那個由客戶端GC pause引發(fā)Redlock失效的例子中涂邀,這個GC pause引發(fā)的后果相當(dāng)于在鎖服務(wù)器和客戶端之間發(fā)生了長時間的消息延遲。Redlock對于這個情況是能處理的箱锐”让悖回想一下Redlock算法的具體過程,它使用起來的過程大體可以分成5步:

  1. 獲取當(dāng)前時間瑞躺。
  2. 完成獲取鎖的整個過程(與N個Redis節(jié)點交互)敷搪。
  3. 再次獲取當(dāng)前時間。
  4. 把兩個時間相減幢哨,計算獲取鎖的過程是否消耗了太長時間赡勘,導(dǎo)致鎖已經(jīng)過期了。如果沒過期捞镰,
  5. 客戶端持有鎖去訪問共享資源闸与。

在Martin舉的例子中凛辣,GC pause或網(wǎng)絡(luò)延遲,實際發(fā)生在上述第1步和第3步之間痊臭。而不管在第1步和第3步之間由于什么原因(進(jìn)程停頓或網(wǎng)絡(luò)延遲等)導(dǎo)致了大的延遲出現(xiàn)缕粹,在第4步都能被檢查出來,不會讓客戶端拿到一個它認(rèn)為有效而實際卻已經(jīng)過期的鎖拷邢。當(dāng)然袱院,這個檢查依賴系統(tǒng)時鐘沒有大的跳躍。這也就是為什么antirez在前面要對時鐘條件進(jìn)行辯護(hù)的原因瞭稼。

有人會說忽洛,在第3步之后,仍然可能會發(fā)生延遲啊环肘。沒錯欲虚,antirez承認(rèn)這一點,他對此有一段很有意思的論證悔雹,原話如下:

The delay can only happen after steps 3, resulting into the lock to be considered ok while actually expired, that is, we are back at the first problem Martin identified of distributed locks where the client fails to stop working to the shared resource before the lock validity expires. Let me tell again how this problem is common with all the distributed locks implementations, and how the token as a solution is both unrealistic and can be used with Redlock as well.
(譯文:延遲只能發(fā)生在第3步之后复哆,這導(dǎo)致鎖被認(rèn)為是有效的而實際上已經(jīng)過期了,也就是說腌零,我們回到了Martin指出的第一個問題上梯找,客戶端沒能夠在鎖的有效性過期之前完成與共享資源的交互。讓我再次申明一下莱没,這個問題對于所有的分布式鎖的實現(xiàn)是普遍存在的初肉,而且基于token的這種解決方案是不切實際的,但也能和Redlock一起用饰躲。)

這里antirez所說的“Martin指出的第一個問題”具體是什么呢牙咏?在本文上半部分我們提到過,Martin的文章分為兩大部分嘹裂,其中前半部分與Redlock沒有直接關(guān)系妄壶,而是指出了任何一種帶自動過期功能的分布式鎖在沒有提供fencing機(jī)制的前提下都有可能失效。這里antirez所說的就是指的Martin的文章的前半部分寄狼。換句話說丁寄,對于大延遲給Redlock帶來的影響,恰好與Martin在文章的前半部分針對所有的分布式鎖所做的分析是一致的泊愧,而這種影響不單單針對Redlock伊磺。Redlock的實現(xiàn)已經(jīng)保證了它是和其它任何分布式鎖的安全性是一樣的。當(dāng)然删咱,與其它“更完美”的分布式鎖相比屑埋,Redlock似乎提供不了Martin提出的那種遞增的token,但antirez在前面已經(jīng)分析過了痰滋,關(guān)于token的這種論證方式本身就是“不切實際”的摘能,或者退一步講续崖,Redlock能提供的unique token也能夠提供完全一樣的效果。

另外团搞,關(guān)于大延遲對Redlock的影響严望,antirez和Martin在Twitter上有下面的對話:

antirez:
@martinkl so I wonder if after my reply, we can at least agree about unbound messages delay to don’t cause any harm.

Martin:
@antirez Agree about message delay between app and lock server. Delay between app and resource being accessed is still problematic.

(譯文:
antirez問:我想知道,在我發(fā)文回復(fù)之后逻恐,我們能否在一點上達(dá)成一致像吻,就是大的消息延遲不會給Redlock的運行造成損害。
Martin答:對于客戶端和鎖服務(wù)器之間的消息延遲梢莽,我同意你的觀點萧豆。但客戶端和被訪問資源之間的延遲還是有問題的奸披。)

通過這段對話可以看出昏名,對于Redlock在第4步所做的鎖有效性的檢查,Martin是予以肯定的阵面。但他認(rèn)為客戶端和資源服務(wù)器之間的延遲還是會帶來問題的轻局。Martin在這里說的有點模糊。就像antirez前面分析的样刷,客戶端和資源服務(wù)器之間的延遲仑扑,對所有的分布式鎖的實現(xiàn)都會帶來影響,這不單單是Redlock的問題了置鼻。

以上就是antirez在blog中所說的主要內(nèi)容镇饮。有一些點值得我們注意一下:

  • antirez是同意大的系統(tǒng)時鐘跳躍會造成Redlock失效的。在這一點上箕母,他與Martin的觀點的不同在于储藐,他認(rèn)為在實際系統(tǒng)中是可以避免大的時鐘跳躍的。當(dāng)然嘶是,這取決于基礎(chǔ)設(shè)施和運維方式钙勃。
  • antirez在設(shè)計Redlock的時候,是充分考慮了網(wǎng)絡(luò)延遲和程序停頓所帶來的影響的聂喇。但是辖源,對于客戶端和資源服務(wù)器之間的延遲(即發(fā)生在算法第3步之后的延遲),antirez是承認(rèn)所有的分布式鎖的實現(xiàn)希太,包括Redlock克饶,是沒有什么好辦法來應(yīng)對的。

討論進(jìn)行到這誊辉,Martin和antirez之間誰對誰錯其實并不是那么重要了矾湃。只要我們能夠?qū)edlock(或者其它分布式鎖)所能提供的安全性的程度有充分的了解,那么我們就能做出自己的選擇了芥映。

Hacker News上的一些討論

針對Martin和antirez的兩篇blog洲尊,很多技術(shù)人員在Hacker News上展開了激烈的討論远豺。這些討論所在地址如下:

在Hacker News上,antirez積極參與了討論坞嘀,而Martin則始終置身事外躯护。

下面我把這些討論中一些有意思的點拿出來與大家一起分享一下(集中在對于fencing token機(jī)制的討論上)。

關(guān)于antirez提出的“Check and Set”操作丽涩,他在blog里并沒有詳加說明棺滞。果然,在Hacker News上就有人出來問了矢渊。antirez給出的答復(fù)如下:

You want to modify locked resource X. You set X.currlock = token. Then you read, do whatever you want, and when you write, you “write-if-currlock == token”. If another client did X.currlock = somethingelse, the transaction fails.

翻譯一下可以這樣理解:假設(shè)你要修改資源X继准,那么遵循下面的偽碼所定義的步驟。

  1. 先設(shè)置X.currlock = token矮男。
  2. 讀出資源X(包括它的值和附帶的X.currlock)移必。
  3. 按照”write-if-currlock == token”的邏輯,修改資源X的值毡鉴。意思是說崔泵,如果對X進(jìn)行修改的時候,X.currlock仍然和當(dāng)初設(shè)置進(jìn)去的token相等猪瞬,那么才進(jìn)行修改憎瘸;如果這時X.currlock已經(jīng)是其它值了,那么說明有另外一方也在試圖進(jìn)行修改操作陈瘦,那么放棄當(dāng)前的修改幌甘,從而避免沖突。

隨后Hacker News上一位叫viraptor的用戶提出了異議痊项,它給出了這樣一個執(zhí)行序列:

  • A: X.currlock = Token_ID_A
  • A: resource read
  • A: is X.currlock still Token_ID_A? yes
  • B: X.currlock = Token_ID_B
  • B: resource read
  • B: is X.currlock still Token_ID_B? yes
  • B: resource write
  • A: resource write

到了最后兩步锅风,兩個客戶端A和B同時進(jìn)行寫操作,沖突了线婚。不過遏弱,這位用戶應(yīng)該是理解錯了antirez給出的修改過程了。按照antirez的意思塞弊,判斷X.currlock是否修改過和對資源的寫操作漱逸,應(yīng)該是一個原子操作。只有這樣理解才能合乎邏輯游沿,否則的話饰抒,這個過程就有嚴(yán)重的破綻。這也是為什么antirez之前會對fencing機(jī)制產(chǎn)生質(zhì)疑:既然資源服務(wù)器本身都能提供互斥的原子操作了诀黍,為什么還需要一個分布式鎖呢袋坑?因此,antirez認(rèn)為這種fencing機(jī)制是很累贅的眯勾,他之所以還是提出了這種“Check and Set”操作枣宫,只是為了證明在提供fencing token這一點上婆誓,Redlock也能做到。但是也颤,這里仍然有一些不明確的地方洋幻,如果將”write-if-currlock == token”看做是原子操作的話,這個邏輯勢必要在資源服務(wù)器上執(zhí)行翅娶,那么第二步為什么還要“讀出資源X”呢文留?除非這個“讀出資源X”的操作也是在資源服務(wù)器上執(zhí)行,它包含在“判斷-寫回”這個原子操作里面竭沫。而假如不這樣理解的話燥翅,“讀取-判斷-寫回”這三個操作都放在客戶端執(zhí)行,那么看不出它們?nèi)绾尾拍軐崿F(xiàn)原子性操作蜕提。在下面的討論中森书,我們暫時忽略“讀出資源X”這一步。

這個基于random token的“Check and Set”操作贯溅,如果與Martin提出的遞增的fencing token對比一下的話拄氯,至少有兩點不同:

  • “Check and Set”對于寫操作要分成兩步來完成(設(shè)置token、判斷-寫回)它浅,而遞增的fencing token機(jī)制只需要一步(帶著token向資源服務(wù)器發(fā)起寫請求)。
  • 遞增的fencing token機(jī)制能保證最終操作共享資源的順序镣煮,那些延遲時間太長的操作就無法操作共享資源了姐霍。但是基于random token的“Check and Set”操作不會保證這個順序,那些延遲時間太長的操作如果后到達(dá)了典唇,它仍然有可能操作共享資源(當(dāng)然是以互斥的方式)镊折。

對于前一點不同,我們在后面的分析中會看到介衔,如果資源服務(wù)器也是分布式的恨胚,那么使用遞增的fencing token也要變成兩步。

而對于后一點操作順序上的不同炎咖,antirez認(rèn)為這個順序沒有意義赃泡,關(guān)鍵是能互斥訪問就行了。他寫下了下面的話:

So the goal is, when race conditions happen, to avoid them in some way.
……
Note also that when it happens that, because of delays, the clients are accessing concurrently, the lock ID has little to do with the order in which the operations were indented to happen.
(譯文: 我們的目標(biāo)是乘盼,當(dāng)競爭條件出現(xiàn)的時候升熊,能夠以某種方式避免。
……
還需要注意的是绸栅,當(dāng)那種競爭條件出現(xiàn)的時候级野,比如由于延遲,客戶端是同時來訪問的粹胯,鎖的ID的大小順序跟那些操作真正想執(zhí)行的順序蓖柔,是沒有什么關(guān)系的辰企。)

這里的lock ID,跟Martin說的遞增的token是一回事况鸣。

隨后蟆豫,antirez舉了一個“將名字加入列表”的操作的例子:

  • T0: Client A receives new name to add from web.
  • T0: Client B is idle
  • T1: Client A is experiencing pauses.
  • T1: Client B receives new name to add from web.
  • T2: Client A is experiencing pauses.
  • T2: Client B receives a lock with ID 1
  • T3: Client A receives a lock with ID 2

你看,兩個客戶端(其實是Web服務(wù)器)執(zhí)行“添加名字”的操作懒闷,A本來是排在B前面的十减,但獲得鎖的順序卻是B排在A前面。因此愤估,antirez說帮辟,鎖的ID的大小順序跟那些操作真正想執(zhí)行的順序,是沒有什么關(guān)系的玩焰。關(guān)鍵是能排出一個順序來由驹,能互斥訪問就行了。那么昔园,至于鎖的ID是遞增的蔓榄,還是一個random token,自然就不那么重要了默刚。

Martin提出的fencing token機(jī)制甥郑,給人留下了無盡的疑惑。這主要是因為他對于這一機(jī)制的描述缺少太多的技術(shù)細(xì)節(jié)荤西。從上面的討論可以看出澜搅,antirez對于這一機(jī)制的看法是,它跟一個random token沒有什么區(qū)別邪锌,而且勉躺,它需要資源服務(wù)器本身提供某種互斥機(jī)制,這幾乎讓分布式鎖本身的存在失去了意義觅丰。圍繞fencing token的問題饵溅,還有兩點是比較引人注目的,Hacker News上也有人提出了相關(guān)的疑問:

  • (1)關(guān)于資源服務(wù)器本身的架構(gòu)細(xì)節(jié)妇萄。
  • (2)資源服務(wù)器對于fencing token進(jìn)行檢查的實現(xiàn)細(xì)節(jié)蜕企,比如是否需要提供一種原子操作。

關(guān)于上述問題(1)嚣伐,Hacker News上有一位叫dwenzek的用戶發(fā)表了下面的評論:

…… the issue around the usage of fencing tokens to reject any late usage of a lock is unclear just because the protected resource and its access are themselves unspecified. Is the resource distributed or not? If distributed, does the resource has a mean to ensure that tokens are increasing over all the nodes? Does the resource have a mean to rollback any effects done by a client which session is interrupted by a timeout?

(譯文:…… 關(guān)于使用fencing token拒絕掉延遲請求的相關(guān)議題糖赔,是不夠清晰的,因為受保護(hù)的資源以及對它的訪問方式本身是沒有被明確定義過的轩端。資源服務(wù)是不是分布式的呢放典?如果是,資源服務(wù)有沒有一種方式能確保token在所有節(jié)點上遞增呢?對于客戶端的Session由于過期而被中斷的情況奋构,資源服務(wù)有辦法將它的影響回滾嗎壳影?)

這些疑問在Hacker News上并沒有人給出解答。而關(guān)于分布式的資源服務(wù)器架構(gòu)如何處理fencing token弥臼,另外一名分布式系統(tǒng)的專家Flavio Junqueira在他的一篇blog中有所提及(我們后面會再提到)宴咧。

關(guān)于上述問題(2),Hacker News上有一位叫reza_n的用戶發(fā)表了下面的疑問:

I understand how a fencing token can prevent out of order writes when 2 clients get the same lock. But what happens when those writes happen to arrive in order and you are doing a value modification? Don’t you still need to rely on some kind of value versioning or optimistic locking? Wouldn’t this make the use of a distributed lock unnecessary?

(譯文: 我理解當(dāng)兩個客戶端同時獲得鎖的時候fencing token是如何防止亂序的径缅。但是如果兩個寫操作恰好按序到達(dá)了掺栅,而且它們在對同一個值進(jìn)行修改,那會發(fā)生什么呢纳猪?難道不會仍然是依賴某種數(shù)據(jù)版本號或者樂觀鎖的機(jī)制氧卧?這不會讓分布式鎖變得沒有必要了嗎?)

一位叫Terr_的Hacker News用戶答:

I believe the “first” write fails, because the token being passed in is no longer “the lastest”, which indicates their lock was already released or expired.

(譯文: 我認(rèn)為“第一個”寫請求會失敗氏堤,因為它傳入的token不再是“最新的”了沙绝,這意味著鎖已經(jīng)釋放或者過期了。)

Terr_的回答到底對不對呢鼠锈?這不好說闪檬,取決于資源服務(wù)器對于fencing token進(jìn)行檢查的實現(xiàn)細(xì)節(jié)。讓我們來簡單分析一下购笆。

為了簡單起見粗悯,我們假設(shè)有一臺(先不考慮分布式的情況)通過RPC進(jìn)行遠(yuǎn)程訪問文件服務(wù)器,它無法提供對于文件的互斥訪問(否則我們就不需要分布式鎖了)∮勺溃現(xiàn)在我們按照Martin給出的說法为黎,加入fencing token的檢查邏輯。由于Martin沒有描述具體細(xì)節(jié)行您,我們猜測至少有兩種可能。

第一種可能剪廉,我們修改了文件服務(wù)器的代碼娃循,讓它能多接受一個fencing token的參數(shù),并在進(jìn)行所有處理之前加入了一個簡單的判斷邏輯斗蒋,保證只有當(dāng)前接收到的fencing token大于之前的值才允許進(jìn)行后邊的訪問捌斧。而一旦通過了這個判斷,后面的處理不變泉沾。

現(xiàn)在想象reza_n描述的場景捞蚂,客戶端1和客戶端2都發(fā)生了GC pause,兩個fencing token都延遲了跷究,它們幾乎同時到達(dá)了文件服務(wù)器姓迅,而且保持了順序。那么,我們新加入的判斷邏輯丁存,應(yīng)該對兩個請求都會放過肩杈,而放過之后它們幾乎同時在操作文件,還是沖突了解寝。既然Martin宣稱fencing token能保證分布式鎖的正確性扩然,那么上面這種可能的猜測也許是我們理解錯了。

當(dāng)然聋伦,還有第二種可能夫偶,就是我們對文件服務(wù)器確實做了比較大的改動,讓這里判斷token的邏輯和隨后對文件的處理放在一個原子操作里了觉增。這可能更接近antirez的理解兵拢。這樣的話,前面reza_n描述的場景中抑片,兩個寫操作都應(yīng)該成功卵佛。

基于ZooKeeper的分布式鎖更安全嗎?

很多人(也包括Martin在內(nèi))都認(rèn)為敞斋,如果你想構(gòu)建一個更安全的分布式鎖截汪,那么應(yīng)該使用ZooKeeper,而不是Redis植捎。那么衙解,為了對比的目的,讓我們先暫時脫離開本文的題目焰枢,討論一下基于ZooKeeper的分布式鎖能提供絕對的安全嗎蚓峦?它需要fencing token機(jī)制的保護(hù)嗎?

我們不得不提一下分布式專家Flavio Junqueira所寫的一篇blog济锄,題目叫“Note on fencing and distributed locks”暑椰,地址如下:

Flavio Junqueira是ZooKeeper的作者之一,他的這篇blog就寫在Martin和antirez發(fā)生爭論的那幾天荐绝。他在文中給出了一個基于ZooKeeper構(gòu)建分布式鎖的描述(當(dāng)然這不是唯一的方式):

  • 客戶端嘗試創(chuàng)建一個znode節(jié)點一汽,比如/lock。那么第一個客戶端就創(chuàng)建成功了低滩,相當(dāng)于拿到了鎖召夹;而其它的客戶端會創(chuàng)建失敗(znode已存在)恕沫,獲取鎖失敗监憎。
  • 持有鎖的客戶端訪問共享資源完成后,將znode刪掉婶溯,這樣其它客戶端接下來就能來獲取鎖了鲸阔。
  • znode應(yīng)該被創(chuàng)建成ephemeral的偷霉。這是znode的一個特性,它保證如果創(chuàng)建znode的那個客戶端崩潰了隶债,那么相應(yīng)的znode會被自動刪除腾它。這保證了鎖一定會被釋放。

看起來這個鎖相當(dāng)完美死讹,沒有Redlock過期時間的問題瞒滴,而且能在需要的時候讓鎖自動釋放。但仔細(xì)考察的話赞警,并不盡然妓忍。

ZooKeeper是怎么檢測出某個客戶端已經(jīng)崩潰了呢?實際上愧旦,每個客戶端都與ZooKeeper的某臺服務(wù)器維護(hù)著一個Session世剖,這個Session依賴定期的心跳(heartbeat)來維持。如果ZooKeeper長時間收不到客戶端的心跳(這個時間稱為Sesion的過期時間)笤虫,那么它就認(rèn)為Session過期了旁瘫,通過這個Session所創(chuàng)建的所有的ephemeral類型的znode節(jié)點都會被自動刪除。

設(shè)想如下的執(zhí)行序列:

  1. 客戶端1創(chuàng)建了znode節(jié)點/lock琼蚯,獲得了鎖酬凳。
  2. 客戶端1進(jìn)入了長時間的GC pause。
  3. 客戶端1連接到ZooKeeper的Session過期了遭庶。znode節(jié)點/lock被自動刪除宁仔。
  4. 客戶端2創(chuàng)建了znode節(jié)點/lock,從而獲得了鎖峦睡。
  5. 客戶端1從GC pause中恢復(fù)過來翎苫,它仍然認(rèn)為自己持有鎖。

最后榨了,客戶端1和客戶端2都認(rèn)為自己持有了鎖煎谍,沖突了。這與之前Martin在文章中描述的由于GC pause導(dǎo)致的分布式鎖失效的情況類似龙屉。

看起來粱快,用ZooKeeper實現(xiàn)的分布式鎖也不一定就是安全的。該有的問題它還是有叔扼。但是,ZooKeeper作為一個專門為分布式應(yīng)用提供方案的框架漫雷,它提供了一些非常好的特性瓜富,是Redis之類的方案所沒有的。像前面提到的ephemeral類型的znode自動刪除的功能就是一個例子降盹。

還有一個很有用的特性是ZooKeeper的watch機(jī)制与柑。這個機(jī)制可以這樣來使用,比如當(dāng)客戶端試圖創(chuàng)建/lock的時候,發(fā)現(xiàn)它已經(jīng)存在了价捧,這時候創(chuàng)建失敗丑念,但客戶端不一定就此對外宣告獲取鎖失敗〗狍客戶端可以進(jìn)入一種等待狀態(tài)脯倚,等待當(dāng)/lock節(jié)點被刪除的時候,ZooKeeper通過watch機(jī)制通知它嵌屎,這樣它就可以繼續(xù)完成創(chuàng)建操作(獲取鎖)推正。這可以讓分布式鎖在客戶端用起來就像一個本地的鎖一樣:加鎖失敗就阻塞住,直到獲取到鎖為止宝惰。這樣的特性Redlock就無法實現(xiàn)植榕。

小結(jié)一下,基于ZooKeeper的鎖和基于Redis的鎖相比在實現(xiàn)特性上有兩個不同:

  • 在正常情況下尼夺,客戶端可以持有鎖任意長的時間尊残,這可以確保它做完所有需要的資源訪問操作之后再釋放鎖。這避免了基于Redis的鎖對于有效時間(lock validity time)到底設(shè)置多長的兩難問題淤堵。實際上寝衫,基于ZooKeeper的鎖是依靠Session(心跳)來維持鎖的持有狀態(tài)的,而Redis不支持Sesion粘勒。
  • 基于ZooKeeper的鎖支持在獲取鎖失敗之后等待鎖重新釋放的事件竞端。這讓客戶端對鎖的使用更加靈活。

順便提一下庙睡,如上所述的基于ZooKeeper的分布式鎖的實現(xiàn)事富,并不是最優(yōu)的。它會引發(fā)“herd effect”(羊群效應(yīng))乘陪,降低獲取鎖的性能统台。一個更好的實現(xiàn)參見下面鏈接:

我們重新回到Flavio Junqueira對于fencing token的分析。Flavio Junqueira指出啡邑,fencing token機(jī)制本質(zhì)上是要求客戶端在每次訪問一個共享資源的時候贱勃,在執(zhí)行任何操作之前,先對資源進(jìn)行某種形式的“標(biāo)記”(mark)操作谤逼,這個“標(biāo)記”能保證持有舊的鎖的客戶端請求(如果延遲到達(dá)了)無法操作資源贵扰。這種標(biāo)記操作可以是很多形式,fencing token是其中比較典型的一個流部。

隨后Flavio Junqueira提到用遞增的epoch number(相當(dāng)于Martin的fencing token)來保護(hù)共享資源戚绕。而對于分布式的資源,為了方便討論枝冀,假設(shè)分布式資源是一個小型的多備份的數(shù)據(jù)存儲(a small replicated data store)舞丛,執(zhí)行寫操作的時候需要向所有節(jié)點上寫數(shù)據(jù)耘子。最簡單的做標(biāo)記的方式,就是在對資源進(jìn)行任何操作之前球切,先把epoch number標(biāo)記到各個資源節(jié)點上去谷誓。這樣,各個節(jié)點就保證了舊的(也就是小的)epoch number無法操作數(shù)據(jù)吨凑。

當(dāng)然捍歪,這里再展開討論下去可能就涉及到了這個數(shù)據(jù)存儲服務(wù)的實現(xiàn)細(xì)節(jié)了。比如在實際系統(tǒng)中怀骤,可能為了容錯费封,只要上面講的標(biāo)記和寫入操作在多數(shù)節(jié)點上完成就算成功完成了(Flavio Junqueira并沒有展開去講)。在這里我們能看到的蒋伦,最重要的弓摘,是這種標(biāo)記操作如何起作用的方式。這有點類似于Paxos協(xié)議(Paxos協(xié)議要求每個proposal對應(yīng)一個遞增的數(shù)字痕届,執(zhí)行accept請求之前先執(zhí)行prepare請求)韧献。antirez提出的random token的方式顯然不符合Flavio Junqueira對于“標(biāo)記”操作的定義,因為它無法區(qū)分新的token和舊的token研叫。只有遞增的數(shù)字才能確保最終收斂到最新的操作結(jié)果上锤窑。

在這個分布式數(shù)據(jù)存儲服務(wù)(共享資源)的例子中,客戶端在標(biāo)記完成之后執(zhí)行寫入操作的時候嚷炉,存儲服務(wù)的節(jié)點需要判斷epoch number是不是最新渊啰,然后確定能不能執(zhí)行寫入操作。如果按照上一節(jié)我們的分析思路申屹,這里的epoch判斷和接下來的寫入操作绘证,是不是在一個原子操作里呢?根據(jù)Flavio Junqueira的相關(guān)描述哗讥,我們相信嚷那,應(yīng)該是原子的。那么既然資源本身可以提供原子互斥操作了杆煞,那么分布式鎖還有存在的意義嗎魏宽?應(yīng)該說有【龊酰客戶端可以利用分布式鎖有效地避免沖突队询,等待寫入機(jī)會,這對于包含多個節(jié)點的分布式資源尤其有用(當(dāng)然构诚,是出于效率的原因)娘摔。

Chubby的分布式鎖是怎樣做fencing的?

提到分布式鎖唤反,就不能不提Google的Chubby凳寺。

Chubby是Google內(nèi)部使用的分布式鎖服務(wù),有點類似于ZooKeeper彤侍,但也存在很多差異肠缨。Chubby對外公開的資料,主要是一篇論文盏阶,叫做“The Chubby lock service for loosely-coupled distributed systems”晒奕,下載地址如下:

另外,YouTube上有一個的講Chubby的talk名斟,也很不錯脑慧,播放地址:

Chubby自然也考慮到了延遲造成的鎖失效的問題。論文里有一段描述如下:

a process holding a lock L may issue a request R, but then fail. Another process may ac- quire L and perform some action before R arrives at its destination. If R later arrives, it may be acted on without the protection of L, and potentially on inconsistent data.

(譯文: 一個進(jìn)程持有鎖L砰盐,發(fā)起了請求R闷袒,但是請求失敗了。另一個進(jìn)程獲得了鎖L并在請求R到達(dá)目的方之前執(zhí)行了一些動作岩梳。如果后來請求R到達(dá)了囊骤,它就有可能在沒有鎖L保護(hù)的情況下進(jìn)行操作,帶來數(shù)據(jù)不一致的潛在風(fēng)險冀值。)

這跟Martin的分析大同小異也物。

Chubby給出的用于解決(緩解)這一問題的機(jī)制稱為sequencer,類似于fencing token機(jī)制列疗。鎖的持有者可以隨時請求一個sequencer滑蚯,這是一個字節(jié)串,它由三部分組成:

  • 鎖的名字抵栈。
  • 鎖的獲取模式(排他鎖還是共享鎖)告材。
  • lock generation number(一個64bit的單調(diào)遞增數(shù)字)。作用相當(dāng)于fencing token或epoch number竭讳。

客戶端拿到sequencer之后创葡,在操作資源的時候把它傳給資源服務(wù)器。然后绢慢,資源服務(wù)器負(fù)責(zé)對sequencer的有效性進(jìn)行檢查灿渴。檢查可以有兩種方式:

  • 調(diào)用Chubby提供的API,CheckSequencer()胰舆,將整個sequencer傳進(jìn)去進(jìn)行檢查骚露。這個檢查是為了保證客戶端持有的鎖在進(jìn)行資源訪問的時候仍然有效唬党。
  • 將客戶端傳來的sequencer與資源服務(wù)器當(dāng)前觀察到的最新的sequencer進(jìn)行對比檢查换薄“峦荩可以理解為與Martin描述的對于fencing token的檢查類似冯乘。

當(dāng)然搪搏,如果由于兼容的原因,資源服務(wù)本身不容易修改飘哨,那么Chubby還提供了一種機(jī)制:

  • lock-delay定拟。Chubby允許客戶端為持有的鎖指定一個lock-delay的時間值(默認(rèn)是1分鐘)。當(dāng)Chubby發(fā)現(xiàn)客戶端被動失去聯(lián)系的時候蹋嵌,并不會立即釋放鎖育瓜,而是會在lock-delay指定的時間內(nèi)阻止其它客戶端獲得這個鎖。這是為了在把鎖分配給新的客戶端之前栽烂,讓之前持有鎖的客戶端有充分的時間把請求隊列排空(draining the queue)躏仇,盡量防止出現(xiàn)延遲到達(dá)的未處理請求。

可見腺办,為了應(yīng)對鎖失效問題焰手,Chubby提供的三種處理方式:CheckSequencer()檢查、與上次最新的sequencer對比怀喉、lock-delay书妻,它們對于安全性的保證是從強(qiáng)到弱的。而且磺送,這些處理方式本身都沒有保證提供絕對的正確性(correctness)驻子。但是,Chubby確實提供了單調(diào)遞增的lock generation number估灿,這就允許資源服務(wù)器在需要的時候崇呵,利用它提供更強(qiáng)的安全性保障。

關(guān)于時鐘

在Martin與antirez的這場爭論中馅袁,沖突最為嚴(yán)重的就是對于系統(tǒng)時鐘的假設(shè)是不是合理的問題域慷。Martin認(rèn)為系統(tǒng)時鐘難免會發(fā)生跳躍(這與分布式算法的異步模型相符),而antirez認(rèn)為在實際中系統(tǒng)時鐘可以保證不發(fā)生大的跳躍汗销。

Martin對于這一分歧發(fā)表了如下看法(原話):

So, fundamentally, this discussion boils down to whether it is reasonable to make timing assumptions for ensuring safety properties. I say no, Salvatore says yes — but that’s ok. Engineering discussions rarely have one right answer.

(譯文: 從根本上來說犹褒,這場討論最后歸結(jié)到了一個問題上:為了確保安全性而做出的記時假設(shè)到底是否合理。我認(rèn)為不合理弛针,而antirez認(rèn)為合理 —— 但是這也沒關(guān)系叠骑。工程問題的討論很少只有一個正確答案。)

那么削茁,在實際系統(tǒng)中宙枷,時鐘到底是否可信呢?對此茧跋,Julia Evans專門寫了一篇文章慰丛,“TIL: clock skew exists”,總結(jié)了很多跟時鐘偏移有關(guān)的實際資料瘾杭,并進(jìn)行了分析诅病。這篇文章地址:

Julia Evans在文章最后得出的結(jié)論是:

clock skew is real (時鐘偏移在現(xiàn)實中是存在的)

Martin的事后總結(jié)

我們前面提到過,當(dāng)各方的爭論在激烈進(jìn)行的時候,Martin幾乎始終置身事外贤笆。但是Martin在這件事過去之后蝇棉,把這個事件的前后經(jīng)過總結(jié)成了一個很長的故事線。如果你想最全面地了解這個事件發(fā)生的前后經(jīng)過苏潜,那么建議去讀讀Martin的這個總結(jié):

在這個故事總結(jié)的最后银萍,Martin寫下了很多感性的評論:

For me, this is the most important point: I don’t care who is right or wrong in this debate — I care about learning from others’ work, so that we can avoid repeating old mistakes, and make things better in future. So much great work has already been done for us: by standing on the shoulders of giants, we can build better software.
……
By all means, test ideas by arguing them and checking whether they stand up to scrutiny by others. That’s part of the learning process. But the goal should be to learn, not to convince others that you are right. Sometimes that just means to stop and think for a while.

(譯文:
對我來說最重要的一點在于:我并不在乎在這場辯論中誰對誰錯 —— 我只關(guān)心從其他人的工作中學(xué)到的東西,以便我們能夠避免重蹈覆轍恤左,并讓未來更加美好。前人已經(jīng)為我們創(chuàng)造出了許多偉大的成果:站在巨人的肩膀上搀绣,我們得以構(gòu)建更棒的軟件飞袋。
……
對于任何想法,務(wù)必要詳加檢驗链患,通過論證以及檢查它們是否經(jīng)得住別人的詳細(xì)審查巧鸭。那是學(xué)習(xí)過程的一部分。但目標(biāo)應(yīng)該是為了獲得知識麻捻,而不應(yīng)該是為了說服別人相信你自己是對的纲仍。有時候,那只不過意味著停下來贸毕,好好地想一想郑叠。)


關(guān)于分布式鎖的這場爭論,我們已經(jīng)完整地做了回顧和分析明棍。

按照鎖的兩種用途乡革,如果僅是為了效率(efficiency),那么你可以自己選擇你喜歡的一種分布式鎖的實現(xiàn)摊腋。當(dāng)然沸版,你需要清楚地知道它在安全性上有哪些不足,以及它會帶來什么后果兴蒸。而如果你是為了正確性(correctness)视粮,那么請慎之又慎。在本文的討論中橙凳,我們在分布式鎖的正確性上走得最遠(yuǎn)的地方蕾殴,要數(shù)對于ZooKeeper分布式鎖、單調(diào)遞增的epoch number以及對分布式資源進(jìn)行標(biāo)記的分析了痕惋。請仔細(xì)審查相關(guān)的論證区宇。

Martin為我們留下了不少疑問,尤其是他提出的fencing token機(jī)制值戳。他在blog中提到议谷,會在他的新書《Designing Data-Intensive Applications》的第8章和第9章再詳加論述。目前堕虹,這本書尚在預(yù)售當(dāng)中卧晓。我感覺芬首,這會是一本值得一讀的書,它不同于為了出名或賺錢而出版的那種短平快的書籍逼裆∮羯裕可以看出作者在這本書上投入了巨大的精力。

轉(zhuǎn)載注: 到現(xiàn)在已可下載《Designing Data-Intensive Applications》全文電子書胜宇,下載地址:https://download.csdn.net/download/paincupid/9952891

最后耀怜,我相信,這個討論還遠(yuǎn)沒有結(jié)束桐愉。分布式鎖(Distributed Locks)和相應(yīng)的fencing方案财破,可以作為一個長期的課題,隨著我們對分布式系統(tǒng)的認(rèn)識逐漸增加从诲,可以再來慢慢地思考它左痢。思考它更深層的本質(zhì),以及它在理論上的證明系洛。

(完)

感謝:

由衷地感謝幾位朋友花了寶貴的時間對本文草稿所做的review:CacheCloud的作者付磊俊性,快手的李偉博,阿里的李波描扯。當(dāng)然定页,文中如果還有錯漏,由我本人負(fù)責(zé)-荆烈。

其它精選文章:

原創(chuàng)文章拯勉,轉(zhuǎn)載請注明出處,并包含下面的二維碼憔购!否則拒絕轉(zhuǎn)載宫峦!

本文鏈接:http://zhangtielei.com/posts/blog-redlock-reasoning.html
本文鏈接:http://zhangtielei.com/posts/blog-redlock-reasoning-part2.html

我的微信公眾號: tielei-blog (張鐵蕾)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市玫鸟,隨后出現(xiàn)的幾起案子导绷,更是在濱河造成了極大的恐慌,老刑警劉巖屎飘,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妥曲,死亡現(xiàn)場離奇詭異,居然都是意外死亡钦购,警方通過查閱死者的電腦和手機(jī)檐盟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來押桃,“玉大人葵萎,你說我怎么就攤上這事。” “怎么了羡忘?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵谎痢,是天一觀的道長。 經(jīng)常有香客問我卷雕,道長节猿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任漫雕,我火速辦了婚禮滨嘱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浸间。我一直安慰自己九孩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布发框。 她就那樣靜靜地躺著,像睡著了一般煤墙。 火紅的嫁衣襯著肌膚如雪梅惯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天仿野,我揣著相機(jī)與錄音铣减,去河邊找鬼。 笑死脚作,一個胖子當(dāng)著我的面吹牛葫哗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播球涛,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼劣针,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亿扁?” 一聲冷哼從身側(cè)響起捺典,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎从祝,沒想到半個月后襟己,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡牍陌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年擎浴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毒涧。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡贮预,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萌狂,我是刑警寧澤档玻,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站茫藏,受9級特大地震影響误趴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜务傲,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一凉当、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧售葡,春花似錦看杭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尖阔,卻和暖如春贮缅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背介却。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工谴供, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人齿坷。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓桂肌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親永淌。 傳聞我的和親對象是個殘疾皇子崎场,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353