跳轉到

Design Pattern of Microservice

Strangler Pattern

img

逐漸用新的應用程序和服務來替代特定功能。創建一個 facade,攔截後端遺留系統的請求。facade 將這些請求路由到舊應用程序或新服務。現有功能可以逐漸遷移到新系統,消費者可以繼續使用相同的接口,不會感知到遷移的發生。

這種模式有助於最大限度地減少遷移的風險,並隨著時間推移擴大開發工作。通過 facade 安全地將用戶路由到正確的應用程序,可以以任何喜歡的速度向新系統添加功能,同時確保舊應用程序繼續運行。隨著時間的推移,功能遷移到新系統,遺留系統最終被“絞殺”,不再需要。一旦這個過程完成,遺留系統就可以安全地退休。

注意事項

  • 需考慮如何處理新系統和舊系統可能使用的服務和數據存儲。確保兩者都可以並行訪問這些資源。
  • 在某些時候,遷移完成時,絞殺者 facade 將會消失,或者演變成遺留客戶端的適配器。
  • 需確保 facade 不會成為單點故障或性能瓶頸。

Anti-corruption Pattern

img

通過在遺留系統和現代系統之間使用防腐層來隔離它們。該層轉換兩個系統之間的通信,允許遺留系統保持不變,同時可以避免損害現代應用程序的設計和技術方法。

現代應用與防腐層之間的通信始終使用應用程序的數據模型和架構。從防腐層到遺留系統的調用都符合該系統的數據模型或方法。防腐層包含兩個系統之間轉換所需的所有邏輯。該層可以作為應用程序中的組件或作為獨立服務來實現。

注意事項

  • 防腐層可能會增加在兩個系統之間進行調用的延遲。
  • 防腐層添加了必須進行管理和維護的附加服務。
  • 需考慮是否需要多個防腐層。你可能希望使用不同的技術或語言將功能分解成多個服務,或者因為其它原因來分割防腐層。
  • 需確保事務和數據的一致性得到維護並可以監控。
  • 需考慮防腐層是否是為永久性的,或者是在所有舊功能都遷移後最終退休。

Gateway Routing Pattern

img

背景與問題

當客戶端需要消費多個服務時,為每個服務建一個單獨的端點並由客戶端去管理每個端點是非常有挑戰性的。例如,某個電子商務應用可能會提供多個服務,如搜索、評論、購物車、付款和訂單歷史等。

每個服務都有不同的 API,客戶端需要與之交互,客戶端必須知道每個端點才能連接到服務。如果某個端點修改或更新了,客戶端也得跟著更新。如果將某個服務重構為兩個或多個獨立服務,代碼就必須同時在服務端和客戶端作出修改。

解決方案

在一組應用、服務或部署程序前面放一個 gateway。使用第 7 層應用路由將請求按路由發送給適當的實例。

使用該模式,客戶端應用只需知道一個單獨的端點並與之通信。如果服務被合併或分解,客戶端並不需要隨之更新。客戶端仍然可以繼續向 gateway 發送請求,只有路由發生了變化。

gateway 還能讓你從客戶端的角度對後端服務進行抽象,允許你在保持客戶端調用簡單的情況下同時允許 gateway 後面的後端服務發生變更。客戶端調用可以被路由給任何一個或幾個需要處理期望的客戶端行為的服務,允許你添加、拆分和重新組織 gateway 後面的服務,而不需修改客戶端。

注意事項

  • gateway 服務可能會引入單點故障。確保在設計時就考慮到可用性需求。在實現時考慮可恢復性與故障容錯能力。
  • gateway 服務可能會成為性能瓶頸。確保 gateway 具有足夠的性能以處理負載,並能夠容易隨著預期增長一致擴展。

Aggregator Pattern

背景與問題

要執行某個任務時,客戶端必須向不同的後端服務發起多次調用。依賴於多個服務的應用在執行任務時,必須為每個請求都擴展資源。當新的特性或服務添加到該應用時,需要添加額外的請求,而且增加了資源需求與網絡調用。這些在客戶端與後端之間的通信量會對應用的性能和可擴展性造成不利影響。微服務架構下這種情況更為普遍,因為應用是由若干個更小的服務構成的,天然地需要更多數量的跨服務調用。

在下圖中,客戶端向服務發送請求(1,2,3)。每個服務處理這些請求並向應用返迴響應(4,5,6)。在高延遲的蜂窩網絡中,以這種方式使用獨立的請求無疑是低效的,而且可能出現連接斷開或未完成的請求。各個請求還有可能並行執行,應用就必須為每個請求發送、等待和處理數據,這些都是在各自獨立的連接中的,增加了失敗的可能性。

img

解決方案

使用 gateway 來減少客戶端與服務之間的通信量。 gateway 接收客戶請求,將請求分發到不同的後端系統,然後將結果聚合後返回給請求的客戶端。

這種模式可以減少應用向後端服務發送請求的數量,並且改善應用在高延遲網絡中的性能。

在下圖中,應用向 gateway 發送一個請求(1)。該請求包括了一系列額外請求組成的包。 gateway 將該請求包分解為不同的請求,並發送給各自的相關服務(2)。每個服務向 gateway 返回一個相應(3)。 gateway 將來自各個服務的響應組合為一個響應後返回給應用(4)。應用只需向 gateway 發送一個單獨的請求,並從 gateway 得到一個單獨的響應。

img

Backend For Frontend (BFF) Pattern

img

背景與問題

應用最初往往是以桌面 web UI 為目標而開始設計的。通常會同時並行開發一個為該 UI 提供所需特性的後端服務。隨著應用用戶數的增長,移動應用會被開發出來,而且必須與同一個後端進行交互。這個後端服務便變成了通用的後端,需要同時滿足桌面和移動界面的需求。

但是,在屏幕尺寸、性能和顯示限制方面,移動設備與桌面瀏覽器的能力往往是有顯著差異的。結果會導致移動應用對後端的需求與桌面 web UI 也是不一致的。

這種差異體現在對後端需求的相互矛盾上。後端需要經常發生顯著的變更,以同時滿足桌面 web UI 和移動應用的需求。通常,每種界面會由不同的前端團隊來打造,這就導致後端成為開發過程中的瓶頸。互相衝突的升級需求,以及需要保持後端服務同時為兩種界面而工作,都會導致花費大量精力在這個唯一的可部署資源上。

img

隨著開發活動不斷聚焦於後端服務,有可能會產生一個獨立的團隊來管理和維護後端。最後,會導致界面和後端開發團隊之間的脫節,並為後端團隊增加負擔,因為需要在不同 UI 團隊的互相矛盾的需求之間作出平衡。當一個界麵團隊需要後端進行修改時,這些修改必須經過其它界麵團隊的驗證,才能集成到後端服務中去。

解決方案

為每種用戶界面創建一個單獨的後端。對每個後端的行為和性能進行精心的調整,以最佳匹配對應前端環境的需求,而不用擔心會影響到其它前端體驗。

img

因為每個後端是為某種界面量身打造的,就可以為該界面進行專屬的優化。結果,與試圖滿足所有種類界面的通用後端相比,專屬的後端會變得更小、更簡單,而且很可能更快。每個界麵團隊對於控制自己的後端有自主權,而不用依賴於一個集中式的後端開發團隊。這為界麵團隊在後端服務的語言選擇、發布節奏、工作優先級排序、特性集成等方面提供了靈活性。

注意事項

  • 如果不同的界面(比如移動客戶端)會發送同樣的請求,考慮是否有必要為每種界面都實現一個後端,還是一個單獨的後端就可以滿足。
  • 當實現這種模式時,不同服務很有可能出現重複的代碼。
  • 專屬於前端的後端服務應該只包含客戶端特定的邏輯與行為而不包含業務邏輯。
  • 考慮需要多長時間實現這種模式。在繼續支持已存在的通用後端的同時,為打造新的後端投入精力,是否會引入技術債?

Database-per-Service Pattern

當一家公司將大型單體系統替換成一組微服務,首先要面臨的最重要決策是關於數據庫。單體架構會使用大型中央數據庫。即使轉移到微服務架構許多架構師仍傾向於保持數據庫不變。雖然有一些短期收益,但它卻是反模式的,特別是在大規模系統中,微服務將在數據庫層嚴重耦合,整個遷移到微服務的目標都將面臨失敗(例如,團隊授權、獨立開發等問題)。

更好的方法是為每個微服務提供自己的數據存儲,這樣服務之間在數據庫層就不存在強耦合。這裡使用數據庫這一術語來表示邏輯上的數據隔離,也就是說微服務可以共享物理數據庫,但應該使用分開的數據結構、集合或者表,這將有助於確保微服務是按照領域驅動設計的方法正確拆分的。

img

Circuit Breaker Pattern

img

斷路器模式可以防止應用程序重複嘗試執行可能失敗的操作。允許它繼續,而不必等待故障修復或浪費 CPU 週期,同時判斷故障是否持續。斷路器模式讓應用程序能夠檢測故障是否已解決。如果問題已經修復,應用程序可以嘗試調用該操作。

斷路器作為可能失敗的操作的代理。應監視最近發生的故障數,並使用此信息來決定是否允許操作繼續,或者立即返回異常。

代理可以被實現為具有模擬電路斷路器功能的狀態機,包含以下的狀態:

關閉:應用程序的請求被路由到操作。代理維護最近的故障數量的計數,如果對操作的調用不成功,則代理會增加此計數。如果最近的故障數量在給定時間段內超出了指定的閾值,則代理將被置於打開狀態。此時,代理啟動一個超時定時器,當該定時器到期時,代理被置於半打開狀態。

超時定時器的目的是讓系統有時間來解決導致故障的問題,然後允許應用程序再次執行該操作。

打開:應用程序的請求立即失敗,返回異常給應用程序。

半開:允許來自應用程序的有限數量的請求通過並調用操作。如果這些請求成功,則假定以前導致故障的問題已經修復,斷路器切換到關閉狀態(故障計數器被復位)。如果任何請求失敗,則斷路器假定故障仍然存在,因此它恢復到打開狀態並重新啟動超時定時器,給系統更多的時間從故障中恢復。

半開狀態有助於防止恢復服務突然被請求淹沒。隨著服務的恢復,它可能能夠支持有限數量的請求,直到恢復完成,但是在恢復進行時,大量的工作可能導致服務超時或失敗。

Event Sourcing Pattern

Command Query Responsibility Segregation (CQRS) Pattern

img

背景和問題

在傳統的數據管理系統中,命令(對數據的更新)和查詢(對數據的請求)是針對單個數據存儲庫中的同一組實體執行的。這些實體可以是關係數據庫(如 SQL Server)中的一個或多個表中的行的子集。通常在這些系統中,所有創建,讀取,更新和刪除(CRUD)操作都應用於實體的相同表示。例如,通過數據訪問層(DAL)從數據存儲器檢索表示客戶的數據傳輸對象(DTO)並在屏幕上顯示。用戶更新 DTO 的某些字段(可能通過數據綁定),然後 DAL 保存 DTO 到數據存儲區。同樣的 DTO 用於讀寫操作。下圖介紹了傳統的 CRUD 架構。

img

當只有有限的業務邏輯被應用於數據操作時,傳統的 CRUD 設計才能正常工作。開發工具提供的腳手架可以非常快速地創建數據訪問代碼,然後可以根據需要定制。

然而,傳統的 CRUD 方法有一些缺點:

  • 這通常意味著數據的讀取和寫入表示之間存在不匹配的情況,例如必須正確更新的附加列或屬性,即使操作不要求這部分。
  • 當記錄鎖定在協作域中的數據存儲中時,數據爭用就會發生風險,其中多個角色在同一組數據上並行操作。或者在並發更新使用樂觀鎖時引起的衝突。這些風險隨著系統的複雜性和吞吐量的增加而增加。此外,由於數據存儲和數據訪問層的負載以及檢索信息所需的查詢的複雜性,傳統的方法可能會對性能產生負面影響。
  • 讓管理安全性和權限更加複雜,因為每個實體都受讀寫操作的限制,這可能會將數據暴露在錯誤的上下文中。

解決方案

命令和查詢責任分離(CQRS)是一種模式,它通過使用單獨的接口來隔離更新數據(命令)的操和讀取數據(查詢)的操作。這意味著用於查詢和更新的數據模型是不同的。然後,模型可以如下圖所示被隔離,儘管這不是絕對要求。

img

與基於 CRUD 的系統中使用的單一數據模型相比,CQRS 的系統中使用單獨的查詢和更新模型來簡化設計和實現。然而,與 CRUD 設計不同,CQRS 代碼的缺點是不能使用腳手架自動生成。

用於讀取數據的查詢模型和用於寫入數據的更新模型可以訪問相同的物理存儲,也許通過使用 SQL 視圖或通過快速生成映射。然而,通常我們會將數據分成不同的物理存儲,以最大限度地提高性能,可擴展性和安全性,如下圖所示。

img

讀存儲可以是寫存儲的只讀副本,或者讀寫存儲可以具有完全不同的結構。使用讀取存儲的多個只讀副本可以大大提高查詢性能和應用程序 UI 響應,特別是在只讀副本靠近應用程序實例分佈式場景中。某些數據庫系統(SQL Server)提供了諸如故障轉移副本的附加功能,以最大限度地提高可用性。讀寫存儲器的分離也允許每個存儲器適當地伸縮以匹配負載。例如,讀存儲器通常遇到比寫存儲器高得多的負載。當查詢/讀模型包含非規範化數據時,為應用程序中的每個視圖讀取數據或查詢系統中的數據時,性能將最大化。

注意事項

在決定如何實現此模式時,請考慮以下幾點:

  • 將數據存儲分為單獨的物理存儲以進行讀寫操作可以提高系統的性能和安全性,但它增加彈性和最終一致性的複雜性。必須更新讀模型存儲以反映對寫模型存儲的更改
  • 難以檢測用戶何時發出請求基於陳舊數據的讀取請求

Saga Pattern

背景和問題

如果微服務使用獨享數據庫,那麼通過分佈式事務管理一致性是一個巨大的挑戰。你無法使用傳統的兩階段提交協議,因為它要馬不可伸縮(關係型數據庫),要馬不被支持(多數非關係型數據庫)。為了提高性能和避免分佈式環境中的競爭,應用程序不應該提供強事務一致性。相反,應該實現最終一致性。在這個模型中,典型的業務操作包括一系列單獨的步驟。當執行這些步驟時,系統狀態的總體視圖可能不一致,但是當操作已經完成並且所有步驟都已執行時,系統應該再次變得一致。

解決方案

Saga 模式是一個本地事務序列,其每個事務在一個單獨的微服務內更新數據存儲並發布一個事件或消息。Saga 中的首個事務是由外部請求(事件或動作)初始化的,一旦本地事務完成(數據已保存在數據存儲且消息或事件已發布),那麼發布的消息或事件則會觸發 Saga 中的下一個本地事務。如果本地事務失敗,Saga 將執行一系列補償事務來回滾前面本地事務的更改。

Saga 事務協調管理主要有兩種形式:

  • Choreography:分散協調,每個微服務生產並監聽其他微服務的事件或消息然後決定是否執行某個動作。
  • Orchestration:集中協調,由一個協調器告訴參與的微服務哪個本地事務需要執行。

案例

旅遊網站讓客人預訂行程。單程可能包括一系列航班和酒店。從西雅圖到倫敦,然後到巴黎的客戶,創建行程時可以執行以下步驟:

  1. 預訂從西雅圖到倫敦的 F1 航班座位。
  2. 預訂從倫敦到巴黎 F2 航班的座位。
  3. 預訂從巴黎到西雅圖 F3 班機的座位。
  4. 在倫敦的 H1 酒店預訂房間。
  5. 在巴黎的 H2 酒店預訂客房。

這些步驟構成了最終一致的操作,儘管每個步驟都是單獨的操作。因此,除了執行這些步驟之外,系統還必須記錄必要的計數器操作,以便在客戶決定取消行程的情況下撤消每個步驟。執行計數器操作所需的步驟之後可以作為補償事務運行。請注意,事務補償中的步驟可能與原始步驟完全相反,事務補償的每個步驟中的邏輯必須考慮到任何特定於業務的規則。例如,在航班上取消預訂座位可能無法讓客戶完全退還所支付的任何費用。下圖展示了生成事務補償以撤銷預訂旅行行程的長時間運行的交易。 img

根據每個步驟設計補償邏輯的方式,補償事務中的步驟可能並行執行。在許多業務解決方案中,單步驟的故障並不總是需要通過使用事務補償來回滾系統。例如,如果在旅遊網站場景中預訂了 F1,F2 和 F3 的航班-客戶無法在酒店 H1 預訂房間,比取消航班更好的是在同一城市的不同酒店為客戶預定房間。在客戶仍然決定取消時(在這種情況下,事務補償運行並撤銷在 F1,F2 和 F3 航班上的預訂),但決定應由客戶而不是由系統做出。

Reference