Java業務中臺確保數據一致性的解決方案

引言

隨著業務的發展,微服務架構逐漸成為當下業務中臺的主流架構形式,它不但解決瞭各個應用之間的解耦問題,同時也解決瞭單體應用的性能問題實現可擴展可動態伸縮的能力。如下圖所示,業務中臺就是將平臺的通用能力進行下沉,避免重復建設,形成底座平臺能力,上層的各個應用服務都是基於中臺能力進行快速構建。但是隨著應用規模的擴大,原本在單體應用中不是問題的問題,在微服務架構中可能就是比較嚴重的問題,本文所要探討的服務之間的數據一致性便是其中最具代表性的問題。本文將結合常見的電商下單場景來說明業務中臺數據一致性方案。

在這裡插入圖片描述

數據一致性原理預備知識

在探討業務中臺數據一致性方案之前,我們先來一起回顧下數據庫事務的相關內容,通過對數據庫事務的分析,我們可以看出來在微服務架構中想要保證數據的一致性將會遇到什麼樣的問題。

1、本地事務

事務的概念對於程序猿來說一定不陌生,這裡的事務指的是數據庫事務。所謂數據庫事務,簡單來理解就是一套關於數據一致性維護的數據庫機制。 我們都知道,實際業務平臺大部分的業務數據還是保存在關系型數據庫中,在單體應用的時代,數據庫實例本身可以保證事務的有效性。
數據庫事務需要滿足四個基本特征:

(1)原子性(Atomicity):極端主義者,要麼大傢一起成功,有一個失敗都不行

(2)一致性(Consistency): 數據具有一致性,不存在狀態不確定的狀況

(3)隔離性(Isolation):事務之間互相不幹擾,你走你的陽關道,我走我的獨木橋

(4)永久性(Durability):一旦事務提交後,數據就記錄就會被持久化

都說王守義 13 香,筆者最近也下單瞭一部 pro 準備換掉三年前的 iphone。那麼我們以下單購買 iphone13 進行舉例說明,我們暫時將如下圖所示,如果在一個完整事務中,存在生成訂單、扣減庫存、增加積分以及發放優惠券這四項業務,那麼要麼這四項都成功,下單夠購買 13 香這個業務才算是成功,中間有一項失敗就會造成業務數據的不一致,因此需要進行事務回滾,回滾到下單前的狀態,以保證業務數據的一致性。

在這裡插入圖片描述

2、分佈式事務

隨著業務的不斷發展,業務復雜度也在不斷的增長,企業基於微服務架構向下沉淀出瞭通用的業務中臺,數據的訪問形式變得復雜瞭,服務節點間的數據訪問通過 API 接口進行。原本單數據庫實例隻能保證數據庫實例內部的事務,但是在跨數據庫實例以及分佈式業務調用過程中,單數據庫實例已經無法保證全局事務的有效性。因此我們需要分佈式的事務機制來保證各個服務節點之間的數據邏輯一致,否則就會出現如下的數據不一致的問題。

在這裡插入圖片描述

針對分佈式場景下的數據一致性問題,業界提出瞭 CAP 理論以及 BASE 理論,同時在這些理論的基礎之上產生瞭相應的分佈式事務解決方案。我們先來看下什麼是 CAP 理論以及 BASE 理論。

CAP 理論

Consistency:數據一致性

Avalibility:數據可用性

Partition tolerance:分區容錯性

在這裡插入圖片描述

任何一個分佈式系統是沒法同時滿足 CAP 中的三項的,為什麼這麼說呢?我們來舉個簡單的例子來進行說明。如下圖所示,訂單服務將生成的訂服務寫入訂單數據主庫,同時將數據同步到訂單數據從庫中,訂單服務從從庫中進行訂單數據查詢,從人實現訂單數據的讀寫分離。那麼我們繼續來看,當系統滿足分區容錯性之後,數據一致性和數據可用性之間存在怎樣的矛盾。

如果必須實現數據的一致性,那麼當訂單數據寫入主庫的時候,由於此時主庫還未將最新的訂單數據同步到從庫當中,因此主庫和從庫出現瞭數據不一致的情況,但是一致性又要求必須實現數據的強一致,那麼此時的隻好將從庫鎖住不對外提供服務,直到主庫數據同步到從庫後再開放訂單數據查詢。因此在 這個過程中無法同時滿足數據一致性以及可用性。

在這裡插入圖片描述

對應的 BASE 理論,其實就是一種 CAP 理論的實際權衡結果,既然無法做到強一致性,那麼各個服務節點可以根據自身的業務特點實現數據的最終一致。所謂 BASE 理論指的就是:

a、Basically Available — 基本可用,畢竟對於分佈式系統來說,可用性比數據一致性性要重要的多

b、Soft state — 軟狀態 指允許系統中的數據存在中間狀態,並認為該中間狀態的存在不會影響系統的整體可用性,即允許系統在不同節點的數據副本之間進行數據同步的過程中存在延時

c、Eventually consistent — 最終一致性,強調的是系統中所有的數據副本,在進過一段時間的同步後,最終能夠達到一個一致的狀態。最終一致性需要保證數據最終能夠一致而不需要保證數據實時的一致性。

看吧,實際上我們也不需要太為難我們自己,既然很難實現強一致性,那麼實現最終一致性相對來說是一個非常劃算以及可行性較高的數據一致性解決方案。
有瞭前人大佬們總結的分佈式理論,我們一起來看下幾種常見的分佈式事務場景吧。

(1)一個事務中包含瞭多數據庫操作

我們還是以上面購買 13 香來舉個栗子,由於業務量的不斷攀升,之前的單數據庫實例已經無法滿足當前業務要求。因此我們將數據庫按照業務域進行瞭拆分。我們簡化下購物的業務流程,簡化後包括生成訂單、扣減庫存以及增加積分,因此一個購物事務中包括瞭三個數據庫的操作,但是數據庫實例隻能保證自身的事務特性,不能保證全局的事務特性。如果訂單生成,但是庫存沒有扣減,積分沒有增加,將數顯數據不一致問題,因此出現瞭分佈式事務問題。

在這裡插入圖片描述

(2)一個事務中包含瞭多服務訪問同一數據庫

隨著業務的發展,原先單體項目的模塊越來越多,維護起來成本較高,比如訂單模塊修改瞭但是庫存模塊沒有修改,但是發佈的時候還是需要發佈整個應用,萬一有個 Bug 啥的還要回滾,不能做到功能和維護上面的隔離。因此我們需要對應用不同的模塊進行拆分,那麼原本的內部調用變成瞭兩個服務之間的遠程調用,原本的本地事務就演變成瞭分佈式事務。

在這裡插入圖片描述

(3)一個事務包含瞭多個微服務調用數據不一致引發的問題

在微服務架構體系下面,原有的服務中的各個業務模塊經過縱向拆分後,成為一個個獨立的服務,如前面的購物業務流程,整個過程涉及到多個微服務,因此數據庫提供的事務機制,隻能保證一個微服務節點的事務,同樣不能保證全局的事務。因此當一個微服務需要調用多個其他微服務完成對應的業務時,分佈式事務的問題就會出現。

在這裡插入圖片描述

數據一致性解決方案

正是因為分佈式微服務的復雜結構,因此給維護數據一致性帶來瞭一定的挑戰,但是由於分佈式理論的發展與實踐,為我們解決分佈式系統提供瞭理論依據。

分佈式系統數據一致性的保證的關鍵點就在於如何實現和單系統一樣的事務控制,在單點系統階段,數據的一致性通過數據庫本身的機制進行保證。但是在分佈式中臺系統中,數據一致性需要借助於外部的力量進行保證。

當下已經有較為成熟的數據一致性解決方案瞭,下面我們來具體分析下各個解決方案,我們按照分佈式系統是否強調數據的強一致性,我們可以將分佈式事務分為剛性事務以及柔性事務。

1、剛性事務

所謂的剛性事務就是追求數據的強一致性,必須滿足數據庫事務的 ACID 特性。典型的剛性事務解決方案就是 XA 模型。它通過引入一個事務協調者的角色,站在全局的角度來看分佈式事務,將各個子域合並為一個大的分佈式事務來實現數據的一致性。

但是在實際的高並發場景下基本不會使用這樣的分佈式解決方案,主要原因有以下幾點,我們以 XA 模型中最常見的兩階段提交的方案來說明其存在的不足之處。

(1)單點故障問題:由於引入瞭分佈式事務的全局協調者的角色,那麼如果一旦全局協調者產生故障,那麼各個子事務參與者並不能獲取事務執行結果狀態,導致子事務阻塞,因此我們需要花費很大精力去保證事務協調者的高可用。

(2)性能問題:在大型分佈式系統高並發場景下,由於參與分佈式事務的 RM 過多,因此網絡通信次數、重試以及通信時間都會增加,導致可能的阻塞時間也會變長,從而降低瞭整個系統的吞吐狀況。

2、柔性事務

既然剛性事務在高並發場景下存在上述的問題,那麼有沒有更好的數據一致性解決方案呢?這時候柔性分佈式事務就派上用場瞭。柔性事務尊屬分佈式事務的 BASE 理論,它允許一段時間內的系統之間數據的不一致,但是在最終狀態下需要保證事務的一致性。

(1)TCC 模式

所謂 TCC 模式即為 Try-Confirm-Cancel,它是二階段提交的一種實現方式。它包含的主要操作如下所示:

**Try:**嘗試執行業務,但是實際並沒有真正執行,隻是進行數據檢查,鎖定業務資源,便於後續業務執行需要

**Confirm:**執行具體的業務操作,使用之前階段預留的業務資源數據

**Cancel:**如果在 try 階段某個事務執行失敗,則取消之前的業務操作

Try 階段:

這個階段主要實現嘗試執行對應的業務,可以理解為一種預備執行的狀態。因為在完成業務流程之前,並不知道各個業務節點或者可以理解為子事務是否可以正常執行,因此嘗試在各個子事務去預先執行,看看能不能正常處理。

回到我們這個購買 13 香的例子當中,訂單中心首先將訂單狀態修改為 UPDATING 狀態,而不是 COMPLETED 狀態。庫存中心可以將 13 香的庫存鎖住,積分中心同樣可以進行預增加積分。

在這裡插入圖片描述

Confirm 階段:

如果有幸進入這個階段,說明前期的 try 階段都已經處理成功瞭,即為訂單的狀態成功變更為 UPDATING 狀態,庫存中的訂單數據量已經被鎖定,用戶對應的購物積分已經預先增加瞭,這三個步驟都已經完美實現瞭。對應的 TCC 框架已經感知到各個 Try 階段的執行結果,因此在執行 Confirm 時候需要執行對應服務提供的 Confirm 接口去完成實際的數據提交。

TCC 框架需要分別調用各個服務的確認提交接口完成對應的本地事務提交。訂單服務需要將訂單狀態修改為訂單完成狀態、 庫存服務需要將將庫存進行真實的扣減,用戶積分服務為用戶增加相應的用戶積分。

在這裡插入圖片描述

Cancel 階段:

如果不幸走到這個階段,無論在哪個階段都需要對之前執行的所有擦偶作執行回滾,恢復原有數據。如在執行到積分添加的過程中出現異常,那麼代表這個分支事務在執行中出現瞭問題,無法完成正常的事務提交。因此為瞭保證數據的一致性,需要將之前的數據進行回滾操作。

在這裡插入圖片描述

在整個 TCC 處理過程之中,還有一件事情需要特別註意,那就是為瞭保證業務的成功率,各個業務服務向 TCC 框架進行 confirm 以及 TCC 向各個業務服務進行 confirm 以及 cancel 的時候都需要進行異常重試,以保證執行的成功率,因此對應的業務服務需要進行冪等處理,防止重試導致的重復操作。

可以看得出來,TCC 模式下的微服務需要業務代碼重度耦合,實際編碼的體感很不好,需要借助於外部的 TCC 框架,同時需要在業務代碼中增加 Try、Cancel 處理流程需要的接口。上述的 TCC 解決方案,需要在用戶執行完下單操作之後依次執行訂單生成接口、庫存扣減接口以及用戶積分接口來完成整體的業務操作,但是在實際的業務場景中,我們大概率不會這麼同步調用多個接口來完成具體業務,下面我們看看另外一種分佈式數據一致性解決方案。

(2)可靠消息最終一致性

在實際的平臺中,我們通常使用消息事件來解決各個微服務之間的耦合問題。我們結合之前的購買 13 香的實際案例來進行說明,可靠消息的事務模型實際上就是基於事件驅動架構,當用戶在購買 13 香之後,創建瞭 13 香的訂單並完成支付,向消息中間件發送訂單已支付事件消息,訂閱瞭訂單支付支付之間消息的庫存服務、積分服務等,接收到對應的訂單支付消息之後,執行其對應的業務流程,如扣減庫存以及增加用戶積分等。

從上述描述中我們可以看出來,可靠消息最終一致性的方案中,消息的可靠投遞是一切後續業務的重要前提,同時需要避免消息的重復消費,因此對應監聽消息的服務的業務接口需要實現冪等性。我們來看如下的偽代碼。

public void generateOrder() {

	try{
    boolean result = orderRepo.saveOrder(orderMpdel);
    
    if(result) {
      mqSender.sendMessage(orderModel);
    }
      
    
  } catch(Exception e) {
    rollback();
  }


}

在上述代碼中,無論是本地訂單數據保存(本地事務)處理失敗還是異步消息發送異常,我們都會進行訂單數據回滾,這代碼看上去沒有什麼毛病,但是我們再仔細分析下是不是真的沒有什麼問題嗎?

在這裡插入圖片描述

由於引入瞭消息中間件,服務之間的調用不再是依次的同步調用,各自服務通過消費對應的訂單消息來實現各自的業務。當用戶進行下單操作後,訂單服務會生成對應的訂單信息,而後發送訂單生成消息。但是由於是分佈式系統,受網絡等因素的影響,有可能出現消息發送完成後訂單服務未接收到消息中間件返回的響應信息,因此訂單服務將之前的訂單數據進行瞭回滾,但是積分服務已經將 MQ 中的訂單信息進行瞭消費,增加瞭用戶積分。這就造成瞭訂單與積分數據不一致的情況。 另外如果在訂單生成之後,訂單服務掛掉瞭無法正常投遞消息也會造成數據不一致的情況。

a、本地消息表

通過本地消息表的方式將分佈式事務拆解為本地事務的實現,如下圖所示,將訂單生成以及消息記錄表包裹在一個本地事務中,生成訂單信息後同時在本地消息表中插入一條訂單消息發送記錄用以記錄消息發送的狀態。如果消息發送失敗則記錄狀態,訂單服務進行重試發送,超過重試次數後可以由定時服務進行定時掃描本地未完成狀態的消息進行重新發行消息,以保證消息的正常投遞。

當消息到達 MQ 之後,如果 MQ 進行瞭正常的響應則業務可以繼續。但是如果 MQ 未正常響應,則訂單服務認為 MQ 未能正常接收消息需要不斷進行重試。

MQ 接收到消息並進行持久化後,則響應訂單服務說我這裡已經接收到你的訂單消息瞭,接下來的事情就交給我我吧,此時訂單服務不再進行消息發送重試,本地消息表中的消息狀態為已發送。

消息發送成功後,積分服務將會消費對應的訂單消息,但是如果積分服務在執行本地積分服務失敗後需要通知訂單服務將原來的訂單信息進行回滾。

在這裡插入圖片描述

b、事務消息

關於事務消息,將在另外一篇文章詳細介紹,主要的思想是借助於 RocketMQ 的事務消息機制,將分佈式事務轉換為兩階段提交的解決方案,從而實現依托於消息中間件的事務一致性解決方案。

總結

本文以最常見的電商購物案例為實際背景,圍繞如何實現業務中臺的數據一致性進行瞭詳細的說明,分別從分佈式系統數據一致性問題產生的背景、相關的分佈式事務理論以及基於理論之上產生的相應的解決方案總結瞭業務中臺的數據一致性的解決方案。重點闡述瞭柔分佈式事務解決方案在業務中臺數據一致性實踐中的應用。

到此這篇關於Java業務中臺確保數據一致性的解決方案的文章就介紹到這瞭,更多相關Java 數據一致性內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: