基於數據庫的事務消息解決分佈式事務方案

轉載請註明出處:http://www.cnblogs.com/lizo/p/8516502.htmlhtml

概述

當單庫已不能支撐當前業務的時候,咱們每每都考慮進行分庫(橫向拆分或者縱向拆分)。但分庫有個沒法迴避的問題,就是事務問題。網上有不少分佈式事務解決方案,例如XA,TCC等,可是最經常使用,也是改形成本最低就是使用最終一致性來保證分佈式事務。
比較經常使用的就是使用消息中間件(RabbitMq,RocketMq),經過事務消息來解決最終一致性。參考https://zhuanlan.zhihu.com/p/25933039?utm_source=tuicool&utm_medium=referralspring

本篇文章將使用數據庫的來達到最終一致性的實現方案。數據庫

名詞解釋

  • 主庫-拆分前,業務訪問的數據庫
  • 分庫-拆分後,部分業務數據放入到分庫中

注:如下有些內容是在使用事務消息(不管是基於數據庫仍是基於消息隊列)應該考慮的地方。網絡

基於數據庫的事務消息

事務消息

所謂基於數據庫的事務消息,其實很好理解,就是在數據庫中建立一個相似消息隊列的表,用於保存事務消息。在拆分前,一個事務中,有多個主庫的數據操做。以下圖,負載均衡

 

可是在拆分數據庫後,有業務被拆分到分庫中去了,這樣,原有的單庫事務被打破,可是經過把拆分出去的業務使用一個事務消息來代替(事務消息表也是在主庫中,因此這裏仍是單庫事務),後續再經過其餘方式去執行該事務消息所對應的業務邏輯便可,這樣,就能夠達到最終一致性,以下圖框架

 

事務消息執行器

前面說到了,事務消息須要一個處理器來進行執行事務消息所對應的業務邏輯。事務處理器應該是順序的去讀取並執行的。異步

設想一個場景:當出現某一條消息處理失敗,若是執行器要等當前消息執行成功才繼續日後執行(甚至該消息永遠不會處理成功),那麼會影響後續消息的執行,致使整個系統出現問題。分佈式

所以,消息處理器即要保證消息處理儘量處理快,又能保證消息最終能執行成功。 在消息執行器中必須設置2個任務:ui

  • 第一個任務,消息處理任務,已最快的速度執行消息,若是消息處理失敗了,跳過該消息繼續執行後面的消息。
  • 第二個任務,消息校驗任務,這個任務就是順序檢查消息,保證全部消息都執行成功,若是失敗,進行重試,屢次重試失敗之後發出告警以讓人工介入處理。 以下圖

 

注:上圖左邊那個是消息隊列及其處理狀態spa

消息執行的特性

  • 延遲處理性。消息不是實時處理的,而是用過消息執行器來異步執行的。所以,若是在原有邏輯中,須要特別注意後續流程對該消息處理結果是否是有實時依賴性(例如後續業務邏輯中會使用該消息處理結果來作一些計算等)。
  • 處理無序性。因爲消息不必定是順序執行的,全部保證即便後生成的消息先執行,也不能出現問題。
  • 最終成功性。對每條插入的消息,保證該條消息必定要能執行成功

如何確認消息已執行成功

設想,若是分庫業務執行成功(更新分庫),而後去更新消息狀態(主庫),這樣,又是一個誇庫事務,因此,得想其餘辦法來避免,最簡單的方法,就是在分庫裏面也建一個消息表,保存處理的成功的消息。這樣,經過對比主庫和分庫的消息表,就知道哪些事務消息沒有執行成功

消息處理器基本框架

前面介紹了,消息處理器的核心功能就:

  • 獲取消息,並把消息發送給業務放處理
  • 保證消息執行的成功 

爲了完成上面功能,須要消息處理任務和消息校驗任務,經過定時調度任務來觸發這2個任務(例如,5s觸發一次)

 

消息處理任務

消息處理任務就是經過掃描待處理的消息,而後通知業務系統執行。

 

再次強調,消息處理任務不會管消息是否執行成功。都是按照消息隊列表順序執行下去。

消息校驗任務

校驗任務就是比較主庫和分庫中的消息記錄(主庫中記錄的全部消息,分庫中記錄的執行成功的消息),對執行未成功的消息發起重試,若是屢次重試失敗則發出告警,須要人工介入。 

 

和基於消息中間件的事務消息比較

相同點

  • 都是採用異步確保最終一致性:
  • 能夠控制異步執行消息的速率,能夠利用RPC調用的負載均衡
  • 消息處理都必須支持重試和冪等性
  • 事務消息異步執行失敗,都沒辦法回滾產生事務消息的事務 

不一樣點

消息事務的提交

使用消息中間件,通常都須要在代碼中顯示的編寫提交中間件事務消息的代碼,相似下面

public boolean transaction(String text){
    try {
        發送事務消息
        執行本地事務
        提交事務消息
        return true;
    } catch (TmcException e) {
         return false;
    }
}

但在實際項目中,事務的傳播性的問題(spring 的事務註解是支持事務的傳播性),就須要修改業務代碼。但使用基於數據庫的消息隊列就沒有這個問題

@Transactional
public void publishAS(String text){   
    執行本地事務邏輯
    插入事務消息
}

因此在既有代碼改造上(特別是複雜系統中),使用數據庫的事務消息能夠減小代碼的改動

不須要回調check

咱們知道,在使用消息中間件的時候,都須要實現一個回調接口,當事務消息長時間沒有commit的時候,會調用該接口來確認是否須要commit(例如發送消息成功,可是在commit的時候網絡不可用)。而基於數據局的事務消息隊列就沒有這個問題

更多的數據庫訪問資源

基於數據庫的事務消息也有一個比較明顯的缺點:

  • 佔用更多的數據庫空間和數據庫訪問資源
  • 須要額外編寫DAO層代碼

小結

基於數據庫和基於消息隊列的事務消息的基本思路都同樣,使用最終一致性來避免分佈式事務帶來的額外系統複雜性和代碼開銷。基於數據庫的事務消息在既有業務改造中,代碼變更較小,也不須要額外的引入消息中間件,可是帶來的問題就是對數據庫更多的訪問。而基於消息中間件的問題就是如何避免在與消息中間件交互的出現問題的時候如何應對。固然,以上只是我我的理解,若是系統有什麼設計不合理或者有改進的地方,歡迎討論。

相關文章
相關標籤/搜索