今天繼續(xù)談下在微服務(wù)架構(gòu)設(shè)計(jì)中的一些實(shí)踐和思考。對(duì)于SOA和微服務(wù),我前面很多文章都進(jìn)行了詳細(xì)的闡述,今天這篇文章重點(diǎn)還是放在一些架構(gòu)設(shè)計(jì)和實(shí)踐的一些關(guān)鍵點(diǎn)思考上面。
再次強(qiáng)調(diào),微服務(wù)架構(gòu)核心是傳統(tǒng)單體應(yīng)用大拆小,同時(shí)拆分為小的微服務(wù)后相互之間以輕量的API接口進(jìn)行通信。而這個(gè)拆分本身又分了多個(gè)方面。
- 開發(fā)團(tuán)隊(duì)的拆分
- 代碼層的拆分,可獨(dú)立構(gòu)建打包
- 數(shù)據(jù)庫(kù)的拆分
在拆分后為了更加敏捷開發(fā)和集成,引入了DevOps和容器云技術(shù)。同時(shí)考慮和SOA,中臺(tái)思想的融合,考慮到API接口的復(fù)用性,進(jìn)一步對(duì)單個(gè)微服務(wù)也進(jìn)行了前后端分離開發(fā)。
從單微服務(wù)的概念來說,微服務(wù)不是指具體的Http API接口服務(wù),而是指拆分后的微服務(wù)模塊,因此微服務(wù)可以理解為:拆分后DB+微服務(wù)模塊+API接口提供。
微服務(wù)架構(gòu)思想符合當(dāng)前復(fù)雜應(yīng)用系統(tǒng)分而治之的思想,這個(gè)和微服務(wù)出來前的組件化開發(fā)思路是一致的,只是微服務(wù)思想出來后對(duì)于拆分的微服務(wù)更加高度解耦和獨(dú)立自治。
系統(tǒng)復(fù)雜性本身也分為了功能和非功能兩個(gè)層面。
比如一個(gè)傳統(tǒng)的大業(yè)務(wù)系統(tǒng),類似ERP,合同管理等,業(yè)務(wù)系統(tǒng)足夠復(fù)雜,需要考慮進(jìn)行分為治之方便后期管理和擴(kuò)展。其次是非功能性需求導(dǎo)致的復(fù)雜性,比如一個(gè)業(yè)務(wù)系統(tǒng)功能并不多,但是文件存儲(chǔ)和獲取量巨大,那么文件服務(wù)就需要單獨(dú)拆分為微服務(wù)。
在很早以前我就強(qiáng)調(diào)過,微服務(wù)拆分后雖然降低了單個(gè)微服務(wù)開發(fā)實(shí)現(xiàn)的難度,但是增加了集成的難度,拆分的越細(xì)集成越復(fù)雜。因此如果本身不具備上面談到的復(fù)雜性需求,一個(gè)業(yè)務(wù)系統(tǒng)沒有必要進(jìn)行微服務(wù)架構(gòu)拆分和改造。
按劃分后的子域拆分?jǐn)?shù)據(jù)庫(kù)
在我們實(shí)際的項(xiàng)目中,一個(gè)原來的單體業(yè)務(wù)系統(tǒng),在進(jìn)行微服務(wù)化后,實(shí)際拆分為了20個(gè)微服務(wù)模塊,那么按標(biāo)準(zhǔn)的微服務(wù)原則,應(yīng)該后端也拆分為20個(gè)數(shù)據(jù)庫(kù)實(shí)例。但是這樣會(huì)導(dǎo)致巨大的集成復(fù)雜度和大量分布式事務(wù)處理問題。

顯然,在這種場(chǎng)景下我們引入業(yè)務(wù)域的概念,即應(yīng)該按業(yè)務(wù)域或子域來拆分?jǐn)?shù)據(jù)庫(kù),可以多個(gè)微服務(wù)共享一個(gè)數(shù)據(jù)庫(kù)。在多個(gè)微服務(wù)共享一個(gè)數(shù)據(jù)庫(kù)實(shí)例的時(shí)候,微服務(wù)本身沒有做到完全解耦,但是也可以實(shí)現(xiàn)代碼層解耦。
比如某一個(gè)需求變更導(dǎo)致微服務(wù)A進(jìn)行了變更,數(shù)據(jù)庫(kù)沒有變化,那么我們只需要持續(xù)集成和發(fā)布微服務(wù)A模塊即可。
同時(shí)在劃分業(yè)務(wù)域后也更加方便進(jìn)行團(tuán)隊(duì)的劃分,即開發(fā)團(tuán)隊(duì)也按照業(yè)務(wù)域進(jìn)行劃分,而不是一個(gè)開發(fā)團(tuán)隊(duì)只負(fù)責(zé)一個(gè)微服務(wù)模塊。
微服務(wù)和微服務(wù)API接口
注意微服務(wù)和微服務(wù)模塊暴露的API接口是兩個(gè)概念,這本身也是進(jìn)行微服務(wù)邊界劃分和微服務(wù)管控的兩種顆粒度。
在主流的微服務(wù)開發(fā)框架實(shí)現(xiàn)中,類似SpringCLoud的實(shí)現(xiàn),對(duì)于Eureka,CloudGateway網(wǎng)關(guān)等實(shí)際都是到微服務(wù)這個(gè)粒度,也就是服務(wù)注冊(cè)和接入的是微服務(wù)模塊,而不是一個(gè)個(gè)獨(dú)立的API接口服務(wù)。一旦微服務(wù)注冊(cè)接入后,消費(fèi)端通過注冊(cè)中心查找到可用的微服務(wù)后,那么該微服務(wù)通過聲明式方式暴露的所有API接口都處于可用狀態(tài)。
在微服務(wù)架構(gòu)開發(fā)下,團(tuán)隊(duì)實(shí)際應(yīng)該有更加明確的邊界,更加粗粒度的接口暴露和交互,而不是簡(jiǎn)單的團(tuán)隊(duì)A在發(fā)現(xiàn)團(tuán)隊(duì)B的微服務(wù)后,里面所有的API接口都可以隨意調(diào)用,這樣反而是導(dǎo)致了更多的內(nèi)部規(guī)則外協(xié),也加強(qiáng)了兩個(gè)微服務(wù)模塊之間的耦合性。
簡(jiǎn)單來說,兩個(gè)微服務(wù)之間,不是通過API接口調(diào)用就真正解耦了,而是兩個(gè)微服務(wù)之間僅僅只有少量粗粒度的API接口交付才算真正解耦。
當(dāng)前實(shí)際我們發(fā)現(xiàn)的一個(gè)關(guān)鍵問題就是微服務(wù)也拆分了,開發(fā)團(tuán)隊(duì)也拆分了,但是多個(gè)微服務(wù)之間仍然是大量接口隨意調(diào)用,這本質(zhì)仍然是一種緊耦合的架構(gòu)而難以擴(kuò)展。

如上圖,微服務(wù)A,D,E分別由不同的開發(fā)團(tuán)隊(duì)開發(fā),那么他們之間的邊界應(yīng)該控制到具體的API接口粒度,而不是微服務(wù)粒度。比如對(duì)于微服務(wù)E只能消費(fèi)微服務(wù)A暴露的第2個(gè)接口,而不能消費(fèi)接口1。如果單純的采用微服務(wù)注冊(cè)中心方式,實(shí)際我們很難真正控制到API接口的粒度?;蛘哒f需要我們自己寫相應(yīng)的代碼來做細(xì)粒度的安全控制。
面向API接口設(shè)計(jì)

這個(gè)是我在前面一直強(qiáng)調(diào)的觀點(diǎn),即大型項(xiàng)目或傳統(tǒng)的大型單體應(yīng)用在進(jìn)行微服務(wù)化的時(shí)候,一定是架構(gòu)設(shè)計(jì)先行。架構(gòu)設(shè)計(jì)的關(guān)鍵工作就是:
- 微服務(wù)模塊拆分,包括數(shù)據(jù)庫(kù)拆分
- 微服務(wù)模塊暴露的API接口識(shí)別定義
當(dāng)做完這兩件事情后,單個(gè)微服務(wù)才能夠真正傳遞給不同的開發(fā)團(tuán)隊(duì)或小組進(jìn)行獨(dú)立的設(shè)計(jì)開發(fā)工作。同時(shí)在微服務(wù)開發(fā)過程中,需要面向API接口而設(shè)計(jì)開發(fā),要優(yōu)先基于前面定義的接口契約來實(shí)現(xiàn)需要暴露給外部其他微服務(wù)使用的接口,其次再考慮內(nèi)部功能邏輯實(shí)現(xiàn)。
接口先行的好處就是大家遵循同樣的一套接口契約,可以并行開始相關(guān)的設(shè)計(jì)和開發(fā)工作,只要接口契約相同,那么后續(xù)在多個(gè)微服務(wù)間集成的時(shí)候就應(yīng)該沒有問題。
API接口的治理管控需要提升到相對(duì)重要的一個(gè)位置。
在微服務(wù)架構(gòu)實(shí)踐中經(jīng)常看到的情況就是前期架構(gòu)設(shè)計(jì)不充分,相關(guān)的邊界劃分不明確,接口定義不明確,導(dǎo)致后期微服務(wù)在設(shè)計(jì)開發(fā)過程中持續(xù)大量的交互,同時(shí)隨意的增加和定義新的API接口,這種場(chǎng)景下必然帶來后續(xù)接口交互和管控治理的混亂。
比如后續(xù)在進(jìn)行微服務(wù)變更的時(shí)候,我們很難快速的分析出該微服務(wù)或API接口變化究竟會(huì)影響到哪些其它的微服務(wù)模塊,微服務(wù)和API間的交互依賴關(guān)系我們也完全不清楚。
這也是我在很早就強(qiáng)調(diào)的一個(gè)觀點(diǎn),不要期望通過后期的APM或服務(wù)鏈監(jiān)控來解決微服務(wù)本身架構(gòu)設(shè)計(jì)階段的不足,而是應(yīng)該在前期就按自頂向下思路設(shè)計(jì)好。
構(gòu)建獨(dú)立的領(lǐng)域組合微服務(wù)
在SOA分層架構(gòu)里面可以看到,最底層是原子服務(wù),在原子服務(wù)上面有組合服務(wù),在組合服務(wù)上面還有流程服務(wù)。也就是說服務(wù)本身也是分層的,雖然越往上走,類似到了組合服務(wù)實(shí)際的復(fù)用度會(huì)降低,但是復(fù)用效率本身卻是加快。
在微服務(wù)架構(gòu)實(shí)踐里面,原有的單體應(yīng)用已經(jīng)拆分為了不同的微服務(wù),每個(gè)微服務(wù)都可以提供獨(dú)立的API接口服務(wù)能力給前端使用。
但是當(dāng)前端需要的是多個(gè)微服務(wù)的組合能力的時(shí)候,這個(gè)能力究竟放在哪里?比如前面我們舉過一個(gè)例子,對(duì)于訂單提交這個(gè)操作,實(shí)際需要調(diào)用后端訂單中心,預(yù)算中心,庫(kù)存中心多個(gè)微服務(wù)接口才能夠完成。
在傳統(tǒng)方式下這個(gè)能力實(shí)際是在前端模塊完成組合和協(xié)同,但是你會(huì)發(fā)現(xiàn)你開發(fā)的應(yīng)用既有傳統(tǒng)的BS端應(yīng)用,也有APP應(yīng)用,那么這個(gè)組合顯然就需要在兩個(gè)地方重復(fù)實(shí)現(xiàn)。同時(shí)這種組合規(guī)則本身也暴露到了前端不合理。

領(lǐng)域組合微服務(wù)實(shí)際上是一類比較特殊的微服務(wù),即這類微服務(wù)本身完成多個(gè)微服務(wù)API接口的組合編排,完成分布式事務(wù)管理和協(xié)調(diào),完成組合業(yè)務(wù)規(guī)則的實(shí)現(xiàn)和處理等。
這類微服務(wù)本身沒有自己獨(dú)立的Owner數(shù)據(jù)庫(kù),也就是這類微服務(wù)不直接進(jìn)行數(shù)據(jù)庫(kù)DB層的數(shù)據(jù)訪問和交互,而是直接復(fù)用已有的接口服務(wù)能力進(jìn)行組合和組裝。
在DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的架構(gòu)分層里面,在領(lǐng)域?qū)由嫌幸粋€(gè)獨(dú)立的應(yīng)用層,這個(gè)應(yīng)用層即和這類談到的領(lǐng)域組合微服務(wù)對(duì)應(yīng)。而下層的領(lǐng)域?qū)觿t由多個(gè)微服務(wù)提供粗粒度的API接口服務(wù)能力。
微服務(wù)網(wǎng)關(guān)和API網(wǎng)關(guān)
在前面我曾經(jīng)專門寫過微服務(wù)網(wǎng)關(guān)。API網(wǎng)關(guān)一般具備獨(dú)立的服務(wù)注冊(cè)接入,負(fù)載均衡和路由能力,而微服務(wù)網(wǎng)關(guān)一般則是通過和服務(wù)注冊(cè)中心的集成來實(shí)現(xiàn)服務(wù)注冊(cè)發(fā)現(xiàn),負(fù)載均衡和路由。
簡(jiǎn)單來說如果當(dāng)前微服務(wù)A模塊有100個(gè)接口服務(wù)。
在有服務(wù)注冊(cè)發(fā)現(xiàn)中心的情況下,微服務(wù)A模塊部署后會(huì)被注冊(cè)中心自動(dòng)發(fā)現(xiàn),并加入到可用集群列表中。因此在微服務(wù)網(wǎng)關(guān)和注冊(cè)中心集成后,所有的接口服務(wù)也自動(dòng)的注冊(cè)和接入到了微服務(wù)網(wǎng)關(guān)中。
當(dāng)用戶訪問網(wǎng)關(guān)提供的服務(wù)地址時(shí)候整體過程如下圖:

在這種場(chǎng)景下可以看到實(shí)際并不用一個(gè)個(gè)的API接口在網(wǎng)關(guān)上面注冊(cè)。但是也無法控制一個(gè)微服務(wù)哪些具體的接口要接入網(wǎng)關(guān),哪些不接入。同時(shí)這里的微服務(wù)網(wǎng)關(guān)實(shí)際上本身也是整體微服務(wù)架構(gòu)體系里面的一個(gè)微服務(wù)模塊,充當(dāng)了服務(wù)消費(fèi)方的角色。
也就是說APP應(yīng)用無法受整體微服務(wù)框架管轄,那么對(duì)應(yīng)的依賴包,代理SDK等無法下放到外部應(yīng)用中,那么這部分內(nèi)容實(shí)際是轉(zhuǎn)移到微服務(wù)網(wǎng)關(guān)上來幫助外部APP應(yīng)用完成。而對(duì)于相對(duì)獨(dú)立的API網(wǎng)關(guān)來說,整體的注冊(cè)和接入過程是在API網(wǎng)關(guān)上面獨(dú)立完成的,而是是控制到API接口服務(wù)粒度進(jìn)行。
當(dāng)然,你也可以不采用微服務(wù)網(wǎng)關(guān),直接采用類似Nginx來進(jìn)行代理和路由轉(zhuǎn)發(fā),但是這個(gè)時(shí)候需要手工進(jìn)行微服務(wù)節(jié)點(diǎn)的配置和心跳檢測(cè)實(shí)現(xiàn)等。
一個(gè)完整的微服務(wù)架構(gòu)你可以看到。比如有三個(gè)獨(dú)立開發(fā)團(tuán)隊(duì)進(jìn)行自己的微服務(wù)開發(fā),每個(gè)團(tuán)隊(duì)本身又采用前后端分離的開發(fā)模式。那么這個(gè)時(shí)候?qū)嶋H上每個(gè)團(tuán)隊(duì)都可以啟用自己的注冊(cè)中心和微服務(wù)網(wǎng)關(guān),但是多個(gè)團(tuán)隊(duì)之間的接口協(xié)同則必須控制到API接口這個(gè)粒度,即多個(gè)團(tuán)隊(duì)之間的接口協(xié)同采用API網(wǎng)關(guān)進(jìn)行。
這個(gè)時(shí)候的API網(wǎng)關(guān)不屬于單個(gè)開發(fā)團(tuán)隊(duì)管理,而屬于整個(gè)平臺(tái)層的集成能力。
共性基礎(chǔ)JAR包依賴

在微服務(wù)架構(gòu)拆分后,各個(gè)微服務(wù)仍然會(huì)使用或依賴一些共性基礎(chǔ)組件,這些組件本身是獨(dú)立工程項(xiàng)目,可以獨(dú)立編譯構(gòu)建。
同時(shí)各個(gè)微服務(wù)本身以黑盒Jar包的方式對(duì)基礎(chǔ)組件包進(jìn)行依賴。
這類似于在各個(gè)微服務(wù)里面本身有一個(gè)基礎(chǔ)的內(nèi)置SDK包,這個(gè)SDK包實(shí)現(xiàn)了一些基礎(chǔ)共性可復(fù)用的方法,或者對(duì)一些技術(shù)能力進(jìn)行了統(tǒng)一封裝。
在這種場(chǎng)景下如果微服務(wù)B對(duì)Common包提出新需求,Common包分析后仍然是共性需求需要實(shí)現(xiàn),那么Common包會(huì)重新編譯構(gòu)建,并進(jìn)行了版本升級(jí)。
在這種場(chǎng)景下,實(shí)際上微服務(wù)A和C兩個(gè)模塊的代碼沒有做任何修改,那么這個(gè)時(shí)候A和C是否需要重新進(jìn)行編譯構(gòu)建?
可以很明確的看到這個(gè)時(shí)候A和C不用進(jìn)行編譯構(gòu)建,而僅僅需要對(duì)微服務(wù)B進(jìn)行編譯構(gòu)建,B在構(gòu)建的時(shí)候會(huì)自動(dòng)獲取到最新的Common Jar包。
那么在這個(gè)場(chǎng)景下,實(shí)際的部署架構(gòu)下是Common包多個(gè)版本共存。
為何要如此處理?
簡(jiǎn)單來說微服務(wù)拆分后,需要做到的就是進(jìn)行最小化的編譯構(gòu)建和部署,來滿足業(yè)務(wù)需求的變化,能夠不重新構(gòu)建的就不構(gòu)建,不重新部署的就不部署。只有這樣才能夠更好的控制住變更范圍,也更加容易分析在版本部署后出現(xiàn)問題。
比如上圖,如果Common包升級(jí)后,微服務(wù)A也重新進(jìn)行了部署構(gòu)建,那么這個(gè)時(shí)候問題究竟出在哪里是很難馬上做出判斷的。
當(dāng)然也存在其它的一些場(chǎng)景:
比如對(duì)于Common包的版本升級(jí),雖然接口沒有變化,但是一個(gè)共性方法的實(shí)現(xiàn)邏輯出現(xiàn)了變化,這個(gè)時(shí)候必須觸發(fā)三個(gè)微服務(wù)部署目錄下的JAR包進(jìn)行升級(jí)。而這個(gè)場(chǎng)景下本身也有兩種方式來做這個(gè)事情。
- 其一是三個(gè)微服務(wù)重新構(gòu)建來獲取新版本Jar包
- 其二是將新的JAR包自動(dòng)分發(fā)到三個(gè)微服務(wù)部署環(huán)境或容器中
就當(dāng)前來說第一種方法很難做,往往都需要對(duì)微服務(wù)重新進(jìn)行編譯構(gòu)建,或者重新進(jìn)行部署。也正是這個(gè)原因可以看到,當(dāng)采用JAR包或SDK代理包這種方式,最大的一個(gè)問題就是版本變化的情況下的升級(jí)問題。
面向解耦而設(shè)計(jì)

前面已經(jīng)談到,不是你用了Http API接口就是松耦合,如果兩個(gè)微服務(wù)模塊之間有大量的API接口交互,那么仍然是一種緊耦合的關(guān)系。
談微服務(wù)的時(shí)候你會(huì)發(fā)現(xiàn),一個(gè)微服務(wù)要成功正常運(yùn)行,有大量的底層技術(shù)組件或微服務(wù)依賴,也有大量的同層的其它微服務(wù)模塊API接口依賴。如果任何一個(gè)依賴的微服務(wù)出現(xiàn)問題,或者數(shù)據(jù)庫(kù)出現(xiàn)問題都會(huì)導(dǎo)致微服務(wù)無法正常運(yùn)行。
不論現(xiàn)在談緩存,還是談消息中間件和事件驅(qū)動(dòng)架構(gòu),你可以看到都是希望對(duì)微服務(wù)間進(jìn)行解耦,對(duì)微服務(wù)和數(shù)據(jù)庫(kù)之間進(jìn)行解耦。
- 對(duì)于核心的解耦思路實(shí)際在前面已經(jīng)談到過,即:
- 對(duì)于查詢,采用緩存方式進(jìn)行解耦
- 對(duì)于導(dǎo)入或CUD接口,采用消息中間件解耦

實(shí)際上面的思路和經(jīng)常談到的CQRS命令查詢職責(zé)分離思路類似,通過CQRS一開始是為了更好的配合讀寫分離的數(shù)據(jù)庫(kù)使用。但是真正CQRS實(shí)現(xiàn)解耦的重點(diǎn)仍然是兩個(gè)。
其一是將命令作為事件推送到消息中間件處理,以避免出現(xiàn)長(zhǎng)周期分布式事務(wù)。其次就是啟用單獨(dú)的R讀庫(kù),可以是數(shù)據(jù)庫(kù),也可以是緩存庫(kù),來實(shí)現(xiàn)查詢功能獨(dú)立解耦和性能提升。
在實(shí)際的實(shí)踐中,不同開發(fā)團(tuán)隊(duì)之間交互接口最好能夠通過消息中間件或緩存進(jìn)行徹底解耦,以降低相互之間的依賴和影響。
比如對(duì)于微服務(wù)A需要推送數(shù)據(jù)到微服務(wù)B,同時(shí)需要從微服務(wù)C查詢數(shù)據(jù)。那么推送數(shù)據(jù)庫(kù)到B的接口可以實(shí)現(xiàn)為消息接口,先推送數(shù)據(jù)到消息中間件;而對(duì)于數(shù)據(jù)的查詢則可以在獲取數(shù)據(jù)后進(jìn)行緩存等。
變更影響分析

在微服務(wù)架構(gòu)實(shí)踐過程中,由于很多接口是采用Http API接口方式進(jìn)行調(diào)用,很多接口修改實(shí)際并不會(huì)引起編譯構(gòu)建期的錯(cuò)誤。因此導(dǎo)致某個(gè)微服務(wù)接口修改后導(dǎo)致其它微服務(wù)模塊功能出現(xiàn)異常的情況。當(dāng)出現(xiàn)問題后,我們才在事后進(jìn)行修復(fù)。
對(duì)于服務(wù)鏈監(jiān)控和鏈路跟蹤是一個(gè)事后的行為,重點(diǎn)是發(fā)現(xiàn)性能問題而不是幫你去分析服務(wù)之間的依賴關(guān)系。
因此提前梳理清楚微服務(wù)間的接口交互和依賴關(guān)系是必須的,如上圖。
通過上圖的接口交互矩陣,可以很清楚的看到當(dāng)某個(gè)接口出現(xiàn)變化的時(shí)候,究竟會(huì)對(duì)哪些微服務(wù)模塊,哪些功能造成影響,那這些影響點(diǎn)就必須考慮配套的變更或者說在提交測(cè)試的時(shí)候,這些影響到的微服務(wù)模塊或功能也需要進(jìn)行測(cè)試。
當(dāng)然如果我們?cè)谖⒎?wù)架構(gòu)實(shí)施過程中,已經(jīng)形成了完整的基于接口的單元測(cè)試和自動(dòng)化測(cè)試,也可以更好的解決和提前發(fā)現(xiàn)問題。當(dāng)你關(guān)注點(diǎn)在微服務(wù)模塊這個(gè)粒度的時(shí)候,很容易忽略微服務(wù)模塊間的交互和協(xié)同實(shí)際需要管控到API接口這個(gè)粒度,這是我們?cè)趯?shí)施微服務(wù)架構(gòu)的時(shí)候需要重點(diǎn)關(guān)注的一個(gè)點(diǎn)。
原文地址:http://dockone.io/article/1282027