經驗分享:分佈式微服務應用如何實現最終一致性?

在分佈式系統中,實現強一致性並不容易。即使2PC、3PC階段提交,也無法保證絕對的強一致性。


我們也不能因為極小的不一致性概率,導致系統整體性能低下,或者擴展性受到影響,並且架構也變得極其複雜。因此,在2PC/3PC提交缺乏大規模應用的情況下,最終一致性是一個較好的方案,在業界得到了大量使用。

一、重試機制

如下圖所示,Service Consumer 同時調用 Service A 和 Service B,如果Service A 調用成功,Service B 調用識別,為了保證最終一致性,最簡單的辦法是重試。

經驗分享:分佈式微服務應用如何實現最終一致性?

重試的時候,要注意設置Service Consumer 的超時時間, 避免長時間等待或卡死,耗盡資源。

Consumer 重試時,需要注意如下幾個方面:

  • 超時時間;
  • 重試的次數;
  • 重試的間隔時間;
  • 重試間隔時間的衰減度;

具體實現細節,可以參考 。

二、本地記錄日誌

通過本地記錄日誌,然後收集到分佈式監控系統或者其他後端系統中,啟動一個定期檢查的工具。根據實際情況,可以選擇人工處理。

日誌格式:TranID-A-B-Detail

  • TransID為事務ID,可以生成一個隨機序列號;
  • Detail 為數據的詳細內容;
  • 如果調用A成功,則記錄 A success;
  • 如果調用B失敗,或者出現故障,沒有記錄等等,也就是日誌中沒有B success,則重新調用B;
  • 可以定期檢測,並處理日誌。

收集識別日誌的設計圖,如下所示。

經驗分享:分佈式微服務應用如何實現最終一致性?

三、可靠消息模式

考慮到實際業務場景中發生故障的概率概率比較低,可以考慮如下方案。

Service Consumer 在調用 Service B 失敗,先進行重試。如果重試一定的次數仍然失敗,則直接發送消息Message Queue,轉換為異步處理。

可以採用分佈式能力比較強的MQ,如Kafka、RocketMQ等開源分佈式消息系統,進行異步處理。

  • Service B 可以專門集成一個錯誤處理的組件,不斷從MQ 收集補償消息。
  • 或者獨立一個錯誤處理的組件,獨立處理MQ 的補償消息,包括其他Service 組件的異常。
經驗分享:分佈式微服務應用如何實現最終一致性?

這種方案也有丟失消息的風險,就是Service Consumer 的消息還沒有發出來就掛了,這是小概率事件。


還有一種方案-可靠消息模式,如下圖所示。Service Consumer 發送一條消息給Message Queue Broker,如RocketMQ、Kafka等等。由Service A和Service B 消費消息。

MQ 可以採用分佈式MQ,並且可以持久化,這樣通過MQ 保證消息不丟失,認為MQ 是可靠的。

經驗分享:分佈式微服務應用如何實現最終一致性?

可靠消息模式的優點:

  • 提升了吞吐量;
  • 在一些場景下,降低了響應時間;

存在問題:

  • 存在不一致的時間窗口(業務數據進入了MQ,但是沒有進入DB,導致一些場景讀不到業務數據);
  • 增加了架構的複雜度;
  • 消費者(Service A/B)需要保證冪等性;


針對上述不一致的時間窗口問題,可以進一步優化。

  • 將業務分為:核心業務和從屬業務
  • 核心業務服務 - 直接調用;
  • 從屬業務服務 - 從MQ 消費消息;
經驗分享:分佈式微服務應用如何實現最終一致性?

直接調用訂單服務(核心服務),將業務訂單數據落地DB;同時,發送向MQ 發送消息。

考慮到在向MQ 發送消息之前,Service Consumer(創建訂單)可以會掛掉,也就是說調用訂單服務和發送Message 必須在一個事務中,因為處理分佈式事務比較麻煩,且影響性能。

因此,創建了另外一張表:事件表,和訂單表在同一個數據庫中,可以添加事務保護,把分佈式事務變成單數據庫事務。

整個流程如下:

(1)創建訂單 - 持久化業務訂單數據,並在事件表中插入一條事件記錄。注意,這裡在一個事務中完成,可以保證一致性。如果失敗了,無須關心業務服務的回退,如果成功則繼續。

(2)發送消息 - 發送訂單消息到消息隊列。

  • 如果發送消息失敗,則進行重試,如果重試成功之前,掛掉了,則由補償服務去重新發送消息(小概率事件)。
  • 補償服務會不斷地輪詢事件表,找出異常的事件進行補償消息發送,如果成功則忽略。
  • 如果發送消息成功,或者補償服務發送消息成功,則可以考慮刪除事件表中的事件信息記錄(邏輯刪除)。

(3)消費消息 - 其他從屬業務服務,則可以消費MQ中的訂單消息,進行自身業務邏輯的處理。


上述設計方案中,有3點需要說明一下:

(1)直接調用訂單服務(核心業務),是為了讓業務訂單數據儘快落地,避免不一致的時間窗口問題,保證寫後讀一致性

(2)創建訂單業務直接發送消息給MQ,是為了增加實時性,只有異常的情況,才使用補償服務。如果對實時性要求不高,也可以考慮去掉Message 直接發送的邏輯。

(3)額外引入一張事件表,是為了將分佈式事務變成單數據庫事務,在一定程度上,也增加了數據庫的壓力。


分享到:


相關文章: