微服務(wù)遷移之旅

微服務(wù)拆分之道

——Zhamak Dehghani

原文

解耦何物,何時(shí)解耦

當(dāng)單體系統(tǒng)龐大到無(wú)法應(yīng)付時(shí),大多企業(yè)都會(huì)被迫把它們拆分成微服務(wù)。這是一次值得的旅行,但卻非坦途。我們學(xué)到了一些如何把這件事情做好的東西。我們需要從簡(jiǎn)單的服務(wù)入手,然后把對(duì)業(yè)務(wù)重要的經(jīng)常變化的垂直功能的服務(wù)挑出來(lái)。這些服務(wù)首先應(yīng)該是大的,而且最好不依賴(lài)單體系統(tǒng)的其他部分。我們應(yīng)該確保遷移的每一步都代表一個(gè)對(duì)整個(gè)架構(gòu)的原子性改善。

遷移單體系統(tǒng)到一個(gè)微服務(wù)生態(tài)系統(tǒng)將會(huì)是一場(chǎng)史詩(shī)搬的旅行。開(kāi)啟這段旅行的人們都希望能夠擴(kuò)大運(yùn)維的規(guī)模,加快變更的步伐以及避免變更的高昂代價(jià)。他們希望他們團(tuán)隊(duì)的數(shù)量獲得增長(zhǎng),同時(shí)可以彼此獨(dú)立并行的交付價(jià)值;他們希望快速試驗(yàn)核心業(yè)務(wù)功能,更快地交付價(jià)值;他們也希望避免因?qū)σ延袉误w系統(tǒng)的改變產(chǎn)生高成本。

把一個(gè)單體系統(tǒng)拆分進(jìn)微服務(wù)生態(tài)系統(tǒng),解耦什么功能,何時(shí)及如何增量遷移是架構(gòu)挑戰(zhàn)的一部分。在這篇文章中,我將分享一些技巧以指導(dǎo)交付團(tuán)隊(duì)——開(kāi)發(fā)人員、架構(gòu)師及技術(shù)經(jīng)理——在這次旅途中如何拆分服務(wù)。

為了闡明這些技巧,我要借用一個(gè)多層的在線(xiàn)零售系統(tǒng)。該系統(tǒng)把用戶(hù)接口,業(yè)務(wù)邏輯和數(shù)據(jù)層緊密結(jié)合在一起。我選擇這個(gè)例子的原因是因?yàn)樗募軜?gòu)具有許多運(yùn)行業(yè)務(wù)的單體應(yīng)用的特征,而且它采用的技術(shù)棧也是很先進(jìn)的,非常適合分解,而不是完全重寫(xiě)和替換。

目的地——微服務(wù)生態(tài)系統(tǒng)

在踏上旅途之前,擁有一個(gè)對(duì)微服務(wù)生態(tài)系統(tǒng)的一致的理解對(duì)我們來(lái)說(shuō)是關(guān)鍵的。微服務(wù)生態(tài)系統(tǒng)就是一個(gè)服務(wù)平臺(tái),其上的每個(gè)服務(wù)封裝了一個(gè)業(yè)務(wù)功能。業(yè)務(wù)功能代表在一個(gè)具體的域里面為了實(shí)現(xiàn)業(yè)務(wù)目標(biāo)和職責(zé)而做的事情。每個(gè)微服務(wù)暴露一個(gè)API,開(kāi)發(fā)者可以發(fā)現(xiàn)并以自服務(wù)方式使用它。微服務(wù)擁有獨(dú)立的生命周期。開(kāi)發(fā)者可以獨(dú)立的構(gòu)建、測(cè)試和發(fā)布每個(gè)微服務(wù)。微服務(wù)生態(tài)系統(tǒng)實(shí)施的組織結(jié)構(gòu)中,團(tuán)隊(duì)是自治的且長(zhǎng)期存在的,每個(gè)團(tuán)隊(duì)負(fù)責(zé)一個(gè)或多個(gè)服務(wù)。與人們普遍的認(rèn)知不同,與微服務(wù)這個(gè)詞中的‘微’字面意思不同,每個(gè)服務(wù)的大小并不重要,它會(huì)因組織運(yùn)維成熟度不同而變化。正如Martin Fowler所說(shuō):“微服務(wù)是一個(gè)標(biāo)簽而不是一種描述”。


Figure 1: Services encapsulate business capabilities, expose data and functionality through self-serve APIs

旅行指南

在開(kāi)始深入了解本指南之前,認(rèn)識(shí)到拆分已有系統(tǒng)到微服務(wù)總體成本會(huì)很高而且可能需要多次迭代很重要。開(kāi)發(fā)者和架構(gòu)師必須仔細(xì)評(píng)估是不是有必要分拆單體系統(tǒng),微服務(wù)本身是不是其真正的歸宿。講完這些,讓我們來(lái)看看本指南。

從簡(jiǎn)單,能合理解耦的功能開(kāi)始熱身

開(kāi)始微服務(wù)之旅需要一個(gè)最低級(jí)別的運(yùn)維就緒。它需要按需訪(fǎng)問(wèn)部署環(huán)境,創(chuàng)建新類(lèi)型的CI管道,獨(dú)立編譯,測(cè)試和部署可執(zhí)行的服務(wù)。同時(shí)它還需要為分布式系統(tǒng)提供安全,調(diào)試以及監(jiān)控的能力。無(wú)論是構(gòu)建全新(greenfield)服務(wù)還是分解已有系統(tǒng)都需要成熟的運(yùn)維就緒環(huán)境。關(guān)于運(yùn)維就緒的更多信息,可以閱讀Martin的關(guān)于微服務(wù)前置條件的文章。不過(guò),好消息是從Martin的那篇文章之后微服務(wù)架構(gòu)運(yùn)維技術(shù)得到快速的演進(jìn)。其中包括誕生了Service Mesh——一個(gè)具體的運(yùn)行快速、可靠及安全的微服務(wù)網(wǎng)格的基礎(chǔ)架構(gòu)層,提供更高層面的部署架構(gòu)抽象的容器編排系統(tǒng),像GoCD這樣的以容器的方式編譯,測(cè)試和部署微服務(wù)的CD系統(tǒng)。

我的建議是開(kāi)發(fā)和運(yùn)維團(tuán)隊(duì)構(gòu)建底層基礎(chǔ)架構(gòu),為第一個(gè)和第二個(gè)被分解出來(lái)或新構(gòu)建的服務(wù)構(gòu)建CI管道和API管理系統(tǒng)。我們從單體系統(tǒng)中能合理分解出來(lái)的功能開(kāi)始。因?yàn)?,他們不需要改變?cè)S多正在使用單體系統(tǒng)的面向客戶(hù)的應(yīng)用,可能也不需要數(shù)據(jù)存儲(chǔ)。在這個(gè)時(shí)候交付團(tuán)隊(duì)需要優(yōu)化他們交付方法,為團(tuán)隊(duì)成員提供技能培訓(xùn),建立最小化的基礎(chǔ)架構(gòu)來(lái)交付安全的可獨(dú)立部署的暴露自服務(wù)API的服務(wù)。比如,對(duì)于在線(xiàn)零售系統(tǒng),第一個(gè)服務(wù)可能是這個(gè)單體系統(tǒng)調(diào)用驗(yàn)證用戶(hù)的“終端用戶(hù)驗(yàn)證”服務(wù)。第二個(gè)可能是“用戶(hù)Profile”服務(wù), 它是一個(gè)面板服務(wù),為新的客戶(hù)應(yīng)用提供一個(gè)更好地消費(fèi)者視圖。

首先,我建議解耦簡(jiǎn)單的邊界服務(wù)。其次我們采用不同的方法來(lái)分解深度嵌入在單體系統(tǒng)中的功能。之所以建議一開(kāi)始分解邊界服務(wù)是因?yàn)樵谶@段旅行之初,交互團(tuán)隊(duì)最大的風(fēng)險(xiǎn)在于不能對(duì)微服務(wù)進(jìn)行合適的運(yùn)維。所以可以使用邊界服務(wù)來(lái)實(shí)踐他們所需的“運(yùn)維前提”。一旦他們解決了運(yùn)維先決條件相關(guān)的問(wèn)題,他們就能夠應(yīng)對(duì)拆分單體系統(tǒng)的關(guān)鍵問(wèn)題。


Figure 2: Warming up with a simple capability that has a small radius of change to build our operational readiness

最小化對(duì)單體系統(tǒng)的向后依賴(lài)

作為一個(gè)最基本的原則,交付團(tuán)隊(duì)?wèi)?yīng)該最小化新形成的微服務(wù)對(duì)原單體系統(tǒng)的依賴(lài)。微服務(wù)主要的一個(gè)優(yōu)勢(shì)在于具有一個(gè)快速且獨(dú)立的發(fā)布周期。與原單體系統(tǒng)的數(shù)據(jù)、邏輯或者API的依賴(lài)會(huì)把服務(wù)與單體系統(tǒng)耦合在一起,妨礙微服務(wù)優(yōu)勢(shì)的發(fā)揮。通常我們從單體系統(tǒng)分解出服務(wù)的原因是與其綁定的功能變更的高成本和低效率。所以,我們希望通過(guò)消除對(duì)這個(gè)單體系統(tǒng)的依賴(lài)逐步解耦這些核心功能。假如團(tuán)隊(duì)遵守這個(gè)原則構(gòu)建功能服務(wù),他們會(huì)發(fā)現(xiàn)依賴(lài)都是反向的從單體系統(tǒng)到服務(wù)。這是所期望的依賴(lài)方向,因?yàn)檫@不會(huì)影響新的服務(wù)的變更速度。

考慮在線(xiàn)零售系統(tǒng),“購(gòu)買(mǎi)”和“促銷(xiāo)”都是核心功能?!百?gòu)買(mǎi)”的功能結(jié)賬的時(shí)候會(huì)使用“促銷(xiāo)”功能根據(jù)用戶(hù)購(gòu)買(mǎi)的產(chǎn)品為他們提供有效的最佳優(yōu)惠。假如我們需要決定先解耦這兩個(gè)功能的哪一個(gè),我建議先解耦“促銷(xiāo)”,再解耦“購(gòu)買(mǎi)”。因?yàn)榘凑者@個(gè)順序我們就能夠減少對(duì)這個(gè)單體系統(tǒng)的依賴(lài)。按照這個(gè)順序,“購(gòu)買(mǎi)”起初還會(huì)維持在老的單體系統(tǒng)內(nèi),但卻是依賴(lài)新的“促銷(xiāo)”微服務(wù)。

下一個(gè)指導(dǎo)原則是關(guān)于開(kāi)發(fā)者決定解耦服務(wù)順序的其他方式。這意味著他們不總是能夠避免要反向依賴(lài)原單體系統(tǒng)。如果新的服務(wù)最終需要回調(diào)單體系統(tǒng),我建議從單體中暴露一個(gè)新的API,在新的服務(wù)中通過(guò)一個(gè)“防腐”層來(lái)調(diào)用該API以確保單體系統(tǒng)的概念不會(huì)浸染微服務(wù)。要盡量確保這個(gè)API按照定義良好的域慨念和結(jié)構(gòu)定義,即使單體系統(tǒng)內(nèi)部實(shí)現(xiàn)可能不同(注:該新的API可以理解成是一個(gè)適配器,它按照新的微服務(wù)定義良好的業(yè)務(wù)域概念設(shè)計(jì),但它需要實(shí)現(xiàn)單體系統(tǒng)中老的域概念到新的域概念的適配。通過(guò)這個(gè)API使得拆分出來(lái)的微服務(wù)對(duì)單體系統(tǒng)中舊的域概念透明,防止了舊的域概念“入侵”。該API起到了防腐層隔離的作用)。在這種不好的情況下,交付團(tuán)隊(duì)可能承受改變單體系統(tǒng)帶來(lái)的成本和難度的問(wèn)題,需要配合單體系統(tǒng)的發(fā)布一起測(cè)試發(fā)布的新的系統(tǒng)。

Figure 3: Decouple the service that doesn’t require a dependency back to the monolith first and minimize changes to the monolith

黏連性功能早拆分

假設(shè)現(xiàn)在交付團(tuán)隊(duì)可以很愉快的構(gòu)建微服務(wù)了,并且準(zhǔn)備好了解決棘手的問(wèn)題。然他們發(fā)現(xiàn)接下來(lái)的功能拆分不依賴(lài)單體系統(tǒng)是不可能的。存在這個(gè)問(wèn)題的根本原因常常是因?yàn)閱误w系統(tǒng)內(nèi)的功能域概念設(shè)計(jì)部完整,沒(méi)有良好定義,單體系統(tǒng)內(nèi)許多其他的功能依賴(lài)它。為了能夠繼續(xù),開(kāi)發(fā)者需要識(shí)別這類(lèi)黏連性功能,分解成定義良好的域概念,然后把這些域概念具體化到不同的服務(wù)中。

比如,在一個(gè)Web單體系統(tǒng)中“會(huì)話(huà)”的概念是最常用的耦合因子之一。 在那個(gè)在線(xiàn)零售的例子中,會(huì)話(huà)常常就是一個(gè)籃子,裝有許多屬性,從涉及不同域邊界的用戶(hù)偏好如發(fā)貨和支付設(shè)置等,到用戶(hù)的意圖和交互,如當(dāng)前訪(fǎng)問(wèn)頁(yè)面,點(diǎn)擊的產(chǎn)品和意向清單等。除非我們對(duì)當(dāng)前“會(huì)話(huà)”概念解耦,分解及具體化,否則我們將為解耦未來(lái)的諸多功能而疲于奔命,因?yàn)樗麄儠?huì)因?yàn)檫@個(gè)不嚴(yán)謹(jǐn)?shù)臅?huì)話(huà)概念和單體系統(tǒng)“糾纏”在一起。我也不鼓勵(lì)在單體系統(tǒng)之外創(chuàng)建“會(huì)話(huà)”服務(wù),因?yàn)樗仓粫?huì)導(dǎo)致和目前在單體進(jìn)程中存在的相似的緊密耦合。而且,情況會(huì)更糟糕,因?yàn)檫@種耦合是進(jìn)程外跨網(wǎng)絡(luò)的。

開(kāi)發(fā)人員可以從逐步從黏連性功能中提取出微服務(wù),一次一個(gè)。比如, 重構(gòu)“消費(fèi)者意向清單”,并提取出來(lái)創(chuàng)建一個(gè)新的服務(wù),然后重構(gòu)“消費(fèi)者支付偏好”,產(chǎn)生一個(gè)新的微服務(wù)。如此重復(fù)。


Figure 4: Identify the most coupling concept and decouple, deconstruct and reify into concrete domain services

使用依賴(lài)和結(jié)構(gòu)化代碼分析工具,比如Structure101,來(lái)識(shí)別單體系統(tǒng)中耦合度最高,約束最大的功能因子。

垂直拆分并盡早切分?jǐn)?shù)據(jù)

從單體中把功能拆分出來(lái)主要是為了能夠單獨(dú)發(fā)布這些功能。第一個(gè)原則就是指導(dǎo)開(kāi)發(fā)人員進(jìn)行如何拆分。單體系統(tǒng)經(jīng)常是由緊密集成的多層,甚至是彼此依賴(lài)脆弱而且需要同時(shí)發(fā)布的多個(gè)(子)系統(tǒng)組成。比如,在線(xiàn)零售系統(tǒng)是由一個(gè)或多個(gè)面向客戶(hù)的在線(xiàn)購(gòu)物應(yīng)用,一個(gè)實(shí)現(xiàn)許多業(yè)務(wù)功能的后端系統(tǒng)以及用于保存狀態(tài)的中心化集成的數(shù)據(jù)存儲(chǔ)組成。

許多拆分都是企圖從提取出面向用戶(hù)的組件入手,同時(shí)拆分出幾個(gè)面板服務(wù)為開(kāi)發(fā)者提供友好的新式用戶(hù)界面的API。然而,數(shù)據(jù)依然維持在一個(gè)schema和一個(gè)存儲(chǔ)系統(tǒng)中。這種方法可以快速獲勝,比如可以更頻繁的改變用戶(hù)界面。但是當(dāng)設(shè)計(jì)核心功能時(shí),交互團(tuán)隊(duì)只能以單體系統(tǒng)和其數(shù)據(jù)存儲(chǔ)變更最慢的部分效率進(jìn)行。簡(jiǎn)單來(lái)說(shuō),數(shù)據(jù)沒(méi)有拆分,就不是微服務(wù)架構(gòu)。把所有數(shù)據(jù)保存在同一個(gè)數(shù)據(jù)存儲(chǔ)里也是不符合去中心化數(shù)據(jù)管理微服務(wù)特征。

為此,我們的策略就是把功能垂直移出,將核心功能和數(shù)據(jù)解耦,并且把所有前端應(yīng)用都重定向到新的服務(wù)API。

多個(gè)應(yīng)用共享中心化數(shù)據(jù)是切分拆分后服務(wù)所使用的數(shù)據(jù)的主要障礙。交付團(tuán)隊(duì)需要采用適合他們環(huán)境的數(shù)據(jù)遷移策略,這取決于他們是否能夠同時(shí)重定向和遷移所有的數(shù)據(jù)讀寫(xiě)者。Stripe的四階段數(shù)據(jù)遷移策略是其中之一,它被應(yīng)用到要求逐步遷移與數(shù)據(jù)庫(kù)集成的應(yīng)用,同時(shí)所有系統(tǒng)都可以持續(xù)運(yùn)行的環(huán)境中。

Figure 5: Decouple capability with its data to a microservice exposing a new interface, modify and redirect consumers to the new API

避免僅僅拆分前端用戶(hù)面板和后端服務(wù)而不拆分?jǐn)?shù)據(jù)。

拆分重要且頻繁變化的業(yè)務(wù)

把功能從單體系統(tǒng)中拆分出來(lái)是件困難的事情。我聽(tīng)到過(guò)Neal Ford用周密的器官手術(shù)對(duì)此做過(guò)類(lèi)比。在在線(xiàn)零售應(yīng)用中,拆分出一個(gè)功能需要把該功能涉及的數(shù)據(jù)、邏輯和用戶(hù)接口組件仔細(xì)地剝離出來(lái),然后重定向到新的服務(wù)中。這需要花費(fèi)不少的工作量,開(kāi)發(fā)者需要基于他們獲得的好處,比如提高效率,擴(kuò)大規(guī)模等持續(xù)評(píng)估拆分的成本。舉例來(lái)說(shuō),如果交互團(tuán)隊(duì)目標(biāo)是加速對(duì)已有的被困于單體系統(tǒng)中功能的修改,那么他們必須識(shí)別出一直被修改的最多的功能然后把它拿出來(lái)。把那些不斷變化的代碼分離出來(lái)。這些代碼正在獲得開(kāi)發(fā)人員大量“關(guān)愛(ài)”,但又在束縛他們快速發(fā)布價(jià)值。交互團(tuán)隊(duì)可以分析代碼提交模式找出以往變化最多的代碼, 然后結(jié)合產(chǎn)品路線(xiàn)圖及其組合來(lái)了解最期望的在不久的將來(lái)亦最受關(guān)注的功能。開(kāi)發(fā)人員需要與業(yè)務(wù)和產(chǎn)品經(jīng)理來(lái)交談以了解對(duì)他們來(lái)說(shuō)真正重要的差異化的功能。

拿在線(xiàn)零售系統(tǒng)的例子來(lái)說(shuō),“客戶(hù)個(gè)性化”功能需要反復(fù)進(jìn)行試驗(yàn)以便為客戶(hù)提供最好的體驗(yàn)。所以它是一個(gè)好的拆分候選項(xiàng)。它是一項(xiàng)對(duì)業(yè)務(wù),對(duì)用戶(hù)體驗(yàn)很重要而且會(huì)頻繁變化的功能。

Figure 6: Identify and decouple the capability that matters most: creates most value for business and customer, while changing regularly

使用社交代碼分析工具如CodeScene發(fā)現(xiàn)最活躍的組件。如果編譯系統(tǒng)在每次代碼提交的時(shí)候恰好會(huì)觸及或者自動(dòng)產(chǎn)生代碼,請(qǐng)確保過(guò)濾掉了這些“雜音信號(hào)”。把這些頻繁變化的代碼和產(chǎn)品路線(xiàn)圖即將發(fā)生的變更相對(duì)應(yīng)然后找出需要拆分的交叉點(diǎn)。

拆分功能,而不是代碼

不管什么時(shí)候,開(kāi)發(fā)人員想從已有系統(tǒng)里頭剝離服務(wù)有兩種方式:代碼萃取和功能重寫(xiě)。

通常缺省的情況下,服務(wù)提取或者單體分解被假定為重用原來(lái)已有的實(shí)現(xiàn)并把它摘取出來(lái)放入單獨(dú)的服務(wù)中。這樣做的部分原因是我們對(duì)自己設(shè)計(jì)和編寫(xiě)的代碼存在認(rèn)知偏差。不管過(guò)程如何痛苦結(jié)果如何不完美,付出的勞動(dòng)會(huì)使我們對(duì)代碼產(chǎn)生感情。事實(shí)上,這被稱(chēng)作為宜家效應(yīng)。不幸的是,這種認(rèn)知偏差將會(huì)妨礙單體系統(tǒng)分解的工作。它會(huì)導(dǎo)致開(kāi)發(fā)人員,甚至包括技術(shù)經(jīng)理即使萃取成本高但價(jià)值低也要重用代碼。

作為一種選擇,交互團(tuán)隊(duì)可以重寫(xiě)功能,而廢除老的代碼。重寫(xiě)為他們提供一個(gè)機(jī)會(huì)來(lái)重新審視業(yè)務(wù)功能,啟動(dòng)與業(yè)務(wù)會(huì)話(huà),簡(jiǎn)化其往業(yè)務(wù)流程,挑戰(zhàn)隨著時(shí)間推移在老的系統(tǒng)形成的陳舊的假設(shè)和約束。這也為技術(shù)更新,為使用對(duì)具體服務(wù)最適合的技術(shù)棧及編程語(yǔ)言實(shí)現(xiàn)服務(wù)的機(jī)會(huì)。

就在線(xiàn)零售系統(tǒng)來(lái)說(shuō),“定價(jià)”和“促銷(xiāo)”功能是一塊復(fù)雜的智能化代碼。它能夠動(dòng)態(tài)配置應(yīng)用定價(jià)促銷(xiāo)規(guī)則,基于諸如消費(fèi)者行為、忠誠(chéng)度及產(chǎn)品包等各種因素提供折扣和優(yōu)惠。

上述功能比較適合代碼萃取重用。然而,“用戶(hù)Profile”是一個(gè)簡(jiǎn)單的CRUD功能,其主要由系樣板式的系列化,處理存儲(chǔ)和配置的代碼組成。所以它比較適合棄用老代碼而重寫(xiě)。

根據(jù)我的經(jīng)驗(yàn),在大部分分解場(chǎng)景中,團(tuán)隊(duì)最好重新實(shí)現(xiàn)新的功能而廢棄老的代碼。但因?yàn)橄旅娴脑?,可以考慮高成本低價(jià)值的重用:

存在大量的處理環(huán)境依賴(lài)的樣板式代碼,比如在運(yùn)行時(shí)訪(fǎng)問(wèn)應(yīng)用配置,訪(fǎng)問(wèn)數(shù)據(jù),緩存,或者是使用老的框架構(gòu)建的。這些樣板式代碼大部分需要重寫(xiě)。而新的運(yùn)行微服務(wù)的框架與十幾年前的老框架差別巨大,需要使用幾乎不同的樣板式代碼;

很有可能已有的功能不是圍繞清晰的域慨念構(gòu)建的。這會(huì)導(dǎo)致沒(méi)有反映新的域模型而需要進(jìn)行大重構(gòu)的數(shù)據(jù)結(jié)構(gòu)傳輸和存儲(chǔ);

長(zhǎng)期遺留的代碼,經(jīng)歷了許多次變更迭代,可能具有很高級(jí)別的代碼毒性和較低的重用價(jià)值。

除非是關(guān)聯(lián)的,和清晰的域概念保持一致的,具有高知識(shí)產(chǎn)權(quán)的功能,我強(qiáng)烈建議對(duì)其重寫(xiě),棄用老的代碼。


Figure 7: Reuse and Extract high value code with low toxicity, Rewrite and Retire low value code with high toxicity

使用代碼毒性分析工具如CheckStyle來(lái)決定重用還是重寫(xiě)

先大后小

從遺留的單體系統(tǒng)中尋找域邊界是一門(mén)藝術(shù)也是一門(mén)科學(xué)。作為一般規(guī)則,應(yīng)用域驅(qū)動(dòng)技術(shù)尋找定義微服務(wù)邊界的“有界上下文”是一個(gè)很好的起點(diǎn)。我承認(rèn),我經(jīng)常看到從大的單體到真正小的服務(wù)的“矯枉過(guò)正”。設(shè)計(jì)這些小服務(wù)是受已有的規(guī)范化的數(shù)據(jù)視圖啟發(fā)和驅(qū)動(dòng)的。這種識(shí)別服務(wù)邊界的方法幾乎總會(huì)圍繞創(chuàng)建、讀取、修改和刪除資源產(chǎn)生大量的弱服務(wù),從而形成“寒武紀(jì)生命大爆炸”。對(duì)于微服務(wù)架構(gòu)的新手來(lái)說(shuō),這會(huì)產(chǎn)生一個(gè)高摩擦的環(huán)境,最終導(dǎo)致無(wú)法使那些服務(wù)進(jìn)行獨(dú)立發(fā)布和運(yùn)行。這會(huì)創(chuàng)建一個(gè)難于調(diào)試的分布式系統(tǒng),一個(gè)跨事務(wù)因而難于保持一致性的分布式系統(tǒng),一個(gè)對(duì)于相對(duì)組織運(yùn)維成熟度來(lái)說(shuō)過(guò)于復(fù)雜的系統(tǒng)。盡管有一些關(guān)于微服務(wù)應(yīng)該多“微”的探討,如團(tuán)隊(duì)的大小,重寫(xiě)服務(wù)的時(shí)間以及什么樣的行為必須封裝等,我的建議是微服務(wù)的大小取決于交互運(yùn)維團(tuán)隊(duì)能夠獨(dú)立發(fā)布,監(jiān)控和運(yùn)維多少服務(wù)。開(kāi)始可以是圍繞邏輯域概念的大服務(wù),當(dāng)團(tuán)隊(duì)運(yùn)維就緒的話(huà),再分拆成多個(gè)服務(wù)。

就分解在線(xiàn)零售系統(tǒng)來(lái)說(shuō),開(kāi)發(fā)人員開(kāi)始可以使“購(gòu)買(mǎi)”服務(wù)既包含包含購(gòu)物袋上下文功能也包含購(gòu)買(mǎi)的功能,比如“結(jié)賬”。隨著他們能夠成立更小的團(tuán)隊(duì),能夠發(fā)布大量的服務(wù),他們可以把“購(gòu)物袋”分離出一個(gè)單獨(dú)的服務(wù)。


Figure 8: Decouple macro services around rich domain concepts and when ready, breakdown services to smaller domain concepts

使用Richardson Maturity Model L3(REST成熟度模型)和超鏈接來(lái)確保未來(lái)的服務(wù)拆分不會(huì)影響調(diào)用者。比如,調(diào)用者能夠發(fā)現(xiàn)如何結(jié)賬而不需要提前知道。

以原子性演進(jìn)方式遷移

傳統(tǒng)的單體應(yīng)用被分解成設(shè)計(jì)優(yōu)美的微服務(wù),然后讓單體應(yīng)用消失的無(wú)影蹤的想法有點(diǎn)荒唐,可以說(shuō)是不可取的。任何經(jīng)驗(yàn)豐富的工程師都可以分享一些關(guān)于嘗試遺留系統(tǒng)遷移和改進(jìn)的故事。這些嘗試都是在抱著對(duì)最終完成過(guò)分樂(lè)觀(guān)的狀態(tài)下計(jì)劃和開(kāi)始的,但大部分都是在足夠好的時(shí)間點(diǎn)被放棄了。這些長(zhǎng)期努力的計(jì)劃之所以取消是因?yàn)榍闆r發(fā)生了一些大的變化,比如項(xiàng)目費(fèi)用用完了;組織目標(biāo)轉(zhuǎn)移到其他方向上了;支持的領(lǐng)導(dǎo)層離職了。所以現(xiàn)實(shí)的做法是如何讓單體應(yīng)用踏上微服務(wù)之旅。我們稱(chēng)之為“架構(gòu)原子演進(jìn)式遷移”,這種方式的遷移每一步都是完整的,也是可以回撤。這一點(diǎn),在我們談到針對(duì)整個(gè)架構(gòu)的改善和服務(wù)解耦的增量迭代的方法時(shí)尤為重要。每個(gè)遷移增量必須使我們更好地朝架構(gòu)目標(biāo)前進(jìn)一步。借用“演進(jìn)式架構(gòu)”適應(yīng)度函數(shù)說(shuō)法,每一次原子性遷移之后,架構(gòu)適應(yīng)度函數(shù)應(yīng)該產(chǎn)生一個(gè)更接近于架構(gòu)目標(biāo)的值。

讓我來(lái)使用一個(gè)例子描述下這點(diǎn)。 想象一下,微服務(wù)架構(gòu)目標(biāo)是提高開(kāi)發(fā)人員修改整體系統(tǒng),交付價(jià)值的速度。團(tuán)隊(duì)決定基于OAuth2.0協(xié)議把終端用戶(hù)驗(yàn)證功能解耦到一個(gè)獨(dú)立服務(wù)中。這個(gè)服務(wù)用來(lái)替換已存在的(老的架構(gòu)中的)客戶(hù)端應(yīng)用用戶(hù)驗(yàn)證功能和作為新的微服務(wù)架構(gòu)中的用戶(hù)驗(yàn)證。讓我們把這個(gè)演進(jìn)式的增量稱(chēng)之為“驗(yàn)證服務(wù)引入”。引入該服務(wù)的一個(gè)方式就是先完成這些步驟:

(1) 構(gòu)建Auth服務(wù), 實(shí)現(xiàn)OAuth 2.0協(xié)議。

(2)增加一條新的驗(yàn)證路徑到單體系統(tǒng)的后端調(diào)用新構(gòu)建的Auth服務(wù),處理終端用戶(hù)請(qǐng)求。

假如團(tuán)隊(duì)到此為止,轉(zhuǎn)而去構(gòu)建其他的服務(wù)和功能,這會(huì)使得整個(gè)架構(gòu)處于熵增狀態(tài)。在這種情況下,有兩種方法驗(yàn)證用戶(hù),一種是通過(guò)新的OAuth 2.0,一種是通過(guò)老的用戶(hù)密碼/會(huì)話(huà)。這個(gè)時(shí)候,實(shí)際上團(tuán)隊(duì)離快速變化的總的目標(biāo)更遠(yuǎn)了。任何新的單體代碼開(kāi)發(fā)人員都需要處理兩條代碼路徑,增加了他們理解代碼的認(rèn)知負(fù)擔(dān),降低了修改測(cè)試代碼的速度。

然而,作為我們的一個(gè)原子性演進(jìn)單元,團(tuán)隊(duì)可以增加下面的步驟:

(3)替換老的用戶(hù)驗(yàn)證調(diào)用

(4)去除單體系統(tǒng)中老的用戶(hù)驗(yàn)證代碼

至此,我們可以說(shuō)團(tuán)隊(duì)已經(jīng)接近架構(gòu)目標(biāo)了。


Figure 9: Evolve the architecture towards microservices with atomic steps of architecture evolution where after each step the overall architecture is improved towards its goal even though intermediary code changes might take it further away from its fitness objective

單體拆分的一個(gè)原子單元包括:

拆分新的服務(wù)

重定向所有消費(fèi)者(服務(wù)消費(fèi)者)到新的用戶(hù)

移除單體系統(tǒng)中老的代碼

反模式:解耦新的服務(wù),新的消費(fèi)者使用新的服務(wù),但老的代碼永遠(yuǎn)不退休。

我經(jīng)常發(fā)現(xiàn)團(tuán)隊(duì)只構(gòu)建新的功能但不讓老的代碼退休就結(jié)束一個(gè)從單體系統(tǒng)功能的遷移,然后宣布全部完成。這就是上面描述的反模式。之所以存在這種情況的原因是,a)只關(guān)注引入一個(gè)新功能的短期好處;b)退休老的代碼所需要總的工作量和構(gòu)建新服務(wù)的優(yōu)先級(jí)有競(jìng)爭(zhēng)。為了做正確的事情,我們要盡可能的努力按原子性步驟去做。

使用這種遷移方法,我們可以把遷移之旅分成一段段更短的旅途,能夠安穩(wěn)的停下,然后重新開(kāi)始,確保整個(gè)旅行的順利,最終“消滅”單體。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容