概述
將單體應用改造為微服務實際上是應用現代化的過程,這是開發者們在過去十年來一直在做的事情,所以已經有一些可以復用的經驗。
全部重寫是絕對不能用的策略,除非你要集中精力從頭構建一個基于微服務的應用。雖然聽起來很有吸引力,但是風險很大,很有可能會失敗。就像MartinFowler所說的:?『The only thing a Big Bang rewrite guarantees is a Big Bang!』
種應用現代化的策略為Strangler Application。這個名字來源于在熱帶雨林發現的一種植物Strangler vine,?為了夠到充足的陽光,?它們繞樹生長,一直向上。當樹木死后,只會留下一個樹形的藤蔓。應用的現代化就是類似的模式,我們會在舊有的應用上,構建一個新的包含微服務的應用,慢慢取代舊的應用。下面一起來看下這些策略:
策略1:止損
Law of Holes告訴我們,如果你正在一個洞里,就不要繼續再挖了。當你的單體應用已經變得無法管理的時候,就不要再繼續擴大它的規模了。?比如你想添加新功能,不要在單體應用中添加代碼,而要將新的代碼放在另一個單獨的微服務中。?下圖展示了使用這種方法后的系統架構:?
除了新服務和舊的單體應用,還有兩個組件。一個是?請求路由?(request router),用來處理過來的(比如HTTP)請求,類似于API網關。這個路由發送與新功能相應的請求到新的服務上,將舊服務相關的請求路由到單體應用上。
另一個組件是?膠水代碼?(glue code),用來將服務與單體應用集成起來。一個服務很少是隔離存在的,需要訪問單體應用的數據。膠水代碼就負責這些數據集成。微服務組件可以通過它來讀寫單體應用中的數據。
一個服務可以通過三種方式訪問單體應用中的數據:
-
通過調用單體應用提供的遠程API
-
直接訪問單體的數據庫
-
保存一份數據的副本,和單體數據庫保持同步
膠水代碼有時被稱為?防腐層(anti-corruption layer)?,可以防止擁有自己原始領域模型的服務,被來自單體領域模型的概念所影響。
膠水代碼可以在兩個不同的模型間充當翻譯官,防腐層這個詞最初出現在Eric Evans寫的《Domain DrivenDesign》一書中。開發一個防腐層不是一個小工程,但如果你想從單體地獄中走出來,這是很重要的。
用輕量級的服務實現一個新功能,有很多好處。首先,可以防止單體應用變得更難以管理;其次,這個應用可以被獨立地開發,部署和擴展。
然而,這個方法并不能解決在舊有的單體部分遇到的問題,你還需要破壞原有的單體部分。
策略2:前后端分離
縮減單體應用的一個策略是將表現層從業務邏輯和數據訪問層中分離出來,一個典型的企業應用至少包括三種組件:
-
表現層:?這層組件用來處理HTTP請求,實現(REST)API或者基于HTML的Web UI。在一個有著復雜的用戶接口的應用中,表現層通常有大量的代碼;
-
業務邏輯層:?應用的核心代碼,用來實現業務規則;
-
數據訪問層:?訪問數據庫或信息中介的組件。
在表現邏輯與業務和數據訪問邏輯之間通常有著明顯的區分。業務層有一個粗粒度的API,包含一個或多個外立面組成,外立面封裝了業務邏輯組件。這個API是自然的『縫合』,所以可以將單體分割為更小的應用,一個應用包含了表現層,另一個應用包含了業務和數據訪問層。分隔后,表現邏輯層應用可以遠程調用業務邏輯層應用,下圖展示了改造前后的架構:
這樣分隔單體應用有兩個主要好處。?首先你可以獨立地開發,部署和擴展兩個應用,?比如對于表現層開發者來說,他們可以實現用戶界面的快速迭代,A/B測試也很容易實現;?其次,這樣做會向外開放一個微服務也可以調用的遠程API。
但是這個策略只是部分解決方案,很有可能會變成兩個混亂的單體應用。需要用下面第三個策略去減少單體部分的比重。
策略3:提取服務
第三個策略的目的是將單體中的模塊,轉變為單獨的微服務。每次提取一個模塊,就改造為微服務,單體部分就縮減了。一旦你轉化了足夠的模塊,最后不管單體部分是完全消失了,還是變小成了另一個微服務,都不是問題了。
優先改造哪個模塊?
一個大型的復雜的單體應用,通常包含數十甚至上百個模塊,都可以被提取出來,選擇先提取哪個是個問題。可以從容易被提取的開始,積累微服務的經驗,然后提取那些能給你帶來最大好處的模塊。
通常提取那些頻繁變化的模塊很有用?,一旦你將這個模塊提取出來,就可以獨立開發和部署它了,可以加速開發。
另外一個就是提取那些資源需求和其它部分有很大不同的模塊。?比如將一個有內存數據庫的模塊轉變為服務,就可以把它部署在內存很大的主機上;同樣的,提取那些實現復雜算法的模塊,就可以把它部署在CPU多的主機上。總之這樣做有助于你擴展應用。
當決定了提取哪個模塊后,需要看下現有的粗粒度邊界,可以幫助你將模塊轉化為服務。比如一個只會通過異步信息和其它應用交互的模塊,就很容易能被改造為微服務。
如何提取一個模塊?
第一步是在模塊和單體之間定義一個粗粒度的接口。?由于單體和微服務的數據互相都有需求,所以它?很像一個雙向的API。但是在這個模塊和應用的剩余部分之間,有著混亂的依賴關系和細粒度的交互模式,所以實現這個API還是很有挑戰的。通過域模型實現的業務邏輯,改造起來尤其困難,因為各個域模型間的關系復雜。通常需要進行大量的代碼修改,去打破這些依賴。
一旦你實現了細粒度的接口,就可以將模塊改造為一個獨立的服務。要寫代碼實現單體和微服務間的通信,通過使用了IPC機制的API。下圖展現了一個架構在改造前,改造中和改造后的樣子:
?
在這個例子中,模塊Z是待提取的模塊。模塊X使用了Z的組件,Z又使用了模塊Y。
-
改造的第一步是定義一對粗粒度的API,第一個接口是模塊X調用模塊Z的入站接口,第二個是模塊Z調用模塊Y的出站接口;
-
第二步是將這個模塊改造為獨立的服務。入站接口和出站接口都通過IPC機制的代碼實現。可能你需要通過結合模塊Z到微服務底盤框架(用來處理橫切關注點,比如服務發現)上,來構建這個服務。
一旦你提取了這個模塊,就可以獨立地開發,部署和擴展它了。你甚至可以從頭重寫這個服務,將這個服務和單體結合起來的API代碼就成了防腐層,相當于兩個域模型之間的翻譯官。每次提取一個服務,都是向著微服務又進了一步,單體的比重會逐步縮減。
總結
將單體架構改造為微服務的過程是一種應用現代化的形式,不應該從頭重寫來實現。而是應該循序漸進地改造你的應用成為一系列微服務。有三種策略可以應用:?用微服務實現新的功能;將表現層組件從業務和數據訪問組件中分離出來;改造單體中的模塊為微服務。?隨著時間的推移,你的微服務比重會增大,你的開發團隊的靈活性和速度也會提高