場景描述
假設你正在開發一個大型服務端企業應用,有如下需求:
- 必須支持多種客戶端,包括:WEB 端瀏覽器、WAP 端瀏覽器以及原生移動 APP。
- 對外暴露公共 API 用於調用
- 處理 HTTP 請求,或者消息,執行對應的業務邏輯。
- 訪問數據庫,緩存或者持久化響應的數據
- 與其他系統進行通信,交換所需的信息
- 返回 HTTP 響應,指定好特定的序列化方式,例如 JSON、 XML 等等
- 根據業務邏輯與功能,設計並划分出不同邏輯模塊
這樣的一個應用,你會如何設計架構並部署呢?
考慮因素
- 這是一個團隊開發的項目,有一個獨立團隊負責
- 團隊成員會發生變化,新加入的成員必須快速上手項目
- 應用程序必須易於理解並修改
- 期望能實現應用的持續集成與部署
- 必須可以多實例部署應用程序,以滿足可伸縮性和可用性要求。
- 想用比較新的技術(框架、編程語言等)
解決方案
定義一個將應用程序構造為一組松散耦合的微服務協作架構,每個微服務滿足:
- 高度可維護和可測試:支持快速和頻繁的開發和部署。
- 與其他微服務松耦合:使團隊能夠在大部分時間獨立地工作在他們自己的微服務上,而不受其他微服務更改導致的影響,同時也不會影響其他微服務。
- 獨立部署:使團隊能夠部署他們的服務,而不必與其他團隊進行協調。
- 減少溝通成本:可以拆分成小團隊專注於各自的微服務,減少大團隊內部溝通成本。
服務使用同步協議(如 HTTP/REST )或異步協議(如 AMQP )進行通信。服務可以彼此獨立開發和部署。每項服務都有其自有數據庫以便與其他服務分離。服務之間的數據一致性使用 SAGA 模式
舉例
假設現在正在設計一個電商應用,功能包括接收來自客戶的訂單(StoreFrontUI),驗證並維護庫存余額(Inventory Service),驗證並維護用戶可用余額(Accounting Service),下單成功並發貨(Shipping Service)。這個應用被設計成一個微服務架構應用,如圖中所示:
分析
好處
- 支持大型復雜應用程序的持續交付和部署
- 可維護性增高:每個服務相對較小,因此更容易理解和更改。
- 更容易被測試:服務更小,測試速度更快。
- 更好的可部署性:服務之間可以獨立部署。
- 工作分工與模塊業務邊界更加明確,可以將某個微服務交付與一個或者多個團隊維護。每個團隊都可以獨立於所有其他團隊開發、測試、部署和擴展他們的服務。
- 每個微服務相對更小:
- 開發人員更容易理解
- IDE 負載更低,更快,提高開發效率
- 應用程序啟動得更快,提高了 Debug 效率,也提高了部署速度。
- 故障隔離。例如,如果一個微服務中存在內存泄漏,那么只有該微服務會受到影響。其他服務可以繼續處理請求。
- 更容易更新技術棧。當開發新的微服務模塊可以采用新的技術棧進行試驗開發並使用,穩定后,可以逐步推廣到其他微服務。
壞處
- 開發人員必須面對分布式系統帶來的額外復雜性:
- 開發人員必須熟悉 RPC 通信,並且寫好故障處理邏輯。
- 實現跨多個服務的請求更加困難。
- 測試服務之間的交互更加困難。單元測試不能覆蓋全部場景,集成測試部署起來更加麻煩。
- 實現跨多個服務的請求需要團隊之間的仔細聯調。
- IDE 大多面向構建單塊應用程序,不提供對開發分布式應用程序的明確支持。
- 部署復雜性。在生產中,部署和管理由許多不同服務組成的系統也具有操作復雜性。目前的容器化以及容器編排方案就是為了解決這一問題。
- 增加內存等資源消耗。假設每個微服務都獨占一個 JVM,那么相比與單體應用,JVM 本身占用的資源(例如 GC 占用的 CPU 和內存,還有元空間,代碼高速緩存等等)比原來是更多了的。如果是一個微服務占用一個容器,乃至一個虛擬機,一個機器,這個資源浪費會更多。
需要考慮的問題
什么時候使用微服務架構?
使用微服務架構的一個挑戰是決定到底什么時候使用它。在開發應用程序的第一個版本時,通常不會遇到需要這種方法解決的問題。此外,使用更為精細的分布式體系結構設計將減緩開發速度。對於初創企業來說,這可能是一個重大問題,它們面臨的最大挑戰往往是如何快速發展業務和快速迭代應用程序。但是,隨着產品不斷迭代,這個單體應用程序將會變得越來越大,團隊的規模也越來越大,需要使用功能分解為微服務架構時,復雜的依賴關系可能會讓應用程序很難分解成一組服務。
如何將應用程序分解為服務?
另一個挑戰是決定如何將系統划分為微服務。這在很大程度上是一門藝術,但有許多策略可以參考:
- 按業務分解並定義與業務功能相對應的微服務。
- 按領域驅動的設計的子域分解
- 按用戶行為與用例分解,並定義負責特定操作的微服務。例如 Shipping Service 負責運送完整的訂單。
- 定義一個負責對某個類型的實體/資源進行操作的微服務。例如 Account Service 負責管理用戶帳戶。
理想情況下,每個服務應該只有一小部分責任。Bob Martin 談到應該使用單一職責原則。就一個類而言,應該僅有一個引起它變化的原因。將單一責任原則應用於服務設計也是有意義的。
另一個有助於服務設計的類推是 Unix 實用程序的設計。Unix提供了大量實用程序,如 grep、cat 和 find。每個實用程序只做一件事情,並且復雜的任務是通過使用shell腳本與其他實用程序組合來實現的。
如何保持數據的一致性?
為了確保松散耦合,每個服務都有自己的數據庫。保持服務之間的數據一致性是一個挑戰,因為對於許多應用程序來說,兩階段提交(2PC)或者分布式事務並不是一種很好的選擇。應用程序必須使用 SAGA 模式,服務在其數據更改時發布事件,其他服務使用該事件並更新其數據。有幾種可靠更新數據和發布事件的方法,包括事件溯源(Event Sourcing)和事務日志跟蹤(Transaction Log Tailing).
如何實現查詢?
另一個挑戰是實現需要檢索多個服務擁有的數據的查詢。
相關的設計模式
- 微服務拆解模式
- 每個微服務數據庫獨立設計模式:每個服務如何擁有自己的數據庫,以確保松散耦合。
- 統一 API 網關模式:定義客戶端如何訪問微服務體系結構中的服務。
- 客戶端服務發現和服務器端服務發現模式用於將客戶端的請求路由到可用的服務實例。
- 每個主機的單一服務和每個主機多個服務模式,是關於部署策略的設計模式
- 橫切關注點設計模式(cross-cutting concerns):例如面向切面的設計,兩個非常不一樣的組件存在一些類似的功能,這時候我們需要切面設計來統一這些類似的功能。
- 斷路器
- 存取令牌
- 可觀測模式
- UI 相關模式
- 測試相關設計模式:服務組件測試和服務集成契約測試(Contract Testing)