譯者 | 李睿
審校 | 重樓
微服務架構已經成為現代應用程序開發的實際選擇。雖然它解決了許多問題,但并不是靈丹妙藥。像所有的軟件一樣,它也面臨一些需要解決的獨有挑戰。這就需要了解微服務中的常見設計模式,并使用可重用的解決方案來解決這些挑戰。
在深入研究設計模式之前,了解構建微服務架構的核心原則非常重要:
圖1微服務架構核心實踐
應用這些原則往往會帶來一些挑戰和問題。本文將提供關鍵微服務模式的細分、它們解決的常見問題以及它們提供的解決方案。
本文可以作為關鍵微服務模式和系統設計策略的參考,以有效地構建微服務架構。
分解設計模式
模式1:按業務能力分解
問題
微服務就是讓服務松散耦合,從而應用單一責任原則。然而,將應用程序分解為更小的部分必須符合邏輯。那么,如何將應用程序分解為更小的服務?
解決方案
其中一種策略是按業務能力進行分解。業務能力是企業為了創造價值所做的事情,給定業務的能力取決于業務類型。例如,保險公司的能力通常包括銷售、營銷、承保、索賠處理、計費、合規等。每種業務能力都可以被視為一種服務——只不過它是面向業務的,而不是技術的。
模式2:按子域分解
問題
使用業務能力分解應用程序可能是一個良好的開端,但是可能遇到所謂的“上帝類” (God Classes)問題,它們不容易分解。這些類在多個服務中是通用的。例如,訂單類將用于訂單管理、訂單接收、訂單交付等。那么,如何分解它們?
解決方案
針對“上帝類”問題,領域驅動設計(DDD)提供了解決方案。它使用子域和有界場景概念來解決這個問題。領域驅動設計(DDD)將為企業創建的整個域模型分解為子域。每個子域都有一個模型,該模型的范圍將被稱為有界場景。每個微服務都將圍繞有界場景進行開發。
注:識別子域并非易事,這需要對業務有所了解。就像識別業務能力一樣,子域是通過分析業務及其組織結構并確定不同的專業領域來識別的。
模式3:扼殺者模式
問題
到目前為止所討論的設計模式都是針對綠地(Greenfield)分解應用程序,但是80%的工作都是針對棕地(Brownfield)應用程序,也就是大型的單片應用程序。將之前討論的所有設計模式直接應用于棕地應用程序將會面臨巨大的挑戰——在保持其運行的同時,嘗試將其分解成更小的組成部分是一項極為艱巨的任務。
解決方案
扼殺者模式可以發揮重要作用。扼殺者模式基于藤蔓纏繞并扼殺樹木的類比。這一解決方案適用于來回調用的Web應用程序,并且對于每個URI調用,可以將服務分解為不同的域并作為獨立的服務托管。這個想法是每次處理一個域。就在同一個URI空間中創建了兩個獨立的應用程序。新重構的應用程序會“扼殺”或取代原來的應用程序,直到最終可以關閉單片應用程序。
集成模式
模式4:API網關模式
問題
當應用程序被分解為更小的微服務時,有幾個問題需要解決:
1.如何調用抽象生產者信息的多個微服務。
2.在不同的渠道(例如臺式機電腦、筆記本電腦和平板電腦)上,應用程序需要不同的數據來響應相同的后端服務,因為用戶界面(UI)可能不同。
3.不同的消費者可能需要來自可重用微服務的不同格式的響應。誰將進行數據轉換或字段操作?
4.如何處理不同類型的協議——其中一些可能不被生產者微服務支持。
解決方案
API網關可以幫助解決微服務實現過程中出現的許多問題,但不限于上述問題:
1.API網關是任何微服務調用的單一入口點。
2.它可以作為代理服務,將請求路由到相關的微服務,抽象生產者的詳細信息。
3.它可以向多個服務發出請求,并將結果聚合并發送回消費者。
4.并沒有一個萬能的API能夠解決所有消費者的需求,這個解決方案可以為每種特定類型的客戶機創建細粒度的API。
5.它還可以將協議請求(例如AMQP)轉換為另一個協議(例如HTTP),反之亦然,以便生產者和消費者可以處理它。
6.它還可以減輕微服務的身份驗證/授權責任。
模式5:聚合器模式
問題
如上所述,在解決API網關模式中的聚合數據問題時通常面臨著挑戰。當將業務功能分解為幾個較小的邏輯代碼片段時,有必要考慮如何協作每個服務返回的數據。這個責任不能留給消費者,因為消費者可能需要理解生產者應用程序的內部實現。
解決方案
聚合器模式有助于解決這個問題。它討論了們如何聚合來自不同服務的數據,然后將最終響應發送給消費者。這可以通過兩種方式實現:
1.復合微服務將調用所有所需的微服務,聚合數據,并在發送回之前轉換數據。
2.API網關還可以將請求劃分為多個微服務,并在將其發送給消費者之前聚合數據。
如果要應用任何業務邏輯,建議選擇復合微服務。否則,API網關為已建立的解決方案。
模式6:客戶端用戶界面組合
問題
當通過分解業務功能/子域來開發服務時,負責用戶體驗的服務必須從幾個微服務中提取數據。在單片架構中,過去只有一個從用戶界面(UI)到后端服務的調用來檢索所有數據和刷新/提交用戶界面(UI)頁面。然而,現在情況不同了,所以需要了解如何做到這一點。
解決方案
對于微服務,用戶界面(UI)必須被設計成包含屏幕/頁面的多個部分/區域的框架。每個部分將調用一個單獨的后端微服務來提取數據。這被稱為組合特定于服務的用戶界面(UI)組件。像AngularJS和ReactJS這樣的框架可以很容易地做到這一點。這些屏幕被稱為單頁應用程序(SPA)。這使得應用程序可以刷新屏幕的特定區域,而不是整個頁面。
數據庫模式
模式7:每個服務使用的數據庫
問題
開發團隊經常面臨如何為微服務定義數據庫架構的挑戰。以下是必須解決的問題:
1.服務必須松散耦合。它們可以獨立開發、部署和擴展。
2.業務事務可以強制執行跨多個服務的不變量。
3.一些業務事務需要查詢由多個服務擁有的數據。
4.數據庫有時必須復制和分片才能擴展。
5.不同的服務有不同的數據存儲需求。
解決方案
為了解決上述問題,必須為每個微服務設計一個數據庫。它必須僅對該服務私有,并且只能由微服務API訪問。其他服務無法直接訪問它。
例如,對于關系數據庫,可以使用“每個服務私有表”、“每個服務模式”或“每個服務數據庫服務器”。每個微服務都應該有一個單獨的數據庫ID,這樣就可以提供單獨的訪問權限,從而建立一個屏障,防止它使用其他服務表。
圖2 每個服務的數據庫架構
模式8:每個服務共享數據庫
問題
之前討論過,每個服務使用一個數據庫是微服務架構的理想狀態,但這只有在應用是全新開發的,并且采用領域驅動設計(DDD)時才能實現。如果應用程序是一個單體,并且試圖分解成微服務,那么非規范化就不那么容易了。那么,在這種情況下,最合適的架構是什么?
解決方案
每個服務共享數據庫并不理想,但這是針對以上場景的有效解決方案。大多數人認為這是微服務的反模式,但對于“棕地”應用程序,這是可以將應用程序分解為更小的邏輯組件的一個很好的起點。
在這種模式下,一個數據庫可以與多個微服務對齊,但最多只能對齊2~3個;否則,擴展、自主和獨立將難以執行:
圖3共享數據庫架構
模式9:命令查詢職責分離(CQRS)
問題
一旦實現了每個服務的數據庫,就需要查詢,這需要來自多個服務的聯合數據——這是不可能的。那么,如何在微服務架構中實現查詢呢?
解決方案
命令查詢職責分離(CQRS)建議將應用程序分成兩部分——命令端和查詢端。命令端處理創建、更新和刪除請求。查詢端通過使用物化視圖來處理查詢組件。事件溯源設計模式通常與它一起用于為任何數據更改創建事件。因此,物化視圖通過訂閱事件流來保持最新狀態。
模式10:Saga模式
問題
當每個服務都有自己的數據庫,并且業務事務跨越多個服務時,如何確保跨服務的數據一致性?例如,對于客戶有信用額度的電子商務應用程序,應用程序必須確保新訂單不會超過客戶的信用額度。由于訂單和客戶在不同的數據庫中,應用程序不能簡單地使用本地ACID事務。
解決方案
Saga代表了一個由多個子請求組成的高級業務流程,每個子請求更新單個服務中的數據。每個請求都有一個補償請求,當請求失敗時執行。它可以通過兩種方式實現:
1.編排——當沒有中央協調機制時,每個服務都會產生并監聽其他服務的事件,并決定是否需要采取行動。
2.編排——編排者(對象)負責一個Saga的決策和排序業務邏輯。
可觀察性模式
接下來深入了解微服務模式的可觀察性。以下是一個示例微服務架構圖,供所有可觀察性主題參考。
圖4微服務可觀察性圖
模式11:日志聚合
問題
考慮一個用例,其中應用程序由在多臺機器上運行的多個服務實例組成。請求通常跨越多個服務實例。每個服務實例都會生成一個標準化格式的日志文件。那么,如何通過特定請求的日志來理解應用程序的行為?
解決方案
需要一個集中的日志服務來聚合來自每個服務實例的日志。用戶可以對日志進行查詢和分析。他們還可以配置警報,當日志中出現特定消息時觸發這些警報。例如,云平臺即服務(PCF)確實有Loggeregator,它從PCF平臺的每個組件(路由器、控制器、Diego等)以及應用程序收集日志。AWS Cloud Watch也提供了類似的功能。
模式12:性能指標
問題
當服務組合由于微服務架構而增加時,密切關注事務變得至關重要,以便在出現問題時可以監控模式并發送警報。那么,應該如何收集指標來監控應用程序性能?
解決方案
需要一個指標服務來收集有關單個操作的統計數據。該服務應該聚合應用程序服務的指標,以提供報告和警報功能。指標聚合有兩種模型:
- Push(推送)模型將指標推送到指標服務,例如New Relic、AppDynamics等。
- Pull(拉取)模型從指標服務中提取指標,例如Prometheus。
模式13:分布式跟蹤
問題
在微服務架構中,請求通常跨越多個服務。每個服務通過跨多個服務執行一個或多個操作來處理請求。那么,如何從端到端跟蹤請求以解決問題呢?
解決方案
需要這樣一項服務:
- 為每個外部請求分配一個唯一的外部請求ID。
- 將外部請求ID傳遞給所有服務。
- 在所有日志消息中包含外部請求ID。
- 記錄在集中服務中處理外部請求時執行的請求和操作的信息(例如,開始時間,結束時間等)。
Spring Cloud sluth和Zipkin server是一個常見的實現示例。
模式14:健康檢查
問題
當實現微服務架構時,有可能出現服務啟動但無法處理事務的情況。在這種情況下,如何確保請求不會轉到那些失敗的實例?可以通過負載平衡模式實現來解決這個問題。
解決方案
每個服務都需要有一個端點,可用于檢查應用程序的運行狀況,例如/health。這個API應該檢查主機的狀態、與其他服務/基礎設施的連接以及任何特定的邏輯。
Spring Boot Actuator實現了/health端點,并且可以自定義實現。
橫切關注點模式
模式15:外部化配置
問題
服務通常也會調用其他服務和數據庫。對于dev、QA、UAT和/或prod等每個環境,端點URL或其他配置屬性可能不同。任何這些屬性的更改都可能需要重新構建和重新部署服務。那么如何避免因配置更改而修改代碼?
解決方案
外部化配置(包括端點URL和憑據)將緩解問題。應用程序應該在啟動時或運行時加載它們。
Spring Cloud配置服務器提供了將屬性外部化到GitHub并將其作為環境屬性加載的選項。這些可以在啟動時由應用程序訪問,也可以在不重新啟動服務器的情況下刷新。
模式16:服務發現
問題
當微服務出現時,需要在調用服務方面解決一些問題:
1.通過容器技術,IP地址可以動態地分配給服務實例。在每次地址更改時,消費者服務都可能中斷并需要人工更改。
2.消費者必須記住每個服務URL,并使其緊密耦合。
那么消費者或路由器如何知道所有可用的服務實例和位置呢?
解決方案
需要創建一個服務注冊表,它將記錄每個生產者服務的元數據。服務實例應在啟動時向注冊表注冊,并在關閉時注銷。因此,消費者或路由器應該查詢注冊表并找出服務的位置。
注冊表還需要對生產者服務進行健康檢查,以確保只有服務的工作實例可用并能夠通過它使用。有兩種類型的服務發現:客戶端和服務器端。客戶端發現的一個示例是Netflix Eureka,服務器端發現的一個示例是AWS ALB。
模式17:斷路器(Circuit Breakers)
問題
服務通常會調用其他服務來檢索數據,下游服務可能會宕機。這樣做有兩個問題:首先,請求將繼續向宕機的服務發送,耗盡網絡資源并降低性能。其次,用戶體驗將會很差且不可預測。那么,如何避免級聯服務故障并從容地處理故障?
解決方案
消費者應該通過代理調用遠程服務,該代理的行為方式類似于斷路器。當連續故障的數量超過閾值時,斷路器跳閘,并且在超時期間,所有調用遠程服務的嘗試都將立即失敗。在超時之后,斷路器允許有限數量的測試請求通過。如果這些請求成功,則斷路器恢復正常操作。否則,如果再次失敗,超時期將重新開始。
Netflix Hystrix是斷路器模式的良好實現。它還有助于定義一個回退機制,可以在斷路器跳閘時使用。這提供了更好的用戶體驗。
模式18:藍-綠部署
問題
使用微服務架構,一個應用程序可以有許多微服務。如果停止所有服務,然后部署增強版本,那么停機時間可能會很長,并影響業務。此外,任何回滾都將是一場噩夢。那么,如何避免或減少部署期間服務的停機時間呢?
解決方案
可以實現藍綠部署策略來減少或消除停機時間。它通過運行兩個相同的生產環境(藍色和綠色)來實現這一點。假設綠色是現有的活動實例,而藍色是應用程序的新版本。在任何時候,只有一個環境是活動的,活動環境為所有生產流量服務。所有云平臺都提供了實現藍綠部署的選項。
結論
還有其他幾個關鍵的微服務架構模式,例如sidecar模式、鏈式微服務、分支微服務、事件溯源設計模式、大使模式等等。隨著微服務架構的不斷演進和發展,這個清單將會持續擴展。
原文標題:Microservices Design Patterns: Essential Architecture and Design Guide,作者:Rajesh Bhojwani