從單體式架構遷移到微服務架構:三個策略敘述
遷移到微服務綜述
遷移單體式應用到微服務架構意味著一系列現代化過程,有點像這幾代開發者一直在做的事情,實時上,當遷移時,我們可以重用一些想法。
一個策略是:不要大規模(big bang)重寫代碼(只有當你承擔重建一套全新基于微服務的應用時候可以采用重寫這種方法)。重寫代碼聽起來很不錯,但實際上充滿了風險最終可能會失敗,就如 Martin Fowler 所說:“the only thing a Big Bang rewrite guarantees is a Big Bang!”
相反,應該采取逐步遷移單體式應用的策略,通過逐步生成微服務新應用,與舊的單體式應用集成,隨著時間推移,單體式應用在整個架構中比例逐漸下降直到消失或者成為微服務架構一部分。這個策略有點像在高速路上限速到 70 邁對車做維護,盡管有挑戰,但是比起重寫的風險小很多。
Martin Fowler 將這種現代化策略成為絞殺(Strangler)應用,名字來源于雨林中的絞殺藤(strangler vine),也叫絞殺榕 (strangler fig)。絞殺藤為了爬到森林頂端都要纏繞著大叔生長,一段時間后,樹死了,留下樹形藤。這種應用也使用同一種模式,圍繞著傳統應用開發了新型微服務應用,傳統應用會漸漸退出舞臺。
我們來看看其他可行策略。
策略 1——停止挖掘
Law of Holes 是說當自己進洞就應該停止挖掘。對于單體式應用不可管理時這是最佳建議。換句話說,應該停止讓單體式應用繼續變大,也就是說當開發新功能時不應該為舊單體應用添加新代碼,最佳方法應該是將新功能開發成獨立微服務。如下圖所示:
除了新服務和傳統應用,還有兩個模塊,其一是請求路由器,負責處理入口(http)請求,有點像之前提到的 API 網關。路由器將新功能請求發送給新開發的服務,而將傳統請求還發給單體式應用。
另外一個是膠水代碼(glue code),將微服務和單體應用集成起來,微服務很少能獨立存在,經常會訪問單體應用的數據。膠水代碼,可能在單體應用或者為服務或者二者兼而有之,負責數據整合。微服務通過膠水代碼從單體應用中讀寫數據。
微服務有三種方式訪問單體應用數據:
換氣單體應用提供的遠程 API
直接訪問單體應用數據庫
自己維護一份從單體應用中同步的數據
膠水代碼也被稱為容災層(anti-corruption layer),這是因為膠水代碼保護微服務全新域模型免受傳統單體應用域模型污染。膠水代碼在這兩種模型間提供翻譯功能。術語 anti-corruption layer 第一次出現在 Eric Evans 撰寫的必讀書 Domain Driven Design,隨后就被提煉為一篇白皮書。開發容災層可能有點不是很重要,但卻是避免單體式泥潭的必要部分。
將新功能以輕量級微服務方式實現由很多優點,例如可以阻止單體應用變的更加無法管理。微服務本身可以開發、部署和獨立擴展。采用微服務架構會給開發者帶來不同的切身感受。
然而,這方法并不解決任何單體式本身問題,為了解決單體式本身問題必須深入單體應用做出改變。我們來看看這么做的策略。
策略 2——將前端和后端分離
減小單體式應用復雜度的策略是講表現層和業務邏輯、數據訪問層分開。典型的企業應用至少有三個不同元素構成:
- 表現層——處理 HTTP 請求,要么響應一個 RESTAPI 請求,要么是提供一個基于 HTML 的圖形接口。對于一個復雜用戶接口應用,表現層經常是代碼重要的部分。
- 業務邏輯層——完成業務邏輯的應用核心。
- 數據訪問層——訪問基礎元素,例如數據庫和消息代理。
在表現層與業務數據訪問層之間有清晰的隔離。業務層有由若干方面組成的粗粒度(coarse-grained)的 API,內部包含了業務邏輯元素。API 是可以將單體業務分割成兩個更小應用的天然邊界,其中一個應用是表現層,另外一個是業務和數據訪問邏輯。分割后,表現邏輯應用遠程調用業務邏輯應用,下圖表示遷移前后架構不同:
單體應用這么分割有兩個好處,其一使得應用兩部分開發、部署和擴展各自獨立,特別地,允許表現層開發者在用戶界面上快速選擇,進行 A/B 測試;其二,使得一些遠程 API 可以被微服務調用。
然而,這種策略只是部分的解決方案。很可能應用的兩部分之一或者全部都是不可管理的,因此需要使用第三種策略來消除剩余的單體架構。
策略 3——抽出服務
第三種遷移策略就是從單體應用中抽取出某些模塊成為獨立微服務。每當抽取一個模塊變成微服務,單體應用就變簡單一些;一旦轉換足夠多的模塊,單體應用本身已經不成為問題了,要么消失了,要么簡單到成為一個服務。
排序那個模塊應該被轉成微服務
一個巨大的復雜單體應用由成十上百個模塊構成,每個都是被抽取對象。決定第一個被抽取模塊一般都是挑戰,一般最好是從最容易抽取的模塊開始,這會讓開發者積累足夠經驗,這些經驗可以為后續模塊化工作帶來巨大好處。
轉換模塊成為微服務一般很耗費時間,一般可以根據獲益程度來排序,一般從經常變化模塊開始會獲益最大。一旦轉換一個模塊為微服務,就可以將其開發部署成獨立模塊,從而加速開發進程。
將資源消耗大戶先抽取出來也是排序標準之一。例如,將內存數據庫抽取出來成為一個微服務會非常有用,可以將其部署在大內存主機上。同樣的,將對計算資源很敏感的算法應用抽取出來也是非常有益的,這種服務可以被部署在有很多 CPU 的主機上。通過將資源消耗模塊轉換成微服務,可以使得應用易于擴展。
查找現有粗粒度邊界來決定哪個模塊應該被抽取,也是很有益的,這使得移植工作更容易和簡單。例如,只與其他應用異步同步消息的模塊就是一個明顯邊界,可以很簡單容易地將其轉換為微服務。
如何抽取模塊
抽取模塊第一步就是定義好模塊和單體應用之間粗粒度接口,由于單體應用需要微服務的數據,反之亦然,因此更像是一個雙向 API。因為必須在負責依賴關系和細粒度接口模式之間做好平衡,因此開發這種 API 很有挑戰性,尤其對使用域模型模式的業務邏輯層來說更具有挑戰,因此經常需要改變代碼來解決依賴性問題,如圖所示:
一旦完成粗粒度接口,也就將此模塊轉換成獨立微服務。為了實現,必須寫代碼使得單體應用和微服務之間通過使用進程間通信(IPC)機制的 API 來交換信息。如圖所示遷移前后對比:
此例中,正在使用 Y 模塊的 Z 模塊是備選抽取模塊,其元素正在被 X 模塊使用,遷移第一步就是定義一套粗粒度 APIs,第一個接口應該是被 X 模塊使用的內部接口,用于激活 Z 模塊;第二個接口是被 Z 模塊使用的外部接口,用于激活 Y 模塊。
遷移第二步就是將模塊轉換成獨立服務。內部和外部接口都使用基于 IPC 機制的代碼,一般都會將 Z 模塊整合成一個微服務基礎框架,來出來割接過程中的問題,例如服務發現。
抽取完模塊,也就可以開發、部署和擴展另外一個服務,此服務獨立于單體應用和其它服務。可以從頭寫代碼實現服務;這種情況下,將服務和單體應用整合的 API 代碼成為容災層,在兩種域模型之間進行翻譯工作。每抽取一個服務,就朝著微服務方向前進一步。隨著時間推移,單體應用將會越來越簡單,用戶就可以增加更多獨立的微服務。 將現有應用遷移成微服務架構的現代化應用,不應該通過從頭重寫代碼方式實現,相反,應該通過逐步遷移的方式。有三種策略可以考慮:將新功能以微服務方式實現;將表現層與業務數據訪問層分離;將現存模塊抽取變成微服務。隨著時間推移,微服務數量會增加,開發團隊的彈性和效率將會大大增加。