若是這是第二次看到個人文章,歡迎點擊文末連接關注 個人公衆號喲~
本文長度爲4229字,建議閱讀11分鐘。html
這是本系列中既「數據一致性」後的第二章節——「高可用」的完結篇。前端
前面幾篇中z哥跟你聊了聊作「高可用」的意義,以及如何作「負載均衡」和「高可用三劍客」(熔斷、限流、降級,文末會附上前文鏈接:))。此次,咱們來聊一聊在保證對外高可用的同時,憋出的「內傷」該如何經過「補償」機制來自行消化。java
以電商的購物場景爲例:程序員
客戶端 ---->購物車微服務 ---->訂單微服務 ----> 支付微服務。算法
這種調用鏈很是廣泛。sql
那麼爲何須要考慮補償機制呢?json
正如以前幾篇文章所說,一次跨機器的通訊可能會通過DNS 服務,網卡、交換機、路由器、負載均衡等設備,這些設備都不必定是一直穩定的,在數據傳輸的整個過程當中,只要任意一個環節出錯,都會致使問題的產生。微信
而在分佈式場景中,一個完整的業務又是由屢次跨機器通訊組成的,因此產生問題的機率成倍數增長。網絡
可是,這些問題並不徹底表明真正的系統沒法處理請求,因此咱們應當儘量的自動消化掉這些異常。數據結構
可能你會問,以前也看到過「補償」和「事務補償」或者「重試」,它們之間的關係是什麼?
你其實能夠不用太糾結這些名字,從目的來講都是同樣的。就是一旦某個操做發生了異常,如何經過內部機制將這個異常產生的「不一致」狀態消除掉。
題外話:在Z哥看來,無論用什麼方式,只要經過額外的方式解決了問題均可以理解爲是「補償」,因此「事務補償」和「重試」都是「補償」的子集。前者是一個逆向操做,然後者則是一個正向操做。
只是從結果來看,二者的意義不一樣。「事務補償」意味着「放棄」,當前操做必然會失敗。
「重試」則還有處理成功的機會。這兩種方式分別適用於不一樣的場景。
由於「補償」已是一個額外流程了,既然可以走這個額外流程,說明時效性並非第一考慮的因素,因此作補償的核心要點是:寧肯慢,不可錯。
所以,不要草率的就肯定了補償的實施方案,須要謹慎的評估。雖然說錯誤沒法100%避免,可是抱着這樣的一個心態或多或少能夠減小一些錯誤的發生。
作「補償」的主流方式就前面提到的「事務補償」和「重試」,如下會被稱做「回滾」和「重試」。
咱們先來聊聊「回滾」。相比「重試」,它邏輯上更簡單一些。
Z哥將回滾分爲2種模式,一種叫「顯式回滾」(調用逆向接口),一種叫「隱式回滾」(無需調用逆向接口)。
最多見的就是「顯式回滾」。這個方案無非就是作2個事情:
首先要肯定失敗的步驟和狀態,從而肯定須要回滾的範圍。一個業務的流程,每每在設計之初就制定好了,因此肯定回滾的範圍比較容易。但這裏惟一須要注意的一點就是:若是在一個業務處理中涉及到的服務並非都提供了「回滾接口」,那麼在編排服務時應該把提供「回滾接口」的服務放在前面,這樣當後面的工做服務錯誤時還有機會「回滾」。
其次要能提供「回滾」操做使用到的業務數據。「回滾」時提供的數據越多,越有益於程序的健壯性。由於程序能夠在收到「回滾」操做的時候能夠作業務的檢查,好比檢查帳戶是否相等,金額是否一致等等。
因爲這個中間狀態的數據結構和數據大小並不固定,因此Z哥建議你在實現這點的時候能夠將相關的數據序列化成一個json,而後存放到一個nosql類型的存儲中。
「隱式回滾」相對來講運用場景比較少。它意味着這個回滾動做你不須要進行額外處理,下游服務內部有相似「預佔」而且「超時失效」的機制的。例如:
電商場景中,會將訂單中的商品先預佔庫存,等待用戶在 15 分鐘內支付。若是沒有收到用戶的支付,則釋放庫存。
下面聊聊能夠有不少玩法,也更容易陷入坑裏的「重試」。
「重試」最大的好處在於,業務系統能夠不須要提供「逆向接口」,這是一個對長期開發成本特別大的利好,畢竟業務是每天在變的。因此,在可能的狀況下,應該優先考慮使用「重試」。
不過,相比「回滾」來講「重試」的適用場景更少一些,因此咱們第一步首先要判斷,當前場景是否適合「重試」。好比:
若是肯定要進行「重試」,咱們還須要選定一個合適的「重試策略」。主流的「重試策略」主要是如下幾種。
策略1.當即重試。有時故障是候暫時性,多是因網絡數據包衝突或硬件組件流量高峯等事件形成的。在此狀況下,適合當即重試操做。不過,當即重試次數不該超過一次,若是當即重試失敗,應改用其它的策略。
策略2.固定間隔。應用程序每次嘗試的間隔時間相同。 這個好理解,例如,固定每 3 秒重試操做。(如下全部示例代碼中的具體的數字僅供參考。)
策略1和策略2多用於前端系統的交互式操做中。
策略3.增量間隔。每一次的重試間隔時間增量遞增。好比,第一次0秒、第二次3秒、第三次6秒,九、十二、15這樣。
return (retryCount - 1) * incrementInterval;複製代碼
使得失敗次數越多的重試請求優先級排到越後面,給新進入的重試請求讓道。
策略4.指數間隔。每一次的重試間隔呈指數級增長。和增量間隔「異曲同工」,都是想讓失敗次數越多的重試請求優先級排到越後面,只不過這個方案的增加幅度更大一些。
return 2 ^ retryCount;複製代碼
策略5.全抖動。在遞增的基礎上,增長隨機性(能夠把其中的指數增加部分替換成增量增加。)。適用於將某一時刻集中產生的大量重試請求進行壓力分散的場景。
return random(0 , 2 ^ retryCount);複製代碼
策略6.等抖動。在「指數間隔」和「全抖動」之間尋求一箇中庸的方案,下降隨機性的做用。適用場景和「全抖動」同樣。
var baseNum = 2 ^ retryCount;
return baseNum + random(0 , baseNum);複製代碼
三、四、五、6策略的表現狀況大體是這樣。(x軸爲重試次數)
爲何說「重試」有坑呢?
正如前面聊到的那樣,出於對開發成本考慮,你在作「重試」的時候多是複用的常規調用的接口。那麼此時就不得不提一個「冪等性」問題。
若是實現「重試」選用的技術方案不能100%確保不會重複發起重試,那麼「冪等性」問題是一個必需要考慮的問題。哪怕技術方案能夠確保100%不會重複發起重試,出於對意外狀況的考量,儘可能也考慮一下「冪等性」問題。
冪等性:無論對程序發起幾回重複調用,程序表現的狀態(全部相關的數據變化)與調用一次的結果是一致的話,就是保證了冪等性。
這意味着能夠根據須要重複或重試操做,而不會致使意外的影響。對於非冪等操做,算法可能必須跟蹤操做是否已經執行。
因此,一旦某個功能支持「重試」,那麼整個鏈路上的接口都須要考慮冪等性問題,不能由於服務的屢次調用而致使業務數據的累計增長或減小。
知足「冪等性」其實就是須要想辦法識別重複的請求,而且將其過濾掉。思路就是:
第1點,咱們可使用一個全局惟一id生成器或者生成服務(能夠擴展閱讀,分佈式系統中的必備良藥 —— 全局惟一單據號生成)。 或者簡單粗暴一些,使用官方類庫自帶的Guid、uuid之類的也行。
而後經過rpc框架在發起調用的客戶端中,對每一個請求增長一個惟一標識的字段進行賦值。
第2點,咱們能夠在服務端經過Aop的方式切入到實際的處理邏輯代碼以前和以後,一塊兒配合作驗證。
大體的代碼思路以下。
【方法執行前】
if(isExistLog(requestId)){ //1.判斷請求是否已被接收過。 對應序號3
var lastResult = getLastResult(); //2.獲取用於判斷以前的請求是否已經處理完成。 對應序號4
if(lastResult == null){
var result = waitResult(); //掛起等待處理完成
return result;
}
else{
return lastResult;
}
}
else{
log(requestId); //3.記錄該請求已接收
}
//do something..
【方法執行後】
logResult(requestId, result); //4.將結果也更新一下。複製代碼
若是「補償」這個工做是經過MQ來進行的話,這事就能夠直接在對接MQ所封裝的SDK中作。在生產端賦值全局惟一標識,在消費端經過惟一標識消重。
再聊一些Z哥積累的最佳實踐吧(劃重點:)),都是針對「重試」的,的確這也是工做中最經常使用的方案。
「重試」特別適合在高負載狀況下被「降級」,固然也應當受到「限流」和「熔斷」機制的影響。當「重試」的「矛」與「限流」和「熔斷」的「盾」搭配使用,效果纔是最好。
須要衡量增長補償機制的投入產出比。一些不是很重要的問題時,應該「快速失敗」而不是「重試」。
過分積極的重試策略(例如間隔過短或重試次數過多)會對下游服務形成不利影響,這點必定要注意。
必定要給「重試」制定一個終止策略。
當回滾的過程很困難或代價很大的狀況下,能夠接受很長的間隔及大量的重試次數,DDD中常常被提到的「saga」模式其實也是這樣的思路。不過,前提是不會由於保留或鎖定稀缺資源而阻止其餘操做(好比一、二、三、四、5幾個串行操做。因爲2一直沒處理完成致使三、四、5無法繼續進行)。
這篇咱們先聊了下作「補償」的意義,以及作補償的2個方式「回滾」和「重試」的實現思路。
而後,提醒你要注意「重試」的時候須要考慮冪等性問題,而且z哥也給出了一個解決思路。
最後,分享了幾個z哥總結的針對「重試」的最佳實踐。
但願對你有所幫助。
Question:
你以前有哪些時候是經過本身人工來作「補償」的經歷嗎?歡迎吐槽~
z哥本身就有屢次熬到半夜才把「意外」形成的混亂清理乾淨,刻骨銘心啊😂。
相關文章:
▶關於做者:張帆(Zachary,我的微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。本文首發於公衆號:「跨界架構師」(ID:Zachary_ZF)。<-- 點擊後閱讀熱門文章
按期發表原創內容:架構設計丨分佈式系統丨產品丨運營丨一些思考。
若是你是初級程序員,想提高但不知道如何下手。又或者作程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注個人公衆號「跨界架構師」,回覆「技術」,送你一份我長期收集和整理的思惟導圖。
若是你是運營,面對不斷變化的市場一籌莫展。又或者想了解主流的運營策略,以豐富本身的「倉庫」。歡迎關注個人公衆號「跨界架構師」,回覆「運營」,送你一份我長期收集和整理的思惟導圖。