教你用 redis 實(shí)現(xiàn)分布式冪等服務(wù)中間件

背景

在編程領(lǐng)域赫段,冪等性是指對(duì)同一個(gè)系統(tǒng)舵抹,使用同樣的條件背蟆,一次請(qǐng)求和重復(fù)的多次請(qǐng)求對(duì)系統(tǒng)資源的影響是一致的鉴分。

在分布式系統(tǒng)里,client 調(diào)用 server 提供的服務(wù)带膀,由于網(wǎng)絡(luò)環(huán)境的復(fù)雜性志珍,調(diào)用可能有以下幾種情況:

server 收到 client 的請(qǐng)求,client 也收到 server 的響應(yīng)結(jié)果

client 發(fā)出了請(qǐng)求垛叨,但 server 未收到伦糯,可能是 server 重啟、網(wǎng)絡(luò)超時(shí)等原因

server 發(fā)出了響應(yīng)嗽元,但 client 未收到

對(duì)于后兩種情況敛纲,client 一般會(huì)進(jìn)行一次重試,這樣 server 可能會(huì)收到多次重復(fù)的請(qǐng)求剂癌。對(duì)于某些天然就冪等的服務(wù)來(lái)說(shuō)淤翔,比如對(duì)資源的讀操作,不管讀多少次佩谷,資源不會(huì)有變化旁壮;但對(duì)非冪等服務(wù),server 執(zhí)行一次和重復(fù)執(zhí)行多次谐檀,對(duì)資源的影響就不確定了抡谐。

例如銀行扣款服務(wù),用函數(shù)表示為?bool withdraw(account_id, amount)桐猬,client 發(fā)起一次調(diào)用?withdraw(1001, 10)?請(qǐng)求從帳戶 1001 中扣除 10 元麦撵,如果發(fā)生了上圖所示的第 2 種錯(cuò)誤,這時(shí)候 server 端在帳戶里已經(jīng)完成了扣款,但 client 并不知道厦坛,如果重試調(diào)用?withdraw(1001, 10)?五垮,server 端又會(huì)從 帳戶 1001 扣除 10 元,顯然這個(gè)非冪等的扣款服務(wù)并不是 client 想要的杜秸。

如果將 client 的一次扣款操作和后續(xù)的重試用一個(gè)額外的 id 來(lái)標(biāo)識(shí):bool withdraw(id, account_id, amount),server 針對(duì)一個(gè) id 的相同請(qǐng)求只執(zhí)行一次润绎,這樣就可以避免上述的問(wèn)題了撬碟。此時(shí)扣款服務(wù)也是冪等的了。

實(shí)現(xiàn)方案

按照上面介紹的冪等的扣款服務(wù)的實(shí)現(xiàn)思路莉撇,抽象出一個(gè)通用的中間層呢蛤,非冪等的服務(wù)要改造成冪等的,只需要增加一個(gè)額外的 id 參數(shù)棍郎。服務(wù)實(shí)現(xiàn)里先根據(jù)此 id 去中間層查詢服務(wù)是否執(zhí)行過(guò)其障,根據(jù)查詢結(jié)果決定的是否繼續(xù)后續(xù)的業(yè)務(wù)流程。中間層相當(dāng)于一個(gè)特殊的分布式互斥鎖涂佃,根據(jù) id 查詢的過(guò)程相當(dāng)于對(duì)某把鎖嘗試加鎖的操作励翼。鎖被鎖住后永遠(yuǎn)不釋放(除非鎖過(guò)期了,這里為了敘述方便簡(jiǎn)單認(rèn)為永遠(yuǎn)不釋放)辜荠。鎖被一個(gè)進(jìn)程鎖住后其他進(jìn)程都無(wú)法再加鎖汽抚,這樣就保證了服務(wù)是冪等的了。

第一個(gè)對(duì)互斥鎖加鎖的進(jìn)程任務(wù)沒(méi)有執(zhí)行完就掛掉伯病,鎖又是不會(huì)釋放的造烁,其他進(jìn)程又無(wú)法重復(fù)加鎖,導(dǎo)致這個(gè)失敗的任務(wù)也不能被其他進(jìn)程重新執(zhí)行午笛。為了避免這種情況惭蟋,將加鎖的操作分成 2 步:

TryAcquire

嘗試獲取鎖,結(jié)果有兩種情況:

1.1 拿到了鎖(鎖轉(zhuǎn)到 TryAcquired 狀態(tài))药磺,這時(shí)候可以執(zhí)行正常的業(yè)務(wù)流程告组,執(zhí)行完了需要再調(diào)用第二步 Confirm 明確鎖已被鎖住(鎖轉(zhuǎn)到 Confirmed 狀態(tài)),這之后其他進(jìn)程都拿不到這把鎖与涡;

1.2 沒(méi)拿到鎖惹谐,可能是以下三種情況之一:

1.2.1 鎖處于 Confirmed 狀態(tài),這種情況不應(yīng)該繼續(xù)業(yè)務(wù)流程處理直接返回驼卖;

1.2.2 鎖處于 TryAcquired 狀態(tài)氨肌,但超時(shí)時(shí)間沒(méi)到,說(shuō)明這個(gè)時(shí)候有其他進(jìn)程拿到了鎖正在進(jìn)行相應(yīng)的業(yè)務(wù)流程酌畜,本進(jìn)程不應(yīng)該執(zhí)行相應(yīng)的業(yè)務(wù)流程直接返回怎囚;

1.2.3 鎖處于 TryAcquired 狀態(tài),但超時(shí)時(shí)間到了,說(shuō)明已有其他進(jìn)程拿到了鎖恳守,但很久沒(méi)有 Confirm 考婴,有可能是執(zhí)行過(guò)程中掛掉了,這時(shí)候本進(jìn)程應(yīng)該要執(zhí)行相應(yīng)的業(yè)務(wù)流程催烘,然后調(diào)用第二步 Confirm 沥阱。

Confirm

將鎖置成 Confirmed 狀態(tài),表示互斥鎖被永久鎖住伊群。

鎖的狀態(tài)轉(zhuǎn)換如下所示(expire 為 redis key 過(guò)期):

使用 Redis 實(shí)現(xiàn)考杉,key 為互斥鎖的標(biāo)識(shí),value 為鎖的狀態(tài):

0:初始狀態(tài)* -1:Confirmed 狀態(tài)

其他值:TryAcquired 狀態(tài)舰始,value 為業(yè)務(wù)執(zhí)行截止時(shí)間 deadline

server 在增加了保證冪等性的流程圖如下(交易表示既定的業(yè)務(wù)執(zhí)行流程):

流程圖里省略了 redis 錯(cuò)誤處理的分支崇棠,redis 錯(cuò)誤 TryAcquire 直接返回 true 。

TryAcqurie 和 Confirm 實(shí)現(xiàn)用偽碼描述如下:


id 的取值

id 由 client 根據(jù)具體的業(yè)務(wù)場(chǎng)景決定丸卷,可以本地生成或者是從第三方服務(wù)獲取枕稀,要求需要保證能唯一標(biāo)識(shí)某個(gè)業(yè)務(wù)下的一次交易。server 端將此 id 視為互斥鎖的唯一標(biāo)識(shí)谜嫉。

timeout 的取值

timeout 應(yīng)該比正常的交易時(shí)間大萎坷,否則會(huì)導(dǎo)致多個(gè)進(jìn)程都能拿到鎖不能保證冪等;但是又不能設(shè)得太大骄恶,否則會(huì)導(dǎo)致交易執(zhí)行失敗時(shí)要過(guò)很久才能重新執(zhí)行交易食铐。

原子性保證

TryAcquire 和 Confirm 都應(yīng)該保證原子性,Confirm 只有一個(gè)簡(jiǎn)單的 SET 操作僧鲁,這個(gè)沒(méi)有問(wèn)題虐呻。TryAcquire 實(shí)際上分成兩步:1.1 SETNX 和 1.2 GET&SET(不是 redis 是 GETSET 命令)。 上面的偽碼中 1.2 GET&SET 的 SET 換成了 INCRBY 并增加了一次返回值比較寞秃,相當(dāng)于使用了樂(lè)觀鎖斟叼,所以 GET&SET 的原子性是 OK 的。在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流裙春寿。交流學(xué)習(xí)裙號(hào):821169538朗涩,里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像

下面說(shuō)明下為什么 1.1 和 1.2 整個(gè)過(guò)程沒(méi)有保證原子性也是 OK 的:

最壞的情況下假設(shè)進(jìn)程 a 進(jìn)入 TryAcquire 執(zhí)行完了 1.1 然后被操作系統(tǒng)調(diào)度出去了,此時(shí)進(jìn)程 b 進(jìn)入 TryAcquire 執(zhí)行了整個(gè)流程拿到了鎖绑改,然后執(zhí)行了一次交易谢床。這時(shí)候進(jìn)程 a 重新被調(diào)度執(zhí)行,這個(gè)時(shí)候由于進(jìn)程 b 更新了 deadline 甚至執(zhí)行完了 Confirm厘线,進(jìn)程 a 會(huì)在 1.2.1 或 1.2.2 處退出并且不會(huì)執(zhí)行交易识腿,如果走到了 1.2.3 并且拿到了鎖說(shuō)明進(jìn)程 b 執(zhí)行交易時(shí)掛掉了,這時(shí)由進(jìn)程 a 重新執(zhí)行交易也是正確的邏輯造壮。

方案的缺陷

這個(gè)方案忽略了 redis 異常情況渡讼,這種情況下 TryAcquire 總是返回 true ,可能會(huì)使交易重復(fù)執(zhí)行不能保證冪等。也可以將 redis 異常返回給調(diào)用者成箫,由調(diào)用者根據(jù)業(yè)務(wù)場(chǎng)景來(lái)決定是否需要重新執(zhí)行交易展箱。

另外一種情況進(jìn)程通過(guò) TryAcquire 拿到鎖后執(zhí)行完了交易,但 Confirm 失敗(掛掉或者網(wǎng)絡(luò)問(wèn)題)蹬昌,這種情況在 dealine 到了后混驰,其他進(jìn)程仍然可以拿到鎖并執(zhí)行交易,這時(shí)候也不能保證冪等凳厢。

缺陷的本質(zhì)是這個(gè)輕量級(jí)的解決方案無(wú)法保證分布式事務(wù)的原子性账胧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市先紫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌筹煮,老刑警劉巖遮精,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異败潦,居然都是意外死亡本冲,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門劫扒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)檬洞,“玉大人,你說(shuō)我怎么就攤上這事沟饥√碚” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵贤旷,是天一觀的道長(zhǎng)广料。 經(jīng)常有香客問(wèn)我,道長(zhǎng)幼驶,這世上最難降的妖魔是什么艾杏? 我笑而不...
    開(kāi)封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮盅藻,結(jié)果婚禮上购桑,老公的妹妹穿的比我還像新娘。我一直安慰自己氏淑,他們只是感情好勃蜘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著夸政,像睡著了一般元旬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天匀归,我揣著相機(jī)與錄音坑资,去河邊找鬼。 笑死穆端,一個(gè)胖子當(dāng)著我的面吹牛袱贮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播体啰,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼攒巍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了荒勇?” 一聲冷哼從身側(cè)響起柒莉,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沽翔,沒(méi)想到半個(gè)月后兢孝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仅偎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年跨蟹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橘沥。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窗轩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出座咆,到底是詐尸還是另有隱情痢艺,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布箫措,位于F島的核電站腹备,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏斤蔓。R本人自食惡果不足惜植酥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弦牡。 院中可真熱鬧友驮,春花似錦、人聲如沸驾锰。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)椭豫。三九已至耻瑟,卻和暖如春旨指,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喳整。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工谆构, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人框都。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓搬素,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親魏保。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熬尺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容