故事|黑熊精 揭祕「補償事務」

回覆悟空領取1000+面試資料 web


這是悟空的第 109 篇原創文章
面試

做者 | 悟空聊架構redis

來源 | 悟空聊架構(ID:PassJava666)算法

轉載請聯繫受權(微信ID:PassJava)

閱讀目錄數據庫

  • 1、背景微信

  • 2、「大唐啥都有」網站的代碼網絡

  • 3、SQL 中的事務架構

  • 4、那如何優化無事務的代碼?併發

  • 5、如何解決無事務的問題?app

  • 6、具備補償功能的解決方案

1、背景

悟空和師父一行人正在前往西天取經的路上,師父在線上買了一個福袋,訂單狀態顯示訂單已支付,可是電子福袋狀態爲未發送。

悟空來到了這家網站的後臺,找到了開發人員小黑熊

悟空:嘿,快查下我師父的訂單,錢都給了,福袋怎麼尚未到?
小黑熊:大聖,咱們也收到異常通知了,更新福袋錶的時候因網絡緣由致使福袋記錄沒有更新成功,因此福袋仍是未發送的。
悟空:福袋沒發出來,那爲何訂單狀態還一直是已支付?你這小兒,可不要瞞我!
小黑熊:大聖,咱們數據庫用的是 MongoDB 3.0,不支持事務啊。
悟空:你說的事務是什麼意思?
小黑熊事務就是保持多個更新或刪除或增長操做,要麼都成功,要麼都失敗。
悟空:也就是說第一步頂單狀態從未支付到訂單成功已經執行成功了,可是第二步更新福袋的時候失敗了,沒有自動將第一步訂單的狀態給改回去?
小黑熊:是的,大聖。
悟空:那大家怎麼沒有退款啊?
小黑熊:大聖,咱們也沒有想到有這種異常發生。
悟空:容我看下大家的代碼。

2、「大唐啥都有」網站的代碼

該網站購物的內部邏輯簡化後以下圖所示:

try {
        order.status = "已支付"; //第一步,更新訂單狀態:訂單已支付
        order.save(); //保存訂單
        luckyBag.status = "已發送"; // 第二步,更新福袋狀態:福袋已發送
        luckyBag.save(); //保存福袋
        goodCounts.count -= 1;// 第三步,更新庫存
        goodCounts.save(); // 保存庫存
        order.status="訂單成功" // 第四步,更新訂單狀態:訂單成功
        order.save(); //保存訂單
    }
    catch (excption e) {
        logError();
    }

那這樣的代碼會有什麼問題呢?

若是第一步執行成功,第二步執行失敗了,拋出了異常,則第一步訂單狀態仍是訂單成功的,福袋狀態未更新,也就是師父遇到的問題。

那如何保證兩步操做的一致性呢?(要麼都更新,要麼都不更新。)

咱們都知道SQL中是有事務這種解決方案的,咱們先來看看SQL中的事務。

3、SQL 中的事務

以前寫過一篇文章,專門來說SQL中的事務:《30分鐘全面解析-SQL事務+隔離級別+阻塞+死鎖》。在這裏用僞代碼來講明下什麼事務。

舉個購買商品的例子:用戶下了一筆單,付款了,而後發放福袋,涉及到訂單表order更新,福袋錶luckyBag更新。

start transaction // 開始事務
  try {
          update order // 第一步,更新訂單狀態
           update luckyBag // 第二步,更新福袋狀態
           commit // 提交兩部操做的更改
   } catch (excption e) {
        rollback // 回滾全部操做
     }
end transaction // 結束事務

更新訂單狀態和更新福袋狀態兩部操做成功,則所有提交到數據庫執行,若是其中任意一步出現問題,則所有回滾,就像沒有執行更新操做同樣,以保證數據的一致性。

4、那如何優化無事務的代碼?

因爲MongoDB 3.0 不支持事務,因此頗有可能出現數據不一致的狀況(訂單已支付,福袋未發送)。

那咱們既然不能享受到事務的一致性,有什麼辦法來優化這部分代碼呢?

咱們先看下代碼的時序圖:

從上面的順序圖來看,分步保存是有問題的,第一步保存成功後,第二步若是保存失敗,則數據不一致。那咱們能夠將保存日後移嗎?

咱們來看下優化後的時序圖,總體將保存日後移。

僞代碼以下:

try {
        order.status = "已支付"; //第一步,更新訂單狀態:訂單已支付
        luckyBag.status = "已發送"; // 第二步,更新福袋狀態:福袋已發送
        goodCounts.count -= 1;// 第三步,更新庫存 
        order.status="訂單成功" //第一步,更新訂單狀態:訂單已支付
 
        luckyBag.save(); //保存福袋記錄
        goodCounts.save(); // 保存庫存記錄
        order.save(); //保存訂單記錄
    }
    catch (excption e) {
        logError();
    }

那這種方式又有什麼優缺點呢?

優勢:前四步的業務邏輯處理任意一步若是出錯了,並不會影響數據庫的記錄

缺點:後三步的保存若是出錯了,和最開始的方案同樣,存在數據不一致的問題。

那如何進行解決這種問題?

5、如何解決無事務的問題?

優化後的代碼仍是可能存在數據不一致的狀況,那咱們怎麼來解決?

問題 1:若是福袋沒有自動發出去,如今還能夠補發嗎?怎麼補發?

問題 2:能夠退款嗎?手動退款仍是自動退款?分別有什麼優勢和缺點?怎麼優化?

問題 3:若是第三步更新庫存失敗,那又該怎麼作呢?

問題 4:如何退款失敗,那又該怎麼作呢?

圍繞上面幾個問題,咱們展開來論述。

問題1.1:對於補發問題,咱們怎麼來補發呢?

方案1:第二步失敗時,當即重試幾回(第一次 3s,第二次間隔 8s,第三次間隔 20s,爲何間隔時間不同?能夠留言討論哦!^_^)

方案2:將失敗的數據放到隊列裏面(能夠是存到數據庫或者 redis 裏面,建議存放到數據庫),定時從隊列裏面獲取異常數據,進行從新發送。

問題 1.2:自動補發的優勢和缺點分別是什麼呢?

方案1的優勢和缺點

優勢

(1)若是是臨時出現的網絡問題,能夠當即在短期內重試幾回,能夠解決問題。

缺點

(1)若是是接口或數據問題,短期內重試再屢次也是會失敗的;

(2)另外若是有大量失敗,重試也是會佔用系統資源的

方案 2 的優勢和缺點

優勢

(1)將重試放到異步任務中來作,能夠減小系統資源的佔用;

(2)若是是長時間出現的網絡問題,等網絡恢復後,必定會重試成功;

缺點

(1)異常數據沒法經過重試來解決,則隊列裏面的數據將一直會進行重試,沒法終止;

(2)若是有大量數據因接口或代碼問題致使失敗,則會積累大量失敗數據,而大量數據進行重試也會對系統資源形成必定壓力;

(3)重試失敗會進行error log的記錄,大量的error log對線上排查問題會形成干擾。

那補發若是一直失敗,是否是還有更好的方式?給用戶退款是否是更合理?(顧客等得很着急,趕忙把錢先退了吧。)這其實就是一種補償措施

問題 2.1 能夠退款嗎?

固然能夠退款。

問題 2.2 自動退款的優缺點?

優勢:減小運營人員的工做量

缺點:在某些狀況下,異常訂單須要多方排查覈實才能退款,就不能走自動退款。好比代碼的邏輯沒有handle某些場景,一刀切的退款會致使錢退了,商品還發給了客戶。

問題 2.3 怎麼優化?

那怎麼優化?提供自動和手動的兩種方式,當某些異常場景須要手動退款的,等開發人員覈實後,再進行手動退款。

帳不平怎麼處理?經過對帳的方式找出哪些帳不平。

問題 3 第三步更新庫存失敗怎麼處理?

咱們很容易想到的方案是及時retry或 隊列retry。那有什麼問題呢?對於秒殺活動,隊列retry確定不可行。

那咱們能夠作一次補償操做嗎?(發起退款,更新訂單狀態爲失敗。)

答案是能夠的。

問題 4 若是退款失敗怎麼處理

每一步失敗咱們都會作補償處理,可是中間某一步補償失敗,咱們該怎麼處理?好比最後錢退不了。

常見方案:

  • 1.退款失敗後主動報警通知運維人員或開發人員

  • 2.手動退款(缺點:人工操做,容易出錯,好比找訂單找錯了)

  • 3.加入隊列,自動退款(缺點:通常退款失敗都是代碼級別問題或微信側問題,因此仍是須要排查問題緣由,在這期間,全部退款失敗異常都會報警,對平常的監控形成沒必要要的干擾)

在我如今作的項目都會將退款失敗的消息如下面兩種形式推送給我:

  • 1.微信的模板消息

  • 2.雲服務商提供的日誌報警短信服務

這樣方便我去排查問題,以及快速退款。

模板消息


信告警

或者用釘釘機器人報警,這裏就不展開了。

6、具備補償功能的解決方案

咱們能夠設計一個具備補償功能的解決方案。

流程圖以下所示:

  • 1.若是第一步失敗,則發起退款

  • 2.若是第二步失敗,則更新訂單狀態爲失敗,併發起退款

  • 3.若是第三步更新庫存失敗,則退回福袋,且更新訂單狀態爲失敗,併發起退款

  • 4.若是第四步更新訂單爲成功時失敗,則庫存 +1,退回福袋,更新訂單狀態失敗,併發起退款

悟空順利解決了問題,黑熊精崇拜地看着悟空,默默地去改代碼去了...

歡迎你們留言討論自家系統是怎麼作的?

歡迎關注個人公衆號:「悟空聊架構

做者簡介:8 年互聯網職場老兵|全棧工程師|90 後超級奶爸|開源踐行者|公衆號萬粉原創號主。   藍橋簽約做者,著有《JVM 性能調優實戰》專欄,手寫了一套 7 萬字 SpringCloud 實戰總結和 3 萬字分佈式算法總結。    歡迎關注個人公衆號「悟空聊架構」,免費獲取資料學習。

我是悟空,努力變強,變身超級賽亞人!

- END -

寫了兩本 PDF, 回覆  分佈式  或  PDF  載。
個人 JVM 專欄已上架,回覆  JVM  領取

我是悟空,努力變強,變身超級賽亞人!

本文分享自微信公衆號 - 悟空聊架構(PassJava666)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索