单体系统如何拆分为微服务
當(dāng)單體系統(tǒng)越來越大,并難于維護時,很多企業(yè)開始有意把單體系統(tǒng)拆分為微服務(wù)風(fēng)格架構(gòu)。這么做很有意義,但不容易。要做好這件事情我們必須學(xué)習(xí),我們從一個簡單的服務(wù)開始,另一方面拉出以垂直功能為基礎(chǔ)的服務(wù),這些功能對業(yè)務(wù)來說很重要并且經(jīng)常變更。這些服務(wù)首先要很大并且最好不要依賴剩余的單體系統(tǒng)。我們應(yīng)該確保每一步遷移對于整體架構(gòu)而已是一個原子改進。
遷移巨型單體系統(tǒng)到微服務(wù)生態(tài)系統(tǒng)是一個史詩性任務(wù)。從事這項任務(wù)的人擁有增加經(jīng)營規(guī)模、促進變化、避免變更帶來的高開銷的意愿。他們想要增加他們的團隊規(guī)模同時讓團隊以并行的方式傳輸價值并彼此獨立。他們想要快速實驗他們的業(yè)務(wù)核心功能并且更快的傳遞價值。他們同時要避免關(guān)于變更現(xiàn)存單體系統(tǒng)高昂的開銷。
決定解耦何種功能、何時、如何逐步遷移以分解單體系統(tǒng)到微服務(wù)生態(tài)是架構(gòu)的挑戰(zhàn)。在這篇文章里,我分享一些技術(shù)以引導(dǎo)交付團隊(開發(fā)者、架構(gòu)師、技術(shù)經(jīng)理)使用這些技術(shù)在過程中做出拆分決定。
微服務(wù)生態(tài)系統(tǒng)目標(biāo)
在開始之前,大家對微服務(wù)生態(tài)系統(tǒng)達成共識是關(guān)鍵。微服務(wù)生態(tài)系統(tǒng)是一個服務(wù)平臺,每一個服務(wù)封裝一個業(yè)務(wù)功能。一個業(yè)務(wù)功能代表業(yè)務(wù)在特殊領(lǐng)域可以做什么(實現(xiàn)目標(biāo)和責(zé)任)。每個微服務(wù)暴露一個 API,以便開發(fā)者發(fā)現(xiàn)并用于自托管模式。微服務(wù)擁有獨立的生命周期。開發(fā)者可以獨立開發(fā)、構(gòu)建、測試和發(fā)布。微服務(wù)生態(tài)系統(tǒng)實施長期自治的團隊組織架構(gòu),每個團隊負(fù)責(zé)一個或多個服務(wù)。與大多數(shù)看法相反,微服務(wù)中的“微”和服務(wù)大小幾乎沒有關(guān)系,而依賴于運營成熟的組織架構(gòu)而改變(運營成熟的組織架構(gòu)決定微服務(wù))。
過程介紹
在深入介紹之前,了解分解現(xiàn)有系統(tǒng)到微服務(wù)會產(chǎn)生很高的總體成本(并且可能產(chǎn)生很多迭代)是很重要的。對于開發(fā)者和架構(gòu)師來說密切的評估是否分解現(xiàn)有系統(tǒng)是正確的道路、微服務(wù)是否是正確的目的地。
通過一個簡單的解耦功能熱身
開始微服務(wù)需要一個最低層次的程序準(zhǔn)備。它需要訪問部署環(huán)境,構(gòu)建新的類型的 CD 管道以獨立構(gòu)建、測試、部署執(zhí)行服務(wù),以及安全性能力,調(diào)試和監(jiān)控一個分布式架構(gòu)。準(zhǔn)備程序就緒成熟化是必須的,無論我們是否構(gòu)建綠地服務(wù)或者分解已有系統(tǒng)。
我的建議是開發(fā)和運維團隊構(gòu)建底層基礎(chǔ)設(shè)施、持續(xù)交付管道和 API 管理系統(tǒng),并分解或構(gòu)建第一個和第二個服務(wù)。從分解一個單體系統(tǒng)的功能開始,這個功能相對目前的單體系統(tǒng)來說不需要變更很多客戶端接口應(yīng)用程序,也不需要數(shù)據(jù)存儲。交付團隊的優(yōu)化點是驗證他們的交付渠道,升級團隊的技能,構(gòu)建最小基礎(chǔ)設(shè)施以交付部署安全服務(wù)以暴露自托管服務(wù)。做為示例,一個在線零售應(yīng)用程序,第一個服務(wù)是“終端用戶認(rèn)證”服務(wù),單體服務(wù)請求以認(rèn)證終端用戶,第二個服務(wù)是“客戶檔案”服務(wù),一個外觀服務(wù)為客戶提供更好的客戶視圖。
首先我推薦解耦簡單的邊緣服務(wù)。下一步我們采用不同的方式解耦深度嵌入的單體系統(tǒng)。我建議先做邊緣服務(wù)是因為在開始之初,交付團隊最大的風(fēng)險是合理的運維微服務(wù)。所以使用邊緣服務(wù)體驗運維很有好處。一旦他們定位到問題,他們就可以定位到分離單體服務(wù)的關(guān)鍵問題。
最小化對單體的依賴
交付團隊的基本原則是最小化新的微服務(wù)對單體系統(tǒng)的依賴。微服務(wù)的主要好處是快速獨立的發(fā)布閉體系。依賴單體系統(tǒng)的數(shù)據(jù)、邏輯、API,耦合服務(wù)到單體系統(tǒng)的發(fā)布體系,禁止使用單體系統(tǒng)的好處。通常從單體系統(tǒng)架構(gòu)跑路的主要動機是由于高昂的代價和封裝在單體系統(tǒng)中功能的緩慢變化,所以我們要緩緩的朝單體系統(tǒng)解耦核心功能的方向移動。如果團隊按照這篇文章的指導(dǎo)來為他們的微服務(wù)增加功能,那么他們會發(fā)現(xiàn)從單體系統(tǒng)到服務(wù)的替換、依賴的反轉(zhuǎn)。這是理想的依賴方向,因為它不會放慢變更服務(wù)的步伐。
考慮一個在線零售系統(tǒng),“購買”和“促銷活動”是核心功能。在結(jié)賬過程中,“購買”使用“促銷活動”給顧客提供最好的促銷活動。如果要決定下一步解耦這兩個功能里的哪一個,我建議先開始解耦“促銷活動”然后才是“購買”。因為在這個順序下我們減少里對單體系統(tǒng)的依賴。在這個順序下,“購買”繼續(xù)鎖在單體系統(tǒng)中,依賴外部的新的“促銷活動”微服務(wù)。
下一步本文將使用其它方式?jīng)Q定解耦服務(wù)。這意味著這些服務(wù)不是總是能避開對單體系統(tǒng)的依賴。如果一個新的服務(wù)最終回調(diào)到單體服務(wù),我建議從單體系統(tǒng)中暴露一個新的 API,新的服務(wù)通過反腐層訪問 API 以確保單體系統(tǒng)的概念不泄漏。力爭定義的 API 對于領(lǐng)域概念和結(jié)構(gòu)有良好的反映,即便單體系統(tǒng)的內(nèi)部實現(xiàn)不是那樣的。在這個不幸的案例中,交付團隊將承擔(dān)改變單體系統(tǒng)的開銷和困難,即測試、發(fā)布新的服務(wù)和單體系統(tǒng)發(fā)布耦合。
盡早分離黏性功能
假設(shè)交付團隊已經(jīng)開始構(gòu)建微服務(wù)并且準(zhǔn)備進攻黏性問題。然而他們可能會發(fā)現(xiàn)他們能力有限,使下一個解耦的功能不依賴于單體系統(tǒng)。根本原因是通常是單體系統(tǒng)功能泄漏,定義的領(lǐng)域概念不好,有很多單體系統(tǒng)功能依賴于它。為了能處理這個問題,開發(fā)者需要辨別黏性功能,把它解構(gòu)為定義良好的領(lǐng)域模型然后把這些領(lǐng)域概念實現(xiàn)到隔離的服務(wù)中。
例如 Web 單體系統(tǒng),“session” 是最為常見的耦合因素之一。在在線零售示例中,session 通常是很多特性的封裝,從用戶的偏好(不同的領(lǐng)域邊界,比如:配送和支付偏好)到用戶的意圖和交互(比如:最近訪問的頁面、點擊的產(chǎn)品和購買清單)。若非我們處理解耦、解構(gòu)和具體化當(dāng)前 session 的概念,我們將陷入解耦功能(這些功能通過泄漏的 session 概念纏住單體系統(tǒng))的競爭中。同時我也不鼓勵在單體系統(tǒng)外創(chuàng)建 session 服務(wù),因為它會導(dǎo)致和單體系統(tǒng)進程中類似的緊耦合,更糟糕的是,在進程外和跨網(wǎng)絡(luò)。
開發(fā)者可以逐步從黏性功能中抽取微服務(wù),每次一個服務(wù)。例如,先重構(gòu)“顧客愿望清單”并抽取到一個新的服務(wù)中,然后重構(gòu)“顧客支付偏好”到另一個服務(wù)中。
垂直解耦,盡早釋放數(shù)據(jù)
從單體系統(tǒng)中解耦功能的主要驅(qū)動是可以獨立發(fā)布它們。這是開發(fā)者在解耦過程中做每一個決定的首要原則。一個單體系統(tǒng)通常由緊密集成層,甚至幾個系統(tǒng)組成(需要發(fā)布在一起并且有脆弱的相互依賴關(guān)系)。例如,在一個在線零售系統(tǒng)中,單體系統(tǒng)由一個或幾個面向顧客的在線購物應(yīng)用程序組成,一個后端系統(tǒng)實現(xiàn)很多業(yè)務(wù)功能(包含一個集中的數(shù)據(jù)存儲)。
大多數(shù)解耦嘗試從抽取面向用戶組件、幾個外觀服務(wù)為 UI 提供友好的開發(fā) API開始,同時數(shù)據(jù)仍然鎖在同一個 schema 中。雖然這種方式在一些方面立竿見影,比如更加頻繁的變更 UI,當(dāng)涉及到核心功能時,交付團隊只能按照最慢的部分步伐,單體系統(tǒng)和它的巨大數(shù)據(jù)存儲。簡單的說,不解耦數(shù)據(jù),架構(gòu)就不是微服務(wù)。所有數(shù)據(jù)在同一個存儲中與微服務(wù)去中心化數(shù)據(jù)管理的特征背道而馳。
策略是垂直移除功能,解耦核心功能和它的數(shù)據(jù),并重定向所有前端應(yīng)用程序到新的 API。
有多個應(yīng)用程序從中心共享數(shù)據(jù)讀寫是服務(wù)解耦數(shù)據(jù)的主要障礙。交付團隊需要納入一個數(shù)據(jù)遷移策略,這個策略適配他們的環(huán)境依賴,無論他們是否同時重定向和遷移所有數(shù)據(jù)讀寫者。四段數(shù)據(jù)遷移策略是其中一種適應(yīng)很多環(huán)境(需要逐步遷移集成數(shù)據(jù)庫的應(yīng)用程序,同時所有系統(tǒng)在變更下需要繼續(xù)運行)的策略。
解耦對業(yè)務(wù)重要和頻繁變更的部分
從單體系統(tǒng)中解耦功能不容易。在在線零售應(yīng)用程序中,抽取一個功能需要仔細(xì)抽取功能的數(shù)據(jù)、邏輯、面向用戶的組件然后重定向它們到新的服務(wù)。因為這是一堆重要的工作,開發(fā)者需要持續(xù)評估解耦得到的好處,比如:跑的更快或者增加規(guī)模。例如,如果交付團隊的目標(biāo)是加速修改已經(jīng)鎖在單體系統(tǒng)中的功能,那么他們必須確定修改最多的功能。解耦代碼中持續(xù)經(jīng)受修改的部分(這部分代碼持續(xù)得到開發(fā)者的關(guān)注,并最大限度限制了開發(fā)者快速交付成功)。交付團隊可以分析代碼提交模型找出歷史上變化最大的內(nèi)容,并將其與產(chǎn)品路線圖和產(chǎn)品組合進行疊加,以了解在不久的將來會受到關(guān)注的最需要的功能。他們需要和業(yè)務(wù)、產(chǎn)品經(jīng)理溝通以了解對他們來說重要的功能差異。
例如在一個在線零售系統(tǒng)中,“顧客個性化”是一個功能,該功能要進行大量的實驗以為顧客提供最好的體驗,并且也是一個好的解耦候選項。它是一個對業(yè)務(wù)很重要的功能,用戶體驗,并且頻繁被修改。
解耦功能,不是代碼
無論何時,開發(fā)者們要從一個現(xiàn)存系統(tǒng)中抽出一個服務(wù),他們有兩種方式:抽取代碼或者重寫功能。
通常情況下,服務(wù)抽取或者單體系統(tǒng)解構(gòu)默認(rèn)假設(shè)為重用已有的實現(xiàn),原樣抽取到一個分離的服務(wù)中。部分原因是我們對我們設(shè)計、編寫的代碼有一個認(rèn)知偏見。建筑(沒錯,這里就是建筑,這里借助 IKEA Effect 理論)過程讓我們對它產(chǎn)生熱愛,無論這個過程多么痛苦,結(jié)果多么不完美。不幸的是這種偏見將阻礙單體系統(tǒng)解構(gòu)的努力。它引發(fā)開發(fā)者們和更多的重要技術(shù)管理者不理會高開銷和低價值的抽取和重用代碼。
交付團隊可以選擇重寫功能然后讓老代碼淘汰。重寫給了他們機會重新訪問業(yè)務(wù)功能,和業(yè)務(wù)開始一個新的談話,簡化遺留的過程和挑戰(zhàn)隨著時間推移建在系統(tǒng)中老的假設(shè)和限制。它同樣提供了一個刷新技術(shù)的機會,使用最合適的一門編程語言和技術(shù)棧實現(xiàn)一個新的服務(wù)。
例如在零售系統(tǒng)中,“定價和促銷活動”功能是一段邏輯復(fù)雜的代碼。它啟用動態(tài)配置和應(yīng)用程序定價、促銷活動規(guī)則,提供折扣(在各種參數(shù)的基礎(chǔ)上,比如:客戶行為、忠誠度、產(chǎn)品包等)。
這個功能可以說是一個很好的重用和抽取的候選項。相反,“顧客文檔”是一個簡單的 CRUD 功能,通常由樣板代碼組成(序列化、處理存儲和配置),因此,它是重寫和淘汰代碼的候選項。
在我看來,在大多數(shù)解構(gòu)場景中,團隊最好重寫功能到一個新的服務(wù)中,并且淘汰老的代碼。這里考慮高開銷和低價值的重用,因為以下幾個原因:
有大量的模版代碼要處理環(huán)境依賴,比如在運行時訪問應(yīng)用程序配置、訪問數(shù)據(jù)存儲、緩存并且構(gòu)建于老的框架。大多數(shù)模版代碼需要重寫。新的基礎(chǔ)設(shè)施要托管一個微服務(wù)和幾十年應(yīng)用程序運行時有很大的不同,并且需要不同種類的模版代碼。
很有可能存在的功能不是構(gòu)建于清晰的領(lǐng)域概念。導(dǎo)致傳輸或者存儲數(shù)據(jù)結(jié)構(gòu)不能反映新的領(lǐng)域模型和需要忍受一個大的重組。
一個長時間存在的遺留代碼經(jīng)歷過很多迭代,導(dǎo)致很高的代碼毒性級別和重用價值低。
除非能力是相關(guān)的,與清晰的領(lǐng)域概念保持一致并且具有很高的知識產(chǎn)權(quán),否則我強烈建議重寫和淘汰舊代碼。
先微服務(wù),然后再劃分的更小
在遺留單體系統(tǒng)中尋找領(lǐng)域邊界既是藝術(shù)也是科學(xué)。通常應(yīng)用領(lǐng)域驅(qū)動設(shè)計技術(shù)查找邊界上下文定義微服務(wù)邊界是一個好的開始。我承認(rèn),我經(jīng)常看到從巨大的單體系統(tǒng)到真正的小服務(wù)的過度修正,真正的小服務(wù)的設(shè)計是由于受到存在規(guī)范化的數(shù)據(jù)視圖的鼓勵和驅(qū)動。這種方式確認(rèn)服務(wù)邊界導(dǎo)致寒武紀(jì)爆發(fā)大量的貧血服務(wù)(CRUD 資源)。對于微服務(wù)架構(gòu)新手來說,這會創(chuàng)建一個高摩擦環(huán)境,最終無法通過獨立發(fā)布和執(zhí)行服務(wù)的測試。它創(chuàng)建了一個難于調(diào)試的分布式系統(tǒng),一個分布式系統(tǒng)打碎了事務(wù)邊界,因此難以保持一致性,對于組織的運營成熟度而言過于復(fù)雜的系統(tǒng)。雖然有一些如何“微”微服務(wù)的啟發(fā)式:團隊大小、重寫服務(wù)的時間、要封裝多少行為等等。我的建議是大小依賴于有多少服務(wù)交付,多少服務(wù)運維團隊可以獨立發(fā)布、監(jiān)控和操作。從圍繞邏輯領(lǐng)域概念的大型服務(wù)開始,并在團隊準(zhǔn)備就緒時將服務(wù)分解為多個服務(wù)。
例如,在解耦零售系統(tǒng)的過程中,開發(fā)者可能開始于服務(wù)“購買”,這個服務(wù)封裝了“購物袋”,功能是購物和購物袋(也就是買單)。隨著他們組建更小團隊和發(fā)布更多服務(wù)的能力的增長,他們可以將購物袋與結(jié)帳分離成單獨的服務(wù)。
以原子進化步驟遷移
通過將一個遺留的單體系統(tǒng)解耦成設(shè)計精美的微服務(wù)而讓它消失的想法在某種程度上是一個神話,可以說是不可取的。任何經(jīng)驗豐富的工程師都可以分享遺留遷移和現(xiàn)代化嘗試的故事,這些嘗試是在對完全完成過于樂觀的情況下計劃和啟動的,但充其量在足夠好的時間點被放棄。由于宏觀條件發(fā)生變化,此類努力的長期計劃被放棄:該計劃的資金用完,組織將重點轉(zhuǎn)向其他事物或支持它的領(lǐng)導(dǎo)層離開。所以這個現(xiàn)實應(yīng)該被設(shè)計成團隊如何處理單體應(yīng)用到微服務(wù)之旅。我稱這種方法為“架構(gòu)演化的原子步驟中的遷移”,其中遷移的每一步都應(yīng)該使架構(gòu)更接近其目標(biāo)狀態(tài)。每個進化單元可能是一小步或一大步,但都是原子的,要么完成,要么恢復(fù)。這一點特別重要,因為我們正在采用迭代和增量方法來改進整體架構(gòu)和解耦服務(wù)。每個增量都必須讓我們在架構(gòu)目標(biāo)方面處于更好的位置。使用進化架構(gòu)適應(yīng)度函數(shù)比喻,遷移的每個原子步驟之后的架構(gòu)適應(yīng)度函數(shù)應(yīng)該產(chǎn)生更接近架構(gòu)目標(biāo)的價值。
讓我通過一個例子圖解這個觀點。假設(shè)微服務(wù)架構(gòu)目的是增加開發(fā)者修改整個系統(tǒng)的速度交付價值。團隊決定解耦用戶認(rèn)證到一個隔離的服務(wù)中,以 OAuth 2.0 協(xié)議為基礎(chǔ)。這個服務(wù)想要同時替換已有客戶端應(yīng)用程序認(rèn)證終端用戶,而且新的架構(gòu)微服務(wù)驗證終端用戶。讓我們將這個進化過程中的增量稱為“Auth 服務(wù)介紹”。一個方法介紹新的服務(wù)是先通過以下步驟:
(1)構(gòu)建 Auth 服務(wù),實現(xiàn) OAuth 2.0 協(xié)議。
(2)在單體系統(tǒng)中添加一個新的認(rèn)證路徑并調(diào)用 Auth 服務(wù)認(rèn)證終端用戶。
如果團隊在這里停下來并轉(zhuǎn)向構(gòu)建一些其他服務(wù)或功能,他們會使整體架構(gòu)處于熵增加的狀態(tài)。在這種狀態(tài)下,有兩種驗證用戶的方法,新的 OAuth 2.0 基本路徑和舊客戶端的基于密碼/會話的路徑。在這一點上,團隊實際上離他們更快地做出改變的總體目標(biāo)更遠了。單體代碼的任何新開發(fā)人員都需要處理兩條代碼路徑,增加理解代碼的認(rèn)知負(fù)擔(dān),以及更慢的更改和測試過程。
團隊可以包含以下步驟到我們的原子進化單元中:
(1)通過 OAuth 2.0 替換老客戶端的密碼/session
(2)從單體系統(tǒng)中淘汰老的認(rèn)證代碼
這時候我們可以說團隊已經(jīng)接近目標(biāo)架構(gòu)了。
單體系統(tǒng)原子單元解構(gòu)包括:
解耦新服務(wù)
重定向消費者到新的服務(wù)
從單體系統(tǒng)中淘汰來的代碼
反模式:解耦新服務(wù)用于新的消費者并且從不淘汰老的服務(wù)。
我經(jīng)常發(fā)現(xiàn)團隊終止從單體系統(tǒng)中遷移功能,新的功能開發(fā)出來以后馬上就宣布勝利了,也不淘汰老的代碼路徑,正如上面描述的反模式。主要原因是:(a)聚焦于引入新功能的短期利益(b)淘汰老的代碼會和構(gòu)建新功能形成競爭優(yōu)先級。為了做正確的事,我們應(yīng)該盡可能的做原子步驟。
引用
How to break a Monolith into Microservices
總結(jié)
以上是生活随笔為你收集整理的单体系统如何拆分为微服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在 .NET 应用中使用 ANTLR
- 下一篇: ABP Framework V4.4 R