溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

分布式系統(tǒng)關(guān)注點——99%的人都能看懂的「補償」以及最佳實踐

發(fā)布時間:2020-07-11 14:37:38 來源:網(wǎng)絡(luò) 閱讀:216 作者:Zachary_Fan 欄目:軟件技術(shù)

如果這是第二次看到我的文章,歡迎文末掃碼訂閱我的個人公眾號(跨界架構(gòu)師)喲~  

本文長度為4229字,建議閱讀11分鐘。

 

這是本系列中既「數(shù)據(jù)一致性」后的第二章節(jié)——「高可用」的完結(jié)篇。

 

前面幾篇中z哥跟你聊了聊做「高可用」的意義,以及如何做「負(fù)載均衡」和「高可用三劍客」(熔斷、限流、降級,文末會附上前文連接:))。這次,我們來聊一聊在保證對外高可用的同時,憋出的“內(nèi)傷”該如何通過「補償」機(jī)制來自行消化。

 

 

一、「補償」機(jī)制的意義?

以電商的購物場景為例:

客戶端 ---->購物車微服務(wù) ---->訂單微服務(wù) ----> 支付微服務(wù)。

這種調(diào)用鏈非常普遍。

 

那么為什么需要考慮補償機(jī)制呢?

正如之前幾篇文章所說,一次跨機(jī)器的通信可能會經(jīng)過DNS 服務(wù),網(wǎng)卡、交換機(jī)、路由器、負(fù)載均衡等設(shè)備,這些設(shè)備都不一定是一直穩(wěn)定的,在數(shù)據(jù)傳輸?shù)恼麄€過程中,只要任意一個環(huán)節(jié)出錯,都會導(dǎo)致問題的產(chǎn)生。

而在分布式場景中,一個完整的業(yè)務(wù)又是由多次跨機(jī)器通信組成的,所以產(chǎn)生問題的概率成倍數(shù)增加。

但是,這些問題并不完全代表真正的系統(tǒng)無法處理請求,所以我們應(yīng)當(dāng)盡可能的自動消化掉這些異常。

 

可能你會問,之前也看到過「補償」和「事務(wù)補償」或者「重試」,它們之間的關(guān)系是什么?

你其實可以不用太糾結(jié)這些名字,從目的來說都是一樣的。就是一旦某個操作發(fā)生了異常,如何通過內(nèi)部機(jī)制將這個異常產(chǎn)生的「不一致」?fàn)顟B(tài)消除掉。

題外話:在Z哥看來,不管用什么方式,只要通過額外的方式解決了問題都可以理解為是「補償」,所以「事務(wù)補償」和「重試」都是「補償」的子集。前者是一個逆向操作,而后者則是一個正向操作。

只是從結(jié)果來看,兩者的意義不同?!甘聞?wù)補償」意味著“放棄”,當(dāng)前操作必然會失敗。

分布式系統(tǒng)關(guān)注點——99%的人都能看懂的「補償」以及最佳實踐

▲事務(wù)補償

「重試」則還有處理成功的機(jī)會。這兩種方式分別適用于不同的場景。

分布式系統(tǒng)關(guān)注點——99%的人都能看懂的「補償」以及最佳實踐

▲重試

 

因為「補償」已經(jīng)是一個額外流程了,既然能夠走這個額外流程,說明時效性并不是第一考慮的因素,所以做補償?shù)暮诵囊c是:寧可慢,不可錯

因此,不要草率的就確定了補償?shù)膶嵤┓桨福枰?jǐn)慎的評估。雖說錯誤無法100%避免,但是抱著這樣的一個心態(tài)或多或少可以減少一些錯誤的發(fā)生。

 

 

二、「補償」該怎么做?

做「補償」的主流方式就前面提到的「事務(wù)補償」和「重試」,以下會被稱作「回滾」和「重試」。

 

我們先來聊聊「回滾」。相比「重試」,它邏輯上更簡單一些。

 

「回滾」

Z哥將回滾分為2種模式,一種叫「顯式回滾」(調(diào)用逆向接口),一種叫「隱式回滾」(無需調(diào)用逆向接口)。

 

最常見的就是「顯式回滾」。這個方案無非就是做2個事情:

首先要確定失敗的步驟和狀態(tài),從而確定需要回滾的范圍。一個業(yè)務(wù)的流程,往往在設(shè)計之初就制定好了,所以確定回滾的范圍比較容易。但這里唯一需要注意的一點就是:如果在一個業(yè)務(wù)處理中涉及到的服務(wù)并不是都提供了「回滾接口」,那么在編排服務(wù)時應(yīng)該把提供「回滾接口」的服務(wù)放在前面,這樣當(dāng)后面的工作服務(wù)錯誤時還有機(jī)會「回滾」

 

其次要能提供「回滾」操作使用到的業(yè)務(wù)數(shù)據(jù)。「回滾」時提供的數(shù)據(jù)越多,越有益于程序的健壯性。因為程序可以在收到「回滾」操作的時候可以做業(yè)務(wù)的檢查,比如檢查賬戶是否相等,金額是否一致等等。

由于這個中間狀態(tài)的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)大小并不固定,所以Z哥建議你在實現(xiàn)這點的時候可以將相關(guān)的數(shù)據(jù)序列化成一個json,然后存放到一個nosql類型的存儲中。

 

「隱式回滾」相對來說運用場景比較少。它意味著這個回滾動作你不需要進(jìn)行額外處理,下游服務(wù)內(nèi)部有類似“預(yù)占”并且“超時失效”的機(jī)制的。例如:

電商場景中,會將訂單中的商品先預(yù)占庫存,等待用戶在 15 分鐘內(nèi)支付。如果沒有收到用戶的支付,則釋放庫存。

 

 

下面聊聊可以有很多玩法,也更容易陷入坑里的「重試」。

 

「重試」 

「重試」最大的好處在于,業(yè)務(wù)系統(tǒng)可以不需要提供「逆向接口」,這是一個對長期開發(fā)成本特別大的利好,畢竟業(yè)務(wù)是天天在變的。所以,在可能的情況下,應(yīng)該優(yōu)先考慮使用「重試」。

 

不過,相比「回滾」來說「重試」的適用場景更少一些,所以我們第一步首先要判斷,當(dāng)前場景是否適合「重試」。比如:

  • 下游系統(tǒng)返回「請求超時」、「被限流中」等臨時狀態(tài)的時候,我們可以考慮重試

  • 而如果是返回“余額不足”、“無權(quán)限”等明確無法繼續(xù)的業(yè)務(wù)性錯誤的時候就不需要重試

  • 一些中間件或者rpc框架中返回Http503、404等沒有何時恢復(fù)的預(yù)期的時候,也不需要重試

 

如果確定要進(jìn)行「重試」,我們還需要選定一個合適的「重試策略」。主流的「重試策略」主要是以下幾種。

 

策略1.立即重試。有時故障是候暫時性,可能是因網(wǎng)絡(luò)數(shù)據(jù)包沖突或硬件組件流量高峰等事件造成的。在此情況下,適合立即重試操作。不過,立即重試次數(shù)不應(yīng)超過一次,如果立即重試失敗,應(yīng)改用其它的策略。

 

策略2.固定間隔。應(yīng)用程序每次嘗試的間隔時間相同。 這個好理解,例如,固定每 3 秒重試操作。(以下所有示例代碼中的具體的數(shù)字僅供參考。

策略1和策略2多用于前端系統(tǒng)的交互式操作中。

 

策略3.增量間隔。每一次的重試間隔時間增量遞增。比如,第一次0秒、第二次3秒、第三次6秒,9、12、15這樣。

return (retryCount - 1) * incrementInterval;

使得失敗次數(shù)越多的重試請求優(yōu)先級排到越后面,給新進(jìn)入的重試請求讓道。

 

策略4.指數(shù)間隔。每一次的重試間隔呈指數(shù)級增加。和增量間隔“殊途同歸”,都是想讓失敗次數(shù)越多的重試請求優(yōu)先級排到越后面,只不過這個方案的增長幅度更大一些。

return 2 ^ retryCount;

 

策略5.全抖動。在遞增的基礎(chǔ)上,增加隨機(jī)性(可以把其中的指數(shù)增長部分替換成增量增長。)。適用于將某一時刻集中產(chǎn)生的大量重試請求進(jìn)行壓力分散的場景。

return random(0 , 2 ^ retryCount);

 

策略6.等抖動。在「指數(shù)間隔」和「全抖動」之間尋求一個中庸的方案,降低隨機(jī)性的作用。適用場景和「全抖動」一樣。

var baseNum = 2 ^ retryCount;return baseNum + random(0 , baseNum);

 

3、4、5、6策略的表現(xiàn)情況大致是這樣。(x軸為重試次數(shù))

分布式系統(tǒng)關(guān)注點——99%的人都能看懂的「補償」以及最佳實踐

 

 

為什么說「重試」有坑呢?

正如前面聊到的那樣,出于對開發(fā)成本考慮,你在做「重試」的時候可能是復(fù)用的常規(guī)調(diào)用的接口。那么此時就不得不提一個「冪等性」問題。 

如果實現(xiàn)「重試」選用的技術(shù)方案不能100%確保不會重復(fù)發(fā)起重試,那么「冪等性」問題是一個必須要考慮的問題。哪怕技術(shù)方案可以確保100%不會重復(fù)發(fā)起重試,出于對意外情況的考量,盡量也考慮一下「冪等性」問題。

 冪等性:不管對程序發(fā)起幾次重復(fù)調(diào)用,程序表現(xiàn)的狀態(tài)(所有相關(guān)的數(shù)據(jù)變化)與調(diào)用一次的結(jié)果是一致的話,就是保證了冪等性。

 這意味著可以根據(jù)需要重復(fù)或重試操作,而不會導(dǎo)致意外的影響。對于非冪等操作,算法可能必須跟蹤操作是否已經(jīng)執(zhí)行。

 

所以,一旦某個功能支持「重試」,那么整個鏈路上的接口都需要考慮冪等性問題,不能因為服務(wù)的多次調(diào)用而導(dǎo)致業(yè)務(wù)數(shù)據(jù)的累計增加或減少。  

 

滿足「冪等性」其實就是需要想辦法識別重復(fù)的請求,并且將其過濾掉。思路就是:

  1. 給每個請求定義一個唯一標(biāo)識。

  2. 在進(jìn)行「重試」的時候判斷這個請求是否已經(jīng)被執(zhí)行或者正在被執(zhí)行,如果是則拋棄該請求。

第1點,我們可以使用一個全局唯一id生成器或者生成服務(wù)(可以擴(kuò)展閱讀,分布式系統(tǒng)中的必備良藥 —— 全局唯一單據(jù)號生成)。 或者簡單粗暴一些,使用官方類庫自帶的Guid、uuid之類的也行。

然后通過rpc框架在發(fā)起調(diào)用的客戶端中,對每個請求增加一個唯一標(biāo)識的字段進(jìn)行賦值。

第2點,我們可以在服務(wù)端通過Aop的方式切入到實際的處理邏輯代碼之前和之后,一起配合做驗證。

分布式系統(tǒng)關(guān)注點——99%的人都能看懂的「補償」以及最佳實踐

大致的代碼思路如下。

【方法執(zhí)行前】if(isExistLog(requestId)){  //1.判斷請求是否已被接收過。  對應(yīng)序號3
    var lastResult = getLastResult();  //2.獲取用于判斷之前的請求是否已經(jīng)處理完成。  對應(yīng)序號4
    if(lastResult == null){  
        var result = waitResult();  //掛起等待處理完成
        return result;
    }
    else{
        return lastResult;
    }  
}
else{
    log(requestId);  //3.記錄該請求已接收
}

//do something..【方法執(zhí)行后】

logResult(requestId, result);  //4.將結(jié)果也更新一下。

 

 

如果「補償」這個工作是通過MQ來進(jìn)行的話,這事就可以直接在對接MQ所封裝的SDK中做。在生產(chǎn)端賦值全局唯一標(biāo)識,在消費端通過唯一標(biāo)識消重。

 

 

三、「重試」的最佳實踐

再聊一些Z哥積累的最佳實踐吧(劃重點:)),都是針對「重試」的,的確這也是工作中最常用的方案。

 

「重試」特別適合在高負(fù)載情況下被「降級」,當(dāng)然也應(yīng)當(dāng)受到「限流」和「熔斷」機(jī)制的影響。當(dāng)「重試」的“矛”與「限流」和「熔斷」的“盾”搭配使用,效果才是最好。

 

需要衡量增加補償機(jī)制的投入產(chǎn)出比。一些不是很重要的問題時,應(yīng)該「快速失敗」而不是「重試」。

 

過度積極的重試策略(例如間隔太短或重試次數(shù)過多)會對下游服務(wù)造成不利影響,這點一定要注意。

 

一定要給「重試」制定一個終止策略。

 

當(dāng)回滾的過程很困難或代價很大的情況下,可以接受很長的間隔及大量的重試次數(shù),DDD中經(jīng)常被提到的「saga」模式其實也是這樣的思路。不過,前提是不會因為保留或鎖定稀缺資源而阻止其他操作(比如1、2、3、4、5幾個串行操作。由于2一直沒處理完成導(dǎo)致3、4、5沒法繼續(xù)進(jìn)行)。

 

  

四、總結(jié)

這篇我們先聊了下做「補償」的意義,以及做補償?shù)?個方式「回滾」和「重試」的實現(xiàn)思路。

然后,提醒你要注意「重試」的時候需要考慮冪等性問題,并且z哥也給出了一個解決思路。

最后,分享了幾個z哥總結(jié)的針對「重試」的最佳實踐。

希望對你有所幫助。

 

 

Question

你之前有哪些時候是通過自己人工來做「補償」的經(jīng)歷嗎?歡迎吐槽~

z哥自己就有多次熬到半夜才把“意外”造成的混亂清理干凈,刻骨銘心啊

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI