架構

微服務架構:拆分單體應用的難點

廣告
廣告

拆分單體應用為服務的難點

從表面上看,通過定義與業務能力或子域相對應的服務來創建微服務架構的策略看起來很簡單。但是,你可能會遇到幾個障礙:

  • 網絡延遲。
  • 同步進程間通信導致可用性降低。 
  • 在服務之間維持數據一致性。
  • 獲取一致的數據視圖。
  • 上帝類阻礙了拆分。

讓我們來看看每個問題,先從網絡延遲開始。

網絡延遲

網絡延遲是分布式系統中一直存在的問題。你可能會發現,對服務的特定分解會導致兩個服務之間的大量往返調用。有時,你可以通過實施批處理API在一次往返中獲取多個對象,從而將延遲減少到可接受的數量。但在其他情況下,解決方案是把多個相關的服務組合在一起,用編程語言的函數調用替換昂貴的進程間通信。

同步進程間通信導致可用性降低

另一個需要考慮的問題是如何處理進程間通信而不降低系統的可用性。例如,實現createOrder()操作最常見的方式是讓Order Service使用REST同步調用其他服務。這樣做的弊端是REST這樣的協議會降低Order Service的可用性。如果任何一個被調用的服務處在不可用的狀態,那么訂單就無法創建了。有時候這可能是一個不得已的折中,但是在第3章中學習異步消息之后,你就會發現其實有更好的辦法來消除這類同步調用產生的緊耦合并提升可用性。

在服務之間維持數據一致性

另一個挑戰是如何在某些系統操作需要更新多個服務中的數據時,仍舊維護服務之間的數據一致性。例如,當餐館接受訂單時,必須在Kitchen Service和Delivery Service中同時進行更新。Kitchen Service會更改Ticket的狀態。Delivery Service安排訂單的交付。這些更新都必須以原子化的方式完成。

傳統的解決方案是使用基于兩階段提交(two phase commit)的分布式事務管理機制。但正如你將在第4章中看到的那樣,對于現今的應用程序而言,這不是一個好的選擇,你必須使用一種非常不同的方法來處理事務管理,這就是Saga。Saga是一系列使用消息協作的本地事務。Saga比傳統的ACID事務更復雜,但它們在許多情況下都能工作得很好。Saga的一個限制是它們最終是一致的。如果你需要以原子方式更新某些數據,那么它必須位于單個服務中,這可能是分解的障礙。

獲取一致的數據視圖

分解的另一個障礙是無法跨多個數據庫獲得真正一致的數據視圖。在單體應用程序中,ACID事務的屬性保證查詢將返回數據庫的一致視圖。相反,在微服務架構中,即使每個服務的數據庫是一致的,你也無法獲得全局一致的數據視圖。如果你需要一些數據的一致視圖,那么它必須駐留在單個服務中,這也是服務分解所面臨的問題。幸運的是,在實踐中這很少帶來真正的問題。

上帝類阻礙了拆分

分解的另一個障礙是存在所謂的上帝類。上帝類是在整個應用程序中使用的全局類。上帝類通常為應用程序的許多不同方面實現業務邏輯。它有大量字段映射到具有許多列的數據庫表。大多數應用程序至少有一個這樣的上帝類,每個類代表一個對領域至關重要的概念:銀行賬戶、電子商務訂單、保險政策,等等。因為上帝類將應用程序的許多不同方面的狀態和行為捆綁在一起,所以將使用它的任何業務邏輯拆分為服務往往都是一個不可逾越的障礙。

Order類是FTGO應用程序中上帝類的一個很好的例子。這并不奇怪:畢竟FTGO的目的是向客戶提供食品訂單。系統的大多數部分都涉及訂單。如果FTGO應用程序具有單個領域模型,則Order類將是一個非常大的類。它將具有與應用程序的許多不同部分相對應的狀態和行為。圖6顯示了使用傳統建模技術創建的Order類的結構。

圖6 Order這個上帝類承載了太多的職責

如你所見,Order類具有與訂單處理、餐館訂單管理、送餐和付款相對應的字段及方法。由于一個模型必須描述來自應用程序的不同部分的狀態轉換,因此該類還具有復雜的狀態模型。在目前情況下,這個類的存在使得將代碼分割成服務變得極其困難。

一種解決方案是將Order類打包到庫中并創建一個中央Order數據庫。處理訂單的所有服務都使用此庫并訪問訪問數據庫。這種方法的問題在于它違反了微服務架構的一個關鍵原則,并導致我們特別不愿意看到的緊耦合。例如,對Order模式的任何更改都要求其他開發團隊同步更新和重新編譯他們的代碼。

另一種解決方案是將Order數據庫封裝在Order Service中,該服務由其他服務調用以檢索和更新訂單。該設計的問題在于這樣的一個Order Service將成為一個純數據服務,成為包含很少或沒有業務邏輯的貧血領域模型(anemic domain model)。這兩種解決方案都沒有吸引力,但幸運的是,DDD提供了一個好的解決方案。

更好的方法是應用DDD并將每個服務視為具有自己的領域模型的單獨子域。這意味著FTGO應用程序中與訂單有關的每個服務都有自己的領域模型及其對應的Order類的版本。Delivery Service是多領域模型的一個很好的例子。如圖7所示為Order,它非常簡單:取餐地址、取餐時間、送餐地址和送餐時間。此外,DeliveryService使用更合適的Delivery名稱,而不是稱之為Order。

圖7 Delivery Service的領域模型

Delivery Service對訂單的任何其他屬性不感興趣。

Kitchen Service有一個更簡單的訂單視圖。它的Order版本就是一個Ticket(后廚工單)。如圖8所示,Ticket只包含status、requestedDeliveryTime、prepareByTime以及告訴餐館準備的訂單項列表。它不關心消費者、付款、交付等這些與它無關的事情。

圖8 Kitchen Service的領域模型

Order Service具有最復雜的訂單視圖,如圖9所示。即使它有相當多的字段和方法,它仍然比原始版本的那個Order上帝類簡單得多。

圖9 Order Service的領域模型

每個領域模型中的Order類表示同一Order業務實體的不同方面。FTGO應用程序必須維持不同服務中這些不同對象之間的一致性。例如,一旦OrderService授權消費者的信用卡,它必須觸發在Kitchen Service中創建Ticket。同樣,如果Kitchen Service拒絕訂單,則必須在Order Service中取消訂單,并且為客戶退款。在第4章中,我們將學習如何使用前面提到的事件驅動機制Saga來維護服務之間的一致性。

除了造成一些技術挑戰以外,擁有多個領域模型還會影響用戶體驗。應用程序必須在用戶體驗(即其自己的領域模型)與每個服務的領域模型之間進行轉換。例如,在FTGO應用程序中,向消費者顯示的Order狀態來自存儲在多個服務中的Order信息。這種轉換通常由API Gateway處理,將在第8章中討論。盡管存在這些挑戰,但在定義微服務架構時,必須識別并消除上帝類。

我們現在來看看如何定義服務API。

6 定義服務API

到目前為止,我們有一個系統操作列表和一個潛在服務列表。下一步是定義每個服務的API:也就是服務的操作和事件。存在服務API操作有以下兩個原因:首先,某些操作對應于系統操作。它們由外部客戶端調用,也可能由其他服務調用。另次,存在一些其他操作用以支持服務之間的協作。這些操作僅由其他服務調用。

服務通過對外發布事件,使其能夠與其他服務協作。第4章將描述如何使用事件來實現Saga,這些Saga可以維護服務之間的數據一致性。第7章將討論如何使用事件來更新CQRS視圖,這些視圖支持有效的查詢。應用程序還可以使用事件來通知外部客戶端。例如,可以使用WebSockets將事件傳遞給瀏覽器。

定義服務API的起點是將每個系統操作映射到服務。之后確定服務是否需要與其他服務協作以實現系統操作。如果需要協作,我們將確定其他服務必須提供哪些API才能支持協作。首先來看一下如何將系統操作分配給服務。

把系統操作分配給服務

第一步是確定哪個服務是請求的初始入口點。許多系統操作可以清晰地映射到服務,但有時映射會不太明顯。例如,考慮使用noteUpdatedLocation()操作來更新送餐員的位置。一方面,因為它與送餐員有關,所以應該將此操作分配給Courier Service。另一方面,它是需要送餐地點的DeliveryService。在這種情況下,將操作分配給需要操作所提供信息的服務是更好的選擇。在其他情況下,將操作分配給具有處理它所需信息的服務可能是有意義的。表4顯示了FTGO應用程序中的哪些服務負責哪些操作。

表4 FTGO應用程序的系統操作映射到具體的服務

把操作分配給服務后,下一步是確定在處理每一個系統操作時,服務之間如何交互。

確定支持服務協作所需要的API

某些系統操作完全由單個服務處理。例如,在FTGO應用程序中,Consumer Service完全獨立地處理createConsumer()操作。但是其他系統操作跨越多個服務。處理這些請求之一所需的數據可能分散在多個服務周圍。例如,為了實現createOrder()操作,Order Service必須調用以下服務以驗證其前置條件并使后置條件成立:

  • Consumer Service:驗證消費者是否可以下訂單并獲取其付款信息。
  • Restaurant Service:驗證訂單行項目,驗證送貨地址和時間是否在餐廳的服務區域內,驗證訂單最低要求,并獲得訂單行項目的價格。
  • Kitchen Service:創建Ticket(后廚工單)。
  • Accounting Service:授權消費者的信用卡。

同樣,為了實現acceptOrder()系統操作,Kitchen Service必須調用Delivery Service來安排送餐員交付訂單。表2-3顯示了服務、修訂后的API及協作者。為了完整定義服務API,你需要分析每個系統操作并確定所需的協作。

表5 服務、修訂后的API及協作者

總結

  • 微服務中的服務是根據業務需求進行組織的,按照業務能力或者子域,而不是技術上的考量。
  • 有兩種分解模式:

        按業務能力分解,其起源于業務架構。

        基于領域驅動設計的概念,通過子域進行分解。

  • 可以通過應用DDD并為每個服務定義單獨的領域模型來消除上帝類,正是上帝類引起了阻礙分解的交織依賴項。

本文摘自《微服務架構設計模式》,經出版方授權發布。

原文鏈接:https://mp.weixin.qq.com/s/2qrEH2_ZwE0HD1M5bQ4i5A

最干貨的java+分布式技術公眾號,兼及研發管理。本號專家陣容:螞蟻金服右軍、易寶CTO陳斌、米么金服總監李偉山、奧琪金科首席架構曲健、螞蟻金服高級技術專家張翔、美團高級技術專家楊彪等。

昨天keep突然裁員,我連夜卷鋪蓋走人

上一篇

Knative 實戰:基于 Kafka 實現消息推送

下一篇

你也可能喜歡

微服務架構:拆分單體應用的難點

長按儲存圖像,分享給朋友

ITPUB 每周精要將以郵件的形式發放至您的郵箱


微信掃一掃

微信掃一掃
双色球常规走势图 北京11选5的走势 旺旺福州麻将 青海十一选五 Welcome-极速11选5 美国棒球比分直播mlb 3d开奖号码今晚开 188比分直播足球比分直播188号pp 澳洲幸运5五位走势图 融胜配资 下载红包麻将 比分直播500足彩开奖 卡五星手机版