前言
最近在參加學(xué)校安排的實(shí)訓(xùn)任務(wù),我們小組需完成一套分布式&微服務(wù)跨境電商,雖然這題目看起來(lái)有點(diǎn)老套,并且隊(duì)友多是 Java 技術(shù)棧,所以我光榮(被迫)
的成為了一名前端,并順路使用 PHP 的 Swoole 幫助負(fù)責(zé)服務(wù)器端的同學(xué)編寫了幾個(gè)微服務(wù)模塊。在小組成員之間的協(xié)作中,還是出現(xiàn)了不少有趣的火花。
在昨天 review 隊(duì)友代碼的過(guò)程中,發(fā)現(xiàn)了我們組分布式鎖的寫法似乎有點(diǎn)問(wèn)題,實(shí)現(xiàn)代碼如下:
加鎖部分
解鎖部分
主要原理是使用了 redis 的 setnx 去插入一組 key-value,其中 key 要上鎖的標(biāo)識(shí)(在項(xiàng)目中是鎖死用戶 userId),如果上鎖失敗則返回 false。但是根據(jù)二段鎖的思路,仔細(xì)思考會(huì)存在這么一個(gè)有趣的現(xiàn)象:
假設(shè)微服務(wù) A 的某個(gè)請(qǐng)求對(duì) userId = 7 的用戶上鎖,則微服務(wù) A 的這個(gè)請(qǐng)求可以讀取這個(gè)用戶的信息,且可以修改其內(nèi)容 ;其他模塊只能讀取這個(gè)用戶的信息,無(wú)法修改其內(nèi)容。
假設(shè)微服務(wù) A 的當(dāng)前請(qǐng)求對(duì) userId = 7 的用戶解鎖,則所有模塊可以讀取這個(gè)用戶的信息,且可以修改其內(nèi)容
如此一來(lái):
很明顯,這三點(diǎn)并不是我們所希望的。那么如何實(shí)現(xiàn)分布式鎖才是最佳實(shí)踐吶?
一個(gè)好的分布式鎖需要實(shí)現(xiàn)什么
我們應(yīng)該怎么做
綜上所述,我們小組的分布式鎖在實(shí)現(xiàn)模塊互斥的情況下,忽略的一個(gè)重要問(wèn)題便是“請(qǐng)求互斥”。我們只需要在加鎖時(shí),key-value 的值保存為當(dāng)前請(qǐng)求的 requestId ,解鎖時(shí)加多一次判斷,是否為同一請(qǐng)求即可。
那么這么修改之后,我們可以高枕無(wú)憂了嗎?
是的,夠用了。因?yàn)槲覀冮_(kāi)發(fā)環(huán)境 Redis 是統(tǒng)一用一臺(tái)服務(wù)器上的單例,采用上述方式實(shí)現(xiàn)的分布式鎖并沒(méi)有什么問(wèn)題,但在準(zhǔn)備部署到生產(chǎn)環(huán)境下時(shí),突然意識(shí)到一個(gè)問(wèn)題:如果實(shí)現(xiàn)主從讀寫分離,redis 多機(jī)主從同步數(shù)據(jù)時(shí),采用的是異步復(fù)制,也便是一個(gè)“寫”操作到我們的 reids 主庫(kù)之后,便馬上返回成功(并不會(huì)等到同步到從庫(kù)后再返回,如果這種是同步完成后再返回便是同步復(fù)制),這將會(huì)造成一個(gè)問(wèn)題:
假設(shè)我們的模塊 A中 id=1 的請(qǐng)求上鎖成功后,沒(méi)同步到從庫(kù)前主庫(kù)被我們玩壞了(宕機(jī)),則 redis 哨兵將會(huì)從從庫(kù)中選擇出一臺(tái)新的主庫(kù),此時(shí)若模塊 A 中 id=2 的請(qǐng)求重新請(qǐng)求加鎖,將會(huì)是成功的。
技不如人,我們只能借助搜索引擎劃水了(大霧),發(fā)現(xiàn)這種情況還真的有通用的解決方案:redlock。
怎么實(shí)現(xiàn) Redlock 分布式安全鎖
首先 redlock 是 redis 官方文檔推薦的實(shí)現(xiàn)方式,本身并沒(méi)有用到主從層面的架構(gòu),采用的是多態(tài)主庫(kù),依次去取鎖的方式。假設(shè)這里有 5 臺(tái)主庫(kù),整體流程大致如下:
加鎖
解鎖
直接向 5 臺(tái)服務(wù)器發(fā)起請(qǐng)求即可,無(wú)論這臺(tái)服務(wù)器上是不是已經(jīng)有鎖。
整體思路很簡(jiǎn)單,但是實(shí)現(xiàn)起來(lái)仍有許多值得注意的地方。在向這 5 臺(tái)服務(wù)器發(fā)送加鎖請(qǐng)求時(shí),由于會(huì)帶上一個(gè)過(guò)期時(shí)間以保證上文所提到的“自動(dòng)解鎖(容錯(cuò)性) ”,考慮到延時(shí)等原因,這 5 臺(tái)機(jī)自動(dòng)解鎖的時(shí)間不完全相同,因此存在一個(gè)加
鎖時(shí)間差的問(wèn)題,一般而言是這么解決的: