工程中實踐的微服務設計模式

最近在讀《微服務架構設計模式》,開始的時候我非常的好奇,因爲在我印象中,設計模式是常說的那23種設計模式,而微服務的設計模式又是什麼呢?這個問題也留給大家,在文末我會附上我對這個問題的理解。本次文章的內容主要是工作中對微服務設計模式的應用,希望能對大家有所啓發。

事務發件箱模式

事務發件箱模式:將消息保存在數據庫 “發件箱”表 作爲事務的一部分

policy 爲處理投保的微服務,以投保事務爲例:

投退保.png

如上圖所示,在投保過程中有4步操作(注意持久化結算任務(Task)A的操作),這個過程便是事務發件箱模式的體現,爲什麼說它是事務發件箱模式呢?

  1. Task 表中持久化的消息任務最終會被髮送到 MQ 中,所以它是發件箱

  2. 持久化 Task 記錄的操作被包含在事務中,所以稱它爲事務發件箱

Task 記錄中保存的消息體是通過另一個服務 service-job 輪詢掃描初始化狀態的任務,並將其發送到 MQ 的,發送成功後任務修改爲完成狀態,這種方式被稱爲事務性發件箱模式中的輪詢發佈數據模式

這種設計模式有一個弊端:隨着數據量不斷增大,經常輪詢數據庫可能造成昂貴的開銷。在我們的系統中採用了兩種措施進行優化:

  • 分庫分表:以空間換時間,避免單表數據量過大造成的開銷

  • 將完成狀態的任務進行“數據結轉”的設計:任務先保存在 Task 表中,被執行完成後被歸檔到 TaskRecord 表中

此外還有一種更加高效但是開發稍複雜的方式:拖尾數據庫日誌模式

拖尾數據庫日誌模式

拖尾事務日誌.png

數據庫的每次更新都對應着一條數據庫事務日誌,通過事務日誌挖掘器讀取事務日誌,並將每條與消息有關的記錄發送給消息代理(開源框架可參考:Github: debezium,可以將MySQL的binlog讀取到Kafka中),但是這種方法的弊端是需要在開發上做一些努力,因爲需要監聽數據庫事務日誌和調用數據庫底層相關的API。

相信“郵遞員”

工程實踐中,還有一種沒有采用事務發件箱模式來保證數據一致性的方法:在事務中先持久化完成狀態的任務,隨後直接將消息發送給消息隊列,如果消息發送失敗,捕獲異常並將任務修改成初始化狀態,隨後依賴 service-job 服務進行補償:即將初始化狀態的任務發送給 MQ。我們還是以投保的過程爲例,如下代碼所示:

// 1. 持久化保單數據
savePolicy();
// 2. 持久化保單明細數據
savePolicyDetail();
// 3. RPC 調用投保接口
rpcPolicy();
// 4. 持久化完成狀態的任務,任務中記錄了要發送給MQ的消息體
int num = insertTask(TaskStatus.COMPLETE);
// 5. 如果插入成功了,藉助線程池發送消息
if (num > 1) {
    threadPoolExecutor.execute(() -> {
        try {
            mq.send(task.info);
        } catch(Exception e) {
            // 發送失敗,拋出異常,修改任務狀態爲初始化狀態,依賴 service-job 服務進行補償
            updateTask(TaskStatus.INIT);
        }
    });
}

這種設計模式默認認爲MQ集羣始終是高可用的,我將這種設計模式命名爲相信“郵遞員”。在生產實踐中,因爲有MQ運維團隊在保障MQ集羣的高可用,所以這種設計模式也是比較穩定的。


在投退保流程中,涉及不同數據庫的修改操作,如保單的持久化、保單狀態的修改以及相關結算的推送,要保證這個過程中的數據一致性,那麼便不能再依賴ACID本地事務,而是需要使用跨服務的 Saga 設計模式來維護數據一致性。下面我們來介紹兩種,分別是協同式Saga編排式Saga

Saga 通過使用異步消息來驅動一系列本地事務,來維護多個服務之間的數據一致性。

協同式Saga

我們先帶着 Saga 的概念來看一下投保的流程:

投退保分享.png

在這個過程中,Saga的決策和執行順序分佈在Saga的每一個參與方中,並且通過消息交換的方式進行溝通,一個Saga的參與方執行完觸發另一個Saga執行,保證數據一致性,這種方式被稱爲協同式Saga

這種設計模式的優劣如下:

  • 優勢:比較簡單,服務間松耦合

  • 弊端:比較難理解,因爲它沒有一個地方定義了Saga的執行流程,Saga的處理邏輯分佈在不同的服務中,需要根據代碼觸發的任務去整理整個流程

爲什麼沒有采用XA來實現分佈式事務

XA採用兩階段提交協議實現分佈式事務,在事務中的所有參與者都成功時提交,有失敗時便回滾。要使用該模式一方面要求所有的事務參與者(數據庫或消息代理)滿足XA標準,另一方面,它本質上是同步的進程間通訊,同步的通訊機制有一個弊端:它會降低分佈式系統的可用性(假如分佈式系統中每個服務的可用性爲99%,如果服務與服務之間是同步調用的方式:服務必須從另外一個服務獲取響應後才能返回它的客戶端調用,那麼分佈式系統的可用性爲各個服務可用性的乘積,隨着同步交互服務的增加,可用性會隨之降低,最大化可用性的方式應該最小化系統間的同步操作量)。所以,一般互聯網公司很少採用強一致性的設計,而是採用最終一致性設計(銀行可能會使用到強一致性)。此外,XA實現分佈式事務需要依賴事務的協調者(如Seata),實現起來相比於上述方式複雜。

編排式Saga

Saga 的另一種實現方式是編排式,編排式 Saga 需要事務的協調者(DTM),全局事務發起人將整個全局事務的編排信息,包括每個步驟的正向操作和反向補償操作定義好之後,提交給事務協調者(DTM),協調者按步驟異步執行Saga邏輯。

如果投保流程使用編排式Saga的話,投保成功的過程如下:

投退保分享3.png

編排式Saga的事務定義執行步驟非常靈活,假如我們要在投保失敗的情況下做取消結算的補償邏輯的話,可以自行定義,圖示如下:

投退保分享4.png

這種設計模式的優劣如下:

優勢:能夠集中流程控制、易於擴展和服務間松耦合,如果服務之間的依賴關係複雜,且業務流程經常變動,使用編排式Saga是合適的
弊端:引入協調者增加了開發複雜性(擴展學習:DTM開源項目文檔


現在我們回到文章開篇的問題:微服務架構設計模式與我們常說的設計模式的區別是什麼?

我們常說的設計模式是面向對象的設計模式,它的解決方案元素是類,而微服務設計模式是站在更高維度即系統架構層面的設計模式,它面向的對象是系統中各個服務,解決方案也由相互協作的服務構成的。


巨人的肩膀

  • 《微服務架構設計模式》:第 1 - 4 章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章