共用方式為


分散式數據管理的挑戰和解決方案

小提示

此內容是適用於容器化 .NET 應用程式的電子書.NET 微服務架構摘錄,可在 .NET Docs 或免費下載的 PDF 中取得,可脫機讀取。

.NET 微服務架構的容器化 .NET 應用程式電子書封面縮圖。

挑戰 #1:如何定義每個微服務的界限

定義微服務界限可能是任何人遇到的第一個挑戰。 每個微服務都必須是應用程式的一部分,而且每個微服務應該是自主的,這伴隨著它所具備的所有優點和挑戰。 但是,如何識別這些界限?

首先,您必須專注於應用程式的邏輯領域模型和相關數據。 嘗試識別相同應用程式內獨立的資料群和不同上下文。 每個內容都可以有不同的商務語言(不同的商務詞彙)。 上下文應該獨立定義和管理。 用於這些不同內容中的詞彙和實體可能聽起來很類似,但您可能會發現在特定內容中,有一個的商務概念用於另一個內容中的不同用途,甚至可能有不同的名稱。 例如,用戶可以稱為身分識別或成員資格內容中的使用者、CRM 內容中的客戶、訂單內容中的購買者等等。

識別多個應用程式情境之間的界限,並各自具有不同的領域的方式,正是識別每個商務微服務以及其相關的領域模型和數據界限的方法。 您通常會嘗試將這些微服務之間的耦合降到最低。 本指南會在稍後 識別每個微服務的定義領域模型界限 一節中,進一步詳細說明此識別和領域模型設計。

挑戰 #2:如何建立從數個微服務擷取數據的查詢

第二個挑戰是實作從數個微服務擷取數據的查詢,同時避免從遠端用戶端應用程式與微服務進行閒聊通訊。 例如,行動應用程式的單一畫面需要顯示購物籃、目錄和使用者身分識別微服務所擁有的用戶資訊。 另一個範例是涉及多個微服務中許多數據表的複雜報表。 正確的解決方案取決於查詢的複雜性。 但無論如何,如果您想要提高系統通訊的效率,您需要一種方法來匯總資訊。 最受歡迎的解決方案如下。

API 閘道。 對於擁有不同資料庫之多個微服務的簡單數據匯總,建議的方法是稱為 API 閘道的匯總微服務。 不過,您必須小心實作此模式,因為它可能是系統中的一個窒息點,而且可能會違反微服務自主原則。 若要減輕這種可能性,您可以擁有多個細粒度的 API 閘道,每個閘道都著重於系統的垂直「切片」或業務區域。 API 閘道模式會在稍後的 API 閘道一節 中詳細說明。

GraphQL 同盟 如果您的微服務已經使用 GraphQL,請考慮其中一個選項是 GraphQL 同盟。 同盟可讓您從其他服務定義「子圖」,並將其撰寫成可做為獨立架構的匯總「超級圖」。

具有查詢/讀取數據表的 CQRS。 從多個微服務匯總數據的另一個解決方案是 具體化檢視模式。 在這種方法中,您需要提前生成一個唯讀表,其中包含多個微服務所持有的數據,並在實際查詢發生之前準備好去除正規化的數據。 數據表的格式適合用戶端應用程式的需求。

可以想像一下行動應用程式的介面畫面。 如果您有單一資料庫,您可以使用執行涉及多個數據表的複雜聯結的 SQL 查詢,來提取該畫面的數據。 不過,當您有多個資料庫,而且每個資料庫都由不同的微服務所擁有時,您無法查詢這些資料庫並建立 SQL 聯結。 您的複雜查詢會成為挑戰。 您可以使用 CQRS 方法來解決需求,您可以在只用於查詢的不同資料庫中建立反正規化數據表。 數據表可以特別針對複雜查詢所需的數據而設計,應用程式畫面和查詢數據表中的數據行之間會有一對一的關聯性。 它也可以用於報告目的。

這種方法不僅解決了原始問題(如何跨微服務查詢和聯結),而且相較於複雜的聯結,它也會大幅改善效能,因為您已經擁有應用程式在查詢數據表中所需的數據。 當然,使用命令和查詢責任隔離 (CQRS) 搭配查詢/讀取數據表意味著額外的開發工作,您必須採用最終一致性。 儘管如此,在 共同作業案例 中效能和高延展性的需求(或競爭案例,視觀點而定)是您應該將 CQRS 套用至多個資料庫的位置。

中央資料庫中的「冷數據」。 對於可能不需要即時數據的複雜報表和查詢,常見的方法是將「熱數據」(來自微服務的事務數據)導出為「冷數據」,並存入只用於報告的大型資料庫。 該中央資料庫系統可以是巨量數據型系統,例如 Hadoop;數據倉儲,例如以 Azure SQL 數據倉儲為基礎的數據倉儲;或甚至只用於報表的單一 SQL 資料庫(如果大小不會是問題)。

請記住,此集中式資料庫只會用於不需要即時數據的查詢和報表。 原始更新和交易紀錄,作為真實來源,必須儲存在微服務數據中。 同步處理數據的方式是使用事件驅動通訊(涵蓋於下一節中),或使用其他資料庫基礎結構匯入/匯出工具。 如果您使用事件驅動通訊,該整合程式會類似於您傳播數據的方式,如 CQRS 查詢數據表先前所述。

不過,如果您的應用程式設計牽涉到持續匯總來自多個微服務的資訊以進行複雜的查詢,則可能是錯誤設計 -a 微服務應該盡可能與其他微服務隔離的徵兆。 (這不包括一律應該使用冷數據中央資料庫的報表/分析。通常遇到這個問題可能是合併微服務的原因。 您必須平衡每個微服務演進和部署的自主權,以及強式相依性、凝聚力和數據匯總。

挑戰 #3:如何跨多個微服務達成一致性

如先前所述,每個微服務所擁有的數據是該微服務的私人數據,而且只能使用其微服務 API 來存取。 因此,呈現的挑戰是如何實作端對端商務程序,同時讓多個微服務保持一致性。

若要分析這個問題,讓我們看看 eShopOnContainers 參考應用程式的範例。 目錄微服務會維護所有產品的相關信息,包括產品價格。 購物籃微服務會管理使用者新增至購物籃之產品項目的時態性數據,包括新增時的項目價格。 當產品的價格在目錄中更新時,該價格也應該在持有該相同產品的現用購物籃中更新,而且系統應該警告使用者,指出特定專案的價格自從他們新增至購物籃后已變更。

在此應用程式的假設整合型版本中,當產品數據表的價格變更時,目錄子系統只需使用 ACID 交易來更新購物籃數據表中的目前價格。

不過,在微服務型應用程式中,Product 和Basket資料表是由其各自的微服務所擁有。 任何微服務都不應該在自己的交易中包含另一個微服務所擁有的數據表/記憶體,即使在直接查詢中也是如此,如圖 4-9 所示。

顯示微服務資料庫數據無法共享的圖表。

圖 4-9。 微服務無法直接存取另一個微服務中的數據表

目錄微服務不應該直接更新購物籃數據表,因為Basket數據表是由購物籃微服務所擁有。 若要更新購物籃微服務,目錄微服務應使用最終一致性,可能建立在異步通訊上,例如整合事件(基於訊息和事件的通訊)。 這就是 eShopOnContainers 參考應用程式跨微服務執行這種類型的一致性的方式。

CAP 定理所述,您必須在可用性與 ACID 強式一致性之間進行選擇。 大部分微服務型案例都要求可用性和高延展性,而不是強式一致性。 任務關鍵應用程式必須持續運行,開發人員可以藉助技術來處理弱式或最終一致性,來迴避強一致性限制。 這是大部分微服務架構採用的方法。

此外,ACID 風格或兩階段提交交易不僅與微服務原則相悖,大部分的 NoSQL 資料庫(例如 Azure Cosmos DB、MongoDB 等)都不支援兩階段提交交易,通常應用在分散式資料庫的情境。 不過,維護服務和資料庫之間的數據一致性至關重要。 這項挑戰也與當特定數據需要備援時,如何將變更傳播到多個微服務的問題有關,例如,當您需要在目錄微服務和購物籃微服務中擁有產品名稱或描述時。

此問題的一個很好的解決方案是使用透過事件驅動通訊和發佈與訂閱系統所表達之微服務之間的最終一致性。 本指南稍後的 異步事件驅動通訊 一節涵蓋這些主題。

挑戰 #4:如何跨微服務界限設計通訊

跨微服務界限進行通訊是一項真正的挑戰。 在此內容中,通訊不會參考您應該使用的通訊協定(HTTP 和 REST、AMQP、傳訊等等)。 相反地,它會解決您應該使用的通訊樣式,特別是微服務的結合程度。 根據結合層級而定,當發生失敗時,該失敗對系統的影響會有很大的差異。

在像微服務型應用程式這樣的分散式系統中,有許多工件在多台伺服器或主機之間移動,並且服務分散時,元件最終可能會出現故障。 部分失敗,甚至更大的中斷將會發生,因此您需要設計微服務及其間通訊,以考慮這種分散式系統中常見的風險。

常見的方法是實作 HTTP(REST)型微服務,因為其簡單性。 HTTP 型方法是完全可接受的;此處的問題與您如何使用它有關。 如果您使用 HTTP 要求和回應,只是為了與用戶端應用程式或 API 閘道的微服務互動,則沒問題。 但是,如果您跨微服務建立同步 HTTP 呼叫的長鏈結,跨其界限進行通訊,就好像微服務是整合型應用程式中的物件一樣,您的應用程式最終將遇到問題。

例如,假設用戶端應用程式對個別微服務進行 HTTP API 呼叫,例如訂購微服務。 如果排序微服務接著在相同的要求/回應週期內使用 HTTP 呼叫其他微服務,您就會建立 HTTP 呼叫鏈結。 一開始聽起來可能很合理。 不過,在走下此路徑時,需要考慮一點:

  • 封鎖和低效能。 由於 HTTP 的同步本質,原始要求在完成所有內部 HTTP 呼叫之前,不會取得回應。 想像一下,如果這些呼叫的數目大幅增加,而且同時封鎖微服務的其中一個中繼 HTTP 呼叫。 結果是效能受到影響,而且隨著其他 HTTP 要求增加,整體延展性會以指數方式受到影響。

  • 將微服務與 HTTP 結合。 商務微服務不應與其他商務微服務結合。 在理想情況下,他們不應該「知道」其他微服務的存在。 如果您的應用程式像例子中那樣依賴耦合微服務,就幾乎不可能達到每個微服務的自主權。

  • 任何一個微服務的失敗。 如果您實作由 HTTP 呼叫連結的微服務鏈結,當任何微服務失敗時(最終會失敗),整個微服務鏈就會失敗。 微服務型系統應該設計成在部分失敗期間仍能盡可能地保持運作良好。 即使您實作使用重試的用戶端邏輯搭配指數退避或電路中斷器機制,HTTP 請求鏈條越複雜,根據 HTTP 實作失敗策略就越複雜。

事實上,如果您的內部微服務是透過建立 HTTP 請求的鏈來進行通訊,那麼可以說您擁有的是一個單體式應用程式,只是基於進程之間的 HTTP,而非進程內部的通訊機制。

因此,為了強制執行微服務自主性並擁有更佳的復原能力,您應該將跨微服務的要求/回應通訊鏈結降至最低。 建議您只針對微服務間通訊使用異步互動,無論是使用異步訊息和事件型通訊,還是使用與原始 HTTP 要求/回應周期無關的 (異步) HTTP 輪詢。

在本指南的稍後部分,異步通訊的使用方式將在異步微服務整合會強制執行微服務的自主權異步訊息式通訊這些章節中進一步詳細說明。

其他資源