模式: 微服務架構
背景
在開發服務端企業應用時,應用需要支持各種不同類型的客戶端,比如桌面瀏覽器、移動瀏覽器以及原生移動應用。應用還需要向第三方提供可訪問的API,並通過Web Service或者消息代理與其它應用實現集成。應用通過執行業務邏輯、訪問數據庫、與其它系統交換信息、並返回一條HTML/JSON/XML響應,來處理請求(HTTP請求與消息)。
應用采用多層架構或者六角架構,主要由以下幾類不同組件構成:
- 展現組件——負責處理HTTP請求並響應HTML或者JSON/XML(對於web Services APIs)
- 業務邏輯——應用的業務邏輯
- 數據庫訪問邏輯——用於訪問數據庫的數據訪問對象
- 應用集成邏輯——消息層,例如基於Spring Integration
不同邏輯組件分別響應應用中的不同功能模塊。
問題
應用的部署架構是什么?
需求
- 應用需要由一個開發者團隊專門負責
- 團隊新成員需要快速上手
- 應用應該易於理解和修改
- 對應用能夠進行持續部署
- 需要在多台設備上運行應用副本,從而滿足可擴展性與可用性的要求
- 使用各種新技術(框架、編程語言等)
方案
用 Scale Cube 方法(特別是Y軸擴展)設計應用架構,將應用程序按功能拆分為一組互相協作的服務。每個服務實現一組特定、相關的功能。舉例來說,一個應用程序可能由訂單管理服務、客戶管理服務等多個服務構成。
服務間的通信則可由HTTP/REST等同步協議或者AMQP等異步協議實現。服務可以彼此獨立開發與部署。每個服務皆有自己的數據庫,從而保證其與其它服務解耦。在必要時,可利用數據庫復制機制或者應用層事件驅動機制,維護數據庫之間的數據一致性。
示例
假設需要構建一款電子商務應用程序,使其能夠接收來自客戶的訂單、驗證庫存信息與可用信用額度,而后進行發貨。該應用程序會包含多個組件,其中StoreFrontUI負責實現用戶界面,而其它后端服務則分別負責檢查信用額度、維護庫存信息以及發送訂單。
此應用程序被部署為一組服務集合。
結果
優勢
此類解決方案擁有以下優勢:
- 每項微服務相對較小
- 易於開發者理解
- IDE處理速度更快,可提高開發者生產效率
- Web容器啟動速度更快,提高開發者生產效率並可加快部署速度
- 每項服務皆可獨立於其它服務進行部署——簡化頻繁部署新服務版本的流程
- 易於實現規模化開發。多團隊可以共同進行開發工作。每個(雙披薩,即團隊成員規模控制在訂購兩塊披薩即可吃飽的程度)團隊負責其中一項服務。各團隊可獨立於其他團隊,進行開發、部署工作及擴展自身服務。
- 改善故障隔離。舉例來說,如果某一服務出現內存外溢,則只有該服務本身受到影響。其它服務將繼續正常處理請求。相比之下,單體架構中的故障組件會令整套系統陷入癱瘓。
- 每項服務可獨立進行開發與部署
- 無需長期使用同一套技術堆棧
弊端:
但這類解決方案中也存在着以下弊端:
- 開發者必須應對創建分布式系統所產生的額外的復雜因素。
- 現有開發者工具/IDE主要面向單體應用程序,因此無法顯式支持分布式應用的開發。
- 測試工作更加困難。
- 開發者必須采取服務間通信機制。
- 很難在不使用分布式事務機制的情況下跨服務實現功能。
- 跨服務實現功能要求各團隊進行密切協作。
- 部署復雜。在生產環境下,對這類多種服務類型構建而成的系統進行部署與管理十分困難。
- 內存占用量更高。微服務架構使用N*M個服務實例替代N個單體應用實例,如果每項服務運行自己的JVM(或者其它類似機制),且各實例之間需要進行隔離,那將導致M倍JVM運行時的額外開銷。另外,如果每項服務都在自己的虛擬機(例如 EC2 實例)上運行,如同Netflix一樣,那么額外開銷會更高。
需要解決的問題
采用微服務架構之前,有若干需要解決的問題。
何時應該使用微服務架構?
應用此類方案帶來的挑戰在於如何把握好時機。在開發應用程序的最初版本時,大家往往不會面臨需要使用微服務架構才能解決的問題。另外,使用復雜的分布式架構會拖慢開發流程。對於初創企業,其面臨的最大挑戰往往在於如何快速發展商業模式及附屬應用。微服務架構中的Y軸拆分方式可能使應用更加難以迅速迭代。但是,如果當面臨需要解決擴展性問題的時候再去進行功能拆分,單體應用的復雜依賴性使其很難被分解為服務集合。
如何將應用拆分為服務?
另一項挑戰在於如何將系統拆分為多個微服務。這雖然很棘手但還是有些可行之策:
- 根據業務能力拆分(Decompose by business capability) - 根據業務能力界定服務的范圍
- 根據領域的子域拆分(Decompose by subdomain) - 根據領域驅動設計中子域的概念界定服務的范圍
- 根據“動詞”或者用例進行服務划分。舉例來說,我們經常會在電子商務應用中發現有單獨的“發貨”服務用於處理已完成訂單。另一種常見的“動詞”划分方式是實現登錄用例的“登錄”服務。
- 根據“名詞”或者資源進行系統划分。這類服務負責利用特定的實體/資源完成一系列操作。舉例來說,大家可能會在電子商務系統當中發現有“庫存”服務用於跟蹤貨物的庫存。
在理想情況下,每項服務都應只面向一小部分職責。(大叔)Bob Martin 曾提出根據單一責任原則(Single Responsibility Principle,簡稱 SRP)進行類的設計。SRP 會用單一變更理由去定義一個類的職責:一個類的狀態變更只能由一個原因導致。同理,我們也可以在微服務設計當中引入 SRP。
另一項可用於指導服務設計的是Unix工具的設計思路。Unix 提供大量工具選項,包括 grep、cat 以及 find 等等。每種工具都只負責實現一項功能,而且功能良好,它們可以通過Shell腳本與其它工具結合進而執行復雜的任務。
如何維護數據一致性?
為了確保松耦合,每個服務都有獨用的數據庫。維護服務間的數據一致性成為了挑戰。在多數應用的架構下,2 階段提交和分布式事務不再是一個可選項。應用需要采用事件驅動架構,一個服務在其數據發生變化時,對外發布一個事件,其他服務訂閱並通過消費這個事件來對應更新自己的數據。有一些可靠的方式可以實現事件的發布和數據的更新,比如事件溯源 和事物日志跟蹤。
如何實現數據查詢?
另一個挑戰是進行跨服務的數據的查詢。一個常用的解決方式是采用CQRS,維護一份包含重要數據的視圖並通過事件流的方式保持數據的更新。
相關模式
微服務架構有很多與之相關的模式,單體架構 便是微服務架構的另一眾選擇。在應用微服務架構時,您還會跟如下這些模式打交道:
- 服務拆分模式
- 根據業務能力拆分
- 根據領域的子域拆分
- 每服務數據庫模式 描述了服務之間采用獨享數據庫的方式實現了解耦合。
- API 網關模式 定義在微服務架構下客戶端訪問服務的方式。
- 客戶端服務發現 和 服務器端服務發現 模式用來在微服務架構中把客戶端請求路由到一個可用的服務實例上。
- 消息和遠程過程調用是服務間通信的兩種選擇。
- 單主機上部署服務的單個實例 和 單主機上部署服務的多個實例 模式是兩種不同的部署策略。
- 解決邊界問題的模式:: 微服務的基底模式 和 配置信息外部化
- 可測試性模式: 服務組件測試 和 服務集成協議測試
- 斷路器
- 訪問令牌
- 可觀測性模式:
- 應用日志
- 應用指標
- 審計日志
- 分布式追蹤
- 異常追蹤
- 健康檢查
- UI 模式:
- 服務器端頁面碎片化元素構建
- 客戶端 UI 構建
已知案例
眾多大型網站將單體架構發展為微服務架構,其中包括 Netflix、Amazon 與 eBay 等。
作為一個熱門視頻流服務,Netflix 利用一套大規模的面向服務的架構來承載高於 30% 的互聯網流量。該公司每天需要處理來自 800 多種設備的 10 億多次視頻流 API 請求。平均每次 API 調用會在后端服務中產生 6 次后續調用。
Amazon.com 最初采用一套雙層架構。為了擴展業務規模,其決定遷移至一套由數百項后端服務構成的面向服務的架構。多個應用調用這些服務,其中包括 Amazon.com網站和Web服務API。Amazon.com 網站需要調用 100 到 150 個服務方可獲取到構建一個 Web 頁面所需的全部數據。
作為拍賣網站,eBay.com 也是從單體架構逐步轉向面向服務的架構。其應用層由多個獨立應用構成。每個應用負責實現完成一組特定功能的業務邏輯,例如購買或者出售。每個應用皆利用X軸進行拆分,部分應用(例如搜索)以Z軸進行拆分。eBay.com 還在數據庫層采用了 X、Y 與 Z 軸相結合的擴展方式。