原文鏈接:Refactoring a Monolith into Microservices
- 微服務(wù)介紹
- 構(gòu)建微服務(wù)之使用API網(wǎng)關(guān)
- 構(gòu)建微服務(wù)之:微服務(wù)架構(gòu)中的進(jìn)程間通信
- 微服務(wù)中的服務(wù)發(fā)現(xiàn)
- 微服務(wù)之事件驅(qū)動(dòng)的數(shù)據(jù)管理
- 選擇一種微服務(wù)部署策略
- 重構(gòu)單體應(yīng)用到微服務(wù)(本文)
這是使用微服務(wù)架構(gòu)構(gòu)建應(yīng)用系列的第七篇也是最后一篇文章,第一篇文章介紹了微服務(wù)架構(gòu)模式匾效,并討論了使用微服務(wù)架構(gòu)的優(yōu)勢(shì)和劣勢(shì)舷蟀,接下來(lái)的文章討論微服務(wù)架構(gòu)的不同方面:使用API網(wǎng)關(guān)、進(jìn)程間通信面哼、服務(wù)發(fā)現(xiàn)野宜、事件驅(qū)動(dòng)的數(shù)據(jù)管理以及部署微服務(wù),本篇文章魔策,讓我們看下如何把一個(gè)單體應(yīng)用重構(gòu)為微服務(wù)架構(gòu)的應(yīng)用匈子。
我希望這個(gè)系列的文章使你對(duì)微服務(wù)架構(gòu)有一些好的理解,比如它的優(yōu)勢(shì)和劣勢(shì)闯袒,何時(shí)使用微服務(wù)等 虎敦,或許微服務(wù)架構(gòu)對(duì)您的組織將非常合適。
然而政敢,你現(xiàn)在更可能正在為一個(gè)龐大的其徙、復(fù)雜的單體應(yīng)用而工作,你每天正經(jīng)歷著開發(fā)和部署單體應(yīng)用的緩慢和痛苦 喷户,微服務(wù)看起來(lái)更像一個(gè)遙遠(yuǎn)的極樂世界唾那。幸運(yùn)的是,我們有幾個(gè)策略可以使你逃離單體的地獄摩骨。本篇文章我將描述如何漸進(jìn)的把單體應(yīng)用重構(gòu)為一系列的微服務(wù)通贞。
重構(gòu)為微服務(wù)架構(gòu)的概覽
把一個(gè)單體應(yīng)用轉(zhuǎn)化為微服務(wù)實(shí)際是應(yīng)用現(xiàn)代化的一種形式朗若,這個(gè)事情開發(fā)者已經(jīng)做了十多年了,因此昌罩,有一些經(jīng)驗(yàn)在我們重構(gòu)應(yīng)用為微服務(wù)時(shí)候可以重用哭懈。
策略之一是不要使用“Big Bang”式的重寫,也就是不要集中所有的力量從頭構(gòu)建一個(gè)新的基于微服務(wù)的應(yīng)用茎用,盡管那個(gè)方式聽起來(lái)很誘人遣总,實(shí)際會(huì)有極大的風(fēng)險(xiǎn),最終也會(huì)以失敗告終轨功。正如 Martin Fowler 所說(shuō):“the only thing a Big Bang rewrite guarantees is a Big Bang!”旭斥。
避免使用Big Bang重寫,我們應(yīng)該漸進(jìn)式的重構(gòu)我們的單體應(yīng)用古涧,我們逐步的構(gòu)建由微服務(wù)組成的新應(yīng)用垂券,并與我們的單體應(yīng)用一起運(yùn)行。隨著時(shí)間的推移羡滑,單體應(yīng)用實(shí)現(xiàn)的功能將會(huì)縮水菇爪,直到完全消失或者變成另外一個(gè)微服務(wù)。這種策略可以類比于在高速路上只把車開到70邁-有挑戰(zhàn)性但是比Big Bang重寫危險(xiǎn)小柒昏。
Martin Fowler 提到了這種應(yīng)用現(xiàn)代化的策略凳宙,稱其為Strangler Application,名稱來(lái)源于熱帶雨林中的扼殺藤蔓职祷,扼殺藤蔓生長(zhǎng)在大樹的周圍企圖得到樹冠處的陽(yáng)光氏涩,最后樹木會(huì)死掉,只留下一堆樹狀的藤蔓有梆。應(yīng)用現(xiàn)代化 遵循這樣的模式是尖,我們將會(huì)圍繞的遺留應(yīng)用構(gòu)建由一系列微服務(wù)組成的新的應(yīng)用,最終遺留應(yīng)用將會(huì)消失泥耀。
讓我們來(lái)看實(shí)現(xiàn)該目標(biāo)可采用的不同策略:
策略一:停止挖掘
Law of Holes 告訴我們一旦你落入洞穴析砸,你應(yīng)該停止繼續(xù)挖洞!一旦你的單體應(yīng)用變的難以管理爆袍,這是一個(gè)需要聽取的極好的建議。換句話講作郭,你應(yīng)該停止讓單體應(yīng)用繼續(xù)變的更加龐大陨囊,這意味著,當(dāng)你需要實(shí)現(xiàn)新功能的時(shí)候夹攒,你不應(yīng)該往單體應(yīng)用中添加新的代碼蜘醋。相反的,這個(gè)策略的重要一點(diǎn)是咏尝,把新代碼放到一個(gè)獨(dú)立的微服務(wù)中去压语。下圖展示了應(yīng)用該方法后的系統(tǒng)架構(gòu):
除了新服務(wù)和遺留的單體應(yīng)用啸罢,還多出來(lái)其他的兩個(gè)組件:第一個(gè)組件是請(qǐng)求路由器,用來(lái)處理HTTP請(qǐng)求胎食,這和我們前面所說(shuō)的API網(wǎng)關(guān)類似扰才,路由器發(fā)送請(qǐng)求到對(duì)應(yīng)新功能的新服務(wù)上去,路由遺留應(yīng)用的請(qǐng)求到單體應(yīng)用中去厕怜。
另一個(gè)組件就是膠水代碼衩匣,用來(lái)集成服務(wù)和單體應(yīng)用。一個(gè)服務(wù)很少獨(dú)立存在粥航,一般都需要訪問(wèn)單體應(yīng)用擁有的數(shù)據(jù)琅捏。膠水代碼存在于服務(wù)端,或者單體端递雀,或者兩端均有柄延,用來(lái)負(fù)責(zé)數(shù)據(jù)的集成,服務(wù)使用膠水代碼對(duì)單體擁有的數(shù)據(jù)進(jìn)行讀寫操作缀程。
服務(wù)有三種策略可以用來(lái)訪問(wèn)單體的數(shù)據(jù):
- 調(diào)用單體提供的遠(yuǎn)程API
- 直接訪問(wèn)單體擁有的數(shù)據(jù)庫(kù)
- 維護(hù)自己的數(shù)據(jù)副本搜吧,副本需要從單體應(yīng)用端同步過(guò)來(lái)
膠水代碼有時(shí)也被稱為anti-corruption layer,這是因?yàn)槟z水代碼防止了擁有自己純凈領(lǐng)域模型的服務(wù)被來(lái)自遺留單體應(yīng)用的領(lǐng)域模型的設(shè)計(jì)理念所污染杠输。膠水代碼在兩種不同的模型間進(jìn)行轉(zhuǎn)換赎败。anti-corruption layer一詞首次出現(xiàn)在Eric Evans 所著的必讀之書 Domain Driven Design 中,并在white paper中被提煉修正蠢甲。開發(fā)一個(gè)anti-corruption layer不是一項(xiàng)簡(jiǎn)單的事情僵刮,但如果不想陷入單一地獄,還是有必要搞一個(gè)的鹦牛。
把新功能實(shí)現(xiàn)為輕量級(jí)的服務(wù)有諸多優(yōu)勢(shì):它避免單體應(yīng)用最終變的不可管理搞糕,服務(wù)同時(shí)可以被獨(dú)立于單體去開發(fā)、部署和擴(kuò)展曼追。你可以通過(guò)創(chuàng)建每一個(gè)新的服務(wù)體會(huì)到微服務(wù)架構(gòu)的優(yōu)勢(shì)窍仰。
然而,這種方式并沒有解決單體中的問(wèn)題礼殊,為了解決這些問(wèn)題驹吮,你需要拆分單體。讓我們看一下拆分的策略:
策略二:前后端分離
縮小單體應(yīng)用的策略之一是把展示層從業(yè)務(wù)邏輯層和數(shù)據(jù)訪問(wèn)層中拆分出來(lái)晶伦。一個(gè)典型的企業(yè)應(yīng)用一般包含至少三種不同的組件:
- 展示層:用來(lái)處理HTTP請(qǐng)求并實(shí)現(xiàn)基于REST API或者基于HTML的Web UI碟狞,在一個(gè)用戶界面復(fù)雜的應(yīng)用中,展示層通常包含大量的代碼
- 業(yè)務(wù)邏輯層:應(yīng)用的核心并實(shí)現(xiàn)業(yè)務(wù)規(guī)則的組件
- 數(shù)據(jù)訪問(wèn)層:訪問(wèn)諸如數(shù)據(jù)庫(kù)和消息中介等基礎(chǔ)架構(gòu)的組件婚陪。
通常展示邏輯對(duì)于后臺(tái)業(yè)務(wù)邏輯與數(shù)據(jù)訪問(wèn)邏輯來(lái)講族沃,彼此有清晰的劃分。業(yè)務(wù)層有由一個(gè)或多個(gè)門面組成的粗粒度API,它封裝了業(yè)務(wù)邏輯組件脆淹,這些API是拆分單體應(yīng)用到兩個(gè)更小應(yīng)用時(shí)候的自然縫隙常空。一個(gè)應(yīng)用包含展示層,另一應(yīng)用包含業(yè)務(wù)邏輯和數(shù)據(jù)訪問(wèn)邏輯盖溺,經(jīng)過(guò)拆分漓糙,展示邏輯的應(yīng)用向業(yè)務(wù)邏輯應(yīng)用發(fā)起遠(yuǎn)程調(diào)用,下圖展示了重構(gòu)前后的架構(gòu):
這種方式拆分單體應(yīng)用有兩個(gè)大的優(yōu)勢(shì):它使得兩個(gè)應(yīng)用可以獨(dú)立的開發(fā)咐柜、部署和擴(kuò)展兼蜈,尤其是它允許展示層開發(fā)者快速迭代用戶界面并容易的進(jìn)行A|B測(cè)試,這種方式的另一優(yōu)勢(shì)是它暴露了可以被其他微服務(wù)調(diào)用的遠(yuǎn)程API拙友。
這種策略为狸,也僅僅是部分解決方案,這樣重構(gòu)完之后遗契,很可能兩個(gè)應(yīng)用逐步變成兩個(gè)不可管理的單體辐棒。你需要使用第三者策略消除剩余的單體部分。
策略三:提取服務(wù)
重構(gòu)第三個(gè)策略是把單體中存在的模塊變成獨(dú)立的微服務(wù)牍蜂。每一次你提取模塊并把其轉(zhuǎn)化為服務(wù)辜御,單體就會(huì)縮小递胧,一旦你覆蓋了足夠多的模塊杠袱,單體就將不再是一個(gè)問(wèn)題铲觉,單體要么消失掉要么小到變成另外一個(gè)微服務(wù)。
確定需要轉(zhuǎn)化為服務(wù)的模塊的優(yōu)先級(jí)
一個(gè)龐大从绘、復(fù)雜的單體應(yīng)用有數(shù)十甚至上百個(gè)模塊組成寄疏,所有的模塊都是需要提取的候選。確定哪一個(gè)模塊需要首先被提取是很有挑戰(zhàn)性的問(wèn)題僵井,一個(gè)好的方式是先選擇容易提取的模塊作為開始陕截,這將給你一些微服務(wù)總體上概覽以及尤其是提取過(guò)程上的經(jīng)驗(yàn)。在這之后批什,你可以提取那些可以給你帶來(lái)最大優(yōu)勢(shì)的模塊农曲。
把一個(gè)模塊轉(zhuǎn)化為一個(gè)服務(wù)通常是需要一定時(shí)間的。你想要根據(jù)你可以獲得優(yōu)勢(shì)的大小排列你的模塊驻债,通常轉(zhuǎn)換經(jīng)常變化的模塊帶來(lái)的優(yōu)勢(shì)最大乳规。一旦你把一個(gè)模塊轉(zhuǎn)化為服務(wù),你就可以獨(dú)立于單體來(lái)開發(fā)合呐、部署它 了驯妄,這會(huì)極大的加速你的開發(fā)效率。
提取那些對(duì)資源有獨(dú)特需求的模塊也會(huì)帶來(lái)很多優(yōu)勢(shì)合砂,比如,把擁有內(nèi)存數(shù)據(jù)的模塊轉(zhuǎn)化為服務(wù),就可以把服務(wù)部署到擁有大量?jī)?nèi)存的主機(jī)上翩伪,同樣的微猖,提取一個(gè)需要實(shí)現(xiàn)計(jì)算密集型算法的模塊也是很值得的,因?yàn)樵摲?wù)可以被部署到有多顆CUP的主機(jī)上缘屹,通過(guò)把有獨(dú)特資源需求的模塊轉(zhuǎn)化為服務(wù)凛剥,可以使得應(yīng)用更容易擴(kuò)展。
當(dāng)決定哪個(gè)模塊需要提取時(shí)轻姿,查看已存在的粗粒度的邊界(也就是縫隙)是很有用的犁珠,這會(huì)使模塊轉(zhuǎn)化到服務(wù)更加簡(jiǎn)單和低廉。邊界的例子之一是互亮,一個(gè)模塊只通過(guò)異步消息與應(yīng)用的其他部分進(jìn)行通信犁享,把該模塊轉(zhuǎn)化成微服務(wù)是相當(dāng)廉價(jià)和簡(jiǎn)單的。
如何提取模塊
提取模塊的第一步是確定模塊與單體應(yīng)用間的粗粒度接口豹休,由于單體和模塊需要訪問(wèn)彼此擁有的數(shù)據(jù)炊昆,一般是一些雙向的API。由于依賴的錯(cuò)綜復(fù)雜以及模塊與應(yīng)用其他部分間細(xì)粒度的交互威根,實(shí)現(xiàn)這些API一般非常有挑戰(zhàn)性凤巨。重構(gòu)使用領(lǐng)域?qū)ο竽J?/a> 設(shè)計(jì)的業(yè)務(wù)邏輯尤其困難,因?yàn)轭I(lǐng)域模型類中彼此包含大量的關(guān)聯(lián)關(guān)系 洛搀。你一般需要進(jìn)行大的代碼改動(dòng)才能打破這些依賴敢茁,下圖展示了重構(gòu)過(guò)程:
一旦你實(shí)現(xiàn)了粗粒度的接口,你就可以把這些模塊轉(zhuǎn)化為獨(dú)立的服務(wù)留美。為了實(shí)現(xiàn)這個(gè)目標(biāo)彰檬,你必須寫代碼使得單體應(yīng)用和服務(wù)可以通過(guò)進(jìn)程間通信機(jī)制的API進(jìn)行交互。下圖展示了應(yīng)用重構(gòu)前独榴、中僧叉、后的架構(gòu):
在這個(gè)例子中,模塊Z是要被提取的候選模塊棺榔,它的組件被模塊X使用瓶堕,它本身使用到模塊Y。重構(gòu)第一步是定義一組粗粒度的API症歇,第一個(gè)接口是模塊X調(diào)用模塊Z的入端接口郎笆,第二個(gè)接口是模塊Z調(diào)用模塊Y的出端接口。
重構(gòu)的第二步是把模塊轉(zhuǎn)化為獨(dú)立的服務(wù)忘晤,出端和入端的接口使用IPC機(jī)制的代碼實(shí)現(xiàn)宛蚓,你最有可能需要通過(guò)模塊 Z 結(jié)合 Microservice Chassis framework來(lái)處理諸如服務(wù)發(fā)現(xiàn)這樣的橫切關(guān)注點(diǎn)。
一旦你提取了某個(gè)模塊设塔,你就擁有了另一個(gè)可以獨(dú)立于單體應(yīng)用和其他服務(wù)來(lái)開發(fā)凄吏、部署、擴(kuò)展的新服務(wù),你甚至可以從頭重寫這個(gè)服務(wù)痕钢; 在這種狀況下图柏,API代碼集成單體和微服務(wù),變成了用以轉(zhuǎn)換兩種領(lǐng)域模型的anti-corruption層任连。每次你提取一個(gè)模塊蚤吹,你就又向微服務(wù)的方向邁出一步,隨著時(shí)間的推移随抠,單體應(yīng)用將會(huì)縮水裁着,你也會(huì)擁有更多的微服務(wù)。
總結(jié)
從一個(gè)現(xiàn)存應(yīng)用遷移到微服務(wù)的過(guò)程是應(yīng)用現(xiàn)代化的一種形式拱她,你不應(yīng)該以從頭完全重寫的方式把現(xiàn)存應(yīng)用變?yōu)槲⒎?wù)二驰,相反的,你應(yīng)該逐步的把應(yīng)用重構(gòu)為一系列的微服務(wù)椭懊。你可以使用三種策略:使用微服務(wù)實(shí)現(xiàn)新的功能诸蚕;把表現(xiàn)層從業(yè)務(wù)邏輯和數(shù)據(jù)訪問(wèn)組件中拆分出來(lái);把單體應(yīng)用中的現(xiàn)有模塊轉(zhuǎn)化為服務(wù)氧猬。隨著時(shí)間推移背犯,微服務(wù)的數(shù)量將會(huì)增長(zhǎng),你團(tuán)隊(duì)的敏捷性和開發(fā)速度也會(huì)提升盅抚。