如何拆分大型單體系統為微服務


單體系統如何拆分為微服務

當單體系統越來越大,並難於維護時,很多企業開始有意把單體系統拆分為微服務架構。這么做很有意義,但不容易。要做好這件事情,我們需要學習一些方法,我們從一個簡單的服務開始,另一方面拉出以垂直功能為基礎的服務,這些功能對業務來說很重要並且經常變更。這些服務首先要很大,並且最好不要依賴剩余的單體系統。我們應該確保每一步遷移對於整體架構而言,是一個原子變更。


遷移巨型單體系統到微服務生態系統是一個史詩任務。從事這項任務的人擁有增加經營規模、促進變化、避免變更帶來的高開銷的意願。他們想要增加他們的團隊規模同時讓團隊以並行的方式傳輸價值並彼此獨立。他們想要快速實驗他們的業務核心功能並且更快的傳遞價值。他們同時要避免變更現存單體系統帶來的高昂開銷。

決定解耦何種功能、何時、如何逐步遷移以分解單體系統到微服務生態是架構的挑戰。在這篇文章里,我將分享一些技術,引導交付團隊(開發者、架構師、技術經理)在拆分過程中使用這些技術做出拆分決定。

微服務生態系統目標

在開始之前,大家對微服務生態系統達成共識是關鍵。微服務生態系統是一個服務平台,每一個服務封裝一個業務功能。一個業務功能代表業務在特殊領域可以做什么(實現目標和責任)。每個微服務暴露一個 API,以便開發者在自托管模式中發現。微服務擁有獨立的生命周期。開發者可以獨立開發、構建、測試和發布。微服務生態系統實施長期自治的團隊組織架構,每個團隊負責一個或多個服務。與大多數看法相反,微服務中的“微”和服務大小幾乎沒有關系,它依賴運營成熟的組織架構(運營成熟的組織架構決定微服務)。

過程介紹

在深入介紹之前,了解現有系統遷移到微服務架構將產生很高的總體成本(並且可能產生很多迭代)很重要。對於開發者和架構師來說,需要密切評估是否要分解現有系統,以及微服務是否是正確的選擇。

解耦一個簡單的功能來熱身

開始微服務需要准備底層程序。它需要訪問部署環境,構建新的 CD 管道,以此實現獨立構建、測試、部署,以及安全性、調試、監控分布式架構。准備基礎程序是必須的,無論我們構建新服務還是分解已有系統。

我的建議是開發和運維團隊構建底層基礎設施、持續交付管道和 API 管理系統,並且分解或構建第一個和第二個服務。從分解單體系統的某個功能開始,這個功能相對目前的單體系統來說不需要變更很多客戶端接口,也不需要存儲數據。交付團隊的優化點是驗證他們的交付渠道,升級團隊的技能,構建最小基礎設施以交付部署安全服務以暴露自托管服務。我們使用一個在線零售應用做為示例,第一個服務是“終端用戶認證”服務,單體服務發起請求來認證終端用戶,第二個服務是“客戶檔案”服務,使用外觀服務為客戶提供更好的客戶視圖。

首先推薦解耦簡單的邊緣服務。下一步我們采用不同的方式解耦深度嵌入的單體系統。我建議先做邊緣服務是因為在開始之初,交付團隊最大的風險是合理的運維微服務。所以使用邊緣服務體驗運維很有好處。一旦他們定位到問題,他們就可以定位到分離單體服務的關鍵問題。

最小化對單體的依賴

交付團隊的基本原則是,新的微服務對單體系統的依賴最小化。微服務的主要好處是快速獨立的發布體系。依賴單體系統的數據、邏輯、API,耦合服務到單體系統的發布體系,禁止使用單體系統的好處。通常從單體系統架構跑路的主要動機是由於高昂的代價和封裝在單體系統中的功能更新過於緩慢,所以我們要緩緩的朝單體系統解耦核心功能的方向移動。如果團隊按照這篇文章的指導來為他們的微服務增加功能,那么他們會發現從單體系統到服務的替換、依賴的反轉。這是理想的依賴方向,因為它不會放慢變更服務的步伐。

考慮一個在線零售系統,核心功能是“購買”和“促銷活動”。在結賬過程中,“購買”使用“促銷活動”給顧客提供最好的促銷活動。如果要決定下一步解耦這兩個功能里的哪一個,我建議先開始解耦“促銷活動”然后才是“購買”。因為在這個順序下我們減少了對單體系統的依賴。在這個順序下,“購買”繼續鎖在單體系統中,依賴新的“促銷活動”微服務。

下一步本文將使用其它方式來決定解耦的服務。這意味着這些服務不是總能避開對單體系統的依賴。如果一個新的服務最終回調單體服務,我建議從單體系統中暴露一個新的 API,新的服務通過反腐層訪問 API,以此確保單體系統的概念不泄漏。力爭定義的 API 對於領域概念和結構有良好的反映,即便單體系統的內部實現不是那樣的。在這個不幸的案例中,交付團隊將承擔改變單體系統的開銷和困難,即測試、發布新的服務和單體系統發布耦合。

盡早分離黏性功能

假設交付團隊已經開始構建微服務並且准備進攻黏性問題。然而他們可能會發現他們能力有限,使下一個解耦的功能不依賴於單體系統。根本原因通常是由於單體系統的功能泄漏,定義的領域概念不好,有很多單體系統功能依賴於它。為了能處理這個問題,開發者需要辨別黏性功能,把它解構為定義良好的領域模型然后把這些領域概念實現到隔離的服務中。

例如 Web 單體系統,“session” 是最為常見的耦合因素之一。在在線零售示例中,session 通常會封裝很多特性,從用戶的偏好(不同的領域邊界,比如:配送和支付偏好)到用戶的意圖和交互(比如:最近訪問的頁面、點擊的產品和購買清單)。若非我們處理解耦、解構和具體化當前 session 的概念,我們將陷入解耦功能(這些功能通過泄漏的 session 概念纏住單體系統)的競爭中。同時我也不鼓勵在單體系統外創建 session 服務,因為它會導致和單體系統進程中類似的緊耦合,更糟糕的是,在進程外和跨網絡。

開發者可以逐步從黏性功能中抽取微服務,每次一個服務。例如,先重構“顧客願望清單”並抽取到一個新的服務中,然后重構“顧客支付偏好”到另一個服務中。

垂直解耦,盡早釋放數據

從單體系統中解耦功能的主要驅動是可以獨立發布它們。這是開發者在解耦過程中做每一個決定的首要原則。一個單體系統通常由緊密集成層,甚至幾個系統組成(需要發布在一起並且有脆弱的相互依賴關系)。例如,在一個在線零售系統中,單體系統由一個或幾個面向顧客的在線購物應用程序組成,一個后端系統實現很多業務功能(包含一個集中的數據存儲)。

大多數解耦嘗試從抽取面向用戶組件、幾個外觀服務為 UI 提供友好的開發 API開始,同時數據仍然鎖在同一個 schema 中。雖然這種方式在一些方面立竿見影,比如更加頻繁的變更 UI,當涉及到核心功能時,交付團隊只能按照最慢的部分步伐,單體系統和它的巨大數據存儲。簡單的說,不解耦數據,架構就不是微服務。所有數據在同一個存儲中與微服務去中心化數據管理的特征背道而馳。

策略是垂直移除功能,解耦核心功能和它的數據,並重定向所有前端應用程序到新的 API。

有多個應用程序從中心共享數據讀寫是服務解耦數據的主要障礙。交付團隊需要納入一個數據遷移策略,這個策略適配他們的環境依賴,無論他們是否同時重定向和遷移所有數據讀寫者。四段數據遷移策略是其中一種適應很多環境(需要逐步遷移集成數據庫的應用程序,同時所有系統在變更下需要繼續運行)的策略。

解耦對業務重要和頻繁變更的部分

從單體系統中解耦功能不容易。在在線零售應用程序中,抽取一個功能需要仔細抽取功能的數據、邏輯、面向用戶的組件然后重定向它們到新的服務。因為這是一堆重要的工作,開發者需要持續評估解耦得到的好處,比如:跑的更快或者增加規模。例如,如果交付團隊的目標是加速修改已經鎖在單體系統中的功能,那么他們必須確定修改最多的功能。解耦代碼中持續經受修改的部分(這部分代碼持續得到開發者的關注,並最大限度限制了開發者快速交付成功)。交付團隊可以分析代碼提交模型找出歷史上變化最大的內容,並將其與產品路線圖和產品組合進行疊加,以了解在不久的將來會受到關注的最需要的功能。他們需要和業務、產品經理溝通以了解對他們來說重要的功能差異。

例如在一個在線零售系統中,“顧客個性化”是一個功能,該功能要進行大量的實驗以為顧客提供最好的體驗,並且也是一個好的解耦候選項。它是一個對業務很重要的功能,用戶體驗,並且頻繁被修改。

解耦功能,不是代碼

無論何時,開發者們要從一個現存系統中抽出一個服務,他們有兩種方式:抽取代碼或者重寫功能。

通常情況下,服務抽取或者單體系統解構,默認假設重用已有的實現,原樣抽取到一個分離的服務中。部分原因是我們對我們設計、編寫的代碼有一個認知偏見。建築(沒錯,這里就是建築,這里借助 IKEA Effect 理論)過程讓我們對它產生熱愛,無論這個過程多么痛苦,結果多么不完美。不幸的是這種偏見將阻礙單體系統解構。它引發開發者們和更多的重要技術管理者不理會高開銷和低價值的抽取和重用代碼。

交付團隊可以選擇重寫功能然后淘汰老代碼。重寫給了他們重新訪問業務功能的機會,和業務展開一輪新的談話,簡化遺留的過程和挑戰,隨着時間推移建在系統中老的假設和限制。它同樣提供了一個刷新技術的機會,使用最合適的一門編程語言和技術棧實現一個新的服務。

例如在零售系統中,“定價和促銷活動”功能是一段邏輯復雜的代碼。它啟用動態配置和應用程序定價、促銷活動規則,提供折扣(在各種參數的基礎上,比如:客戶行為、忠誠度、產品包等)。

這個功能可以說是一個很好的重用、抽取的候選項。相反,“顧客文檔”是一個簡單的 CRUD 功能,通常由樣板代碼組成(序列化、處理存儲和配置),因此,它是重寫、淘汰代碼的候選項。

在我看來,在大多數解構場景中,團隊最好重寫功能到一個新的服務中,並且淘汰老代碼。這里考慮高開銷、低價值的重用,有以下幾個原因:

  • 有大量的模版代碼要處理環境依賴,比如在運行時訪問應用程序配置、訪問數據存儲、緩存並且在老框架的基礎上構建。大多數模版代碼需要重寫。新的基礎設施要托管一個微服務和幾十年應用程序運行時有很大的不同,並且需要不同種類的模版代碼。
  • 很有可能存在的功能的領域概念不清晰。導致傳輸或者存儲數據結構不能反映新的領域模型並且需要忍受巨大的重組。
  • 一個長時間存在的遺留代碼經歷過很多迭代,導致很高的代碼毒性級別和重用價值低。

除非能力相關,與清晰的領域概念保持一致並且有很高的知識產權,否則我強烈建議重寫和淘汰舊代碼。

先微服務,然后再划分的更小

在遺留單體系統中尋找領域邊界既是藝術也是科學。通常應用領域驅動設計技術查找邊界上下文定義微服務邊界是一個好的開始。我承認,我經常看到從巨大的單體系統到真正的小服務的過度修正,真正的小服務的設計是由於受到存在規范化的數據視圖的鼓勵和驅動。這種方式確認服務邊界導致寒武紀爆發大量的貧血服務(CRUD 資源)。對於微服務架構新手來說,這會創建一個高摩擦環境,最終無法通過獨立發布,執行服務的測試。它創建了一個難以調試的分布式系統,一個分布式系統打碎了事務邊界,因此難以保持一致性,對於組織的運營成熟度而言過於復雜的系統。雖然有一些如何“微”微服務的啟發式:團隊大小、重寫服務的時間、要封裝多少行為等等。我的建議是大小依賴於有多少服務交付,多少服務運維團隊可以獨立發布、監控和操作。從圍繞邏輯領域概念的大型服務開始,並在團隊准備就緒時將服務分解為多個服務。

例如,在解耦零售系統的過程中,開發者可能開始於服務“購買”,這個服務封裝了“購物袋”,功能是購物和購物袋(也就是買單)。隨着他們組建更小團隊和發布更多服務的能力的增長,他們可以將購物袋與結帳分離成單獨的服務。

以原子進化步驟遷移

通過將一個遺留的單體系統解耦成設計精致的微服務,然后讓單體系統消失的想法在某種程度上是一個神話,可以說是不可取。任何經驗豐富的工程師都可以分享遷移、嘗試現代化的故事,這些嘗試是在對完全完成過於樂觀的情況下計划和啟動的,但充其量在足夠好的時間點被放棄。由於宏觀條件發生變化,此類努力的長期計划被放棄:該計划的資金耗盡,組織將重點轉向其他事物或支持它的領導層離開。所以這個現實應該被設計成團隊如何處理單體應用到微服務之旅。我稱這種方法為“架構演化的原子步驟中的遷移”,其中遷移的每一步都應該使架構更接近其目標狀態。每個進化單元可能是一小步或一大步,但都是原子的,要么完成,要么恢復。這一點特別重要,因為我們正在采用迭代和增量方法來改進整體架構和解耦服務。每個增量都必須讓我們更接近架構目標。使用進化架構適應度函數比喻,遷移的每個原子步驟之后的架構適應度函數應該產生更接近架構目標的價值。

讓我通過一個例子圖解這個觀點。假設微服務架構目的是增加開發者修改整個系統的速度交付價值。團隊決定解耦用戶認證到一個隔離的服務中,以 OAuth 2.0 協議為基礎。這個服務想要同時替換已有客戶端應用程序認證終端用戶,而且新的架構微服務驗證終端用戶。讓我們將這個進化過程中的增量稱為“Auth 服務介紹”。一個方法介紹新的服務是先通過以下步驟:
(1)構建 Auth 服務,實現 OAuth 2.0 協議。
(2)在單體系統中添加一個新的認證路徑並調用 Auth 服務認證終端用戶。

如果團隊在這里停下來並轉向構建一些其他服務或功能,他們會使整體架構處於熵增加的狀態。在這種狀態下,有兩種驗證用戶的方法,新的 OAuth 2.0 基本路徑和舊客戶端的基於密碼/會話的路徑。在這一點上,團隊實際上離他們更快地做出改變的總體目標更遠了。單體代碼的任何新開發人員都需要處理兩條代碼路徑,增加理解代碼的認知負擔,以及更慢的更改和測試過程。

團隊可以包含以下步驟到我們的原子進化單元中:
(1)通過 OAuth 2.0 替換老客戶端的密碼/session
(2)從單體系統中淘汰老的認證代碼

這時候我們可以說團隊已經接近目標架構了。

單體系統原子單元解構包括:

  • 解耦新服務
  • 重定向消費者到新的服務
  • 從單體系統中淘汰來的代碼

反模式:解耦新服務用於新的消費者並且從不淘汰老的服務。

我經常發現團隊終止從單體系統中遷移功能,新的功能開發出來以后馬上就宣布勝利了,也不淘汰老的代碼路徑,正如上面描述的反模式。主要原因是:(a)聚焦於引入新功能的短期利益(b)淘汰老的代碼會和構建新功能形成競爭優先級。為了做正確的事,我們應該盡可能的做原子步驟。

引用

How to break a Monolith into Microservices


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM