在很多環(huán)境下悍赢,多個(gè)不同的進(jìn)程需要以排他的形式使用共享資源饵溅,這是使用分布式鎖機(jī)制是一種傳統(tǒng)但有效的方案民泵。
有很多的庫(kù)和博客都介紹了如何用Redis去實(shí)現(xiàn)DLM(Distributed Lock Manger跷乐,分布式鎖管理器)屠橄,但是每個(gè)庫(kù)彼此之間的實(shí)現(xiàn)方式都不相同兔魂。而且很多的實(shí)現(xiàn)方式都比較簡(jiǎn)單烤芦,保險(xiǎn)級(jí)別較低。
此篇文章試圖介紹一種更為標(biāo)準(zhǔn)的用redis來(lái)實(shí)現(xiàn)分布式鎖的算法析校,我們將這種算法叫做Redlock构罗,我們相信用此算法實(shí)現(xiàn)的DLM比普通的單redis實(shí)例實(shí)現(xiàn)方法更加安全。我們希望社區(qū)可以對(duì)其進(jìn)行評(píng)估智玻,給予反饋绰播,或者把它作為其他復(fù)雜算法實(shí)現(xiàn)的切入點(diǎn)或替代方案。
Implementations | 實(shí)現(xiàn)
在介紹算法之前尚困,如下是基于此算法的一些現(xiàn)成的實(shí)現(xiàn)蠢箩,可以用來(lái)作為參考:
- Redlock-rb (Ruby implementation). There is also a fork of Redlock-rb that adds a gem for easy distribution and perhaps more.
- Redlock-py (Python implementation).
- Aioredlock (Asyncio Python implementation).
- Redlock-php (PHP implementation).
- PHPRedisMutex (further PHP implementation)
- cheprasov/php-redis-lock (PHP library for locks)
- Redsync.go (Go implementation).
- Redisson (Java implementation).
- Redis::DistLock (Perl implementation).
- Redlock-cpp (C++ implementation).
- Redlock-cs (C#/.NET implementation).
- RedLock.net (C#/.NET implementation). Includes async and lock extension support.
- ScarletLock (C# .NET implementation with configurable datastore)
- node-redlock (NodeJS implementation). Includes support for lock extension.
Safety and Liveness guarantees | 安全和存活性保證
我們的算法設(shè)計(jì)是基于如下三個(gè)特性建模的,在我們看來(lái)事甜,這也是為了高效的使用分布式鎖所需要的最低的保證:
- 安全特性:排它性谬泌。在任何時(shí)間點(diǎn),只能有一個(gè)客戶端可以持有鎖逻谦。
- 存活特性A:無(wú)死鎖掌实。總是可以獲取到鎖邦马,即使在一個(gè)被客戶端鎖住的資源損壞了或分區(qū)了的情況下贱鼻。
- 存活特性B:可容錯(cuò)。只要大多數(shù)的redis節(jié)點(diǎn)是可用的滋将,那么客戶端就可以獲取和釋放鎖邻悬。
Why failover-based implementations are not enough | 為什么基于故障轉(zhuǎn)移的實(shí)現(xiàn)不能夠完全滿足要求
為了理解我們希望提高的地方,讓我們先來(lái)分析一下當(dāng)前大多數(shù)基于redis的分布式鎖的庫(kù)的實(shí)現(xiàn)狀況随闽。
通過(guò)redis來(lái)鎖定一個(gè)資源的最簡(jiǎn)單的實(shí)現(xiàn)就是在redis實(shí)例中創(chuàng)建一個(gè)key父丰,創(chuàng)建的同時(shí)給key設(shè)置一個(gè)有效期。利用redis的過(guò)期機(jī)制掘宪,這個(gè)key最終總是會(huì)被釋放(上述的第二條特性)蛾扇。當(dāng)客戶端需要釋放資源的時(shí)候,它只需要?jiǎng)h除這個(gè)key即可魏滚。
表面上來(lái)看這種方式?jīng)]有什么問(wèn)題镀首,但是這種實(shí)現(xiàn)存在一個(gè)單點(diǎn)故障的問(wèn)題。如果redis節(jié)點(diǎn)宕機(jī)了怎么辦鼠次?好吧更哄,那讓我們加上一個(gè)slave節(jié)點(diǎn)靖秩,當(dāng)redis的master節(jié)點(diǎn)宕機(jī)了之后,slave節(jié)點(diǎn)可以接管服務(wù)竖瘾。但是沟突,很遺憾此種方法是不可行的,因此這種master-slave的實(shí)現(xiàn)無(wú)法完全保證互斥性捕传,因?yàn)閞edis主從節(jié)點(diǎn)之間的復(fù)制是異步的惠拭。
在這個(gè)模型中顯然存在一個(gè)資源競(jìng)爭(zhēng)因素:
- 客戶端A在master節(jié)點(diǎn)獲取到鎖。
- master節(jié)點(diǎn)在還沒(méi)有將key復(fù)制給slave節(jié)點(diǎn)之前宕機(jī)了庸论。
- slave節(jié)點(diǎn)被提升為master职辅。
- 客戶端B獲取到鎖,這樣子客戶端A和B就針對(duì)同一資源都拿到了鎖聂示。違背了互斥性域携。
有時(shí)在特殊的情況下,這種實(shí)現(xiàn)方式是完全ok的鱼喉。比如在發(fā)生故障的時(shí)候秀鞭,多個(gè)客戶端允許同時(shí)持有鎖。除此之外扛禽,我們建議用此文章描述的算法來(lái)實(shí)現(xiàn)DLM較為妥當(dāng)锋边。
Correct implementation with a single instance | 單redis實(shí)例的正確實(shí)現(xiàn)方式
在嘗試用Redlock解決上述案例的缺點(diǎn)之前,讓我們先看看如何正確使用redis單實(shí)例來(lái)實(shí)現(xiàn)分布式鎖编曼。如果應(yīng)用程序的非頻繁的競(jìng)態(tài)條件是可接受的豆巨,那么單redis實(shí)例是一個(gè)可行的方案,而且這種方案的實(shí)現(xiàn)也是接下來(lái)要介紹的分布式算法的基礎(chǔ)掐场。
要獲取一個(gè)鎖往扔,可以使用如下的命令:
SET resource_name my_random_value NX PX 30000
這條命令只有當(dāng)key不存在時(shí)才會(huì)生成它(NX選項(xiàng)),并且超時(shí)時(shí)間為30000毫秒(PX選項(xiàng))熊户。key的值被設(shè)置為my_random_value
萍膛。這個(gè)值必須要在所有的客戶端和所有的鎖請(qǐng)求中唯一。這個(gè)隨機(jī)的值用來(lái)以一種安全的方式釋放鎖敏弃,通過(guò)一個(gè)腳本來(lái)通知redis:當(dāng)這個(gè)key存在卦羡,并且key的值也完全與期望的一致噪馏,才移除這個(gè)key麦到。這樣一個(gè)邏輯通過(guò)Lua腳本的實(shí)現(xiàn)如下:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
這樣子做可以避免刪除其他客戶端創(chuàng)建的鎖。比如說(shuō)欠肾,一個(gè)客戶端可能請(qǐng)求一個(gè)鎖瓶颠,這個(gè)客戶端接下來(lái)進(jìn)行其他任務(wù),并且其他任務(wù)執(zhí)行了較長(zhǎng)的時(shí)間刺桃,超過(guò)了key過(guò)期的時(shí)間粹淋,然后此客戶端再回來(lái)刪除鎖,如果這個(gè)時(shí)候它直接使用DEL
命令來(lái)刪除的話,有可能刪除的是其他客戶端的鎖(因?yàn)閗ey已近超時(shí)被刪除桃移,其他客戶端獲取到了新的鎖)屋匕。使用上面的邏輯,相當(dāng)于給每一個(gè)客戶端創(chuàng)建的鎖都做了一次“簽名”(通過(guò)給key賦予隨機(jī)且唯一的值)借杰,客戶端刪除鎖的時(shí)候只能刪除之前“簽名”過(guò)的那個(gè)鎖过吻。
那么這個(gè)隨機(jī)的值應(yīng)該是什么形式的呢?最好是從/dev/urandom
中生成的20字節(jié)的隨機(jī)值蔗衡,但是你也可以使用其他更廉價(jià)的方式生成唯一值纤虽,只要它足夠的“唯一”。比如說(shuō)通過(guò)使用/dev/urandom
選取RC4種子绞惦,然后從中生成偽隨機(jī)數(shù)據(jù)流逼纸。一個(gè)更簡(jiǎn)單的方案就是使用帶有微秒的unix時(shí)間戳和其他數(shù)據(jù)的組合,比如客戶端id济蝉,這樣子不是絕對(duì)安全杰刽,但是對(duì)于大多數(shù)任務(wù)來(lái)說(shuō)都已經(jīng)足夠了。
這里的key的生存時(shí)間我們叫做“鎖的有效時(shí)間”王滤。它既是鎖的自動(dòng)釋放時(shí)間专缠,也是當(dāng)前客戶端在其他客戶端獲取鎖之前執(zhí)行必要操作所花的時(shí)間。這樣子在技術(shù)上沒(méi)有違反互斥性原則淑仆,只是從獲取鎖的那一刻開(kāi)始涝婉,限制一個(gè)指定的時(shí)間窗口。
那么到目前為止蔗怠,我們已經(jīng)有了一個(gè)很好的請(qǐng)求和釋放鎖的方案墩弯。這是一個(gè)非分布式系統(tǒng),構(gòu)建于一個(gè)單一的redis實(shí)例之上寞射,只要理論上此redis實(shí)例永遠(yuǎn)在線渔工,那么這個(gè)方案就是安全的。 接下來(lái)讓我們將這些概念擴(kuò)展到一個(gè)分布式系統(tǒng)之上桥温,在這個(gè)分布式系統(tǒng)上引矩,我們不需要保證每個(gè)redis實(shí)例的永久可用性。
The Redlock algorithm | Redlock算法
在這個(gè)算法的分布式版本中侵浸,我們假設(shè)擁有N個(gè)redis主節(jié)點(diǎn)旺韭。這些節(jié)點(diǎn)是完全彼此獨(dú)立的,沒(méi)有使用replication或其他協(xié)調(diào)系統(tǒng)掏觉。我們已經(jīng)介紹了如何在一個(gè)單redis實(shí)例中獲取和釋放鎖区端,這個(gè)Redlock算法在單redis實(shí)例上對(duì)鎖的操作機(jī)制也是一樣的。在接下來(lái)的舉例中澳腹,我們?cè)O(shè)置節(jié)點(diǎn)數(shù)為5织盼,因此我們需要在5臺(tái)不同的機(jī)器上分別運(yùn)行redis實(shí)例杨何,確保它們?cè)阱礄C(jī)時(shí)彼此之間不會(huì)有影響。
為了獲取鎖沥邻,客戶端需要執(zhí)行如下操作步驟:
- 獲取當(dāng)前系統(tǒng)的以毫秒為單位的系統(tǒng)時(shí)間危虱。
- 嘗試按照順序在N個(gè)redis實(shí)例中獲取鎖,并且保持在所有的實(shí)例中都使用相同的key和value唐全。在此步驟中槽地,當(dāng)客戶端在每一個(gè)實(shí)例中獲取鎖時(shí),它同時(shí)會(huì)設(shè)置一個(gè)超時(shí)時(shí)間芦瘾,這個(gè)超時(shí)時(shí)間應(yīng)該遠(yuǎn)小于鎖的自動(dòng)釋放時(shí)間捌蚊。比如說(shuō)如果鎖的自動(dòng)釋放時(shí)間為10秒,那么這個(gè)超時(shí)時(shí)間就可以設(shè)置在5-50毫秒之間近弟。這樣子就避免客戶端在與一個(gè)已經(jīng)宕機(jī)的redis實(shí)例的通信中浪費(fèi)太多時(shí)間缅糟,如果一個(gè)節(jié)點(diǎn)不可用,客戶端應(yīng)該馬上與下一個(gè)節(jié)點(diǎn)進(jìn)行通信祷愉。
- 客戶端在每一個(gè)redis節(jié)點(diǎn)上獲取鎖時(shí)都會(huì)計(jì)算從開(kāi)始到現(xiàn)在總共過(guò)去了多少時(shí)間窗宦,此計(jì)算只需要用當(dāng)前的時(shí)間減去第一步中保存下來(lái)的時(shí)間即可得到。當(dāng)且僅當(dāng)客戶端在多數(shù)節(jié)點(diǎn)中獲取到了鎖(至少3個(gè))二鳄,并且總共消耗的時(shí)間小于鎖的初始有效時(shí)間赴涵,這個(gè)鎖才被認(rèn)為是獲取成功了。
- 如果獲取鎖成功订讼,那么鎖的有效時(shí)間應(yīng)該設(shè)置為初始有效時(shí)間減去時(shí)間流逝的時(shí)長(zhǎng)髓窜,即步驟3的計(jì)算結(jié)果。
- 如果客戶端獲取鎖失斊鄣睢(沒(méi)有在超過(guò)N/2+1個(gè)節(jié)點(diǎn)上獲取到鎖或計(jì)算出有效時(shí)間是負(fù)數(shù)都認(rèn)為獲取鎖失敿淖荨),此時(shí)客戶端會(huì)嘗試對(duì)所有的節(jié)點(diǎn)進(jìn)行解鎖操作(即使對(duì)于那些獲取鎖失敗的節(jié)點(diǎn))
譯者注:之所以在每個(gè)redis節(jié)點(diǎn)上面都計(jì)算時(shí)間的流逝時(shí)長(zhǎng)脖苏,并將lock的有效時(shí)間設(shè)置為:初始有效時(shí)間 - 流逝時(shí)長(zhǎng)程拭。是因?yàn)檫@樣子可以保證,所有的redis節(jié)點(diǎn)的lock會(huì)在同一個(gè)時(shí)間點(diǎn)過(guò)期棍潘。如果在每一個(gè)redis節(jié)點(diǎn)上面設(shè)置相同的lock有效時(shí)間恃鞋,由于在每個(gè)結(jié)點(diǎn)上獲取鎖操作是串行的,那么此時(shí)必然會(huì)造成后面加鎖的節(jié)點(diǎn)的鎖過(guò)期會(huì)晚于前面的節(jié)點(diǎn)亦歉,這樣子就造成了不同步恤浪。
Is the algorithm asynchronous? | 這個(gè)算法是異步的嗎?
這個(gè)算法依賴于所有運(yùn)行redis實(shí)例的主機(jī)相互之間都沒(méi)有做時(shí)鐘同步的情形鳍徽,它們彼此都使用自己的本地時(shí)間资锰,時(shí)鐘周期應(yīng)該是極度近似的(理論上來(lái)說(shuō)每一臺(tái)機(jī)器的時(shí)鐘周期都不可能是絕對(duì)相同的,但是彼此之間的這種極微小差異阶祭,是可以容忍和忽略的)绷杜。這種類(lèi)比就好像是真實(shí)世界中的計(jì)算機(jī)一樣:每一臺(tái)計(jì)算機(jī)都有一個(gè)本地的時(shí)鐘,他們彼此之間的時(shí)鐘的偏移是極小的濒募,通常是可承受的鞭盟。
在這一點(diǎn)上,我們需要更加詳細(xì)的說(shuō)明我們算法的互斥性規(guī)則: 它保證了瑰剃,只要客戶端持有鎖齿诉,客戶端就會(huì)在鎖的有效期之內(nèi)(這里的有效期指的是上面的步驟3獲取到的時(shí)間,而不是鎖的初始有效期時(shí)間)完成它對(duì)資源的操作工作晌姚,當(dāng)然還得減去一些時(shí)間(這些時(shí)間通常已毫秒為單位,是針對(duì)不同進(jìn)程間的時(shí)鐘偏移的補(bǔ)償)抵恋。
關(guān)于需要約束時(shí)鐘偏移的相似系統(tǒng)的更多信息宝磨,這邊文章是比較受人關(guān)注的:Leases: an efficient fault-tolerant mechanism for distributed file cache consistency弧关。
Retry on failure | 失敗重試
當(dāng)一個(gè)客戶端無(wú)法獲取到鎖時(shí),它應(yīng)該在一個(gè)隨機(jī)的延時(shí)時(shí)間之后重新嘗試唤锉,以避免多個(gè)客戶端延時(shí)相同的時(shí)間,造成它們?cè)谕粫r(shí)間對(duì)同一資源進(jìn)行訪問(wèn)的問(wèn)題(此時(shí)容易造成腦裂現(xiàn)象)株憾。同樣的,客戶端發(fā)送消息的延時(shí)越小晒衩,出現(xiàn)腦裂條件(和所需要的重試)的時(shí)間窗口就越小号胚,所以,理想的情況是浸遗,客戶端應(yīng)該嘗試通過(guò)多播的方式跛锌,在同一時(shí)間向所有redis實(shí)例發(fā)送SET指令弃秆。
客戶端如果沒(méi)有在多數(shù)redis實(shí)例上獲取到鎖菠赚,它應(yīng)該立刻在已經(jīng)獲取到鎖的節(jié)點(diǎn)上釋放鎖郑藏,對(duì)于這一點(diǎn)是值的重點(diǎn)強(qiáng)調(diào)的衡查。這樣子的話拌牲,對(duì)于這個(gè)資源的鎖就不用等到key自動(dòng)過(guò)期之后才能夠被再次獲取(然而塌忽,如果出現(xiàn)網(wǎng)絡(luò)分區(qū)這種情況,客戶端將無(wú)法與redis實(shí)例進(jìn)行通信枣购,此資源就必須等到key自動(dòng)過(guò)期之后才能被使用了)擦耀。
Releasing the lock | 釋放鎖
對(duì)于鎖的釋放就比較簡(jiǎn)單了,客戶端只需要在所有的redis實(shí)例上執(zhí)行鎖的釋放命令分瘾,而不用管客戶端是否之前在此節(jié)點(diǎn)上成功的獲取了鎖账磺。
Safety arguments | 安全性論證
這個(gè)算法真的安全嗎?我們接下來(lái)模擬一下在不同的場(chǎng)景下都會(huì)發(fā)生些什么氏捞。
在開(kāi)始之前冒版,我們假設(shè)客戶端可以在多數(shù)的redis實(shí)例上獲取到鎖。所有的實(shí)例都存在一個(gè)相同的key辞嗡,并且此key有相同的生存時(shí)間,然而栋烤,在不同的實(shí)例上挺狰,針對(duì)于此key設(shè)置的實(shí)際生存時(shí)間是不同的,所以key會(huì)在不同的時(shí)候過(guò)期薯定。但是如果第一個(gè)key在最壞的情況下設(shè)置的時(shí)間為T(mén)1(這個(gè)時(shí)間是與第一個(gè)redis實(shí)例通信之前的時(shí)間)瞳购,最后一個(gè)key在最壞的情況下設(shè)置的時(shí)間為T(mén)2(從最后一個(gè)服務(wù)器的響應(yīng)中獲取的),我們確信在整個(gè)集合中的第一個(gè)key的存活時(shí)間至少為MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT
年堆。其他的key都會(huì)在其之后失效,所以我們確信所有的key會(huì)至少在這個(gè)時(shí)間內(nèi)被同時(shí)設(shè)置完成嘀韧。
在這個(gè)時(shí)間期間锄贷,多數(shù)的key會(huì)被設(shè)置曼月,其他客戶端將不可能得到鎖,因?yàn)橐呀?jīng)有超過(guò)半數(shù)的key已經(jīng)被設(shè)定哑芹,所以N/2+1 SET NX
操作會(huì)失敗。所以一旦lock被獲取碴萧,它就不可能在同一時(shí)間被其他客戶端獲饶┕骸(不然就違背了互斥性)。
然而曹质,我們也想要確保多個(gè)客戶端在同一時(shí)間不可能同時(shí)成功的獲取到鎖擎场。
如果一個(gè)客戶端鎖住了多數(shù)redis實(shí)例,使用的時(shí)間接近于或超過(guò)了鎖的最大存活時(shí)間(TTL的最大初始值)宅静,它會(huì)認(rèn)為獲取的鎖無(wú)效站欺,并且會(huì)解鎖所有的redis實(shí)例,所以我們只需要考慮客戶端可以獲取到大多數(shù)redis節(jié)點(diǎn)的鎖镊绪,并且時(shí)間小于TTL有效時(shí)長(zhǎng)的這種情況蝴韭。在這種情況下,這個(gè)爭(zhēng)論就如上面鎖描述的那樣榄鉴,對(duì)于MIN_VALIDITY
蛉抓,沒(méi)有客戶端可以重復(fù)獲取到鎖剃诅。所以矛辕,多個(gè)客戶端同時(shí)鎖定n/2 +1個(gè)redis實(shí)例的情況只會(huì)發(fā)生于鎖定時(shí)長(zhǎng)超過(guò)TTL時(shí)間的這種情況,而這種情況發(fā)生后聊品,客戶端會(huì)標(biāo)識(shí)此鎖為無(wú)效的,并解除鎖定陈哑。
如果你可以提供一個(gè)對(duì)此安全性的有效的證據(jù)伸眶,指出現(xiàn)存的相似的算法,或發(fā)現(xiàn)了bug界酒,我們將會(huì)非常感激你的提醒涂臣。
Liveness arguments | 存活性論證
系統(tǒng)的存活性基于三個(gè)主要的特性:
- 鎖的自動(dòng)釋放(由于key過(guò)期),過(guò)期之后key可以被其他客戶端獲取署辉。
- 實(shí)際上客戶端也會(huì)有移除鎖的機(jī)制岩四,當(dāng)客戶端獲取鎖失敗時(shí),或者當(dāng)
鎖獲取到了材鹦,但是需要加鎖處理的工作被終結(jié)了的時(shí)候耕姊。這種情況下,我們就不需要等到key自動(dòng)失效了尤泽。 - 當(dāng)客戶端需要重新嘗試獲取一個(gè)鎖時(shí),它等待的時(shí)長(zhǎng)會(huì)超過(guò)需要獲取鎖的總時(shí)長(zhǎng)熊咽。從而在概率上就使得在資源競(jìng)爭(zhēng)中的腦裂現(xiàn)象變得不太可能闹丐。
然而這一機(jī)制的實(shí)現(xiàn)是以可用性為代價(jià)的。當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)時(shí)卿拴,整個(gè)系統(tǒng)的可用性代價(jià)就是TTL時(shí)長(zhǎng)巍棱。如果發(fā)生連續(xù)性的網(wǎng)絡(luò)分區(qū)蛋欣,那么可用性就無(wú)法確定了。這種情況發(fā)生于客戶端獲取到了鎖到踏,還沒(méi)來(lái)得及釋放鎖時(shí)被網(wǎng)絡(luò)分區(qū)隔離了尚猿。
Performance, crash-recovery and fsync | 性能,故障修復(fù)和fsync
很多用戶在需要高性能的場(chǎng)景中使用redis作為鎖服務(wù)器伴榔。為了滿足這一需求庄萎,使用多播技術(shù)(或poor man's multiplexing,將socket放在非阻塞同步模型中糠涛,發(fā)送所有的命令忍捡,然后之后讀取這些命令集漾,假設(shè)客戶端和每個(gè)redis實(shí)例中的RTT都類(lèi)似)與N個(gè)redis實(shí)例通信可以用來(lái)降低延時(shí)砸脊。
然而如果我們需要實(shí)現(xiàn)一個(gè)故障恢復(fù)的模型的話那就需要考慮數(shù)據(jù)的持久化了。
我們先假設(shè)所有的redis實(shí)例沒(méi)有配置持久化驱显。一個(gè)客戶端在總共5個(gè)實(shí)例中的3個(gè)中獲取到了鎖。之后這3個(gè)實(shí)例中的其中一個(gè)重啟了秒紧,這時(shí)對(duì)于同一個(gè)資源熔恢,又有個(gè)3個(gè)redis實(shí)例可以獲取鎖,其他客戶端就會(huì)對(duì)此資源進(jìn)行鎖定叙淌,這樣子就違背了鎖的排它性鹰霍。
如果我們啟用AOF持久化,事情會(huì)變得好一點(diǎn)茂洒。例如說(shuō)督勺,我們可以對(duì)其中一個(gè)redis服務(wù)器發(fā)起SHUTDOWN指令來(lái)重啟它。在重啟完成之后智哀,此redis實(shí)例從AOF文件中將數(shù)據(jù)恢復(fù)出來(lái),給鎖設(shè)置的生存時(shí)間也會(huì)同步減少重啟所花費(fèi)的這些時(shí)間屯吊,一切都沒(méi)有問(wèn)題摹菠。然后,也只有當(dāng)是正常關(guān)機(jī)的時(shí)候才不會(huì)引發(fā)問(wèn)題世落,如果是一個(gè)電源中斷的宕機(jī)呢糟需?如果redis配置的是每秒fsync數(shù)據(jù)到硬盤(pán),那么重啟之后有可能我們的key會(huì)丟失武花,理論上杈帐,如果我們要確保在任意形式的服務(wù)器重啟的情況下的鎖的安全性专钉,我們需要配置fsync=always
跃须。而這種配置會(huì)極大的降低性能娃兽,性能上甚至都趕不上傳統(tǒng)的以一種安全的方法實(shí)施分布式鎖的相同等級(jí)的CP系統(tǒng)。
然而第练,事情比看起來(lái)要好很多玛荞。只要實(shí)例在宕機(jī)之后能夠重新啟動(dòng),算法的安全性就沒(méi)有影響婴梧。當(dāng)它重啟后凡恍,他不會(huì)影響到當(dāng)前已激活的鎖的計(jì)算,所以,當(dāng)前已激活的鎖會(huì)從此機(jī)器之外的實(shí)例中獲取竟坛。
為了保證這一點(diǎn),在redis服務(wù)器重啟完成之后涎跨,我們需要讓此redis實(shí)例在啟動(dòng)之后的開(kāi)始的一段時(shí)間不可用崭歧,此時(shí)長(zhǎng)要大于最大TTL時(shí)長(zhǎng)一點(diǎn)點(diǎn)率碾。這么設(shè)計(jì)是為了保證在此服務(wù)器宕機(jī)時(shí)的那些key能夠自動(dòng)過(guò)期。
使用delayed restarts
就可以達(dá)到上述目標(biāo)所宰,甚至不需要配置任何的redis持久化仔粥。然而蟹但,這一點(diǎn)會(huì)轉(zhuǎn)化為對(duì)系統(tǒng)可用性的懲罰谭羔。例如,如果大多數(shù)的redis實(shí)例都宕機(jī)了缅阳,這個(gè)系統(tǒng)在TTL時(shí)長(zhǎng)內(nèi)就變?yōu)槿植豢捎脿顟B(tài)了(這里的全局不可用的意思是客戶端沒(méi)法針對(duì)資源加鎖景描,導(dǎo)致接下來(lái)的業(yè)務(wù)無(wú)法進(jìn)行下去)
Making the algorithm more reliable: Extending the lock | 將這個(gè)算法變得更可靠:鎖的延期
如果業(yè)務(wù)的執(zhí)行由客戶端的一系列步驟所組成超棺,默認(rèn)可以將鎖的有效時(shí)間設(shè)置的更小,并實(shí)現(xiàn)一個(gè)鎖的ttl延長(zhǎng)機(jī)制棠绘。如果客戶端的計(jì)算工作只處理了一半,同時(shí)鎖的有效時(shí)間已經(jīng)不多了夜矗,它可以通過(guò)一個(gè)Lua腳本的來(lái)給所有的redis實(shí)例延長(zhǎng)key的ttl让虐,并且此key的值保持不變赡突。
客戶端應(yīng)該只有當(dāng)它可以在鎖的有效期之內(nèi)在多數(shù)redis實(shí)例上延長(zhǎng)鎖時(shí)才會(huì)考慮鎖的重新獲取±四希基本上漱受,鎖的延長(zhǎng)算法非常類(lèi)似于獲取鎖的算法。
這一機(jī)制對(duì)整體的算法沒(méi)有影響昂羡,所以鎖的重新獲取的最大嘗試數(shù)量也應(yīng)該被限制紧憾,否則,就會(huì)違背存活性特性憔四。
Want to help? | 尋求幫助?
如果你在開(kāi)發(fā)分布式系統(tǒng)了赵,對(duì)此有自己的觀點(diǎn)和分析柿汛,請(qǐng)告訴我們〔锰妫或者幫助我們將此算法使用其他開(kāi)發(fā)語(yǔ)言來(lái)實(shí)現(xiàn)它貌笨。
非常感謝!
Analysis of Redlock | Redlock的外部分析
完!
感覺(jué)中間關(guān)于Safety arguments這一段理解的不是很透徹劫流,基本是按照字面意思翻譯的暑认,如果有錯(cuò)誤的地方麻煩提出指正。多謝!