原文鏈接跳轉(zhuǎn) -> 張鐵蕾
今天,我們就繼續(xù)探討這個(gè)話題的后半部分。本文中,我們將從antirez反駁Martin Kleppmann的觀點(diǎn)開(kāi)始講起,然后會(huì)涉及到Hacker News上出現(xiàn)的一些討論內(nèi)容,接下來(lái)我們還會(huì)討論到基于Zookeeper和Chubby的分布式鎖是怎樣的,并和Redlock進(jìn)行一些對(duì)比。最后,我們會(huì)提到Martin對(duì)于這一事件的總結(jié)。
還沒(méi)有看過(guò)上半部分的同學(xué),請(qǐng)先閱讀:
- 基于Redis的分布式鎖到底安全嗎(上)
antirez的反駁
Martin在發(fā)表了那篇分析分布式鎖的blog (How to do distributed locking)之后,該文章在Twitter和Hacker News上引發(fā)了廣泛的討論。但人們更想聽(tīng)到的是Redlock的作者antirez對(duì)此會(huì)發(fā)表什么樣的看法。
Martin的那篇文章是在2016-02-08這一天發(fā)表的,但據(jù)Martin說(shuō),他在公開(kāi)發(fā)表文章的一星期之前就把草稿發(fā)給了antirez進(jìn)行review,而且他們之間通過(guò)email進(jìn)行了討論。不知道Martin有沒(méi)有意料到,antirez對(duì)于此事的反應(yīng)很快,就在Martin的文章發(fā)表出來(lái)的第二天,antirez就在他的博客上貼出了他對(duì)于此事的反駁文章,名字叫"Is Redlock safe?",地址如下:
- http://antirez.com/news/101
這是高手之間的過(guò)招。antirez這篇文章也條例非常清晰,并且中間涉及到大量的細(xì)節(jié)。antirez認(rèn)為,Martin的文章對(duì)于Redlock的批評(píng)可以概括為兩個(gè)方面(與Martin文章的前后兩部分對(duì)應(yīng)):
- 帶有自動(dòng)過(guò)期功能的分布式鎖,必須提供某種fencing機(jī)制來(lái)保證對(duì)共享資源的真正的互斥保護(hù)。Redlock提供不了這樣一種機(jī)制。
- Redlock構(gòu)建在一個(gè)不夠安全的系統(tǒng)模型之上。它對(duì)于系統(tǒng)的記時(shí)假設(shè)(timing assumption)有比較強(qiáng)的要求,而這些要求在現(xiàn)實(shí)的系統(tǒng)中是無(wú)法保證的。
antirez對(duì)這兩方面分別進(jìn)行了反駁。
首先,關(guān)于fencing機(jī)制。antirez對(duì)于Martin的這種論證方式提出了質(zhì)疑:既然在鎖失效的情況下已經(jīng)存在一種fencing機(jī)制能繼續(xù)保持資源的互斥訪問(wèn)了,那為什么還要使用一個(gè)分布式鎖并且還要求它提供那么強(qiáng)的安全性保證呢?即使退一步講,Redlock雖然提供不了Martin所講的遞增的fencing token,但利用Redlock產(chǎn)生的隨機(jī)字符串(my_random_value
)可以達(dá)到同樣的效果。這個(gè)隨機(jī)字符串雖然不是遞增的,但卻是唯一的,可以稱之為unique token。antirez舉了個(gè)例子,比如,你可以用它來(lái)實(shí)現(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)開(kāi)始和共享資源交互的時(shí)候,我們將它的狀態(tài)設(shè)置成“<token>
”,然后僅在token沒(méi)改變的情況下我們才執(zhí)行“讀取-修改-寫(xiě)回”操作。)
第一遍看到這個(gè)描述的時(shí)候,我個(gè)人是感覺(jué)沒(méi)太看懂的?!癈heck and Set”應(yīng)該就是我們平常聽(tīng)到過(guò)的CAS操作了,但它如何在這個(gè)場(chǎng)景下工作,antirez并沒(méi)有展開(kāi)說(shuō)(在后面講到Hacker News上的討論的時(shí)候,我們還會(huì)提到)。
然后,antirez的反駁就集中在第二個(gè)方面上:關(guān)于算法在記時(shí)(timing)方面的模型假設(shè)。在我們前面分析Martin的文章時(shí)也提到過(guò),Martin認(rèn)為Redlock會(huì)失效的情況主要有三種:
- 時(shí)鐘發(fā)生跳躍。
- 長(zhǎng)時(shí)間的GC pause。
- 長(zhǎng)時(shí)間的網(wǎng)絡(luò)延遲。
antirez肯定意識(shí)到了這三種情況對(duì)Redlock最致命的其實(shí)是第一點(diǎn):時(shí)鐘發(fā)生跳躍。這種情況一旦發(fā)生,Redlock是沒(méi)法正常工作的。而對(duì)于后兩種情況來(lái)說(shuō),Redlock在當(dāng)初設(shè)計(jì)的時(shí)候已經(jīng)考慮到了,對(duì)它們引起的后果有一定的免疫力。所以,antirez接下來(lái)集中精力來(lái)說(shuō)明通過(guò)恰當(dāng)?shù)倪\(yùn)維,完全可以避免時(shí)鐘發(fā)生大的跳動(dòng),而Redlock對(duì)于時(shí)鐘的要求在現(xiàn)實(shí)系統(tǒng)中是完全可以滿足的。
Martin在提到時(shí)鐘跳躍的時(shí)候,舉了兩個(gè)可能造成時(shí)鐘跳躍的具體例子:
- 系統(tǒng)管理員手動(dòng)修改了時(shí)鐘。
- 從NTP服務(wù)收到了一個(gè)大的時(shí)鐘更新事件。
antirez反駁說(shuō):
- 手動(dòng)修改時(shí)鐘這種人為原因,不要那么做就是了。否則的話,如果有人手動(dòng)修改Raft協(xié)議的持久化日志,那么就算是Raft協(xié)議它也沒(méi)法正常工作了。
- 使用一個(gè)不會(huì)進(jìn)行“跳躍”式調(diào)整系統(tǒng)時(shí)鐘的ntpd程序(可能是通過(guò)恰當(dāng)?shù)呐渲茫瑢?duì)于時(shí)鐘的修改通過(guò)多次微小的調(diào)整來(lái)完成。
而Redlock對(duì)時(shí)鐘的要求,并不需要完全精確,它只需要時(shí)鐘差不多精確就可以了。比如,要記時(shí)5秒,但可能實(shí)際記了4.5秒,然后又記了5.5秒,有一定的誤差。不過(guò)只要誤差不超過(guò)一定范圍,這對(duì)Redlock不會(huì)產(chǎn)生影響。antirez認(rèn)為呢,像這樣對(duì)時(shí)鐘精度并不是很高的要求,在實(shí)際環(huán)境中是完全合理的。
好了,到此為止,如果你相信antirez這里關(guān)于時(shí)鐘的論斷,那么接下來(lái)antirez的分析就基本上順理成章了。
關(guān)于Martin提到的能使Redlock失效的后兩種情況,Martin在分析的時(shí)候恰好犯了一個(gè)錯(cuò)誤(在本文上半部分已經(jīng)提到過(guò))。在Martin給出的那個(gè)由客戶端GC pause引發(fā)Redlock失效的例子中,這個(gè)GC pause引發(fā)的后果相當(dāng)于在鎖服務(wù)器和客戶端之間發(fā)生了長(zhǎng)時(shí)間的消息延遲。Redlock對(duì)于這個(gè)情況是能處理的。回想一下Redlock算法的具體過(guò)程,它使用起來(lái)的過(guò)程大體可以分成5步:
- 獲取當(dāng)前時(shí)間。
- 完成獲取鎖的整個(gè)過(guò)程(與N個(gè)Redis節(jié)點(diǎn)交互)。
- 再次獲取當(dāng)前時(shí)間。
- 把兩個(gè)時(shí)間相減,計(jì)算獲取鎖的過(guò)程是否消耗了太長(zhǎng)時(shí)間,導(dǎo)致鎖已經(jīng)過(guò)期了。如果沒(méi)過(guò)期,
- 客戶端持有鎖去訪問(wèn)共享資源。
在Martin舉的例子中,GC pause或網(wǎng)絡(luò)延遲,實(shí)際發(fā)生在上述第1步和第3步之間。而不管在第1步和第3步之間由于什么原因(進(jìn)程停頓或網(wǎng)絡(luò)延遲等)導(dǎo)致了大的延遲出現(xiàn),在第4步都能被檢查出來(lái),不會(huì)讓客戶端拿到一個(gè)它認(rèn)為有效而實(shí)際卻已經(jīng)過(guò)期的鎖。當(dāng)然,這個(gè)檢查依賴系統(tǒng)時(shí)鐘沒(méi)有大的跳躍。這也就是為什么antirez在前面要對(duì)時(shí)鐘條件進(jìn)行辯護(hù)的原因。
有人會(huì)說(shuō),在第3步之后,仍然可能會(huì)發(fā)生延遲啊。沒(méi)錯(cuò),antirez承認(rèn)這一點(diǎn),他對(duì)此有一段很有意思的論證,原話如下:
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)為是有效的而實(shí)際上已經(jīng)過(guò)期了,也就是說(shuō),我們回到了Martin指出的第一個(gè)問(wèn)題上,客戶端沒(méi)能夠在鎖的有效性過(guò)期之前完成與共享資源的交互。讓我再次申明一下,這個(gè)問(wèn)題對(duì)于_所有的分布式鎖的實(shí)現(xiàn)_是普遍存在的,而且基于token的這種解決方案是不切實(shí)際的,但也能和Redlock一起用。)
這里antirez所說(shuō)的“Martin指出的第一個(gè)問(wèn)題”具體是什么呢?在本文上半部分我們提到過(guò),Martin的文章分為兩大部分,其中前半部分與Redlock沒(méi)有直接關(guān)系,而是指出了任何一種帶自動(dòng)過(guò)期功能的分布式鎖在沒(méi)有提供fencing機(jī)制的前提下都有可能失效。這里antirez所說(shuō)的就是指的Martin的文章的前半部分。換句話說(shuō),對(duì)于大延遲給Redlock帶來(lái)的影響,恰好與Martin在文章的前半部分針對(duì)所有的分布式鎖所做的分析是一致的,而這種影響不單單針對(duì)Redlock。Redlock的實(shí)現(xiàn)已經(jīng)保證了它是和其它任何分布式鎖的安全性是一樣的。當(dāng)然,與其它“更完美”的分布式鎖相比,Redlock似乎提供不了Martin提出的那種遞增的token,但antirez在前面已經(jīng)分析過(guò)了,關(guān)于token的這種論證方式本身就是“不切實(shí)際”的,或者退一步講,Redlock能提供的unique token也能夠提供完全一樣的效果。
另外,關(guān)于大延遲對(duì)Redlock的影響,antirez和Martin在Twitter上有下面的對(duì)話:
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問(wèn):我想知道,在我發(fā)文回復(fù)之后,我們能否在一點(diǎn)上達(dá)成一致,就是大的消息延遲不會(huì)給Redlock的運(yùn)行造成損害。Martin答:對(duì)于客戶端和鎖服務(wù)器之間的消息延遲,我同意你的觀點(diǎn)。但客戶端和被訪問(wèn)資源之間的延遲還是有問(wèn)題的。)
通過(guò)這段對(duì)話可以看出,對(duì)于Redlock在第4步所做的鎖有效性的檢查,Martin是予以肯定的。但他認(rèn)為客戶端和資源服務(wù)器之間的延遲還是會(huì)帶來(lái)問(wèn)題的。Martin在這里說(shuō)的有點(diǎn)模糊。就像antirez前面分析的,客戶端和資源服務(wù)器之間的延遲,對(duì)所有的分布式鎖的實(shí)現(xiàn)都會(huì)帶來(lái)影響,這不單單是Redlock的問(wèn)題了。
以上就是antirez在blog中所說(shuō)的主要內(nèi)容。有一些點(diǎn)值得我們注意一下:
- antirez是同意大的系統(tǒng)時(shí)鐘跳躍會(huì)造成Redlock失效的。在這一點(diǎn)上,他與Martin的觀點(diǎn)的不同在于,他認(rèn)為在實(shí)際系統(tǒng)中是可以避免大的時(shí)鐘跳躍的。當(dāng)然,這取決于基礎(chǔ)設(shè)施和運(yùn)維方式。
- antirez在設(shè)計(jì)Redlock的時(shí)候,是充分考慮了網(wǎng)絡(luò)延遲和程序停頓所帶來(lái)的影響的。但是,對(duì)于客戶端和資源服務(wù)器之間的延遲(即發(fā)生在算法第3步之后的延遲),antirez是承認(rèn)所有的分布式鎖的實(shí)現(xiàn),包括Redlock,是沒(méi)有什么好辦法來(lái)應(yīng)對(duì)的。
討論進(jìn)行到這,Martin和antirez之間誰(shuí)對(duì)誰(shuí)錯(cuò)其實(shí)并不是那么重要了。只要我們能夠?qū)edlock(或者其它分布式鎖)所能提供的安全性的程度有充分的了解,那么我們就能做出自己的選擇了。
Hacker News上的一些討論
針對(duì)Martin和antirez的兩篇blog,很多技術(shù)人員在Hacker News上展開(kāi)了激烈的討論。這些討論所在地址如下:
- 針對(duì)Martin的blog的討論:https://news.ycombinator.com/item?id=11059738
- 針對(duì)antirez的blog的討論:https://news.ycombinator.com/item?id=11065933
在Hacker News上,antirez積極參與了討論,而Martin則始終置身事外。
下面我把這些討論中一些有意思的點(diǎn)拿出來(lái)與大家一起分享一下(集中在對(duì)于fencing token機(jī)制的討論上)。
關(guān)于antirez提出的“Check and Set”操作,他在blog里并沒(méi)有詳加說(shuō)明。果然,在Hacker News上就有人出來(lái)問(wèn)了。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,那么遵循下面的偽碼所定義的步驟。
- 先設(shè)置X.currlock = token。
- 讀出資源X(包括它的值和附帶的X.currlock)。
- 按照"write-if-currlock == token"的邏輯,修改資源X的值。意思是說(shuō),如果對(duì)X進(jìn)行修改的時(shí)候,X.currlock仍然和當(dāng)初設(shè)置進(jìn)去的token相等,那么才進(jìn)行修改;如果這時(shí)X.currlock已經(jīng)是其它值了,那么說(shuō)明有另外一方也在試圖進(jìn)行修改操作,那么放棄當(dāng)前的修改,從而避免沖突。
隨后Hacker News上一位叫viraptor的用戶提出了異議,它給出了這樣一個(gè)執(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
到了最后兩步,兩個(gè)客戶端A和B同時(shí)進(jìn)行寫(xiě)操作,沖突了。不過(guò),這位用戶應(yīng)該是理解錯(cuò)了antirez給出的修改過(guò)程了。按照antirez的意思,判斷X.currlock是否修改過(guò)和對(duì)資源的寫(xiě)操作,應(yīng)該是一個(gè)原子操作。只有這樣理解才能合乎邏輯,否則的話,這個(gè)過(guò)程就有嚴(yán)重的破綻。這也是為什么antirez之前會(huì)對(duì)fencing機(jī)制產(chǎn)生質(zhì)疑:既然資源服務(wù)器本身都能提供互斥的原子操作了,為什么還需要一個(gè)分布式鎖呢?因此,antirez認(rèn)為這種fencing機(jī)制是很累贅的,他之所以還是提出了這種“Check and Set”操作,只是為了證明在提供fencing token這一點(diǎn)上,Redlock也能做到。但是,這里仍然有一些不明確的地方,如果將"write-if-currlock == token"看做是原子操作的話,這個(gè)邏輯勢(shì)必要在資源服務(wù)器上執(zhí)行,那么第二步為什么還要“讀出資源X”呢?除非這個(gè)“讀出資源X”的操作也是在資源服務(wù)器上執(zhí)行,它包含在“判斷-寫(xiě)回”這個(gè)原子操作里面。而假如不這樣理解的話,“讀取-判斷-寫(xiě)回”這三個(gè)操作都放在客戶端執(zhí)行,那么看不出它們?nèi)绾尾拍軐?shí)現(xiàn)原子性操作。在下面的討論中,我們暫時(shí)忽略“讀出資源X”這一步。
這個(gè)基于random token的“Check and Set”操作,如果與Martin提出的遞增的fencing token對(duì)比一下的話,至少有兩點(diǎn)不同:
- “Check and Set”對(duì)于寫(xiě)操作要分成兩步來(lái)完成(設(shè)置token、判斷-寫(xiě)回),而遞增的fencing token機(jī)制只需要一步(帶著token向資源服務(wù)器發(fā)起寫(xiě)請(qǐng)求)。
- 遞增的fencing token機(jī)制能保證最終操作共享資源的順序,那些延遲時(shí)間太長(zhǎng)的操作就無(wú)法操作共享資源了。但是基于random token的“Check and Set”操作不會(huì)保證這個(gè)順序,那些延遲時(shí)間太長(zhǎng)的操作如果后到達(dá)了,它仍然有可能操作共享資源(當(dāng)然是以互斥的方式)。
對(duì)于前一點(diǎn)不同,我們?cè)诤竺娴姆治鲋袝?huì)看到,如果資源服務(wù)器也是分布式的,那么使用遞增的fencing token也要變成兩步。
而對(duì)于后一點(diǎn)操作順序上的不同,antirez認(rèn)為這個(gè)順序沒(méi)有意義,關(guān)鍵是能互斥訪問(wèn)就行了。他寫(xiě)下了下面的話:
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)競(jìng)爭(zhēng)條件出現(xiàn)的時(shí)候,能夠以某種方式避免?!€需要注意的是,當(dāng)那種競(jìng)爭(zhēng)條件出現(xiàn)的時(shí)候,比如由于延遲,客戶端是同時(shí)來(lái)訪問(wèn)的,鎖的ID的大小順序跟那些操作真正想執(zhí)行的順序,是沒(méi)有什么關(guān)系的。)
這里的lock ID,跟Martin說(shuō)的遞增的token是一回事。
隨后,antirez舉了一個(gè)“將名字加入列表”的操作的例子:
- 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
你看,兩個(gè)客戶端(其實(shí)是Web服務(wù)器)執(zhí)行“添加名字”的操作,A本來(lái)是排在B前面的,但獲得鎖的順序卻是B排在A前面。因此,antirez說(shuō),鎖的ID的大小順序跟那些操作真正想執(zhí)行的順序,是沒(méi)有什么關(guān)系的。關(guān)鍵是能排出一個(gè)順序來(lái),能互斥訪問(wèn)就行了。那么,至于鎖的ID是遞增的,還是一個(gè)random token,自然就不那么重要了。
Martin提出的fencing token機(jī)制,給人留下了無(wú)盡的疑惑。這主要是因?yàn)樗麑?duì)于這一機(jī)制的描述缺少太多的技術(shù)細(xì)節(jié)。從上面的討論可以看出,antirez對(duì)于這一機(jī)制的看法是,它跟一個(gè)random token沒(méi)有什么區(qū)別,而且,它需要資源服務(wù)器本身提供某種互斥機(jī)制,這幾乎讓分布式鎖本身的存在失去了意義。圍繞fencing token的問(wèn)題,還有兩點(diǎn)是比較引人注目的,Hacker News上也有人提出了相關(guān)的疑問(wèn):
- (1)關(guān)于資源服務(wù)器本身的架構(gòu)細(xì)節(jié)。
- (2)資源服務(wù)器對(duì)于fencing token進(jìn)行檢查的實(shí)現(xiàn)細(xì)節(jié),比如是否需要提供一種原子操作。
關(guān)于上述問(wèn)題(1),Hacker News上有一位叫dwenzek的用戶發(fā)表了下面的評(píng)論:
… 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拒絕掉延遲請(qǐng)求的相關(guān)議題,是不夠清晰的,因?yàn)槭鼙Wo(hù)的資源以及對(duì)它的訪問(wèn)方式本身是沒(méi)有被明確定義過(guò)的。資源服務(wù)是不是分布式的呢?如果是,資源服務(wù)有沒(méi)有一種方式能確保token在所有節(jié)點(diǎn)上遞增呢?對(duì)于客戶端的Session由于過(guò)期而被中斷的情況,資源服務(wù)有辦法將它的影響回滾嗎?)
這些疑問(wèn)在Hacker News上并沒(méi)有人給出解答。而關(guān)于分布式的資源服務(wù)器架構(gòu)如何處理fencing token,另外一名分布式系統(tǒng)的專家Flavio Junqueira在他的一篇blog中有所提及(我們后面會(huì)再提到)。
關(guān)于上述問(wèn)題(2),Hacker News上有一位叫reza_n的用戶發(fā)表了下面的疑問(wèn):
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)兩個(gè)客戶端同時(shí)獲得鎖的時(shí)候fencing token是如何防止亂序的。但是如果兩個(gè)寫(xiě)操作恰好按序到達(dá)了,而且它們?cè)趯?duì)同一個(gè)值進(jìn)行修改,那會(huì)發(fā)生什么呢?難道不會(huì)仍然是依賴某種數(shù)據(jù)版本號(hào)或者樂(lè)觀鎖的機(jī)制?這不會(huì)讓分布式鎖變得沒(méi)有必要了嗎?)
一位叫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)為“第一個(gè)”寫(xiě)請(qǐng)求會(huì)失敗,因?yàn)樗鼈魅氲膖oken不再是“最新的”了,這意味著鎖已經(jīng)釋放或者過(guò)期了。)
Terr_的回答到底對(duì)不對(duì)呢?這不好說(shuō),取決于資源服務(wù)器對(duì)于fencing token進(jìn)行檢查的實(shí)現(xiàn)細(xì)節(jié)。讓我們來(lái)簡(jiǎn)單分析一下。
為了簡(jiǎn)單起見(jiàn),我們假設(shè)有一臺(tái)(先不考慮分布式的情況)通過(guò)RPC進(jìn)行遠(yuǎn)程訪問(wèn)文件服務(wù)器,它無(wú)法提供對(duì)于文件的互斥訪問(wèn)(否則我們就不需要分布式鎖了)?,F(xiàn)在我們按照Martin給出的說(shuō)法,加入fencing token的檢查邏輯。由于Martin沒(méi)有描述具體細(xì)節(jié),我們猜測(cè)至少有兩種可能。
第一種可能,我們修改了文件服務(wù)器的代碼,讓它能多接受一個(gè)fencing token的參數(shù),并在進(jìn)行所有處理之前加入了一個(gè)簡(jiǎn)單的判斷邏輯,保證只有當(dāng)前接收到的fencing token大于之前的值才允許進(jìn)行后邊的訪問(wèn)。而一旦通過(guò)了這個(gè)判斷,后面的處理不變。
現(xiàn)在想象reza_n描述的場(chǎng)景,客戶端1和客戶端2都發(fā)生了GC pause,兩個(gè)fencing token都延遲了,它們幾乎同時(shí)到達(dá)了文件服務(wù)器,而且保持了順序。那么,我們新加入的判斷邏輯,應(yīng)該對(duì)兩個(gè)請(qǐng)求都會(huì)放過(guò),而放過(guò)之后它們幾乎同時(shí)在操作文件,還是沖突了。既然Martin宣稱fencing token能保證分布式鎖的正確性,那么上面這種可能的猜測(cè)也許是我們理解錯(cuò)了。
當(dāng)然,還有第二種可能,就是我們對(duì)文件服務(wù)器確實(shí)做了比較大的改動(dòng),讓這里判斷token的邏輯和隨后對(duì)文件的處理放在一個(gè)原子操作里了。這可能更接近antirez的理解。這樣的話,前面reza_n描述的場(chǎng)景中,兩個(gè)寫(xiě)操作都應(yīng)該成功。
基于ZooKeeper的分布式鎖更安全嗎?
很多人(也包括Martin在內(nèi))都認(rèn)為,如果你想構(gòu)建一個(gè)更安全的分布式鎖,那么應(yīng)該使用ZooKeeper,而不是Redis。那么,為了對(duì)比的目的,讓我們先暫時(shí)脫離開(kāi)本文的題目,討論一下基于ZooKeeper的分布式鎖能提供絕對(duì)的安全嗎?它需要fencing token機(jī)制的保護(hù)嗎?
我們不得不提一下分布式專家Flavio Junqueira所寫(xiě)的一篇blog,題目叫“Note on fencing and distributed locks”,地址如下:
- https://fpj.me/2016/02/10/note-on-fencing-and-distributed-locks/
Flavio Junqueira是ZooKeeper的作者之一,他的這篇blog就寫(xiě)在Martin和antirez發(fā)生爭(zhēng)論的那幾天。他在文中給出了一個(gè)基于ZooKeeper構(gòu)建分布式鎖的描述(當(dāng)然這不是唯一的方式):
- 客戶端嘗試創(chuàng)建一個(gè)znode節(jié)點(diǎn),比如
/lock
。那么第一個(gè)客戶端就創(chuàng)建成功了,相當(dāng)于拿到了鎖;而其它的客戶端會(huì)創(chuàng)建失?。▃node已存在),獲取鎖失敗。 - 持有鎖的客戶端訪問(wèn)共享資源完成后,將znode刪掉,這樣其它客戶端接下來(lái)就能來(lái)獲取鎖了。
- znode應(yīng)該被創(chuàng)建成ephemeral的。這是znode的一個(gè)特性,它保證如果創(chuàng)建znode的那個(gè)客戶端崩潰了,那么相應(yīng)的znode會(huì)被自動(dòng)刪除。這保證了鎖一定會(huì)被釋放。
看起來(lái)這個(gè)鎖相當(dāng)完美,沒(méi)有Redlock過(guò)期時(shí)間的問(wèn)題,而且能在需要的時(shí)候讓鎖自動(dòng)釋放。但仔細(xì)考察的話,并不盡然。
ZooKeeper是怎么檢測(cè)出某個(gè)客戶端已經(jīng)崩潰了呢?實(shí)際上,每個(gè)客戶端都與ZooKeeper的某臺(tái)服務(wù)器維護(hù)著一個(gè)Session,這個(gè)Session依賴定期的心跳(heartbeat)來(lái)維持。如果ZooKeeper長(zhǎng)時(shí)間收不到客戶端的心跳(這個(gè)時(shí)間稱為Sesion的過(guò)期時(shí)間),那么它就認(rèn)為Session過(guò)期了,通過(guò)這個(gè)Session所創(chuàng)建的所有的ephemeral類型的znode節(jié)點(diǎn)都會(huì)被自動(dòng)刪除。
設(shè)想如下的執(zhí)行序列:
- 客戶端1創(chuàng)建了znode節(jié)點(diǎn)
/lock
,獲得了鎖。 - 客戶端1進(jìn)入了長(zhǎng)時(shí)間的GC pause。
- 客戶端1連接到ZooKeeper的Session過(guò)期了。znode節(jié)點(diǎn)
/lock
被自動(dòng)刪除。 - 客戶端2創(chuàng)建了znode節(jié)點(diǎn)
/lock
,從而獲得了鎖。 - 客戶端1從GC pause中恢復(fù)過(guò)來(lái),它仍然認(rèn)為自己持有鎖。
最后,客戶端1和客戶端2都認(rèn)為自己持有了鎖,沖突了。這與之前Martin在文章中描述的由于GC pause導(dǎo)致的分布式鎖失效的情況類似。
看起來(lái),用ZooKeeper實(shí)現(xiàn)的分布式鎖也不一定就是安全的。該有的問(wèn)題它還是有。但是,ZooKeeper作為一個(gè)專門(mén)為分布式應(yīng)用提供方案的框架,它提供了一些非常好的特性,是Redis之類的方案所沒(méi)有的。像前面提到的ephemeral類型的znode自動(dòng)刪除的功能就是一個(gè)例子。
還有一個(gè)很有用的特性是ZooKeeper的watch機(jī)制。這個(gè)機(jī)制可以這樣來(lái)使用,比如當(dāng)客戶端試圖創(chuàng)建/lock
的時(shí)候,發(fā)現(xiàn)它已經(jīng)存在了,這時(shí)候創(chuàng)建失敗,但客戶端不一定就此對(duì)外宣告獲取鎖失敗??蛻舳丝梢赃M(jìn)入一種等待狀態(tài),等待當(dāng)/lock
節(jié)點(diǎn)被刪除的時(shí)候,ZooKeeper通過(guò)watch機(jī)制通知它,這樣它就可以繼續(xù)完成創(chuàng)建操作(獲取鎖)。這可以讓分布式鎖在客戶端用起來(lái)就像一個(gè)本地的鎖一樣:加鎖失敗就阻塞住,直到獲取到鎖為止。這樣的特性Redlock就無(wú)法實(shí)現(xiàn)。
小結(jié)一下,基于ZooKeeper的鎖和基于Redis的鎖相比在實(shí)現(xiàn)特性上有兩個(gè)不同:
- 在正常情況下,客戶端可以持有鎖任意長(zhǎng)的時(shí)間,這可以確保它做完所有需要的資源訪問(wèn)操作之后再釋放鎖。這避免了基于Redis的鎖對(duì)于有效時(shí)間(lock validity time)到底設(shè)置多長(zhǎng)的兩難問(wèn)題。實(shí)際上,基于ZooKeeper的鎖是依靠Session(心跳)來(lái)維持鎖的持有狀態(tài)的,而Redis不支持Sesion。
- 基于ZooKeeper的鎖支持在獲取鎖失敗之后等待鎖重新釋放的事件。這讓客戶端對(duì)鎖的使用更加靈活。
順便提一下,如上所述的基于ZooKeeper的分布式鎖的實(shí)現(xiàn),并不是最優(yōu)的。它會(huì)引發(fā)“herd effect”(羊群效應(yīng)),降低獲取鎖的性能。一個(gè)更好的實(shí)現(xiàn)參見(jiàn)下面鏈接:
- http://zookeeper.apache.org/doc/r3.4.9/recipes.html#sc_recipes_Locks
我們重新回到Flavio Junqueira對(duì)于fencing token的分析。Flavio Junqueira指出,fencing token機(jī)制本質(zhì)上是要求客戶端在每次訪問(wèn)一個(gè)共享資源的時(shí)候,在執(zhí)行任何操作之前,先對(duì)資源進(jìn)行某種形式的“標(biāo)記”(mark)操作,這個(gè)“標(biāo)記”能保證持有舊的鎖的客戶端請(qǐng)求(如果延遲到達(dá)了)無(wú)法操作資源。這種標(biāo)記操作可以是很多形式,fencing token是其中比較典型的一個(gè)。
隨后Flavio Junqueira提到用遞增的epoch number(相當(dāng)于Martin的fencing token)來(lái)保護(hù)共享資源。而對(duì)于分布式的資源,為了方便討論,假設(shè)分布式資源是一個(gè)小型的多備份的數(shù)據(jù)存儲(chǔ)(a small replicated data store),執(zhí)行寫(xiě)操作的時(shí)候需要向所有節(jié)點(diǎn)上寫(xiě)數(shù)據(jù)。最簡(jiǎn)單的做標(biāo)記的方式,就是在對(duì)資源進(jìn)行任何操作之前,先把epoch number標(biāo)記到各個(gè)資源節(jié)點(diǎn)上去。這樣,各個(gè)節(jié)點(diǎn)就保證了舊的(也就是小的)epoch number無(wú)法操作數(shù)據(jù)。
當(dāng)然,這里再展開(kāi)討論下去可能就涉及到了這個(gè)數(shù)據(jù)存儲(chǔ)服務(wù)的實(shí)現(xiàn)細(xì)節(jié)了。比如在實(shí)際系統(tǒng)中,可能為了容錯(cuò),只要上面講的標(biāo)記和寫(xiě)入操作在多數(shù)節(jié)點(diǎn)上完成就算成功完成了(Flavio Junqueira并沒(méi)有展開(kāi)去講)。在這里我們能看到的,最重要的,是這種標(biāo)記操作如何起作用的方式。這有點(diǎn)類似于Paxos協(xié)議(Paxos協(xié)議要求每個(gè)proposal對(duì)應(yīng)一個(gè)遞增的數(shù)字,執(zhí)行accept請(qǐng)求之前先執(zhí)行prepare請(qǐng)求)。antirez提出的random token的方式顯然不符合Flavio Junqueira對(duì)于“標(biāo)記”操作的定義,因?yàn)樗鼰o(wú)法區(qū)分新的token和舊的token。只有遞增的數(shù)字才能確保最終收斂到最新的操作結(jié)果上。
在這個(gè)分布式數(shù)據(jù)存儲(chǔ)服務(wù)(共享資源)的例子中,客戶端在標(biāo)記完成之后執(zhí)行寫(xiě)入操作的時(shí)候,存儲(chǔ)服務(wù)的節(jié)點(diǎn)需要判斷epoch number是不是最新,然后確定能不能執(zhí)行寫(xiě)入操作。如果按照上一節(jié)我們的分析思路,這里的epoch判斷和接下來(lái)的寫(xiě)入操作,是不是在一個(gè)原子操作里呢?根據(jù)Flavio Junqueira的相關(guān)描述,我們相信,應(yīng)該是原子的。那么既然資源本身可以提供原子互斥操作了,那么分布式鎖還有存在的意義嗎?應(yīng)該說(shuō)有??蛻舳丝梢岳梅植际芥i有效地避免沖突,等待寫(xiě)入機(jī)會(huì),這對(duì)于包含多個(gè)節(jié)點(diǎn)的分布式資源尤其有用(當(dāng)然,是出于效率的原因)。
Chubby的分布式鎖是怎樣做fencing的?
提到分布式鎖,就不能不提Google的Chubby。
Chubby是Google內(nèi)部使用的分布式鎖服務(wù),有點(diǎn)類似于ZooKeeper,但也存在很多差異。Chubby對(duì)外公開(kāi)的資料,主要是一篇論文,叫做“The Chubby lock service for loosely-coupled distributed systems”,下載地址如下:
- https://research.google.com/archive/chubby.html
另外,YouTube上有一個(gè)的講Chubby的talk,也很不錯(cuò),播放地址:
- https://www.youtube.com/watch?v=PqItueBaiRg&feature=youtu.be&t=487
Chubby自然也考慮到了延遲造成的鎖失效的問(wèn)題。論文里有一段描述如下:
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.
(譯文: 一個(gè)進(jìn)程持有鎖L,發(fā)起了請(qǐng)求R,但是請(qǐng)求失敗了。另一個(gè)進(jìn)程獲得了鎖L并在請(qǐng)求R到達(dá)目的方之前執(zhí)行了一些動(dòng)作。如果后來(lái)請(qǐng)求R到達(dá)了,它就有可能在沒(méi)有鎖L保護(hù)的情況下進(jìn)行操作,帶來(lái)數(shù)據(jù)不一致的潛在風(fēng)險(xiǎn)。)
這跟Martin的分析大同小異。
Chubby給出的用于解決(緩解)這一問(wèn)題的機(jī)制稱為sequencer,類似于fencing token機(jī)制。鎖的持有者可以隨時(shí)請(qǐng)求一個(gè)sequencer,這是一個(gè)字節(jié)串,它由三部分組成:
- 鎖的名字。
- 鎖的獲取模式(排他鎖還是共享鎖)。
- lock generation number(一個(gè)64bit的單調(diào)遞增數(shù)字)。作用相當(dāng)于fencing token或epoch number。
客戶端拿到sequencer之后,在操作資源的時(shí)候把它傳給資源服務(wù)器。然后,資源服務(wù)器負(fù)責(zé)對(duì)sequencer的有效性進(jìn)行檢查。檢查可以有兩種方式:
- 調(diào)用Chubby提供的API,CheckSequencer(),將整個(gè)sequencer傳進(jìn)去進(jìn)行檢查。這個(gè)檢查是為了保證客戶端持有的鎖在進(jìn)行資源訪問(wèn)的時(shí)候仍然有效。
- 將客戶端傳來(lái)的sequencer與資源服務(wù)器當(dāng)前觀察到的最新的sequencer進(jìn)行對(duì)比檢查。可以理解為與Martin描述的對(duì)于fencing token的檢查類似。
當(dāng)然,如果由于兼容的原因,資源服務(wù)本身不容易修改,那么Chubby還提供了一種機(jī)制:
- lock-delay。Chubby允許客戶端為持有的鎖指定一個(gè)lock-delay的時(shí)間值(默認(rèn)是1分鐘)。當(dāng)Chubby發(fā)現(xiàn)客戶端被動(dòng)失去聯(lián)系的時(shí)候,并不會(huì)立即釋放鎖,而是會(huì)在lock-delay指定的時(shí)間內(nèi)阻止其它客戶端獲得這個(gè)鎖。這是為了在把鎖分配給新的客戶端之前,讓之前持有鎖的客戶端有充分的時(shí)間把請(qǐng)求隊(duì)列排空(draining the queue),盡量防止出現(xiàn)延遲到達(dá)的未處理請(qǐng)求。
可見(jiàn),為了應(yīng)對(duì)鎖失效問(wèn)題,Chubby提供的三種處理方式:CheckSequencer()檢查、與上次最新的sequencer對(duì)比、lock-delay,它們對(duì)于安全性的保證是從強(qiáng)到弱的。而且,這些處理方式本身都沒(méi)有保證提供絕對(duì)的正確性(correctness)。但是,Chubby確實(shí)提供了單調(diào)遞增的lock generation number,這就允許資源服務(wù)器在需要的時(shí)候,利用它提供更強(qiáng)的安全性保障。
關(guān)于時(shí)鐘
在Martin與antirez的這場(chǎng)爭(zhēng)論中,沖突最為嚴(yán)重的就是對(duì)于系統(tǒng)時(shí)鐘的假設(shè)是不是合理的問(wèn)題。Martin認(rèn)為系統(tǒng)時(shí)鐘難免會(huì)發(fā)生跳躍(這與分布式算法的異步模型相符),而antirez認(rèn)為在實(shí)際中系統(tǒng)時(shí)鐘可以保證不發(fā)生大的跳躍。
Martin對(duì)于這一分歧發(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.
(譯文:從根本上來(lái)說(shuō),這場(chǎng)討論最后歸結(jié)到了一個(gè)問(wèn)題上:為了確保安全性而做出的記時(shí)假設(shè)到底是否合理。我認(rèn)為不合理,而antirez認(rèn)為合理 —— 但是這也沒(méi)關(guān)系。工程問(wèn)題的討論很少只有一個(gè)正確答案。)
那么,在實(shí)際系統(tǒng)中,時(shí)鐘到底是否可信呢?對(duì)此,Julia Evans專門(mén)寫(xiě)了一篇文章,“TIL: clock skew exists”,總結(jié)了很多跟時(shí)鐘偏移有關(guān)的實(shí)際資料,并進(jìn)行了分析。這篇文章地址:
- http://jvns.ca/blog/2016/02/09/til-clock-skew-exists/
Julia Evans在文章最后得出的結(jié)論是:
clock skew is real(時(shí)鐘偏移在現(xiàn)實(shí)中是存在的)
Martin的事后總結(jié)
我們前面提到過(guò),當(dāng)各方的爭(zhēng)論在激烈進(jìn)行的時(shí)候,Martin幾乎始終置身事外。但是Martin在這件事過(guò)去之后,把這個(gè)事件的前后經(jīng)過(guò)總結(jié)成了一個(gè)很長(zhǎng)的故事線。如果你想最全面地了解這個(gè)事件發(fā)生的前后經(jīng)過(guò),那么建議去讀讀Martin的這個(gè)總結(jié):
- https://storify.com/martinkl/redlock-discussion
在這個(gè)故事總結(jié)的最后,Martin寫(xiě)下了很多感性的評(píng)論:
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.
(譯文:對(duì)我來(lái)說(shuō)最重要的一點(diǎn)在于:我并不在乎在這場(chǎng)辯論中誰(shuí)對(duì)誰(shuí)錯(cuò) —— 我只關(guān)心從其他人的工作中學(xué)到的東西,以便我們能夠避免重蹈覆轍,并讓未來(lái)更加美好。前人已經(jīng)為我們創(chuàng)造出了許多偉大的成果:站在巨人的肩膀上,我們得以構(gòu)建更棒的軟件?!瓕?duì)于任何想法,務(wù)必要詳加檢驗(yàn),通過(guò)論證以及檢查它們是否經(jīng)得住別人的詳細(xì)審查。那是學(xué)習(xí)過(guò)程的一部分。但目標(biāo)應(yīng)該是為了獲得知識(shí),而不應(yīng)該是為了說(shuō)服別人相信你自己是對(duì)的。有時(shí)候,那只不過(guò)意味著停下來(lái),好好地想一想。)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-846288.html
關(guān)于分布式鎖的這場(chǎng)爭(zhēng)論,我們已經(jīng)完整地做了回顧和分析。
按照鎖的兩種用途,如果僅是為了效率(efficiency),那么你可以自己選擇你喜歡的一種分布式鎖的實(shí)現(xiàn)。當(dāng)然,你需要清楚地知道它在安全性上有哪些不足,以及它會(huì)帶來(lái)什么后果。而如果你是為了正確性(correctness),那么請(qǐng)慎之又慎。在本文的討論中,我們?cè)诜植际芥i的正確性上走得最遠(yuǎn)的地方,要數(shù)對(duì)于ZooKeeper分布式鎖、單調(diào)遞增的epoch number以及對(duì)分布式資源進(jìn)行標(biāo)記的分析了。請(qǐng)仔細(xì)審查相關(guān)的論證。
Martin為我們留下了不少疑問(wèn),尤其是他提出的fencing token機(jī)制。他在blog中提到,會(huì)在他的新書(shū)《Designing Data-Intensive Applications》的第8章和第9章再詳加論述。目前,這本書(shū)尚在預(yù)售當(dāng)中。我感覺(jué),這會(huì)是一本值得一讀的書(shū),它不同于為了出名或賺錢(qián)而出版的那種短平快的書(shū)籍??梢钥闯鲎髡咴谶@本書(shū)上投入了巨大的精力。
最后,我相信,這個(gè)討論還遠(yuǎn)沒(méi)有結(jié)束。分布式鎖(Distributed Locks)和相應(yīng)的fencing方案,可以作為一個(gè)長(zhǎng)期的課題,隨著我們對(duì)分布式系統(tǒng)的認(rèn)識(shí)逐漸增加,可以再來(lái)慢慢地思考它。思考它更深層的本質(zhì),以及它在理論上的證明。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-846288.html
到了這里,關(guān)于基于Redis的分布式鎖到底安全嗎(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!