微服務(wù)的架構(gòu)設(shè)計(jì)
近年來,服務(wù)化和微服務(wù)的架構(gòu)隨著線上業(yè)務(wù)對響應(yīng)變化和發(fā)布頻率要求的不斷提高已經(jīng)變得日益常見。下面yjbys小編為大家準(zhǔn)備了關(guān)于微服務(wù)架構(gòu)設(shè)計(jì)的文章,歡迎閱讀。
前言
許多企業(yè)對微服務(wù)思考的關(guān)注點(diǎn)也從最初的“該不該用”、“該如何用”逐漸轉(zhuǎn)向“如何評價(jià)服務(wù)拆分的好壞”、“服務(wù)拆分后的數(shù)據(jù)如何治理”這樣更加具體而實(shí)際的方面。縱觀社區(qū)里與微服務(wù)有關(guān)的話題,既有一些適合大眾閱讀的概念入門科普文章,也有一些像“架構(gòu)去中心化”、“消息異步解耦”、“最終一致性補(bǔ)償”這類專業(yè)性的技術(shù)討論,但卻很少看到能夠比較深入的介紹微服務(wù)實(shí)施經(jīng)驗(yàn),說清楚“什么地方會有什么坑”這類問題的實(shí)踐性內(nèi)容。
若將微服務(wù)的方方面面鋪開,將是一個(gè)非常龐大而復(fù)雜的知識體系。萬事開頭難,如何邁出服務(wù)劃分的第一步是對于那些從未采用過微服務(wù)的項(xiàng)目首先要面對的問題。雖然服務(wù)的劃分本來是一件十分憑借架構(gòu)師個(gè)人經(jīng)驗(yàn)和對業(yè)務(wù)理解的主觀工作,但其中仍然有些規(guī)律可循。根據(jù)服務(wù)的類型特點(diǎn),可以有以下幾種常見的方式:
1. 根據(jù)業(yè)務(wù)進(jìn)行建模,依據(jù)業(yè)務(wù)領(lǐng)域的邊界劃分,這也是當(dāng)下微服務(wù)社區(qū)十分推崇的領(lǐng)域驅(qū)動設(shè)計(jì)(Domain Driven Design,簡稱DDD)方法;
2. 根據(jù)資源使用類型劃分,這種方式主要使用在對硬件資源需求很高、或?qū)μ囟ㄓ布愋?區(qū)域地址等存在依賴的大型服務(wù)系統(tǒng),例如系統(tǒng)的某些功能非常消耗CPU、或是某些功能必須在有加密狗設(shè)備的機(jī)器上運(yùn)行。此時(shí)可以依據(jù)系統(tǒng)各部分對不同資源的需要作為邊界進(jìn)行最安全的劃分,以最大化運(yùn)行效率;
3. 根據(jù)數(shù)據(jù)邊界劃分,直接從數(shù)據(jù)表結(jié)構(gòu)或數(shù)據(jù)源著手,依據(jù)數(shù)據(jù)歸屬關(guān)系界定服務(wù)。這種劃分方法簡單粗暴且能避免數(shù)據(jù)耦合,但容易導(dǎo)致潛在隱患,甚至可以被認(rèn)為是一種反模式。僅僅對于強(qiáng)數(shù)據(jù)驅(qū)動的系統(tǒng),如某些報(bào)表系統(tǒng)和多數(shù)據(jù)源ETL系統(tǒng)等適用,在實(shí)際案例中很少見。
從現(xiàn)實(shí)上來說,我們平時(shí)遇到的絕大部分系統(tǒng)都是需要為特定終端用戶群體服務(wù)的。因此,采用基于業(yè)務(wù)領(lǐng)域的建模的方法通常都會是個(gè)不錯(cuò)的選擇,它也是目前在劃分服務(wù)問題中的最主流的方法。不過即使有了理論指導(dǎo)的支撐,服務(wù)劃分里的利弊權(quán)衡并不是非黑即白的事情,依然存在不少模棱兩可之處,只有在犯下一些錯(cuò)誤后才能摸索出適合自己系統(tǒng)的方法。本文將會聚焦在這些點(diǎn)。
沒有什么能比一個(gè)具體而貼近真實(shí)的案例更具有代表性了。因此,我們將剖析一個(gè)傳統(tǒng)行業(yè)企業(yè)的線上業(yè)務(wù):某汽車產(chǎn)品代理商的線上銷售和售后平臺,介紹它在微服務(wù)轉(zhuǎn)型過程中遇到的種種情況,希望能以此作為前車之鑒,讓后來者避開不必要的趟坑。這個(gè)案例中講述的場景原型并非完全來自同一個(gè)項(xiàng)目,而是從我們過去一年中所經(jīng)歷的多個(gè)項(xiàng)目的實(shí)際場景抽取出來的,去除了其中的敏感信息,并添加了基于真實(shí)情況的適當(dāng)演繹,使之更加完整。簡單介紹一下案例的背景:這是一家頗具規(guī)模的汽車代理商企業(yè),承接多個(gè)國際一線品牌汽車的銷售和周邊資源整合的業(yè)務(wù),具有龐大的實(shí)體店網(wǎng)點(diǎn)。其線上的IT業(yè)務(wù)系統(tǒng)已經(jīng)存在了十余年,提供的功能從最初的進(jìn)銷存管理、客戶信息管理等到現(xiàn)在的面向消費(fèi)者客戶的服務(wù)系統(tǒng),十分復(fù)雜。系統(tǒng)中的線上銷售和售后平臺部分是相對比較新、且需求變化特別迅速的部分,也是目前出問題最頻繁、收到抱怨最多的部分。
不要從數(shù)據(jù)庫開始建模
傳統(tǒng)軟件開發(fā)中,數(shù)據(jù)模型被認(rèn)為是整個(gè)系統(tǒng)的核心,業(yè)務(wù)邏輯僅僅是對數(shù)據(jù)的CRUD加上簡單的計(jì)算呈現(xiàn)。有些項(xiàng)目團(tuán)隊(duì)的架構(gòu)評審會花很多時(shí)間來討論系統(tǒng)龐大的ER圖(實(shí)體關(guān)系圖)設(shè)計(jì)。但在微服務(wù)架構(gòu)設(shè)計(jì)時(shí),ER圖并非最佳選擇,特別是在服務(wù)劃分時(shí)采用ER圖進(jìn)行建模甚至是十分有害的。
在領(lǐng)域驅(qū)動設(shè)計(jì)的實(shí)踐中,有一個(gè)和ER圖建模有些相似的環(huán)節(jié),叫做“領(lǐng)域建模”。相比ER圖建模通常只有開發(fā)人員參與,并從數(shù)據(jù)表的角度考慮模型的方法,領(lǐng)域建模的過程需要由產(chǎn)品的業(yè)務(wù)人員和核心開發(fā)人員共同參與,先梳理用戶場景,然后從業(yè)務(wù)領(lǐng)域角度,逐步確定場景中的實(shí)體、關(guān)聯(lián)和聚合等元素。其中的“實(shí)體、關(guān)聯(lián)”與ER圖中的“實(shí)體、關(guān)系”有些相似,但含義并不一致。領(lǐng)域模型中的“實(shí)體”本質(zhì)上是業(yè)務(wù)場景中需要被持久化存儲的對象,但存儲方式不一定是數(shù)據(jù)庫,更不一定是關(guān)系型數(shù)據(jù)庫,而ER圖中的“實(shí)體”最終對應(yīng)的就是關(guān)系型數(shù)據(jù)庫的表。領(lǐng)域模型中的“關(guān)聯(lián)”是兩個(gè)實(shí)體在業(yè)務(wù)上下文之間有業(yè)務(wù)含義的聯(lián)系,而ER圖中的“關(guān)系”只的是兩張表之間的一個(gè)關(guān)聯(lián)外鍵。
那么,使用ER圖給微服務(wù)建模會存在什么問題呢?
首先,ER圖設(shè)計(jì)時(shí)假定了使用的是關(guān)系型的數(shù)據(jù)庫。微服務(wù)的一個(gè)特點(diǎn)在于它支持異構(gòu)架構(gòu),系統(tǒng)的不同部分,依據(jù)實(shí)際需要可選擇不同的編程語言、框架以及數(shù)據(jù)庫的類型。關(guān)系型數(shù)據(jù)庫采用平面表的結(jié)構(gòu),如果有兩類嵌套關(guān)系的對象,只能使用關(guān)聯(lián)表來表達(dá)兩個(gè)實(shí)體之間的關(guān)系,然后通過復(fù)雜的SQL語句在查詢時(shí)將多個(gè)表拼成更大的平面表,最后在業(yè)務(wù)代碼里再分解到各個(gè)獨(dú)立的對象里去。這些單獨(dú)的表又可能在其他地方與另一個(gè)表存在關(guān)聯(lián)查詢。這樣設(shè)計(jì)出來的表結(jié)構(gòu)的冗余很低,且使用非常靈活,但正是這種靈活性往導(dǎo)致系統(tǒng)中多個(gè)業(yè)務(wù)邏輯表出現(xiàn)錯(cuò)中纏繞的關(guān)系,從而使剝離單獨(dú)服務(wù)進(jìn)行異構(gòu)設(shè)計(jì)變得困難。
其次,ER圖還會導(dǎo)致實(shí)體相似的不同業(yè)務(wù)邏輯在設(shè)計(jì)時(shí)被耦合在一起。舉一個(gè)具體的例子,在汽車代理銷售系統(tǒng)中,不同品牌汽車的購買流程后端實(shí)際上分別對接的是完全不同的分銷渠道系統(tǒng)和邏輯流程,但它們在用戶視圖上所需要的信息比較一致,因此存儲的數(shù)據(jù)結(jié)構(gòu)也比較相似。這個(gè)系統(tǒng)在最初設(shè)計(jì)時(shí)采用了ER圖的方式建模,由于ER圖模型不關(guān)心業(yè)務(wù)層面上的東西,不同品牌汽車的實(shí)體數(shù)據(jù)看上去都是一種類型的數(shù)據(jù),僅僅是一個(gè)品牌字段的差異而已,因此被理所當(dāng)然的設(shè)計(jì)成了同一張表。上層邏輯實(shí)現(xiàn)的時(shí)候使用了大量的if-else語句來區(qū)分各種品牌的購買流程,結(jié)果使得多條完全不同的業(yè)務(wù)線糅合在同一個(gè)上下文里,后來的開發(fā)非常容易在這里錯(cuò)改、漏改代碼。若當(dāng)初使用的是領(lǐng)域建模,則不同的購車流程會自然的被劃分到各種不同的用戶場景,即使它們在數(shù)據(jù)結(jié)構(gòu)上看起來基本相同,也會被識別為兩個(gè)獨(dú)立的上下文,這就會使得未來劃分服務(wù)時(shí)能夠更加容易。
最后,ER圖設(shè)計(jì)的架構(gòu)會使得系統(tǒng)的模塊之間傾向于使用數(shù)據(jù)庫集成,而非API集成。在ER圖中沒有明確劃分模塊和表的所有權(quán)關(guān)系,所有的數(shù)據(jù)表對所有的模塊都是可見的,倘若不加額外約束,各個(gè)模塊便都會輕易的讀寫其中的內(nèi)容。數(shù)據(jù)集成并不會直接導(dǎo)致服務(wù)無法拆分,但由于數(shù)據(jù)的所有權(quán)不清晰,十分容易引發(fā)的意想不到的狀況。還是舉銷售平臺的例子,在ER圖建模得到的.模型中,有一張與購買記錄相關(guān)的表。在一次銷售業(yè)務(wù)的代碼更新中,對購買結(jié)果的增加了字段,在測試過程中沒有發(fā)現(xiàn)問題,結(jié)果上線幾天以后,由于售后服務(wù)也在修改這個(gè)表而導(dǎo)致出現(xiàn)了臟數(shù)據(jù),造成難以排查的故障。
當(dāng)然,我們并非要完全否認(rèn)ER圖建模的價(jià)值,只不過通過數(shù)據(jù)庫角度建立模型的過程容易傾向于設(shè)計(jì)出龐大的單體應(yīng)用,因而不太適應(yīng)于服務(wù)劃分的目的。
端到端的劃分服務(wù)
在拆服務(wù)時(shí)要端到端的劃分,這是我們在設(shè)計(jì)微服務(wù)時(shí)經(jīng)常聽到的一句話。端到端的劃分,指的是一個(gè)服務(wù)負(fù)責(zé)一個(gè)業(yè)務(wù)領(lǐng)域,這個(gè)功能領(lǐng)域的所有邏輯、數(shù)據(jù)都?xì)w它管,這有利于微服務(wù)的數(shù)據(jù)治理。與之相對的是MVC那樣的橫向服務(wù)劃分,將所有數(shù)據(jù)歸一塊,所有邏輯歸另一塊,特別是在跨團(tuán)隊(duì)管理的情況,橫向劃分服務(wù)會帶來十分高昂的協(xié)調(diào)和聯(lián)調(diào)成本。
道理不必多說,還是講個(gè)例子吧。在汽車銷售平臺的最初架構(gòu)中,使用了典型的MVC三層結(jié)構(gòu),由于人比較多,分成了前臺組、后臺組,就時(shí)不時(shí)要出現(xiàn)新開發(fā)一個(gè)功能,一動底層數(shù)據(jù)結(jié)構(gòu),結(jié)果上層的另一個(gè)不相干頁面掛了,一查發(fā)現(xiàn)原來那這個(gè)頁面間接的用了同一個(gè)數(shù)據(jù)模型。比較典型的例子是,有一回負(fù)責(zé)后臺的小組調(diào)整了汽車銷售服務(wù)里面的汽車參數(shù)信息相關(guān)的對象結(jié)構(gòu),結(jié)果一上線,銷售功能正常,試駕服務(wù)的頁面掛了。原來是試駕功能的前端開發(fā)人員在處理汽車信息時(shí)候,看到銷售模塊有現(xiàn)成功能,就直接拿來復(fù)用了。這便是服務(wù)上下層分團(tuán)隊(duì)開發(fā)導(dǎo)致的問題。
此外,橫向劃分服務(wù)也不利于系統(tǒng)的開發(fā)效率的提升。不同業(yè)務(wù)服務(wù)對功能發(fā)布頻率的的需求是不一樣的,比如試駕平臺經(jīng)常推出新的優(yōu)惠促銷活動,需要盡快進(jìn)行一次版本更新,于此同時(shí)售后服務(wù)有一個(gè)需要和第三方聯(lián)調(diào)的功能也已經(jīng)差不多完成并提交到代碼倉庫了,但由于需要協(xié)調(diào)第三方系統(tǒng)的時(shí)間,最近還不能夠上線,此時(shí)兩邊的業(yè)務(wù)主管就要開掐了。類似這樣的情況其實(shí)經(jīng)常發(fā)生,通常使用特性分支、特性開關(guān)等流程或者技術(shù)手段能夠規(guī)避一部分業(yè)務(wù)開發(fā)進(jìn)度不一致的風(fēng)險(xiǎn),但若要從根本上解決這種問題,還是需要端到端的按業(yè)務(wù)來劃分服務(wù)。
最后,橫向劃分模塊對于問題的追蹤調(diào)試也不友好,幾乎每次事故調(diào)查總是要穿插涉及在幾個(gè)團(tuán)隊(duì)之間不停的協(xié)調(diào)開會,因?yàn)橐粋(gè)完整業(yè)務(wù)流總是要貫穿前后幾個(gè)層的功能。
需要指出的是,端到端劃分服務(wù)并非是說服務(wù)與服務(wù)之間都是平級的。實(shí)際上,服務(wù)之間可以再聚合成更高層級的組合服務(wù)的,以及在最頂端的API Gateway也可以算是一類服務(wù)。只不過,在核心業(yè)務(wù)層的這些服務(wù),每個(gè)都單獨(dú)提供了某項(xiàng)特定的業(yè)務(wù)價(jià)值。
識別核心業(yè)務(wù)服務(wù)
微服務(wù)的架構(gòu)通常并非是一開始就重頭設(shè)計(jì)出來的,而是先有整塊的單體架構(gòu),隨著業(yè)務(wù)的復(fù)雜度上升,才逐步拆分出來。業(yè)務(wù)領(lǐng)域建模除了能用來指導(dǎo)適當(dāng)?shù)姆⻊?wù)劃分,另一個(gè)重要的作用是讓工作聚焦到核心的業(yè)務(wù)服務(wù)中。
無論多復(fù)雜系統(tǒng)都是為特定業(yè)務(wù)價(jià)值而存在的。在系統(tǒng)的實(shí)現(xiàn)中必然會存在與核心業(yè)務(wù)最相關(guān)的部分、輔助核心業(yè)務(wù)的部分、和非核心業(yè)務(wù)關(guān)系的部分。它們在領(lǐng)域驅(qū)動設(shè)計(jì)的術(shù)語中稱為“核心域”、“支撐子域”和“通用子域”。在進(jìn)行領(lǐng)域建模的時(shí)候就應(yīng)該順便識別出系統(tǒng)里的關(guān)鍵領(lǐng)域。將系統(tǒng)的業(yè)務(wù)領(lǐng)域羅列出來然后劃分出重要性,這件事情聽起來似乎是多此一舉,甚至有點(diǎn)荒唐,但對于復(fù)雜系統(tǒng),實(shí)際去做這件事帶來的價(jià)值可能遠(yuǎn)比它看起來更大。