互聯網那些事之數據丟失

 

互聯網那些事之數據丟失

本系列故事的全部案例和解決方案只是筆者之前在互聯網工做期間的一些事例,僅供你們參考,實際操做應該根據業務和項目狀況設計,歡迎你們留言提出寶貴的意見java

背景

小王和小明分別維護分佈式系統中A、b兩個服務,有一個場景是 A服務會向B服務經過MQ發送事件而且推送用戶信息,而後B服務保存用戶信息。
在這裏插入圖片描述spring

有一天,小王和小明由於一件事討論得熱火朝天、各執己見,事情由來以下:數據庫

  • 風控部的童鞋找小明說在B服務的數據庫找不到一些用戶資料
  • 小明通過排查,B服務表裏確實沒有這批用戶的數據,在日誌裏偶爾看到了一些Redis鏈接超時異常,小明想小王手動幫忙重推試試
  • 小王通過排查,確保本身已經成功推送了那幾個用戶的數據,而且推送的時候A服務並無發現MQ異常,以爲本身沒有義務去幫忙重推,應該小明本身解決

這時候,在一旁掃地的清潔工老梁過來調解,並幫忙排查分析,致使這個問題的主要緣由以下:服務器

  • B服務在接受MQ的處理類捕獲了異常,由於異常並無拋出,因此框架默認自動回覆了ACK,MQ認爲已經消費者處理成功,就再也不重複投放到隊列,但此時方法體內由於工具包出現Redis鏈接超時,拋出異常,致使消息並無被正常處理

僞代碼以下:網絡

@RabbitHandler
    public void handle(byte[] message) {
        try {
            t = parseBody(messageStr);
        } catch (Exception e) {
            log.error("消費消息失敗", e.getCause());
        }
    }

    private void handleMessage(T t) throws MQHandleException {
	    //惟一標識
        String key = t.getLockedId();
        //獲取鎖
        DistributedLock lock = DistributedLockFactory.getLock(key);
        try {
            // 解決分佈式服務提交相同資料併發問題
            lock.lock(CacheConstants.LOCK_WAIT_TIME, CacheConstants.LOCK_LEASE_TIME, CacheConstants.DEFAULT_CACHE_UNIT);
            // 處理業務邏輯
            handleBusinessLogic(t);
        } catch (LockException e) {
            throw new MQHandleException(e);
        } finally {
            // 釋放鎖
            lock.unLock();
        }
    }

  • 頻繁Redis超時是由於A、B服務共用一個Redis,A服務Key太多把Redis內存資源佔滿了(也可能鏈接佔滿),致使了B服務常常出現鏈接超時(該故障不是本章主要關注目標)
    架構

  • B服務在已經成功接受到消息後,沒有把消息先保存起來,因此也致使了自身並無能力重跑併發

清潔工老梁跟小王和小明進行一番詳談後,瞭解到他們主要需求有兩個:框架

  • B服務儘量本身從新消費信息,而不是一昧依賴A服務手動重推
  • B服務對已接收到的消息,能本身從新消費,固然,這裏指的是有意義的消息,若是一些自己A服務推送過來的消息就是有問題的,例如格式錯誤之類的,這些B服務能夠要求A重推

解決思路

通過上面的分析,老梁的解題思路主要分爲兩個方向:分佈式

  • B服務創建本身的本地異常消息事件表。
  • B服務作異常分類,只對能夠重跑的消息事件進行重跑

本地異常消息事件表

通常來講,常見的微服務架構實現最終一致性有三種模式:可靠事件模式、業務補償模式、TCC模式。這裏AB服務是經過業務補償模式實現最終一致性,但這裏又跟咱們通常的分佈式架構的事務問題不一樣,這裏咱們只須要保證B服務能最終把正常消息事件消費成功便可。微服務

實現思路:

  • 創建一張本地異常消息事件表,爲了不太多數據庫IO操做,這裏只會記錄異常事件
  • 提取一個通用消息處理層,統一保存異常消息事件,並進行狀態更新
  • 提取一個事件恢復模塊,統一對失敗事件進行追蹤
  • 對於重跑仍失敗消息事件,設置一個重跑次數上限,進行自動重跑,能夠經過調度任務去作(事件恢復模塊),當重跑屢次仍然失敗(像網絡異常和數據庫異常之類,短期不會被修復),則後期進行人工重跑

表設計


針對於B服務,對於收到的MQ信息沒有進行有效的記錄,並且MQ信息處理以後,存在修改錯誤,無法進行對應信息補充修復的功能,增長通用消息處理層,進行消息體的記錄和回溯。 在獲取消息以後進行一次記錄,進行冪等操做和對應的狀態更新, 消息狀態在業務相關操做完成後,標記爲處理完成,認爲對應消息狀態結束。

這裏hash_value是對請求體進行hash計算得出來的一個值,例如:MD五、SHA-2,保證每一個不一樣請求的hash碼不同,相同的請求hash碼相同,能夠用於冪等控制。

表大體操做流程:

異常消息狀態設計

異常消息有4個狀態

  • 待處理 當系統消費失敗時,會對特定的異常插入異常事件表,初始狀態爲 待處理
  • 處理中 當失敗恢復模塊開始執行任務時會把當前異常事件狀態設置爲 處理中
  • 處理完成 當失敗事件重跑成功後,會把當前異常事件狀態設置爲 處理完成
  • 異常 當失敗事件重跑超過上限次數後,會把當前異常事件狀態設置爲 異常,等待後期人工重跑

事件恢復模塊

失敗事件隊列在這裏是採用數據庫表代替


異常分類

由於並不是全部的異常都能重跑就能解決問題,咱們只能針對能夠修復的異常進行重試,這裏把異常分爲兩大類:

  • 可修復異常:可修復異常指的是能夠經過重跑解決的異常,如:數據庫超時、數據庫缺乏字段、Redis獲取鎖失敗、處理邏輯有問題致使信息缺失、系統升級致使消費失敗、網絡問題、服務器不穩定等引發。
    • 可當即修復異常:指一些能夠經過當即重試就能恢復的異常。例如短暫的網絡中斷引發的異常,通常能夠在功能代碼級進行當即重試,可使用spring-retry等組件
    • 延遲修復異常:指一些短期內不能當即恢復的異常,須要延遲執行,等待故障修復。例如依賴的下游系統正在升級,致使一段時間服務接口中斷不能夠用,須要等待服務啓動才能使用,通常經過定時任務設定必定時間間隔或者重跑次數去解決
    • 人工修復異常:指系統沒辦法直接修復,出現了一些未知異常或者短期內不可解決的異常,例如Redis宕掉沒法預知修復時間、上線時腳本遺漏致使表裏缺乏字段等,須要人工干預進行重跑,通常經過後臺管理頁面操做
  • 不可修復異常:不可修復異常指不能經過重跑就能解決的異常。如:上游系統傳輸格式有問題、消息事件內容自己有誤等引發的異常,這些即便重跑也解決不了問題,應該要從上游系統或者根源去解決。

B服務異常處理流程

最後小明負責的B服務按照老梁的思路,從新調整了代碼,異常處理流程以下:

相關文章
相關標籤/搜索