摘要:本文中,我們將進(jìn)一步理解微服務(wù)架構(gòu)的核心要點(diǎn)和實(shí)現(xiàn)原理,為讀者的實(shí)踐提供微服務(wù)的設(shè)計(jì)模式,以期讓微服務(wù)在讀者正在工作的項(xiàng)目中起到積極的作用。
微服務(wù)架構(gòu)中職能團(tuán)隊(duì)的劃分
大家可以點(diǎn)擊加入群:561614305【JAVA大神交流二群】里面有Java高級(jí)大牛直播講解知識(shí)點(diǎn) 走的就是高端路線 (如果你想跳槽換工作 但是技術(shù)又不夠 或者工作上遇到了 瓶頸 我這里有一個(gè)JAVA的免費(fèi)直播課程 講的是高端的知識(shí)點(diǎn)
傳統(tǒng)單體架構(gòu)將系統(tǒng)分成具有不同職責(zé)的層次,對(duì)應(yīng)的項(xiàng)目管理也傾向于將大的團(tuán)隊(duì)分成不同的職能團(tuán)隊(duì),主要包括:用戶交互UI團(tuán)隊(duì)、后臺(tái)業(yè)務(wù)邏輯處理團(tuán)隊(duì)與數(shù)據(jù)存取ORM團(tuán)隊(duì)、DBA團(tuán)隊(duì)等。每個(gè)團(tuán)隊(duì)只對(duì)自己分層的職責(zé)負(fù)責(zé),并對(duì)使用方提供組件服務(wù)質(zhì)量保證。如果其中一個(gè)模塊化組件需要升級(jí)、更新,那么這個(gè)變更會(huì)涉及不同的分層團(tuán)隊(duì),即使升級(jí)和變更的改變很小,也需要進(jìn)行跨團(tuán)隊(duì)溝通:需求階段需要跨團(tuán)隊(duì)溝通產(chǎn)品功能,設(shè)計(jì)階段需要跨團(tuán)隊(duì)溝通設(shè)計(jì)方案,開發(fā)階段需要跨團(tuán)隊(duì)溝通具體的接口定義,測(cè)試階段需要溝通業(yè)務(wù)回歸等事宜,甚至上線都需要跨團(tuán)隊(duì)溝通應(yīng)用的上線順序。可見在傳統(tǒng)的整體架構(gòu)下,后期的維護(hù)成本很高,出現(xiàn)事故的風(fēng)險(xiǎn)很大。
根據(jù)康威定律,團(tuán)隊(duì)的交流機(jī)制應(yīng)該與架構(gòu)設(shè)計(jì)機(jī)制相對(duì)應(yīng)。因此,在微服務(wù)架構(gòu)下,職能團(tuán)隊(duì)的劃分方法是我們首先要考慮的一個(gè)核心要素。
微服務(wù)時(shí)代的團(tuán)隊(duì)溝通方式如圖1-9所示。
圖1-9
微服務(wù)架構(gòu)按照業(yè)務(wù)的功能進(jìn)行劃分,每個(gè)單一的業(yè)務(wù)功能叫作一個(gè)服務(wù),每個(gè)服務(wù)對(duì)應(yīng)一個(gè)獨(dú)立的職能團(tuán)隊(duì),團(tuán)隊(duì)里包含用戶交互UI設(shè)計(jì)師、后臺(tái)服務(wù)開發(fā)人員、DBA、運(yùn)營(yíng)和運(yùn)維人員。
在傳統(tǒng)的整體架構(gòu)中,軟件是有生命周期的,經(jīng)歷需求分析、開發(fā)和測(cè)試,然后被交付給運(yùn)維團(tuán)隊(duì),這時(shí)開發(fā)團(tuán)隊(duì)將會(huì)解散,這是對(duì)軟件的一個(gè)“放手”。而在微服務(wù)架構(gòu)中,提倡運(yùn)維人員也是服務(wù)項(xiàng)目團(tuán)隊(duì)的一員,倡導(dǎo)誰開發(fā)、誰維護(hù),實(shí)施終生維護(hù)制度。
在業(yè)務(wù)服務(wù)的內(nèi)部實(shí)現(xiàn)需要升級(jí)或者變更時(shí),團(tuán)隊(duì)內(nèi)的各角色成員進(jìn)行溝通即可,而不需要進(jìn)行跨團(tuán)隊(duì)溝通,這大大提高了溝通效率。只有服務(wù)之間的接口需要變更時(shí)才需要跨部門溝通,如果前期在服務(wù)之間的交互上定義了良好的接口,則接口變更的概率并不大,即使接口模式有變更,也可以通過一定的設(shè)計(jì)模式和規(guī)范來解決,可參考1.3.3節(jié)。
微服務(wù)的去中心化治理
筆者曾經(jīng)在一個(gè)互聯(lián)網(wǎng)平臺(tái)上工作,平臺(tái)的決策者倡導(dǎo)建設(shè)API網(wǎng)關(guān),所有外部服務(wù)和內(nèi)部服務(wù)都由統(tǒng)一的API網(wǎng)關(guān)進(jìn)行管理。在項(xiàng)目初期,中心化的API網(wǎng)關(guān)統(tǒng)一了所有API的入口,這看起來很規(guī)范,但從技術(shù)角度來看限制了API的多樣化。隨著業(yè)務(wù)的發(fā)展,API網(wǎng)關(guān)開始暴露問題,每個(gè)用戶請(qǐng)求經(jīng)過機(jī)房時(shí)只要有服務(wù)之間的交互,則都會(huì)從API網(wǎng)關(guān)進(jìn)行路由,服務(wù)上量以后,由于內(nèi)部服務(wù)之間的交互都會(huì)疊加在API網(wǎng)關(guān)的調(diào)用上,所以在很大程度上放大了API網(wǎng)關(guān)的調(diào)用TPS,API網(wǎng)關(guān)很快就遇到了性能瓶頸。
這個(gè)案例是典型的微服務(wù)的反模式,微服務(wù)倡導(dǎo)去中心化的治理,不推薦每個(gè)微服務(wù)都使用相同的標(biāo)準(zhǔn)和技術(shù)來開發(fā)和使用服務(wù)。在微服務(wù)架構(gòu)下可以使用C++開發(fā)一個(gè)服務(wù),來對(duì)接Java開發(fā)的另外一個(gè)服務(wù),對(duì)于異構(gòu)系統(tǒng)之間的交互標(biāo)準(zhǔn),通常可以使用工具來補(bǔ)償。開發(fā)者可以開發(fā)共用的工具,并分享給異構(gòu)系統(tǒng)的開發(fā)者使用,來解決異構(gòu)系統(tǒng)不一致的問題,例如:Thrift遠(yuǎn)程調(diào)用框架使用中間語言(IDL)來定義接口,中間語言是獨(dú)立于任何語言的,并提供了工具來生成中間語言,以及在中間語言與具體語言之間的代碼轉(zhuǎn)換。
微服務(wù)架構(gòu)倡導(dǎo)去中心化的服務(wù)管理和治理,盡量不設(shè)置中心化的管理服務(wù),最差也需要在中心化的管理服務(wù)宕機(jī)時(shí)有替代方案和設(shè)計(jì)。在筆者工作的支付平臺(tái)服務(wù)化建設(shè)中,第1層SOA服務(wù)化采用Dubbo框架進(jìn)行定制化,如果Dubbo服務(wù)化出現(xiàn)了大面積的崩潰,則服務(wù)化體系會(huì)切換到點(diǎn)對(duì)點(diǎn)的hessian遠(yuǎn)程調(diào)用,這被稱為服務(wù)化降級(jí),降級(jí)后點(diǎn)對(duì)點(diǎn)的hessian遠(yuǎn)程調(diào)用時(shí)沒有中心化節(jié)點(diǎn),整體上符合微服務(wù)的原理。
微服務(wù)的交互模式
本節(jié)介紹微服務(wù)之間交互的通用設(shè)計(jì)模式,這些設(shè)計(jì)模式對(duì)微服務(wù)之間的交互定義契約,服務(wù)的生產(chǎn)者和調(diào)用者都需要遵守這些契約,才能保證微服務(wù)不出問題。
1. 讀者容錯(cuò)模式
讀者容錯(cuò)模式(Tolerant Reader)指微服務(wù)化中服務(wù)提供者和消費(fèi)者之間如何對(duì)接口的改變進(jìn)行容錯(cuò)。從字面上來講,消費(fèi)者需要對(duì)提供者提供的功能進(jìn)行兼容性設(shè)計(jì),尤其對(duì)服務(wù)提供者返回的內(nèi)容進(jìn)行兼容,或者解決在服務(wù)提供者改變接口或者數(shù)據(jù)的格式的情況下,如何讓服務(wù)消費(fèi)者正常運(yùn)行。
任何一個(gè)產(chǎn)品在設(shè)計(jì)時(shí)都無法預(yù)見將來可能增加的所有需求,服務(wù)的開發(fā)者通常通過迭代及時(shí)地增加新功能,或者讓服務(wù)提供的API自然地演進(jìn)。不過,服務(wù)提供者對(duì)外提供的接口的數(shù)據(jù)格式的改變、增加和刪除,都會(huì)導(dǎo)致服務(wù)的消費(fèi)者不能正常工作。
因此,在服務(wù)消費(fèi)者處理服務(wù)提供者返回的消息的過程中,需要對(duì)服務(wù)返回的消息進(jìn)行過濾,只提取自己需要的內(nèi)容,對(duì)多余或者未知的內(nèi)容采取拋棄的策略,而不是硬生生地拋錯(cuò)處理。
在實(shí)現(xiàn)過程中不推薦使用嚴(yán)格的校驗(yàn)策略,而是推薦使用寬松的校驗(yàn)策略,即使服務(wù)消費(fèi)者拿到的消息報(bào)文發(fā)生了改變,程序也只需盡最大努力提取需要的數(shù)據(jù),同時(shí)忽略不可識(shí)別的數(shù)據(jù)。只有在服務(wù)消費(fèi)者完全不能識(shí)別接收到的消息,或者無法通過識(shí)別的信息繼續(xù)處理流程時(shí),才能拋出異常。
服務(wù)的消費(fèi)者的容錯(cuò)模式忽略了新的消息項(xiàng)、可選的消息項(xiàng)、未知的數(shù)據(jù)值及服務(wù)消費(fèi)者不需要的數(shù)據(jù)項(xiàng)。
筆者當(dāng)前在某個(gè)支付公司工作,公司里幾乎每個(gè)業(yè)務(wù)都需要使用枚舉類型,在微服務(wù)平臺(tái)下,筆者在研發(fā)流程規(guī)范中定義了一條枚舉值使用規(guī)范:
在服務(wù)接口的定義中,參數(shù)可以使用枚舉值,在返回值的DTO中禁止使用枚舉值。
這條規(guī)范就是讀者容錯(cuò)模式在實(shí)踐中的一個(gè)實(shí)例,之所以在參數(shù)中允許使用枚舉值,是因?yàn)槿绻?wù)的提供者升級(jí)了接口,增加了枚舉值,若服務(wù)的消費(fèi)者并沒有感知,則服務(wù)的消費(fèi)者得知新的枚舉值就可以傳遞新的枚舉值了;但是如果接口的返回DTO中使用了枚舉值,并且因?yàn)槟撤N原因增加了枚舉值,則服務(wù)消費(fèi)者在反序列化枚舉時(shí)就會(huì)報(bào)錯(cuò),因此在返回值中我們應(yīng)該使用字符串等互相認(rèn)可的類型,來做到雙方的互相兼容,并實(shí)現(xiàn)讀者容錯(cuò)模式。
2. 消費(fèi)者驅(qū)動(dòng)契約模式
消費(fèi)者驅(qū)動(dòng)契約模式用來定義服務(wù)化中服務(wù)之間交互接口改變的最佳規(guī)則。
服務(wù)契約分為:提供者契約、消費(fèi)者契約及消費(fèi)者驅(qū)動(dòng)的契約,它從期望與約束的角度描述了服務(wù)提供者與服務(wù)消費(fèi)者之間的聯(lián)動(dòng)關(guān)系。
提供者契約:是我們最常用的一種服務(wù)契約,顧名思義,提供者契約是以提供者為中心的,提供者提供了什么功能和消息格式,各消費(fèi)者都會(huì)無條件地遵守這些約定,不論消費(fèi)者實(shí)際需要多少功能,消費(fèi)者接受了提供者契約時(shí),都會(huì)根據(jù)服務(wù)提供者的規(guī)則來使用服務(wù)。
消費(fèi)者契約:是對(duì)某個(gè)消費(fèi)者的需求進(jìn)行更為精確的描述,在一次具體的服務(wù)交互場(chǎng)景下,代表消費(fèi)者需要提供者提供功能中的哪部分?jǐn)?shù)據(jù)。消費(fèi)者契約可以被用來標(biāo)識(shí)現(xiàn)有的提供者契約,也可以用來發(fā)現(xiàn)一個(gè)尚未明確的提供者契約。
消費(fèi)者驅(qū)動(dòng)的契約:代表服務(wù)提供者向其所有當(dāng)前消費(fèi)者承諾遵守的約束。一旦各消費(fèi)者把自己的具體期望告知提供者,則提供者無論在什么時(shí)間和場(chǎng)景下,都不應(yīng)該打破契約。
在現(xiàn)實(shí)的服務(wù)交互設(shè)計(jì)中,上面這三種契約是同時(shí)存在的,筆者所在的支付平臺(tái)里,交易系統(tǒng)在完成一筆支付后,需要到賬務(wù)系統(tǒng)為商戶入賬,在這個(gè)過程中,服務(wù)契約表現(xiàn)如下。
生產(chǎn)者契約:賬務(wù)系統(tǒng)提供Dubbo服務(wù)化接口,參數(shù)為商戶賬戶ID、入賬訂單號(hào)和入賬金額。
消費(fèi)者契約:賬務(wù)系統(tǒng)返回DTO,包含商戶賬戶ID、入賬訂單號(hào)、入賬金額、入賬時(shí)間、賬務(wù)流水號(hào)、入賬狀態(tài)等,而交易系統(tǒng)只需使用其中的入賬訂單號(hào)和入賬狀態(tài)。
消費(fèi)者驅(qū)動(dòng)的契約:為了保證資金安全,交易系統(tǒng)作為入賬的發(fā)起者向賬務(wù)提出要求,需要賬務(wù)做冪等和濾重處理,對(duì)重復(fù)的入賬請(qǐng)求進(jìn)行攔截;賬務(wù)系統(tǒng)在接受這個(gè)契約后,即使將來有任何改變,也不能打破這個(gè)限制,否則就會(huì)造成資金的損失,這在金融系統(tǒng)中是最嚴(yán)重的問題。
服務(wù)之間的交互需要使用的三種服務(wù)契約如圖1-10所示。
圖1-10
從圖1-10可以看到,服務(wù)提供者契約是服務(wù)提供者單方面定下的規(guī)則,而一個(gè)消費(fèi)者契約會(huì)成為提供者契約的一部分,多個(gè)服務(wù)消費(fèi)者可以對(duì)服務(wù)提供者提出約束,服務(wù)提供者需要在將來遵守服務(wù)消費(fèi)者提出的契約,這就是消費(fèi)者驅(qū)動(dòng)的契約。
3. 去數(shù)據(jù)共享模式
與SOA服務(wù)化對(duì)比,微服務(wù)是去ESB總線、去中心化及分布式的;而SOA還是以ESB為核心實(shí)現(xiàn)遺留系統(tǒng)的集成,以及基于Web Service為標(biāo)準(zhǔn)實(shí)現(xiàn)的通用的面向服務(wù)的架構(gòu)。在微服務(wù)領(lǐng)域,微服務(wù)之間的交互通過定義良好的接口來實(shí)現(xiàn),不允許使用共享數(shù)據(jù)來實(shí)現(xiàn)。
在實(shí)踐的過程中,有些方案的設(shè)計(jì)使用緩存或者數(shù)據(jù)庫作為兩個(gè)微服務(wù)之間的紐帶,在業(yè)務(wù)流程的處理過程中,為了處理簡(jiǎn)單,前一個(gè)服務(wù)將中間結(jié)果存入數(shù)據(jù)庫或者緩存,下一個(gè)服務(wù)從緩存或者數(shù)據(jù)庫中拿出數(shù)據(jù)繼續(xù)處理。處理流程如圖1-11所示。
圖1-11
這種交互流程的缺點(diǎn)如下。
使得微服務(wù)之間的交互除了接口契約,還存在數(shù)據(jù)存儲(chǔ)契約。
上游的數(shù)據(jù)格式發(fā)生變化時(shí),可能導(dǎo)致下游的處理邏輯出現(xiàn)問題。
多個(gè)服務(wù)共享一個(gè)資源服務(wù),對(duì)資源服務(wù)的運(yùn)維難以劃清職責(zé)和界限。
在做雙機(jī)房獨(dú)立部署時(shí),需要考慮服務(wù)和資源的路由情況,跨機(jī)房的服務(wù)調(diào)用不能使用獨(dú)立的資源部署模式,因此難以實(shí)現(xiàn)服務(wù)自治。
因此,在設(shè)計(jì)微服務(wù)架構(gòu)時(shí),一定不要共享緩存和數(shù)據(jù)庫等資源,也不要使用總線模式,服務(wù)之間的通信和交互只能依賴定義良好的接口,通常使用RESTful樣式的API或者透明的RPC調(diào)用框架。
微服務(wù)的分解和組合模式
使用微服務(wù)架構(gòu)劃分服務(wù)和團(tuán)隊(duì)是微服務(wù)架構(gòu)實(shí)施的重要一步,良好的劃分和拆分使系統(tǒng)達(dá)到松耦合和高內(nèi)聚的效果,然后通過微服務(wù)的靈活組裝可以滿足上層的各種各樣的業(yè)務(wù)處理需求。
在微服務(wù)架構(gòu)的需求分析和架構(gòu)設(shè)計(jì)過程中,通常是用領(lǐng)域的動(dòng)詞和名詞來劃分微服務(wù)的,例如,對(duì)于一個(gè)電商后臺(tái)系統(tǒng),可以分解為訂單、商品、商品目錄、庫存、購物車、交易、支付、發(fā)票、物流等子系統(tǒng),每個(gè)名詞和動(dòng)詞都可以是一個(gè)微服務(wù),將這幾個(gè)微服務(wù)組合在一起,就實(shí)現(xiàn)了電商平臺(tái)用戶購買商品的整個(gè)業(yè)務(wù)流。
這樣拆分以后,系統(tǒng)具有敏捷性、靈活性、可伸縮性等,拆分后有多個(gè)高度自治的微服務(wù),那么以什么方式組合微服務(wù)呢?
1. 服務(wù)代理模式
服務(wù)代理模式是最簡(jiǎn)單的服務(wù)組合模式,它根據(jù)業(yè)務(wù)的需求選擇調(diào)用后端的某個(gè)服務(wù)。在返回給使用端之前,代理可以對(duì)后端服務(wù)的輸出進(jìn)行加工,也可以直接把后端服務(wù)的返回結(jié)果返回給使用端。
服務(wù)代理模式的架構(gòu)如圖1-12所示。
圖1-12
在筆者工作的微服務(wù)化架構(gòu)平臺(tái)下,經(jīng)常會(huì)使用這種模式,典型的案例是做平滑的系統(tǒng)遷移,通常經(jīng)歷如下4個(gè)階段。
在新老系統(tǒng)上雙寫。
遷移雙寫之前的歷史遺留數(shù)據(jù)。
將讀請(qǐng)求切換到新系統(tǒng)。
下調(diào)雙寫邏輯,只寫新系統(tǒng)。
服務(wù)代理模式常常應(yīng)用到第3步,一般會(huì)對(duì)讀請(qǐng)求切換設(shè)計(jì)一個(gè)開關(guān),開關(guān)打開時(shí)查詢新系統(tǒng),開關(guān)關(guān)閉時(shí)查詢老系統(tǒng)。
遷移案例中開關(guān)的邏輯如圖1-13所示。
圖1-13
2. 服務(wù)聚合模式
服務(wù)聚合模式是最常用的服務(wù)組合模式,它根據(jù)業(yè)務(wù)流程處理的需要,以一定的順序調(diào)用依賴的多個(gè)微服務(wù),對(duì)依賴的微服務(wù)返回的數(shù)據(jù)進(jìn)行組合、加工和轉(zhuǎn)換,最后以一定的形式返回給使用方。
這里,每個(gè)被依賴的微服務(wù)都有自己的緩存和數(shù)據(jù)庫,聚合服務(wù)本身可以有自己的數(shù)據(jù)存儲(chǔ),包括緩存和數(shù)據(jù)庫等,也可以是簡(jiǎn)單的聚合,不需要持久化任何數(shù)據(jù)。
服務(wù)聚合模式的架構(gòu)如圖1-14所示。
圖1-14
這里體現(xiàn)了DRY(Don’t Repeat Yourself)原則的設(shè)計(jì)理念,在設(shè)計(jì)或者構(gòu)造應(yīng)用時(shí),最大限度地重用了現(xiàn)有的實(shí)現(xiàn)。假如一塊業(yè)務(wù)邏輯由三個(gè)獨(dú)立的邏輯塊組成,每個(gè)獨(dú)立的邏輯塊可能有多個(gè)使用方,則DRY原則推薦將三個(gè)獨(dú)立的邏輯塊封裝成三個(gè)獨(dú)立運(yùn)行的微服務(wù),然后使用本節(jié)的服務(wù)聚合模式開發(fā)聚合服務(wù),將三個(gè)獨(dú)立的邏輯塊聚合在一起提供給上層組合服務(wù)。這樣的設(shè)計(jì)原則有如下好處。
三個(gè)獨(dú)立的子服務(wù)可以各自獨(dú)立開發(fā)、敏捷變更和部署。
聚合服務(wù)封裝下層的業(yè)務(wù)處理服務(wù),由三個(gè)獨(dú)立的子服務(wù)完成數(shù)據(jù)持久化等工作,項(xiàng)目結(jié)構(gòu)清晰明了。
三個(gè)獨(dú)立的子服務(wù)對(duì)于其他使用方仍然可以重用。
考慮到本節(jié)開頭的例子,在對(duì)微服務(wù)進(jìn)行拆分時(shí),將電商后臺(tái)系統(tǒng)大致拆分成訂單、商品、商品目錄、庫存、購物車、交易、支付、發(fā)票、物流等微服務(wù),那么電商平臺(tái)的前端應(yīng)用就是后端各個(gè)微服務(wù)的一個(gè)最大的聚合服務(wù),前端應(yīng)用通過調(diào)用商品和商品目錄顯示商品列表,提供給用戶選擇商品的功能,用戶選擇商品后增加商品到購物車,在用戶從購物車結(jié)算時(shí),調(diào)用交易系統(tǒng)完成交易和支付等。
電商前臺(tái)的聚合模式的案例架構(gòu)如圖1-15所示。
圖1-15
另外,聚合服務(wù)也可以是一個(gè)純后臺(tái)服務(wù),通過聚合對(duì)使用方輸出組合的服務(wù),例如在上面的電商系統(tǒng)案例中,在用戶選擇結(jié)算后,系統(tǒng)調(diào)用交易,交易系統(tǒng)會(huì)調(diào)用庫存系統(tǒng)鎖庫存,然后創(chuàng)建交易訂單,引導(dǎo)用戶去支付,支付成功后扣減庫存,最后通過發(fā)票服務(wù)開具電子發(fā)票。
電商后臺(tái)交易服務(wù)的聚合模式架構(gòu)如圖1-16所示。
圖1-16
3. 服務(wù)串聯(lián)模式
服務(wù)串聯(lián)模式類似于一個(gè)工作流,最前面的服務(wù)1負(fù)責(zé)接收請(qǐng)求和響應(yīng)使用方,串聯(lián)服務(wù)后再與服務(wù)1交互,隨后服務(wù)1與服務(wù)2交互,最后,從服務(wù)2產(chǎn)生的結(jié)果經(jīng)過服務(wù)1和串聯(lián)服務(wù)逐個(gè)處理后返回給使用方,如圖1-17所示。
圖1-17
服務(wù)串聯(lián)模式之間的調(diào)用通常使用同步的RESTful風(fēng)格的遠(yuǎn)程調(diào)用實(shí)現(xiàn),注意,這種模式采用的是同步調(diào)用方式,在串聯(lián)服務(wù)沒有完成并返回之前,所有服務(wù)都會(huì)阻塞和等待,一個(gè)請(qǐng)求會(huì)占用一個(gè)線程來處理,因此在這種模式下不建議服務(wù)的層級(jí)太多,如果能用服務(wù)聚合模式代替,則優(yōu)先使用服務(wù)聚合模式,而不是使用這種服務(wù)串聯(lián)模式。
相對(duì)于服務(wù)聚合模式,服務(wù)串聯(lián)模式有一個(gè)優(yōu)點(diǎn),即串聯(lián)鏈路上再增加一個(gè)節(jié)點(diǎn)時(shí),只要不是在串聯(lián)服務(wù)的正后面增加,那么串聯(lián)服務(wù)是無感知的。
在串聯(lián)服務(wù)中調(diào)用鏈的最后端增加服務(wù)無感知的架構(gòu)如圖1-18所示。
圖1-18
在上面提及的電商案例中,UI前端應(yīng)用調(diào)用交易,交易調(diào)用商品庫存系統(tǒng)鎖定庫存和扣減庫存,使用的就是服務(wù)串聯(lián)模式。
服務(wù)串聯(lián)模式案例的架構(gòu)如圖1-19所示。
圖1-19
4. 服務(wù)分支模式
服務(wù)分支模式是服務(wù)代理模式、服務(wù)聚合模式和服務(wù)串聯(lián)模式相結(jié)合的產(chǎn)物。
分支服務(wù)可以擁有自己的數(shù)據(jù)庫存儲(chǔ),調(diào)用多個(gè)后端的服務(wù)或者服務(wù)串聯(lián)鏈,然后將結(jié)果進(jìn)行組合處理再返回給客戶端。分支服務(wù)也可以使用代理模式,簡(jiǎn)單地調(diào)用后端的某個(gè)服務(wù)或者服務(wù)鏈,然后將返回的數(shù)據(jù)直接返回給使用方。
服務(wù)分支模式的架構(gòu)如圖1-20所示。
圖1-20
在實(shí)際的業(yè)務(wù)平臺(tái)建設(shè)中,由于業(yè)務(wù)的復(fù)雜性,抽象的微服務(wù)可能有多層的依賴關(guān)系,依賴關(guān)系并不會(huì)太簡(jiǎn)單,經(jīng)常呈現(xiàn)樹形的分支結(jié)構(gòu)。
以電商平臺(tái)的支付服務(wù)架構(gòu)為例,如圖1-21所示。
圖1-21
支付服務(wù)對(duì)接兩個(gè)外部的支付網(wǎng)關(guān),都要經(jīng)過各自的支付渠道網(wǎng)關(guān),同時(shí)支持賬戶余額支付,這個(gè)支付服務(wù)其實(shí)就是一個(gè)分支模式,在實(shí)際項(xiàng)目中這種服務(wù)分支模式很多。
筆者在構(gòu)建支付平臺(tái)時(shí),由于大量地使用了服務(wù)分支模式,所以發(fā)現(xiàn)了一個(gè)比較有趣的現(xiàn)象,如下所述。
假設(shè)有一個(gè)基礎(chǔ)服務(wù),在服務(wù)分支模式的多個(gè)層次中對(duì)基礎(chǔ)服務(wù)都有依賴,那么當(dāng)基礎(chǔ)服務(wù)的一臺(tái)機(jī)器宕機(jī)時(shí),假設(shè)基礎(chǔ)服務(wù)有8臺(tái)機(jī)器,則最后受影響的流量并不是1/8。假設(shè)基礎(chǔ)服務(wù)6共有8臺(tái)機(jī)器,服務(wù)1、服務(wù)3和服務(wù)5組成某服務(wù)的一個(gè)調(diào)用鏈,則調(diào)用鏈過程中會(huì)多次調(diào)用基礎(chǔ)服務(wù)6。
具體服務(wù)的調(diào)用鏈?zhǔn)疽鈭D如圖1-22所示。
圖1-22
某天,基礎(chǔ)服務(wù)6的8臺(tái)機(jī)器中的1臺(tái)宕機(jī),按照常理,大家都認(rèn)為只影響其中1/8的流量,而統(tǒng)計(jì)結(jié)果顯示影響的業(yè)務(wù)結(jié)果竟然大于1/8。
仔細(xì)思考,造成這個(gè)結(jié)果的原因是調(diào)用鏈上有多個(gè)層次重復(fù)調(diào)用了基礎(chǔ)服務(wù),導(dǎo)致基礎(chǔ)服務(wù)掛掉時(shí)影響的流量有累加效果,具體計(jì)算如下。
假設(shè)進(jìn)入系統(tǒng)的流量為n,調(diào)用鏈從服務(wù)3開始調(diào)用服務(wù)6,服務(wù)3有1/8的流量失敗,這時(shí)剩下的成功的流量為7/8 ×n,剩下的成功的流量繼續(xù)走到服務(wù)5,服務(wù)5再次調(diào)用服務(wù)6,又有1/8的流量失敗,剩下7/8 × 7/8× n。
假設(shè)基礎(chǔ)服務(wù)資源池中的機(jī)器個(gè)數(shù)為i,一次掛掉的機(jī)器個(gè)數(shù)為j,一個(gè)調(diào)用鏈中調(diào)用x次基礎(chǔ)服務(wù),那么正確處理的流量的計(jì)算公式為:
假設(shè)允許的可用性波動(dòng)率為a,求出底層服務(wù)一次宕機(jī)1臺(tái)時(shí)最少應(yīng)該配置的機(jī)器數(shù)為:
對(duì)公式進(jìn)行轉(zhuǎn)換:
由于一次只允許一臺(tái)機(jī)器宕機(jī):
所以得出需要設(shè)置的機(jī)器數(shù)量i為:
對(duì)于上面的案例,每次最多允許基礎(chǔ)服務(wù)6宕機(jī)1臺(tái),在這種情況下需要保持可用性的波動(dòng)率小于25%,一共有兩層服務(wù)依賴基礎(chǔ)服務(wù)6,通過上述公式計(jì)算得出:
i > 7.5
結(jié)果,至少為服務(wù)6部署9臺(tái)機(jī)器,這樣在1臺(tái)機(jī)器宕機(jī)時(shí),對(duì)可用性的波動(dòng)性影響控制在25%以內(nèi)。
由于分支模式放大了服務(wù)的依賴關(guān)系,因此在現(xiàn)實(shí)的微服務(wù)設(shè)計(jì)中盡量保持服務(wù)調(diào)用級(jí)別的簡(jiǎn)單,在使用服務(wù)組合和服務(wù)代理模式時(shí),不要使用服務(wù)串聯(lián)模式和服務(wù)分支模式,以保持服務(wù)依賴關(guān)系的清晰明了,這也減少了日后維護(hù)的工作量。
5. 服務(wù)異步消息模式
前面的所有服務(wù)組合模式都使用同步的RESTful風(fēng)格的同步調(diào)用來實(shí)現(xiàn),同步調(diào)用模式在調(diào)用的過程中會(huì)阻塞線程,如果服務(wù)提供方遲遲沒有返回,則服務(wù)消費(fèi)方會(huì)一直阻塞,在嚴(yán)重情況下會(huì)撐滿服務(wù)的線程池,出現(xiàn)雪崩效應(yīng)。
因此,在構(gòu)建微服務(wù)架構(gòu)系統(tǒng)時(shí),通常會(huì)梳理核心系統(tǒng)的最小化服務(wù)集合,這些核心的系統(tǒng)服務(wù)使用同步調(diào)用,而其他核心鏈路以外的服務(wù)可以使用異步消息隊(duì)列進(jìn)行異步化。
服務(wù)異步消息模式的架構(gòu)如圖1-23所示。
圖1-23
在圖1-23中,聚合服務(wù)同步調(diào)用服務(wù)1和服務(wù)2,而服務(wù)2通過消息隊(duì)列將異步消息傳遞給服務(wù)3和服務(wù)4。
典型的案例就是在電商系統(tǒng)中,交易完成后向物流系統(tǒng)發(fā)起消息通知,通知物流系統(tǒng)發(fā)貨,如圖1-24所示。
圖1-24
6. 服務(wù)共享數(shù)據(jù)模式
服務(wù)共享數(shù)據(jù)模式其實(shí)是反模式,在1.3.3節(jié)中提出了去數(shù)據(jù)共享模式,由于去掉了數(shù)據(jù)共享,所以僅僅通過服務(wù)之間良好定義的接口進(jìn)行交互和通信,使得每個(gè)服務(wù)都是自治的,服務(wù)本身和服務(wù)的團(tuán)隊(duì)包含全角色棧的技術(shù)和運(yùn)營(yíng)人員,這些人都是專業(yè)的人做專業(yè)的事,使溝通在團(tuán)隊(duì)內(nèi)部解決,因此可以使效率最大化。
服務(wù)共享數(shù)據(jù)模式的架構(gòu)如圖1-25所示。
圖1-25
然而,在下面兩種場(chǎng)景下,我們?nèi)匀恍枰獢?shù)據(jù)共享模式。
單元化架構(gòu)
一些平臺(tái)由于對(duì)性能有較高的要求,所以采用微服務(wù)化將服務(wù)進(jìn)行拆分,通過網(wǎng)絡(luò)服務(wù)進(jìn)行通信,盡管網(wǎng)絡(luò)通信的帶寬已經(jīng)很寬,但是還會(huì)有性能方面的損耗,在這種場(chǎng)景下,可以讓不同的微服務(wù)共享一些資源,例如:緩存、數(shù)據(jù)庫等,甚至可以將緩存和數(shù)據(jù)在物理拓?fù)渖吓c微服務(wù)部署在一個(gè)物理機(jī)中,最大限度地減少網(wǎng)絡(luò)通信帶來的性能損耗,我們將這種方法稱為“單元化架構(gòu)”。
單元化架構(gòu)的示意圖如圖1-26所示。
圖1-26
遺留的整體服務(wù)
對(duì)于歷史遺留的傳統(tǒng)單體服務(wù),我們?cè)谥貥?gòu)微服務(wù)的過程中,發(fā)現(xiàn)單體服務(wù)依賴的數(shù)據(jù)庫表耦合在一起,對(duì)其拆分需要進(jìn)行反規(guī)范化的處理,可能會(huì)造成數(shù)據(jù)一致性問題,在沒有對(duì)其完全理解和有把握的前提下,會(huì)選擇保持現(xiàn)狀,讓不同的微服務(wù)暫時(shí)共享數(shù)據(jù)存儲(chǔ)。
除了上面提到的兩個(gè)場(chǎng)景,任何場(chǎng)景都不能使用服務(wù)數(shù)據(jù)共享模式。
微服務(wù)的容錯(cuò)模式
在使用了微服務(wù)架構(gòu)以后,整體的業(yè)務(wù)流程被拆分成小的微服務(wù),并組合在一起對(duì)外提供服務(wù),微服務(wù)之間使用輕量級(jí)的網(wǎng)絡(luò)協(xié)議通信,通常是RESTful風(fēng)格的遠(yuǎn)程調(diào)用。由于服務(wù)與服務(wù)的調(diào)用不再是進(jìn)程內(nèi)的調(diào)用,而是通過網(wǎng)絡(luò)進(jìn)行的遠(yuǎn)程調(diào)用,眾所周知,網(wǎng)絡(luò)通信是不穩(wěn)定、不可靠的,一個(gè)服務(wù)依賴的服務(wù)可能出錯(cuò)、超時(shí)或者宕機(jī),如果沒有及時(shí)發(fā)現(xiàn)和隔離問題,或者在設(shè)計(jì)中沒有考慮如何應(yīng)對(duì)這樣的問題,那么很可能在短時(shí)間內(nèi)服務(wù)的線程池中的線程被用滿、資源耗盡,導(dǎo)致出現(xiàn)雪崩效應(yīng)。本節(jié)針對(duì)微服務(wù)架構(gòu)中可能遇到的這些問題,講解應(yīng)該采取哪些措施和方案來解決。
1. 艙壁隔離模式
這里用航船的設(shè)計(jì)比喻艙壁隔離模式,若一艘航船遇到了意外事故,其中一個(gè)船艙進(jìn)了水,則我們希望這個(gè)船艙和其他船艙是隔離的,希望其他船艙可以不進(jìn)水,不受影響。在微服務(wù)架構(gòu)中,這主要體現(xiàn)在如下兩個(gè)方面。
1)微服務(wù)容器分組
筆者所在的支付平臺(tái)應(yīng)用了微服務(wù),將微服務(wù)的每個(gè)節(jié)點(diǎn)的服務(wù)池分為三組:準(zhǔn)生產(chǎn)環(huán)境、灰度環(huán)境和生產(chǎn)環(huán)境。準(zhǔn)生產(chǎn)環(huán)境供內(nèi)側(cè)使用;灰度環(huán)境會(huì)跑一些普通商戶的流量;大部分生產(chǎn)流量和VIP商戶的流量則跑在生產(chǎn)環(huán)境中。這樣,在一次比較大的重構(gòu)過程中,我們就可以充分利用灰度環(huán)境的隔離性進(jìn)行預(yù)驗(yàn)證,用普通商戶的流量驗(yàn)證重構(gòu)沒有問題后,再上生產(chǎn)環(huán)境。
另外一個(gè)案例是一些社交平臺(tái)將名人的自媒體流量全部路由到服務(wù)的核心池子中,而將普通用戶的流量路由到另外一個(gè)服務(wù)池子中,有效隔離了普通用戶和重要用戶的負(fù)載。
其服務(wù)分組如圖1-27所示。
圖1-27
2)線程池隔離
在微服務(wù)架構(gòu)實(shí)施的過程中,我們不一定將每個(gè)服務(wù)拆分到微小的力度,這取決于職能團(tuán)隊(duì)和財(cái)務(wù)的狀況,我們一般會(huì)將同一類功能劃分在一個(gè)微服務(wù)中,盡量避免微服務(wù)過細(xì)而導(dǎo)致成本增加,適可而止。
這樣就會(huì)導(dǎo)致多個(gè)功能混合部署在一個(gè)微服務(wù)實(shí)例中,這些微服務(wù)的不同功能通常使用同一個(gè)線程池,導(dǎo)致一個(gè)功能流量增加時(shí)耗盡線程池的線程,而阻塞其他功能的服務(wù)。
線程池隔離如圖1-28所示。
圖1-28
2. 熔斷模式
可以用家里的電路保險(xiǎn)開關(guān)來比喻熔斷模式,如果家里的用電量過大,則電路保險(xiǎn)開關(guān)就會(huì)自動(dòng)跳閘,這時(shí)需要人工找到用電量過大的電器來解決問題,然后打開電路保險(xiǎn)開關(guān)。在這個(gè)過程中,電路保險(xiǎn)開關(guān)起到保護(hù)整個(gè)家庭電路系統(tǒng)的作用。
對(duì)于微服務(wù)系統(tǒng)也一樣,當(dāng)服務(wù)的輸入負(fù)載迅速增加時(shí),如果沒有有效的措施對(duì)負(fù)載進(jìn)行熔斷,則會(huì)使服務(wù)迅速被壓垮,服務(wù)被壓垮會(huì)導(dǎo)致依賴的服務(wù)都被壓垮,出現(xiàn)雪崩效應(yīng),因此,可通過模擬家庭的電路保險(xiǎn)開關(guān),在微服務(wù)架構(gòu)中實(shí)現(xiàn)熔斷模式。
微服務(wù)化的熔斷模式的狀態(tài)流轉(zhuǎn)如圖1-29所示。
圖1-29
3. 限流模式
服務(wù)的容量和性能是有限的,在第3章中會(huì)介紹如何在架構(gòu)設(shè)計(jì)過程中評(píng)估服務(wù)的最大性能和容量,然而,即使我們?cè)谠O(shè)計(jì)階段考慮到了性能壓力的問題,并從設(shè)計(jì)和部署上解決了這些問題,但是業(yè)務(wù)量是隨著時(shí)間的推移而增長(zhǎng)的,突然上量對(duì)于一個(gè)飛速發(fā)展的平臺(tái)來說是很常見的事情。
針對(duì)服務(wù)突然上量,我們必須有限流機(jī)制,限流機(jī)制一般會(huì)控制訪問的并發(fā)量,例如每秒允許處理的并發(fā)用戶數(shù)及查詢量、請(qǐng)求量等。
有如下幾種主流的方法實(shí)現(xiàn)限流。
1)計(jì)數(shù)器
通過原子變量計(jì)算單位時(shí)間內(nèi)的訪問次數(shù),如果超出某個(gè)閾值,則拒絕后續(xù)的請(qǐng)求,等到下一個(gè)單位時(shí)間再重新計(jì)數(shù)。
在計(jì)數(shù)器的實(shí)現(xiàn)方法中通常定義了一個(gè)循環(huán)數(shù)組(見圖1-30),例如:定義5個(gè)元素的環(huán)形數(shù)組,計(jì)數(shù)周期為1s,可以記錄4s內(nèi)的訪問量,其中有1個(gè)元素為當(dāng)前時(shí)間點(diǎn)的標(biāo)志,通常來說每秒程序都會(huì)將前面3s的訪問量打印到日志,供統(tǒng)計(jì)分析。
圖1-30
我們將時(shí)間的秒數(shù)除以數(shù)組元素的個(gè)數(shù)5,然后取模,映射到環(huán)形數(shù)組里的數(shù)據(jù)元素,假如當(dāng)前時(shí)間是1 000 000 002s,那么對(duì)應(yīng)當(dāng)前時(shí)間的環(huán)形數(shù)組里的第3個(gè)元素,下標(biāo)為2。
此時(shí)的數(shù)組元素的數(shù)據(jù)如圖1-31所示。
圖1-31
在圖1-31中,當(dāng)前時(shí)間為1 000 000 002s,對(duì)應(yīng)的計(jì)數(shù)器在第3個(gè)元素,下標(biāo)為2,當(dāng)前請(qǐng)求是在這個(gè)時(shí)間周期內(nèi)的第1個(gè)訪問請(qǐng)求,程序首先需要對(duì)后一個(gè)元素即第4個(gè)元素,也就是下標(biāo)為3的元素清零;在1 000 000 002s內(nèi),任何一個(gè)請(qǐng)求如果發(fā)現(xiàn)下標(biāo)為3的元素不為0,則都會(huì)將原子變量3清零,并記錄清零的時(shí)間。
這時(shí)程序可以對(duì)第3個(gè)元素即下標(biāo)為2的元素,進(jìn)行累加并判斷是否達(dá)到閾值,如果達(dá)到閾值,則拒絕請(qǐng)求,否則請(qǐng)求通過;同時(shí),打印本次及之前3秒的數(shù)據(jù)訪問量,打印結(jié)果如下。
當(dāng)前:1次,前1s:302次,前2s:201次,前3s:518次
然而,如果當(dāng)前秒一直沒有請(qǐng)求量,下一秒的計(jì)數(shù)器始終不能清零,則下一秒的請(qǐng)求到達(dá)后要首先清零再使用,并更新清零時(shí)間。
在下一秒的請(qǐng)求到達(dá)后,若檢查到當(dāng)前秒對(duì)應(yīng)的原子變量計(jì)數(shù)器不為0,而且最后的清零時(shí)間不是上一秒,則先對(duì)當(dāng)前秒的計(jì)數(shù)器清零,再進(jìn)行累加操作,這避免發(fā)生上一秒無請(qǐng)求的場(chǎng)景,或者上一秒的請(qǐng)求由于線程調(diào)度延遲而沒有清零下一秒的場(chǎng)景,后面這種場(chǎng)景發(fā)生的概率較小。
另外一種實(shí)現(xiàn)計(jì)數(shù)器的簡(jiǎn)單方法是單獨(dú)啟動(dòng)一個(gè)線程,每隔一定的時(shí)間間隔執(zhí)行對(duì)下一秒的原子變量計(jì)數(shù)器清零操作,這個(gè)時(shí)間間隔必須小于計(jì)數(shù)時(shí)間間隔。
2)令牌筒
令牌筒是一個(gè)流行的實(shí)現(xiàn)限流的技術(shù)方案,它通過一個(gè)線程在單位時(shí)間內(nèi)生產(chǎn)固定數(shù)量的令牌,然后把令牌放入隊(duì)列,每次請(qǐng)求調(diào)用需要從桶中拿取一個(gè)令牌,拿到令牌后才有資格執(zhí)行請(qǐng)求調(diào)用,否則只能等待拿到令牌再執(zhí)行,或者直接丟棄。
令牌筒的結(jié)構(gòu)如圖1-32所示。
圖1-32
3)信號(hào)量
限流類似于生活中的漏洞,無論倒入多少油,下面有漏管的流量是有限的,實(shí)際上我們?cè)趹?yīng)用層使用的信號(hào)量也可以實(shí)現(xiàn)限流。
使用信號(hào)量的示例如下:
publicclassSemaphoreExample {privateExecutorService exec = Executors.newCachedThreadPool();publicstaticvoidmain(String[] args) {? ? ? ? final Semaphore sem =newSemaphore(5);for(intindex =0; index <20; index++) {? ? ? ? ? ? Runnable run =newRunnable() {publicvoidrun() {try{// 獲得許可sem.acquire();// 同時(shí)只有5個(gè)請(qǐng)求可以到達(dá)這里Thread.sleep((long) (Math.random()));// 釋放許可sem.release();? ? ? ? ? ? ? ? ? ? ? ? System.out.println("剩余許可:"+ sem.availablePermits());? ? ? ? ? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? };? ? ? ? ? ? exec.execute(run);? ? ? ? }? ? ? ? exec.shutdown();}}
4. 失效轉(zhuǎn)移模式
若微服務(wù)架構(gòu)中發(fā)生了熔斷和限流,則該如何處理被拒絕的請(qǐng)求呢?解決這個(gè)問題的模式叫作失效轉(zhuǎn)移模式,通常分為下面幾種。
采用快速失敗的策略,直接返回使用方錯(cuò)誤,讓使用方知道發(fā)生了問題并自行決定后續(xù)處理。
是否有備份服務(wù),如果有備份服務(wù),則迅速切換到備份服務(wù)。
失敗的服務(wù)有可能是某臺(tái)機(jī)器有問題,而不是所有機(jī)器有問題,例如OOM問題,在這種情況下適合使用failover策略,采用重試的方法來解決,但是這種方法要求服務(wù)提供者的服務(wù)實(shí)現(xiàn)了冪等性。
微服務(wù)的粒度
在服務(wù)化系統(tǒng)或者微服務(wù)架構(gòu)中,我們?nèi)绾尾鸱址?wù)才是最合理的?服務(wù)拆分到什么樣的粒度最合適?
按照微服務(wù)的初衷,服務(wù)要按照業(yè)務(wù)的功能進(jìn)行拆分,直到每個(gè)服務(wù)的功能和職責(zé)單一,甚至不可再拆分為止,以至于每個(gè)服務(wù)都能獨(dú)立部署,擴(kuò)容和縮容方便,能夠有效地提高利用率。拆得越細(xì),服務(wù)的耦合度越小,內(nèi)聚性越好,越適合敏捷發(fā)布和上線。
然而,拆得太細(xì)會(huì)導(dǎo)致系統(tǒng)的服務(wù)數(shù)量較多,相互依賴的關(guān)系較復(fù)雜,更重要的是根據(jù)康威定律,團(tuán)隊(duì)要響應(yīng)系統(tǒng)的架構(gòu),每個(gè)微服務(wù)都要有相應(yīng)的獨(dú)立、自治的團(tuán)隊(duì)來維護(hù),這也是一個(gè)不切實(shí)際的想法。
因此,這里倡導(dǎo)對(duì)微服務(wù)的拆分適可而止,原則是拆分到可以讓使用方自由地編排底層的子服務(wù)來獲得相應(yīng)的組合服務(wù)即可,同時(shí)要考慮團(tuán)隊(duì)的建設(shè)及人員的數(shù)量和分配等。
有的公司把每個(gè)接口包裝成一個(gè)工程,或者把每一次JDBC調(diào)用包裝成一個(gè)工程,然后號(hào)稱是“微服務(wù)”,最后有成百上千的微服務(wù)項(xiàng)目,這是不合理的。當(dāng)然,有的公司把一套接口完成的一個(gè)粗粒度的流程耦合在一個(gè)項(xiàng)目中,導(dǎo)致上層服務(wù)想要使用這套接口中某個(gè)單獨(dú)的服務(wù)時(shí),由于這個(gè)服務(wù)與其他邏輯耦合在一起,所以需要在流程中做定制化才能實(shí)現(xiàn)使用方使用部分服務(wù)的需求,這也是不合理的,原因是服務(wù)粒度太粗。
總之,拆分的粒度太細(xì)和太粗都是不合理的,根據(jù)業(yè)務(wù)需要,能夠滿足上層服務(wù)對(duì)底層服務(wù)自由編排并獲得更多的業(yè)務(wù)功能即可,并需要適合團(tuán)隊(duì)的建設(shè)和布局。
大家可以點(diǎn)擊加入群:561614305【JAVA大神交流二群】里面有Java高級(jí)大牛直播講解知識(shí)點(diǎn) 走的就是高端路線 (如果你想跳槽換工作 但是技術(shù)又不夠 或者工作上遇到了 瓶頸 我這里有一個(gè)JAVA的免費(fèi)直播課程 講的是高端的知識(shí)點(diǎn)