微服務(wù)被認(rèn)為是一種理想的架構(gòu)模式,因此,Steven Lemon 所在公司的領(lǐng)導(dǎo)層決定從單體架構(gòu)向微服務(wù)架構(gòu)遷移,這讓整個(gè)開發(fā)團(tuán)隊(duì)在隨后的的日子里苦不堪言,七大現(xiàn)實(shí)問題擺在面前無法解決,微服務(wù)架構(gòu)的好處也沒有享受到,并發(fā)現(xiàn)這不單單是一個(gè)技術(shù)問題。最終,整個(gè)團(tuán)隊(duì)決定放棄。
最近,我所在的開發(fā)團(tuán)隊(duì)在緊張的交付周期結(jié)束后,有了短暫的休息機(jī)會。領(lǐng)導(dǎo)層認(rèn)為可以利用這段時(shí)間將單體架構(gòu)遷移至微服務(wù)。經(jīng)過一個(gè)月的調(diào)研和準(zhǔn)備之后,我們最終放棄遷移計(jì)劃,繼續(xù)使用原先的單體架構(gòu)。在我們看來,微服務(wù)不僅不會幫到我們,反而會對開發(fā)流程造成嚴(yán)重影響。
微服務(wù)被認(rèn)為是一種理想的架構(gòu)模式,但并不適合我們。我們公司的情況是這樣的:一共有 200 多名開發(fā)人員,不過我們團(tuán)隊(duì)只有 5 個(gè)人,大概有 5% 的后端開發(fā)工作涉及公司層面的單體系統(tǒng),這是一個(gè)巨大的 C#應(yīng)用程序。剩下的時(shí)間,我們開發(fā)了自己的兩個(gè) Node 服務(wù)。
我們團(tuán)隊(duì)負(fù)責(zé)的這兩個(gè)服務(wù)都很小巧,完全可以控制開發(fā)、架構(gòu)和部署整個(gè)流程。遇到性能問題時(shí),我們會把生產(chǎn)環(huán)境中的實(shí)例數(shù)量增加一倍,直到把底層問題解決。我們幾乎不與其他團(tuán)隊(duì)合作,因?yàn)檫@些服務(wù)是用 TypeScript 開發(fā)的,所以我們團(tuán)隊(duì) (主要是前端開發(fā)人員) 能夠在前端和后端使用相同的編程語言。最重要的是,我們可以在客戶端及后端的驗(yàn)證和報(bào)告服務(wù)中包含復(fù)雜的規(guī)則計(jì)算引擎??偠灾?,我們整個(gè)團(tuán)隊(duì)專注于特定業(yè)務(wù)。
首先需要聲明:我們不喜歡開發(fā)單體系統(tǒng),因?yàn)樵黾有鹿δ?、編譯和運(yùn)行測試都很慢,而且架構(gòu)經(jīng)常發(fā)生變化,在構(gòu)建過程中總會出現(xiàn)難以預(yù)料的東西。所以,當(dāng)領(lǐng)導(dǎo)提出遷移至微服務(wù)架構(gòu)時(shí),我們也同意了。
然而, 在整個(gè)調(diào)研過程中,我們發(fā)現(xiàn)了如下七大難以解決的問題,這些問題讓我們最終選擇放棄。
我們的整體應(yīng)用程序是一個(gè)建立在外部產(chǎn)品之上的自定義 UI 層,集成了自定義業(yè)務(wù)規(guī)則,并提供了用于交互的界面??蛻舳耸且粋€(gè) UWP 應(yīng)用程序,還有一些后端服務(wù),用于在我們和第三方域之間進(jìn)行轉(zhuǎn)換。
因?yàn)榇罅恳蕾嚨谌剑覀冊谶M(jìn)行微服務(wù)劃分時(shí)遇到了一些問題,例如,為了讓第三方的某個(gè)域起作用,應(yīng)用程序必須在域之間做一些轉(zhuǎn)換工作,讓第三方域看起來像是我們的域的一部分。如果前端和第三方之間只有一個(gè)服務(wù),那么這種轉(zhuǎn)換還是可行的。當(dāng)我們試圖將域劃分成多個(gè)獨(dú)立的微服務(wù)時(shí),域之間的轉(zhuǎn)換工作帶來了很多麻煩。比如,微服務(wù)劃分是否要跟第三方保持一致,并在兩邊服務(wù)之間重復(fù)實(shí)現(xiàn)前端需求?或者根據(jù)自己的原則來劃分微服務(wù),并通過一個(gè)微服務(wù)從第三方的多個(gè)域獲取數(shù)據(jù)?這兩種方法都違反了微服務(wù)原則,并且會導(dǎo)致額外的耦合。
此外,我們經(jīng)常要與外部方協(xié)同工作,因?yàn)橐恍┨匦砸箅p方都做出改動。實(shí)際上,外部方成了我們之外的另一個(gè)開發(fā)團(tuán)隊(duì)。如此緊密的合作意味著我們的發(fā)布流程必須與他們保持同步。微服務(wù)的一個(gè)好處是每個(gè)團(tuán)隊(duì)都可以獨(dú)立發(fā)布自己的服務(wù),不需要與其他團(tuán)隊(duì)進(jìn)行協(xié)調(diào),但跨團(tuán)隊(duì)甚至是跨公司的協(xié)調(diào)發(fā)布流程導(dǎo)致我們無法享受到這些好處。
微服務(wù)的核心思想之一是打破“不同層由不同團(tuán)隊(duì)負(fù)責(zé)開發(fā)”的模式。在微服務(wù)架構(gòu)中,每個(gè)團(tuán)隊(duì)需要處理與其業(yè)務(wù)相關(guān)的整個(gè)技術(shù)棧。對于我們來說,因?yàn)橥獠糠绞且患彝耆?dú)立的公司,所以進(jìn)行這種重構(gòu)是不現(xiàn)實(shí)的。
我們無法在單體系統(tǒng)中找到可以被明顯拆分成微服務(wù)的部分。于是,我們隨意挑了幾個(gè)領(lǐng)域模型,得到了一個(gè)需要創(chuàng)建的微服務(wù)清單,但在著手調(diào)研時(shí),我們發(fā)現(xiàn)這些微服務(wù)之間存在很多共享的業(yè)務(wù)邏輯和隱式耦合。我們又進(jìn)一步嘗試將這些微服務(wù)再細(xì)分為更小的服務(wù),但這樣卻帶來了更多耦合、無處不在的消息總線,以及潛在的通信大爆炸——一個(gè)服務(wù)需要與十個(gè)甚至更多的微服務(wù)通信。
這些微服務(wù)之所以耦合得很厲害而且難以拆分,主要是因?yàn)槲覀冊鹊膯误w架構(gòu)只為一個(gè)業(yè)務(wù)提供服務(wù)。UI 應(yīng)用程序的主要設(shè)計(jì)目標(biāo)是將第三方基礎(chǔ)應(yīng)用程序的數(shù)據(jù)聚合在一起。為了方便用戶,我們創(chuàng)建了跨領(lǐng)域的工作流,將分散的功能聚合在一起。
在整個(gè)過程,我們并沒有正確理解應(yīng)該怎樣拆分微服務(wù),而且低估了正確選擇微服務(wù)邊界的重要性。如果按照我們的方式來拆分,那么實(shí)現(xiàn)一個(gè)標(biāo)準(zhǔn)的功能需要同時(shí)修改多個(gè)微服務(wù)。每個(gè)功能都需要不同的微服務(wù)團(tuán)隊(duì)參與開發(fā),這就導(dǎo)致單個(gè)微服務(wù)無法只由某個(gè)團(tuán)隊(duì)負(fù)責(zé)。
我們大約有 12 名開發(fā)人員在做這件事情,分布在兩個(gè)功能團(tuán)隊(duì)和一個(gè)支持團(tuán)隊(duì)。但我們負(fù)責(zé)的工作是波動的,一個(gè)團(tuán)隊(duì)不會只負(fù)責(zé)開發(fā)應(yīng)用程序的某個(gè)部分,兩個(gè)團(tuán)隊(duì)同時(shí)修改同一處代碼的情況并不少見,所以不能將某個(gè)微服務(wù)的所有權(quán)賦予某個(gè)團(tuán)隊(duì)。
康威定律指出,軟件架構(gòu)將以一種與組織和團(tuán)隊(duì)結(jié)構(gòu)類似的方式增長。如果有很多獨(dú)立的團(tuán)隊(duì),這些團(tuán)隊(duì)可以負(fù)責(zé)不同的業(yè)務(wù)關(guān)注點(diǎn),那么可以考慮采用微服務(wù)架構(gòu)。但是,如果只有很少的團(tuán)隊(duì),并且開發(fā)的是同樣的功能,那么還是不要這么做。
因?yàn)橛懈鞣N各樣的問題,至少在 6 個(gè)月內(nèi),我們必須同時(shí)部署舊的單體應(yīng)用和新開發(fā)的微服務(wù),無法使用與微服務(wù)相關(guān)的工具,例如容器、Kubernetes、服務(wù)總線、API 網(wǎng)關(guān)等。沒有這些工具,微服務(wù)之間的通信變得更加困難。
因此,我們在每個(gè)微服務(wù)中都包含了共享邏輯。因?yàn)槲茨苷_拆分,所以出現(xiàn)了很多重復(fù)工作。例如,有一個(gè)特別復(fù)雜但很重要的業(yè)務(wù)邏輯,我們不得不在四個(gè)微服務(wù)中復(fù)制、粘貼和維護(hù)。
開發(fā)團(tuán)隊(duì)只對接下來 6 個(gè)月做什么有粗略想法,除此之外就沒有其他東西了。業(yè)務(wù)經(jīng)常發(fā)生變化,需求突然發(fā)生變化的情況并不少見。這種不確定性使微服務(wù)的開發(fā)變得更加困難,因?yàn)闊o法預(yù)測會出現(xiàn)什么新狀況。例如,微服務(wù)之間的關(guān)系和耦合會一直增長嗎?幾個(gè)月后,我們需不需要花時(shí)間把它們重新連接在一起?
今年早些時(shí)候,我們已經(jīng)試著進(jìn)行微服務(wù)概念驗(yàn)證,但隨著業(yè)務(wù)需求的變化,這被否決了。
時(shí)間安排得很緊,領(lǐng)導(dǎo)留的那點(diǎn)時(shí)間也就夠把單體拆分成計(jì)劃好的微服務(wù),根本沒有多余時(shí)間反思自己做了什么或者在必要時(shí)調(diào)整方向。我們在計(jì)劃階段就發(fā)現(xiàn)了很多問題和挑戰(zhàn),在實(shí)現(xiàn)階段就更不用說了,開發(fā)團(tuán)隊(duì)因此陷入一片焦灼。
除了面臨風(fēng)險(xiǎn)和時(shí)間壓力外,負(fù)責(zé)設(shè)計(jì)和實(shí)現(xiàn)微服務(wù)的人之前沒有相關(guān)經(jīng)驗(yàn)。由于沒有足夠的標(biāo)準(zhǔn)工具可用,導(dǎo)致情況更加惡化,我們不得不自己實(shí)現(xiàn)基礎(chǔ)設(shè)施平臺。在與一些有微服務(wù)經(jīng)驗(yàn)但沒有參與我們項(xiàng)目的人交流之后,引發(fā)了更大的恐慌。他們建議的基礎(chǔ)設(shè)施我們沒有,他們還指出了我們在領(lǐng)域模型之間劃分界限可能帶來的后果。
到目前為止,我們的計(jì)劃中包含了很多不得已的妥協(xié),多少都偏離了標(biāo)準(zhǔn)的微服務(wù)模式。時(shí)間緊迫,沒有專家指導(dǎo),犯錯成了家常便飯,我們以極高的代價(jià)換來血的教訓(xùn)。
當(dāng)這些事情變得越來越困難,清晰的前行之路開始變得模糊。我們才意識到當(dāng)初都不知道為什么要做這些事情,我們沒有列出痛點(diǎn)是什么,也不知道做這些事情是否可以解決問題。更糟糕的是,微服務(wù)可能會帶來新的問題。
我們開始分析這些問題:應(yīng)該從重構(gòu)中得到什么好處?要解決什么問題?我們試圖通過無休止的會議搞清楚這些問題。在每一次休息時(shí)間,開發(fā)人員之間的每一次對話都在討論和質(zhì)疑微服務(wù),但仍然無法得到答案。
事實(shí)證明,相比于微服務(wù),我們確實(shí)有其他更緊迫的痛點(diǎn)需要解決,只是這些痛點(diǎn)在我們考慮遷移到微服務(wù)的過程中被忽視了,但我們沒有足夠的時(shí)間來解決這些痛點(diǎn),所以我們最后既沒有得到微服務(wù)的好處,也沒能解決其他痛點(diǎn)。
在意識到并不清楚采用微服務(wù)的目的之后,我們開始研究微服務(wù)能夠帶來哪些好處。
在采用微服務(wù)架構(gòu)時(shí),開發(fā)團(tuán)隊(duì)擁有交付特性所需的整個(gè)技術(shù)棧的控制權(quán),好處是可以減少與其他團(tuán)隊(duì)之間的協(xié)調(diào)工作,互不影響。
在采用單體架構(gòu)時(shí),開發(fā)任務(wù)的分配是不固定的,任何人都有可能分配到任意的任務(wù)。但如果每個(gè)團(tuán)隊(duì)可以擁有自己的服務(wù),就可以在特定業(yè)務(wù)領(lǐng)域積累專業(yè)知識,理解特定領(lǐng)域的業(yè)務(wù)規(guī)則和需求。他們對自己的技術(shù)棧十分了解,在做出變更時(shí)更有信心。
在采用微服務(wù)時(shí),開發(fā)者可以根據(jù)每個(gè)服務(wù)的性能需求進(jìn)行伸縮。而在采用單體架構(gòu)時(shí),雖然也可通過添加更多服務(wù)器進(jìn)行水平伸縮,但卻不能讓單體的每個(gè)組件進(jìn)行獨(dú)立伸縮。此外,細(xì)粒度的微服務(wù)可以更容易根據(jù)需要進(jìn)行垂直伸縮。例如,有時(shí)可能希望多處理一些負(fù)載,而在處理性能問題時(shí)又需要一些額外的機(jī)會。
如果回滾某個(gè)功能,只需要修改單個(gè)微服務(wù)就可以直接回滾,不會影響其他團(tuán)隊(duì)的工作。此外,微服務(wù)架構(gòu)有助于降低因單個(gè)微服務(wù)故障導(dǎo)致整個(gè)系統(tǒng)宕機(jī)的風(fēng)險(xiǎn)。
如果是一個(gè)大型系統(tǒng),每次版本發(fā)布都很耗時(shí),且伴隨著風(fēng)險(xiǎn)。回歸測試需要覆蓋很多東西,從而限制發(fā)布節(jié)奏。開發(fā)人員可能需要經(jīng)過很多人準(zhǔn)許,并在所有參與版本發(fā)布的團(tuán)隊(duì)之間進(jìn)行協(xié)調(diào)。微服務(wù)縮小了變更范圍,減少了團(tuán)隊(duì)之間的協(xié)調(diào)工作量。開發(fā)團(tuán)隊(duì)可以根據(jù)自己的時(shí)間表發(fā)布版本,而不是被一個(gè)整體的節(jié)奏所束縛。
微服務(wù)的各個(gè)團(tuán)隊(duì)可以為要解決的問題選擇最合適的技術(shù),可以使用最新的技術(shù),而單體系統(tǒng)則很難升級,只能停留在過時(shí)的技術(shù)平臺上。
給大型應(yīng)用程序升級框架從來都不是件有趣的事情,而且通常都伴有風(fēng)險(xiǎn)。當(dāng)需要在多個(gè)團(tuán)隊(duì)間協(xié)調(diào)相互關(guān)聯(lián)的變更時(shí),一切都變得更加困難。而在采用微服務(wù)架構(gòu)時(shí),可以只升級必要服務(wù),每次只讓一個(gè)團(tuán)隊(duì)升級一個(gè)服務(wù)。
應(yīng)用程序的不同部分以不同的速度發(fā)生變化,大部分組件可能幾個(gè)月甚至幾年都不需要改動,將很少發(fā)生變化的代碼與頻繁發(fā)生變化的代碼分開可以降低意外回歸帶來的風(fēng)險(xiǎn)。
較小的服務(wù)更容易理解。一個(gè)服務(wù)只由一個(gè)團(tuán)隊(duì)負(fù)責(zé)開發(fā),服務(wù)的設(shè)計(jì)風(fēng)格就能夠保持一致。小巧的微服務(wù)更容易進(jìn)行重構(gòu)。相比之下,單體架構(gòu)可能會出現(xiàn)不一致,因?yàn)殡S著時(shí)間的推移,不同的團(tuán)隊(duì)會向單體系統(tǒng)中加入不一樣的設(shè)計(jì)想法。
采用微服務(wù)有很多潛在的好處,但我們能撈到這些好處嗎?
最終,單體架構(gòu)中無法變更的部分和我們不得不做出的妥協(xié)導(dǎo)致無法獲得這些好處。開發(fā)團(tuán)隊(duì)需要在不同的微服務(wù)之間協(xié)調(diào),一些功能分散在多個(gè)共享的微服務(wù)中,這說明我們并沒有獲得微服務(wù)的隔離性好處:減少協(xié)調(diào)和專門化。
微服務(wù)之間的差異變成了缺點(diǎn),而不是優(yōu)點(diǎn)。開發(fā)每一個(gè)新功能都需要了解新的微服務(wù)以及需要其他團(tuán)隊(duì)做出哪些改動。我們對第三方的依賴嚴(yán)重阻礙了開發(fā)進(jìn)程,讓我們無法獲得微服務(wù)伸縮性的優(yōu)勢。
采用微服務(wù)架構(gòu)并不是沒有代價(jià),需要解決很多問題,而大部分在單體系統(tǒng)中已經(jīng)解決過了,例如:日志、監(jiān)控、異常處理、容錯、回退服務(wù)間通信、消息格式、容器化、服務(wù)發(fā)現(xiàn)、備份、遙測、警報(bào)、跟蹤、構(gòu)建管道、發(fā)布管道、工具、共享基礎(chǔ)設(shè)施代碼、文檔、伸縮、時(shí)區(qū)支持、API 版本控制、網(wǎng)絡(luò)延遲、健康檢查、負(fù)載均衡、CDC 測試、容錯、在本地開發(fā)環(huán)境調(diào)試和開發(fā)多個(gè)微服務(wù)等。
更糟糕的是,因?yàn)闆]有現(xiàn)成的微服務(wù)平臺,我們不得不自己完成上面這些事情。要轉(zhuǎn)向微服務(wù),我們確實(shí)存在痛點(diǎn)和困難,也確信無法從微服務(wù)架構(gòu)獲得任何好處,如果要支持微服務(wù),還需要做一長串額外工作。
下圖分別是我們當(dāng)前的單體架構(gòu)、計(jì)劃中的架構(gòu)和微服務(wù)架構(gòu)。從結(jié)構(gòu)上看,新的架構(gòu)跟當(dāng)前的單體架構(gòu)很像,所有東西仍然緊密耦合在一起。
自從微服務(wù)架構(gòu)大火之后,“單體”成了一個(gè)不太好的名詞,好像“單體”就是糟糕的東西,而“微服務(wù)”就是好東西。但回望過去,我們的開發(fā)團(tuán)隊(duì)在開發(fā)單體系統(tǒng)時(shí)并沒有遇到什么問題。開發(fā)和擴(kuò)展都非常簡單,已經(jīng)有一個(gè)非常好的 CI/CD 管道,部署和回滾都非常容易。我們的分支管理和測試策略確保了很少會有問題進(jìn)入到生產(chǎn)環(huán)境。
我們對微服務(wù)研究得越多,就越覺得它與技術(shù)無關(guān),更多的是與團(tuán)隊(duì)的結(jié)構(gòu)和工作模式有關(guān)。我們把微服務(wù)視為純粹的技術(shù)問題,或許是我們錯了?
除此之外,還有很多全局性的問題無法得到解答。
微服務(wù)遷移是件大事情,在幾個(gè)月的時(shí)間里,所有開發(fā)人員都停止開發(fā)新功能,并在很多先決條件都還不滿足的情況下開始拆解單體系統(tǒng)。為了做這件事而做,我們并沒有想過是否真的有必要。
這不僅不是從 A 到 B 的問題,反而是一種倒退。我們先是創(chuàng)建微服務(wù),然后搭建基礎(chǔ)設(shè)施,還忽略了重組團(tuán)隊(duì)結(jié)構(gòu)。如果我們先根據(jù)業(yè)務(wù)關(guān)注點(diǎn)重組團(tuán)隊(duì),然后準(zhǔn)備好基礎(chǔ)設(shè)施,這就為微服務(wù)的自然出現(xiàn)做好了準(zhǔn)備。一旦出現(xiàn)任何新的業(yè)務(wù)問題,就可以將它們直接放到新的服務(wù)中。
在拆分微服務(wù)時(shí),我們必須預(yù)先確定每個(gè)微服務(wù)的大小。關(guān)于微服務(wù)大小這個(gè)問題,有很多相互矛盾的建議。有人建議微服務(wù)應(yīng)該足夠大,大到可以由一個(gè)團(tuán)隊(duì)負(fù)責(zé)開發(fā);另一些人則建議微服務(wù)應(yīng)該足夠小,小到可以在腦子里浮現(xiàn)出服務(wù)結(jié)構(gòu),甚至小到可以在兩周內(nèi)重寫;還有一些人建議,應(yīng)該與業(yè)務(wù)大小相仿。
領(lǐng)導(dǎo)層決定基于我們的領(lǐng)域模型來拆分微服務(wù),如果還有問題,就繼續(xù)把它們拆分成更小的服務(wù)。這導(dǎo)致了上面提到的一些問題。事后看來,如果我們先把先決條件準(zhǔn)備好,并讓微服務(wù)自然而然地出現(xiàn),最終可能會得到切合實(shí)際的微服務(wù)大小。
隨著微服務(wù)發(fā)布日子的臨近,我們的團(tuán)隊(duì)發(fā)現(xiàn)了越來越多問題。我們做出了更多妥協(xié),微服務(wù)帶給我們的好處也進(jìn)一步消失殆盡。從開始實(shí)現(xiàn)微服務(wù)的第一個(gè) sprint 開始,已經(jīng)過去了四天,但我們?nèi)匀豢床坏绞裁词斋@,反而問題越來越多。我們召開了一次會議,不管領(lǐng)導(dǎo)層想要什么,關(guān)于這條路是否要繼續(xù)走下去,答案都寫在每個(gè)開發(fā)人員的臉上。最終,我們?nèi)∠宿D(zhuǎn)向微服務(wù)的計(jì)劃。
因?yàn)橹鞍阉芯Χ挤旁诹巳绾无D(zhuǎn)向微服務(wù)上,所以沒有花時(shí)間研究其他替代方案。但在放棄微服務(wù)之后,我們開始研究其他替代方案。最終,我們沒有將單體拆分成微服務(wù),而是將它拆分成多個(gè)項(xiàng)目。這種拆分為我們提供了一些額外的結(jié)構(gòu),我們可以更容易地看出哪里存在耦合和重復(fù),沒有額外的負(fù)擔(dān),也不需要面對微服務(wù)架構(gòu)中存在的問題。
此外,這種結(jié)構(gòu)讓我們的領(lǐng)域模型變得更加清晰,能夠更容易地評估哪些部分可以被拆分成微服務(wù)。如果某些部分被證明是一個(gè)合適的微服務(wù)候選對象,這個(gè)部分就可以從單體中剝離出來,成為一個(gè)微服務(wù)。
領(lǐng)導(dǎo)層決定轉(zhuǎn)向微服務(wù),但沒有考慮到現(xiàn)狀和需要面對的挑戰(zhàn)。經(jīng)過評估,我們發(fā)現(xiàn)微服務(wù)并不適合,我們需要做出大量妥協(xié)。這些妥協(xié)導(dǎo)致無法獲得微服務(wù)的好處,所以轉(zhuǎn)向微服務(wù)對我們來說是一種損失。
在決定轉(zhuǎn)向微服務(wù)時(shí),我們并沒有評估團(tuán)隊(duì)結(jié)構(gòu)等非技術(shù)方面的問題。經(jīng)過幾個(gè)月的調(diào)研和努力,我們最終放棄了這個(gè)想法,并用剩下的時(shí)間對“單體”進(jìn)行了一些小的重構(gòu)。
對于微服務(wù),你怎么看?歡迎參與討論:#微服務(wù)真的很好用嗎?#
聯(lián)系客服