從本課時開始,我將開始介紹跨微服務(wù)的協(xié)作與查詢,這一部分的內(nèi)容主要涉及微服務(wù)之間的交互方式。由于每個微服務(wù)一般都各自獨立存儲數(shù)據(jù),所以在不同微服務(wù)之間共享數(shù)據(jù)變得復(fù)雜。本課時將講解微服務(wù)架構(gòu)的應(yīng)用中的數(shù)據(jù)一致性問題,以及 Saga 模式。
數(shù)據(jù)一致性是軟件開發(fā)中經(jīng)常會遇到的問題,指的是相互關(guān)聯(lián)的數(shù)據(jù)的值出現(xiàn)了不一致,破壞了業(yè)務(wù)邏輯中的不變量。數(shù)據(jù)一致性是一個很寬泛的話題,很多開發(fā)中的常見問題都可以歸類為數(shù)據(jù)一致性的問題。數(shù)據(jù)一致性的問題通常涉及多個動作,每個動作都會對一些數(shù)據(jù)進行修改,這些動作的整體才是對數(shù)據(jù)的完整修改,這幾個動作在邏輯上組成了一個工作單元(Unit of Work)。我們需要保證的是同一工作單元中的全部動作在執(zhí)行前后,業(yè)務(wù)邏輯中所規(guī)定的不變量不被破壞。
一個典型的問題是銀行賬戶之間的轉(zhuǎn)賬。當(dāng)從賬戶 A 轉(zhuǎn)賬一定金額到賬戶 B 時,在轉(zhuǎn)賬操作執(zhí)行前后,這兩個賬戶的余額總和應(yīng)該保持不變,這就是轉(zhuǎn)賬這一業(yè)務(wù)邏輯的不變量。如果轉(zhuǎn)賬操作成功,那么賬戶 A 的余額會減去轉(zhuǎn)賬金額,同時賬戶 B 的余額會加上轉(zhuǎn)賬金額;如果轉(zhuǎn)賬失敗,則兩個賬戶的余額保持不變。這兩種情況都保證了業(yè)務(wù)邏輯的不變量。如果轉(zhuǎn)賬金額從賬戶 A 中被扣掉,而賬戶 B 的余額沒有增加,這就表示業(yè)務(wù)邏輯的不變量被破壞,也就是出現(xiàn)了數(shù)據(jù)一致性的問題。
數(shù)據(jù)一致性問題的一個典型場景是在數(shù)據(jù)庫操作中,關(guān)系型數(shù)據(jù)庫通過事務(wù)來解決一致性問題。
數(shù)據(jù)一致性問題的一個解決辦法是保證工作單元的原子性,也就是說,工作單元中的全部動作,要么全部發(fā)生,要么全部不發(fā)生。在關(guān)系式數(shù)據(jù)庫管理系統(tǒng)中,事務(wù)用來作為多個語句執(zhí)行時的單元。數(shù)據(jù)庫事務(wù)滿足 ACID 特性,ACID 是原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)對應(yīng)的英文單詞首字母的縮寫。
原子性指的是每個事務(wù)都被當(dāng)成一個獨立的單元,其中包含的語句要么全部成功,要么全部不執(zhí)行。如果事務(wù)中的一個語句執(zhí)行失敗,整個事務(wù)會被回滾,不會對數(shù)據(jù)庫產(chǎn)生影響。上面提到的銀行賬戶之間轉(zhuǎn)賬的例子,如果對兩個賬戶的操作都在一個事務(wù)中完成,那么事務(wù)的原子性可以保證業(yè)務(wù)邏輯中的不變量不被破壞。
一致性指的是事務(wù)只會把數(shù)據(jù)庫從一個合法的狀態(tài)帶到另外一個合法的狀態(tài),并保持數(shù)據(jù)庫的不變量。數(shù)據(jù)庫的不變量與之前提到的業(yè)務(wù)邏輯的不變量并不相同。數(shù)據(jù)庫的不變量指的是為了保證數(shù)據(jù)的完整性所定義的規(guī)則,包括約束、級聯(lián)操作和觸發(fā)器等。常用的規(guī)則包括,數(shù)據(jù)庫表中的主鍵必須唯一,外鍵所引用的主鍵必須存在等。
隔離性與事務(wù)的并發(fā)執(zhí)行有關(guān)。事務(wù)通常是并發(fā)執(zhí)行的,也就是說,多個事務(wù)可能同時對同一個數(shù)據(jù)庫表進行修改。隔離性要求多個事務(wù)在并發(fā)執(zhí)行的結(jié)果,與這些事務(wù)按順序執(zhí)行所得到的結(jié)果是一樣的。也就是說,每個事務(wù)都相當(dāng)于在自己隔離的空間中運行,不受其他事務(wù)的影響。
持久性指的是一旦事務(wù)被提交,那么即便是系統(tǒng)崩潰,該事件仍然處于已提交狀態(tài)。一般的做法是使用事務(wù)日志來記錄已提交的事件,持久性保證了事務(wù)的執(zhí)行結(jié)果不會受到系統(tǒng)崩潰的影響。
之前提到的數(shù)據(jù)一致性問題,如果使用數(shù)據(jù)庫事務(wù),就可以輕松解決。很多的編程語言和框架都支持數(shù)據(jù)庫事務(wù),聲明式的事務(wù)更加簡化了開發(fā)人員的工作。比如,在 Java 中,只需要在類或方法上添加 @Transactional 注解,就可以啟用事務(wù)。如果相關(guān)的操作涉及多個數(shù)據(jù)庫,可以使用基于兩階段提交協(xié)議的 XA 事務(wù)。
在一個分布式系統(tǒng)中,事務(wù)并不總是可用的。在第 15 課時提到過,Apache Kafka 不支持 XA 事務(wù),因此無法參與到關(guān)系型數(shù)據(jù)庫的事務(wù)中來。即便是可以使用 XA 事務(wù),其成本也是很高的。在分布式系統(tǒng)中,可以考慮的另外一種一致性模型是最終一致性。
最終一致性(Eventual Consistency)指的是,對于一個數(shù)據(jù)項,如果沒有對它做新的改動,那么所有對該數(shù)據(jù)項的訪問最終都會返回最后一次更新的值。最終一致性所提供的特性是 BASE,即基本可用(Basically Available)、軟狀態(tài)(Soft State)和最終一致性(Eventual Consistency)的縮寫。BASE 在化學(xué)上的含義是堿,剛好與 ACID 的含義酸相對應(yīng)。
基本可用指的是基本的讀取和寫入操作是盡可能可用的,但是并不保證一致性。也就是說,讀取操作不一定返回的是最近一次更新的值,寫入操作只有在解決沖突之后才會被持久化。軟狀態(tài)指的是由于沒有一致性的保證,在某個時間點上,我們只能對系統(tǒng)的狀態(tài)有一個大致的認知。最終一致性的含義如上面所述,只需要等待足夠長的時間,系統(tǒng)的狀態(tài)就會最終恢復(fù)一致性。
最終一致性的目標是提高系統(tǒng)的可用性,這就要提到分布式系統(tǒng)中的 CAP 定理。CAP 定理指的是一個分布式數(shù)據(jù)存儲最多只能提供一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition Tolerance)這三個保證的兩個保證。
這三個保證的內(nèi)容分別是:
由于分布式系統(tǒng)中的網(wǎng)絡(luò)錯誤不可避免,分區(qū)容錯性的保證是必須要有的。所以基于 CAP 定理,當(dāng)出現(xiàn)網(wǎng)絡(luò)分區(qū)時,就需要在一致性和可用性之間進行選擇。一種做法是直接出錯,這樣保證了一致性,但是會降低可用性,因為不能再提供請求的響應(yīng);另外一種做法是返回系統(tǒng)已知的最近值,但是該值不一定是最新的,這樣保證了可用性,但是丟失了一致性。
這里需要注意的是,CAP 定理并不是說永遠只能在一致性、可用性和分區(qū)容錯性這三者中選擇兩個。事實上,當(dāng)網(wǎng)絡(luò)沒有問題時,一致性和可用性是可以兼顧的。一致性和可用性的取舍,只發(fā)生在網(wǎng)絡(luò)出現(xiàn)問題時。
微服務(wù)架構(gòu)的本質(zhì)是一個分布式系統(tǒng),也同樣也會遇到一致性的問題,這種一致性不僅體現(xiàn)在數(shù)據(jù)層面上,更多的是在業(yè)務(wù)邏輯上。在微服務(wù)架構(gòu)的應(yīng)用中,一個業(yè)務(wù)場景可能會由多個微服務(wù)來協(xié)作完成,所有參與的微服務(wù)的數(shù)據(jù)必須在業(yè)務(wù)邏輯上保持一致。比如,在一個外賣訂餐系統(tǒng)中,當(dāng)用戶下單之后,訂單服務(wù)需要進行記錄,同時通知餐館開始準備訂單中的菜品,支付服務(wù)也需要進行扣款。如果扣款失敗,那么訂單的狀態(tài)需要更新,餐館也需要得到通知。當(dāng)一個訂單成功完成時,訂單服務(wù)、餐館服務(wù)和支付服務(wù)中關(guān)于這一訂單的數(shù)據(jù)應(yīng)該是匹配的。
在微服務(wù)架構(gòu)的應(yīng)用中,最終一致性是解決數(shù)據(jù)一致性問題的最現(xiàn)實方案。當(dāng)業(yè)務(wù)流程橫跨多個微服務(wù)時,完成一個業(yè)務(wù)流程的時間可能會比較長。如果從業(yè)務(wù)流程的生命周期全過程中的某個時間點來看,相關(guān)的數(shù)據(jù)可能處于不一致的狀態(tài)。比如,一個外賣訂單已經(jīng)扣款成功,但是餐館由于自身原因,暫時無法確認是否能提供全部菜品,在這個時間點上來說,用戶完成了支付,但是對應(yīng)的菜品處于未確定狀態(tài)。如果餐館無法提供菜品,而導(dǎo)致訂單取消,在完成退款操作之前,用戶付了錢,但可能沒有得到任何菜品。如果等整個業(yè)務(wù)流程全部完成,那么系統(tǒng)的狀態(tài)會恢復(fù)一致性。
在微服務(wù)架構(gòu)中,描述業(yè)務(wù)流程,需要用到下面介紹的 Saga 模式。
Saga 最早在 1987 年作為解決數(shù)據(jù)庫系統(tǒng)中的長時間運行的事務(wù)的方案而出現(xiàn),該模式通常又被稱為長時間運行的事務(wù)(Long-Running Transaction)。一個長時間運行的事務(wù),由多個小的本地事務(wù)組成,它避免了對非本地資源的鎖定,并通過補償機制來處理失敗。長時間運行的事務(wù)并不具備數(shù)據(jù)庫事務(wù)的全部 ACID 特性,但是組成它的本地事務(wù)具有 ACID 特性。如果某個本地事務(wù)出現(xiàn)錯誤,那么對于那些已提交的本地事務(wù),會應(yīng)用其對應(yīng)的補償機制來恢復(fù)狀態(tài)。
以銀行賬戶之間的轉(zhuǎn)賬操作為例,如果以 Saga 模式來實現(xiàn),那么從源賬戶轉(zhuǎn)出和轉(zhuǎn)入到目標賬戶這兩個操作都由本地事務(wù)來完成。假設(shè)從賬戶 A 轉(zhuǎn)賬 100 元到賬戶 B,如果從賬戶 A 的轉(zhuǎn)出操作成功,而轉(zhuǎn)入賬戶 B 的操作失敗,那么會執(zhí)行對應(yīng)的補償操作,也就是對賬戶 A 存入 100 元。這樣就保證了數(shù)據(jù)的一致性。
雖然 Saga 模式起源于數(shù)據(jù)庫系統(tǒng),它非常適合于微服務(wù)架構(gòu),該模式用來保證業(yè)務(wù)事務(wù)(Business Transaction)的數(shù)據(jù)一致性。業(yè)務(wù)事務(wù)可能橫跨多個微服務(wù)的邊界,涉及不同類型的數(shù)據(jù)存儲,還可能有人員的參與。這樣的業(yè)務(wù)事務(wù)有自己的狀態(tài),而且可能耗時漫長,Saga 模式是實現(xiàn)業(yè)務(wù)事務(wù)的良好解決方案。
在應(yīng)用 Saga 模式之后,每個微服務(wù)更新本地的數(shù)據(jù)庫,并發(fā)布事件來推動業(yè)務(wù)事務(wù)往前發(fā)展。根據(jù)是否有協(xié)調(diào)者,Saga 分成編排型(Choreography)和編制型(Orchestration)兩種,其中編制型有協(xié)調(diào)者。編排型 Saga 中的本地事務(wù)由事件來直接觸發(fā),而編制型中 Saga 的本地事務(wù)的觸發(fā)由協(xié)調(diào)者來確定。
每個 Saga 中有多個參與者,每個參與者需要定義所執(zhí)行的操作,以及對應(yīng)的補償操作。補償操作不一定與執(zhí)行的操作完全相反。比如,訂單服務(wù)中的創(chuàng)建訂單操作的補償操作是把訂單的狀態(tài)改為已取消,同時根據(jù)不同的情況,可能收取一定的取消費用。每個參與者只負責(zé)完成整個業(yè)務(wù)事務(wù)中的某一步,并根據(jù)執(zhí)行的結(jié)果來確定下一步的操作。編排型 Saga 中的業(yè)務(wù)邏輯散落在每個參與者之中,而編制型 Saga 中的業(yè)務(wù)邏輯由協(xié)調(diào)者來統(tǒng)一管理。業(yè)務(wù)事務(wù)的進程推進由事件和消息來完成,當(dāng)業(yè)務(wù)事務(wù)進行到最后一步時,這個 Saga 處于已完成的狀態(tài)。
下圖是編排型 Saga 的示意圖。圖中的每個六邊形表示一個服務(wù),其中的箭頭表示事件。對事件的處理發(fā)生在每個服務(wù)的內(nèi)部,處理的結(jié)果會導(dǎo)致新的事件產(chǎn)生。整個業(yè)務(wù)事務(wù)的狀態(tài)可以從訂單對象的狀態(tài)中得到。
下圖是編制型 Saga 的示意圖。服務(wù)之間傳遞的是命令和命令的響應(yīng),圖中以雙向箭頭來表示。訂單服務(wù)中有專門的 Saga 實體來維護業(yè)務(wù)事務(wù)的狀態(tài),這個 Saga 實體也負責(zé)根據(jù)之前命令的響應(yīng)結(jié)果,來確定下一步需要調(diào)用的命令。
微服務(wù)架構(gòu)中的數(shù)據(jù)一致性是一個相對復(fù)雜的問題,不同微服務(wù)中獨立的數(shù)據(jù)存儲,使得維護數(shù)據(jù)的一致性變得困難。本課時對數(shù)據(jù)一致性的問題做了介紹,包括數(shù)據(jù)庫事務(wù)的 ACID 特性,以及最終一致性的 BASE 特性;最后介紹了用來保證數(shù)據(jù)一致性的 Saga 模式。通過本課時的學(xué)習(xí),你將對數(shù)據(jù)一致性問題有更清楚的認識,了解到 ACID 和 BASE 這兩種一致性特性,并對 Saga 模式有最基本的認識。