分佈式系統下數據最終一致性的實現策略
今天我們來討論一下數據最終一致性的相關問題。這個問題在分佈式環境下非常典型,我們可以通過一個具體的業務場景來進行說明,下面是這個業務場景的示例圖:
設想以下業務場景:系統中同時運行着兩個關鍵服務——訂單服務和積分服務。用戶完成一筆訂單後,訂單信息需被記錄到訂單數據庫中,同時,系統會根據訂單金額給用戶增加對應的積分。這一系列操作橫跨了訂單數據庫和積分數據庫,因此確保這兩個服務間數據的一致性變得尤爲重要。
因爲這裏的訂單服務和積分服務是兩個獨立的微服務,所以保存訂單信息和積分信息的實現過程伴隨着一個典型的問題,也就是如何確保訂單數據與用戶積分數據的一致性。
雖然通過分佈式事務,我們可以在一定程度上實現這一目標,但分佈式事務提供的是一種強一致性方案,缺乏系統彈性和擴展性。爲了更好地處理這一場景,我們將探討數據最終一致性的三種實現模式,也就是可靠事件模式、補償模式和 TCC 模式。
可靠事件模式
我們先來看可靠事件模式。就像這個模式的名稱所表明的,這個模式依賴於事件,可以認爲是一種事件驅動架構的具體應用方式。但爲了更好地保證數據在不同服務之間的一致性,我們還需要引入一些特定的實現技巧。
針對事件驅動架構,我們首先想到的實現方案是引入消息中間件。在可靠事件模式中,當訂單服務和積分服務之間的數據需要進行統一管理時,我們引入消息中間件來完成數據的傳遞。接下來,我們看一下具體的操作流程。
流程的第一步,是用戶進行訂單付款流程。成功付款後,訂單服務會首先保存這個訂單信息,這是本地數據庫操作,不涉及分佈式場景。在這之後,訂單服務會生成一條 “訂單支付完成消息” 併發送到消息中間件。這一操作流程可以用下圖加以表示:
第二步是,當消息中間件接收到 “訂單支付完成消息” 之後,把這條消息發送到積分服務。作爲消息的消費者,積分服務會根據這條消息進行積分計算,並最終保存到本地數據庫中。
請注意,因爲消息發送和消費過程都可能出現異常,所以這時候積分服務需要發送一條 “回執消息” 給到消息中間件,這一過程可以用下圖表示:
在流程的第三步,我們再次來到訂單服務,該服務收到積分服務發送的 “回執消息” 之後同樣也會執行一定的業務邏輯,比方說設置訂單的狀態等。
在整個流程中,很顯然的一點是:我們需要確保消息發送和接收過程的可靠性,這也是 “可靠事件” 這一名稱的由來。目前,諸如 RabbitMQ、Kafka 等主流的消息中間件都支持消息持久化操作,同時也具備消息至少被投遞一次的功能特性,這在一定程度上能夠確保消息的可靠傳遞。
補償模式
我們要介紹的第二種模式是補償模式。所謂補償,是指對所有已生成的數據來說,都具備一種類似 “撤銷” 的能力。在整個數據處理鏈路中,如果某一個環節出現了異常,那麼我們就可以通過補償機制恢復那些已經保存的數據。
讓我們回到前面的案例。顯然,如果在積分服務中針對用戶積分的處理過程發生了異常,爲了保證數據一致性,那麼就應該同時取消原先的訂單所生成的數據。爲了達到這種效果,訂單服務和積分服務都需要提供額外的補償操作入口,而系統中也應該存在一個獨立的補償服務來根據業務運行結果統一調用這些入口。
顯然,如果想要實現補償操作,我們需要明確補償的對象。所以,通常的做法都是在各個微服務中保存詳細的操作日誌和對應的業務數據。
就今天討論的案例而言,當用戶發起一次操作時,系統需要爲對應的請求生成全局唯一的操作編號,該編號會在整個調用鏈路中進行傳遞。同時,對於訂單服務而言,需要根據這個唯一編號來更新業務狀態。而在積分服務中,同樣需要記錄用戶積分相關的流水信息和業務狀態。
一旦發生異常,例如用戶積分計算失敗,那麼補償服務就會執行補償過程,它可以從操作日誌和業務狀態中知道補償的範圍以及對應的業務數據。
TCC 模式
講完補償模式,我們接着來看 TCC 模式。所謂的 TCC,實際上是三個操作的組合,即 Try(嘗試)、Confirm(確認)和 Cancel(取消),它的基本結構如下圖所示:
在上圖中,我們對 TCC 模式中的三個操作做一個展開。其中,Try 操作用於完成所有業務規則檢查,對業務數據進行預先的處理;Confirm 操作則用來執行具體的業務操作;而 Cancel 操作實際上就是一種補償操作,用戶補償 Try 階段被佔用的業務數據。
我們還是通過案例分析進一步理解 TCC 模式的設計思路,下圖展示了基於 TCC 模式的用戶操作流程。
請注意,在上圖中,Try、Confirm 和 Cancel 這三個操作具備嚴格的時序性,開發人員需要按順序來依次執行這三個操作。接下來,我們同樣結合具體的業務場景來分析各個操作的具體內容。
-
Try 階段:進行業務規則檢查,預留必要的業務資源。例如,檢查訂單金額是否足夠,以及預扣庫存等。
-
Confirm 階段:若 Try 階段成功,則執行實際的業務操作,如完成訂單處理並正式扣除庫存,同時向積分服務發送積分發放請求。
-
Cancel 階段:若發生異常,則釋放 Try 階段預留的資源,並執行相應的補償操作,如撤銷訂單並恢復庫存,同時通知積分服務撤銷積分的發放。
講到這裏,我們可以把 TCC 模式看作是對補償模式的一種優化。一方面,在 TCC 模式中,我們把如何對數據進行補償這一過程進行了拆分,從而能夠讓開發人員更好地完成補償數據的管理,業務系統和補償服務之間的分工也更加明確。另一方面,正如上面圖中所示的那樣,基於 TCC 模式,補償服務通常被設計成一種平臺化的框架,這種框架能夠協調各個服務中 Try、Confirm 和 Cancel 操作的時序,典型的例子就是阿里巴巴開源的 Seata TCC 框架。
總結
數據一致性是一個重要且複雜的話題。今天,我們基於典型的案例,對如何實現分佈式環境下的數據最終一致性的思路和方案做了分析,並提供了業界具有代表性的幾種實現模式,包括可靠事件模式、補償模式和 TCC 模式。
對於可靠事件模式來說,它依賴於事件,可以認爲是一種事件驅動架構的具體應用方式;對於補償模式來說,它側重於通過補償機制恢復那些已經保存的數據處理鏈路中的數據;對於 TCC 模式來說,我們可以把它看作是對補償模式的一種優化,這些模式各都有各的設計思想和功能特性。
當然,無論採用哪一種模式,想要實現最終數據一致性,開發人員還需要準備一種最基礎的處理機制,即人工干預機制。可以認爲基於業務數據進行人工的補償就是一種兜底方案,這也是在使用這些數據最終一致性模式時同時需要考慮的重要一點。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/rfWtepDsaGoPCdDEYPtKng