在微服務(wù)架構(gòu)中,需要調(diào)用很多服務(wù)才能完成一項(xiàng)功能。服務(wù)之間如何互相調(diào)用就變成微服務(wù)架構(gòu)中的一個(gè)關(guān)鍵問題。
服務(wù)調(diào)用有兩種方式,一種是RPC方式,另一種是事件驅(qū)動(dòng)(Event-driven)方式,也就是發(fā)消息方式。
消息方式是松耦合方式,比緊耦合的RPC方式要優(yōu)越,但RPC方式如果用在適合的場(chǎng)景也有它的一席之地。
我們總在談耦合,那么耦合到底意味著什么呢?
耦合的種類:
時(shí)間耦合: 客戶端和服務(wù)端必須同時(shí)上線才能工作。發(fā)消息時(shí),接受消息隊(duì)列必須運(yùn)行,但后臺(tái)處理程序暫時(shí)不工作也不影響。
容量耦合: 客戶端和服務(wù)端的處理容量必須匹配。發(fā)消息時(shí),如果后臺(tái)處理能力不足也不要緊,消息隊(duì)列會(huì)起到緩沖的作用。
接口耦合: RPC調(diào)用有函數(shù)標(biāo)簽,而消息隊(duì)列只是一個(gè)消息。例如買了商品之后要調(diào)用發(fā)貨服務(wù),如果是發(fā)消息,那么就只需發(fā)送一個(gè)商品被買消息。
發(fā)送方式耦合: RPC是點(diǎn)對(duì)點(diǎn)方式,需要知道對(duì)方是誰,它的好處是能夠傳回返回值。消息既可以點(diǎn)對(duì)點(diǎn),也可以用廣播的方式,這樣減少了耦合,但也使返回值比較困難。
下面我們來逐一分析這些耦合的影響。第一,時(shí)間耦合,對(duì)于多數(shù)應(yīng)用來講,你希望能馬上得到回答,因此即使使用消息隊(duì)列,后臺(tái)也需要一直工作。
第二,容量耦合,如果你對(duì)回復(fù)有時(shí)間要求,那么消息隊(duì)列的緩沖功能作用不大,因?yàn)槟阆M皶r(shí)響應(yīng)。
真正需要的是自動(dòng)伸縮(Auto-scaling),它能自動(dòng)調(diào)整服務(wù)端處理能力去匹配請(qǐng)求數(shù)量。第三和第四,接口耦合和發(fā)送方式耦合,這兩個(gè)確實(shí)是RPC方式的軟肋。
事件驅(qū)動(dòng)(Event-Driven)方式
Martin Fowler把事件驅(qū)動(dòng)分成四種方式(What do you mean by “Event-Driven”),簡(jiǎn)化之后本質(zhì)上只有兩種方式。一種就是我們熟悉的的事件通知(Event Notification),另一種是事件溯源(Event Sourcing)。
事件通知就是微服務(wù)之間不直接調(diào)用,而是通過發(fā)消息來進(jìn)行合作。事件溯源有點(diǎn)像記賬,它把所有的事件都記錄下來,作為永久存儲(chǔ)層,再在它的基礎(chǔ)之上構(gòu)建應(yīng)用程序。
實(shí)際上從應(yīng)用的角度來講,它們并不應(yīng)該分屬一類,它們的用途完全不同。事件通知是微服務(wù)的調(diào)用(或集成)方式,應(yīng)該和RPC分在一起。事件溯源是一種存儲(chǔ)數(shù)據(jù)的方式,應(yīng)該和數(shù)據(jù)庫分在一起。
事件通知(Event Notification)方式
讓我們用具體的例子來看一下。在下面的例子中,有三個(gè)微服務(wù),“Order Service”, “Customer Service” 和“Product Service”。
先說讀數(shù)據(jù),假設(shè)要?jiǎng)?chuàng)建一個(gè)“Order”,在這個(gè)過程中需要讀取“Customer”的數(shù)據(jù)和“Product”數(shù)據(jù)。
如果用事件通知的方式就只能在“Order Service”本地也創(chuàng)建只讀“Customer”和“Product”表,并把數(shù)據(jù)用消息的方式同步過來。
再說寫數(shù)據(jù),如果在創(chuàng)建一個(gè)“Order”時(shí)需要?jiǎng)?chuàng)建一個(gè)新的“Customer”或要修改“Customer”的信息,那么可以在界面上跳轉(zhuǎn)到用戶創(chuàng)建頁面,然后在“Customer Service”創(chuàng)建用戶之后再發(fā)”用戶已創(chuàng)建“的消息,“Order Service”接到消息,更新本地“Customer”表。
這并不是一個(gè)很好的使用事件驅(qū)動(dòng)的例子,因?yàn)槭录?qū)動(dòng)的優(yōu)點(diǎn)就是不同的程序之間可以獨(dú)立運(yùn)行,沒有綁定關(guān)系。但現(xiàn)在“Order Service”需要等待“Customer Service”創(chuàng)建完了之后才能繼續(xù)運(yùn)行,來完成整個(gè)創(chuàng)建“Order”的工作。主要是因?yàn)椤癘rder”和“Customer”本身從邏輯上來講就是緊耦合關(guān)系,沒有“Customer”你是不能創(chuàng)建“Order”的。
在這種緊耦合的情況下,也可以使用RPC。你可以建立一個(gè)更高層級(jí)的管理程序來管理這些微服務(wù)之間的調(diào)用,這樣“Order Service”就不必直接調(diào)用“Customer Service”了。
當(dāng)然它從本質(zhì)上來講并沒有解除耦合,只是把耦合轉(zhuǎn)移到了上一層,但至少現(xiàn)在“order Service”和“Customer Service”可以互不影響了。之所以不能根除這種緊耦合關(guān)系是因?yàn)樗鼈冊(cè)跇I(yè)務(wù)上是緊耦合的。
再舉一個(gè)購物的例子。用戶選好商品之后進(jìn)行“Checkout”,生成“Order”,然后需要“payment”,再從“Inventory”取貨,最后由“Shipment”發(fā)貨,它們每一個(gè)都是微服務(wù)。這個(gè)例子用RPC方式和事件通知方式都可以完成。
當(dāng)用RPC方式時(shí),由“Order”服務(wù)調(diào)用其他幾個(gè)服務(wù)來完成整個(gè)功能。用事件通知方式時(shí),“Checkout”服務(wù)完成之后發(fā)送“Order Placed”消息,“Payment”服務(wù)收到消息,接收用戶付款,發(fā)送“Payment received”消息。
“Inventory”服務(wù)收到消息,從倉庫里取貨,并發(fā)送“Goods fetched”消息?!癝hipment”服務(wù)得到消息,發(fā)送貨物,并發(fā)送“Goods shipped”消息。
對(duì)這個(gè)例子來講,使用事件驅(qū)動(dòng)是一個(gè)不錯(cuò)的選擇,因?yàn)槊總€(gè)服務(wù)發(fā)消息之后它不需要任何反饋,這個(gè)消息由下一個(gè)模塊接收來完成下一步動(dòng)作,時(shí)間上的要求也比上一個(gè)要寬松。用事件驅(qū)動(dòng)的好處是降低了耦合度,壞處是你現(xiàn)在不能在程序里找到整個(gè)購物過程的步驟。
如果一個(gè)業(yè)務(wù)邏輯有它自己相對(duì)固定的流程和步驟,那么使用RPC或業(yè)務(wù)流程管理(BPM)能夠更方便地管理這些流程。在這種情況下選哪種方案呢?在我看來好處和壞處是大致相當(dāng)?shù)摹募夹g(shù)上來講要選事件驅(qū)動(dòng),從業(yè)務(wù)上來講要選RPC。不過現(xiàn)在越來越多的人采用事件通知作為微服務(wù)的集成方式,它似乎已經(jīng)成了微服務(wù)之間的標(biāo)椎調(diào)用方式。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
事件溯源(Event Sourcing)
這是一種具有顛覆性質(zhì)的的設(shè)計(jì),它把系統(tǒng)中所有的數(shù)據(jù)都以事件(Event)的方式記錄下來,它的持久存儲(chǔ)叫Event Store, 一般是建立在數(shù)據(jù)庫或消息隊(duì)列(例如Kafka)基礎(chǔ)之上,并提供了對(duì)事件進(jìn)行操作的接口,例如事件的讀寫和查詢。事件溯源是由領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design)提出來的。
DDD中有一個(gè)很重要的概念,有界上下文(Bounded Context),可以用有界上下文來劃分微服務(wù),每個(gè)有界上下文都可以是一個(gè)微服務(wù)。下面是有界上下文的示例。下圖中有兩個(gè)服務(wù)“Sales”和“Support”。
有界上下文的一個(gè)關(guān)鍵是如何處理共享成員, 在圖中是“Customer”和“Product”。在不同的有界上下文中,共享成員的含義、用法以及他們的對(duì)象屬性都會(huì)有些不同,DDD建議這些共享成員在各自的有界上下文中都分別建自己的類(包括數(shù)據(jù)庫表),而不是共享。可以通過數(shù)據(jù)同步的手段來保持?jǐn)?shù)據(jù)的一致性。下面還會(huì)詳細(xì)講解。
事件溯源是微服務(wù)的一種存儲(chǔ)方式,它是微服務(wù)的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。因此你可以決定哪些微服務(wù)采用事件溯源方式,哪些不采用,而不必所有的服務(wù)都變成事件溯源的。通常整個(gè)應(yīng)用程序只有一個(gè)Event Store, 不同的微服務(wù)都通過向Event Store發(fā)送和接受消息而互相通信。
Event Store內(nèi)部可以分成不同的stream(相當(dāng)于消息隊(duì)列中的Topic), 供不同的微服務(wù)中的領(lǐng)域?qū)嶓w(Domain Entity)使用。
事件溯源的一個(gè)短板是數(shù)據(jù)查詢,它有兩種方式來解決。第一種是直接對(duì)stream進(jìn)行查詢,這只適合stream比較小并且查詢比較簡(jiǎn)單的情況。
查詢復(fù)雜的話,就要采用第二種方式,那就是建立一個(gè)只讀數(shù)據(jù)庫,把需要的數(shù)據(jù)放在庫中進(jìn)行查詢。數(shù)據(jù)庫中的數(shù)據(jù)通過監(jiān)聽Event Store中相關(guān)的事件來更新。
數(shù)據(jù)庫存儲(chǔ)方式只能保存當(dāng)前狀態(tài),而事件溯源則存儲(chǔ)了所有的歷史狀態(tài),因而能根據(jù)需要回放到歷史上任何一點(diǎn)的狀態(tài),具有很大優(yōu)勢(shì)。但它也不是一點(diǎn)問題都沒有。
第一,它的程序比較復(fù)雜,因?yàn)槭录且坏裙?,你必須把業(yè)務(wù)邏輯按照事件的方式整理出來,然后用事件來驅(qū)動(dòng)程序。第二,如果你要想修改事件或事件的格式就比較麻煩,因?yàn)榕f的事件已經(jīng)存儲(chǔ)在Event Store里了(事件就像日志,是只讀的),沒有辦法再改。
由于事件溯源和事件通知表面上看起來很像,不少人都搞不清楚它們的區(qū)別。事件通知只是微服務(wù)的集成方式,程序內(nèi)部是不使用事件溯源的,內(nèi)部實(shí)現(xiàn)仍然是傳統(tǒng)的數(shù)據(jù)庫方式。
只有當(dāng)要與其他微服務(wù)集成時(shí)才會(huì)發(fā)消息。而在事件溯源中,事件是一等公民,可以不要數(shù)據(jù)庫,全部數(shù)據(jù)都是按照事件的方式存儲(chǔ)的。
雖然事件溯源的踐行者有不同的意見,但有不少人都認(rèn)為事件溯源不是微服務(wù)的集成方式,而是微服務(wù)的一種內(nèi)部實(shí)現(xiàn)方式。因此,在一個(gè)系統(tǒng)中,可以某些微服務(wù)用事件溯源,另外一些微服務(wù)用數(shù)據(jù)庫。
當(dāng)你要集成這些微服務(wù)時(shí),你可以用事件通知的方式。注意現(xiàn)在有兩種不同的事件需要區(qū)分開,一種是微服務(wù)的內(nèi)部事件,是顆粒度比較細(xì)的,這種事件只發(fā)送到這個(gè)微服務(wù)的stream中,只被事件溯源使用。
另一種是其他微服務(wù)也關(guān)心的,是顆粒度比較粗的,這種事件會(huì)放到另外一個(gè)或幾個(gè)stream中,被多個(gè)微服務(wù)使用,是用來做服務(wù)之間集成的。這樣做的好處是限制了事件的作用范圍,減少了不相關(guān)事件對(duì)程序的干擾。詳見"Domain Events vs. Event Sourcing"。
事件溯源出現(xiàn)已經(jīng)很長(zhǎng)時(shí)間了,雖然熱度一直在上升(尤其是這兩年),但總的來說非常緩慢,談?wù)摰娜瞬簧?,但生產(chǎn)環(huán)境使用的不多。究其原因就是應(yīng)為它對(duì)現(xiàn)在的體系結(jié)構(gòu)顛覆太大,需要更改數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)和程序的工作方式,還是有一定風(fēng)險(xiǎn)的。
另外,微服務(wù)已經(jīng)形成了一整套體系,從程序部署,服務(wù)發(fā)現(xiàn)與注冊(cè),到監(jiān)控,服務(wù)韌性(Service Resilience),它們基本上都是針對(duì)RPC的,雖然也支持消息,但成熟度就差多了,因此有不少工作還是要自己來做。
有意思的是Kafka一直在推動(dòng)它作為事件驅(qū)動(dòng)的工具,也取得了很大的成功。但它卻沒有得到事件溯源圈內(nèi)的認(rèn)可。
多數(shù)事件溯源都使用一個(gè)叫evenstore的開源Event Store,或是基于某個(gè)數(shù)據(jù)庫的Event Store,只有比較少的人用Kafka做Event Store。
但如果用Kafka實(shí)現(xiàn)事件通知就一點(diǎn)問題都沒有。總的來說,對(duì)大多數(shù)公司來講事件溯源是有一定挑戰(zhàn)的,應(yīng)用時(shí)需要找到合適的場(chǎng)景。如果你要嘗試的話,可以先拿一個(gè)微服務(wù)試水。
雖然現(xiàn)在事件驅(qū)動(dòng)還有些生澀,但從長(zhǎng)遠(yuǎn)來講,還是很看好它的。像其他全新的技術(shù)一樣,事件溯源需要大規(guī)模的適用場(chǎng)景來推動(dòng)。例如容器技術(shù)就是因?yàn)槲⒎?wù)的流行和推動(dòng),才走向主流。
事件溯源以前的適用場(chǎng)景只限于記賬和源代碼庫,局限性較大。區(qū)塊鏈可能會(huì)成為它的下一個(gè)機(jī)遇,因?yàn)樗玫囊彩鞘录菰醇夹g(shù)。
另外AI今后會(huì)滲入到具體程序中,使程序具有學(xué)習(xí)功能。而RPC模式注定沒有自適應(yīng)功能。事件驅(qū)動(dòng)本身就具有對(duì)事件進(jìn)行反應(yīng)的能力,這是自我學(xué)習(xí)的基礎(chǔ)。因此,這項(xiàng)技術(shù)長(zhǎng)遠(yuǎn)來講定會(huì)大放異彩,但短期內(nèi)(3-5年)大概不會(huì)成為主流。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
RPC方式
RPC的方式就是遠(yuǎn)程函數(shù)調(diào)用,像RESTFul,gRPC, DUBBO 都是這種方式。它一般是同步的,可以馬上得到結(jié)果。在實(shí)際中,大多數(shù)應(yīng)用都要求立刻得到結(jié)果,這時(shí)同步方式更有優(yōu)勢(shì),代碼也更簡(jiǎn)單。
服務(wù)網(wǎng)關(guān)(API Gateway)
熟悉微服務(wù)的人可能都知道服務(wù)網(wǎng)關(guān)(API Gateway)。當(dāng)UI需要調(diào)用很多微服務(wù)時(shí),它需要了解每個(gè)服務(wù)的接口,這個(gè)工作量很大。
于是就用服務(wù)網(wǎng)關(guān)創(chuàng)建了一個(gè)Facade,把幾個(gè)微服務(wù)封裝起來,這樣UI就只調(diào)用服務(wù)網(wǎng)關(guān)就可以了,不需要去對(duì)付每一個(gè)微服務(wù)。下面是API Gateway示例圖:
服務(wù)網(wǎng)關(guān)(API Gateway)不是為了解決微服務(wù)之間調(diào)用的緊耦合問題,它主要是為了簡(jiǎn)化客戶端的工作。其實(shí)它還可以用來降低函數(shù)之間的耦合度。
有了API Gateway之后,一旦服務(wù)接口修改,你可能只需要修改API Gateway, 而不必修改每個(gè)調(diào)用這個(gè)函數(shù)的客戶端,這樣就減少了程序的耦合性。
服務(wù)調(diào)用
可以借鑒API Gateway的思路來減少RPC調(diào)用的耦合度,例如把多個(gè)微服務(wù)組織起來形成一個(gè)完整功能的服務(wù)組合,并對(duì)外提供統(tǒng)一的服務(wù)接口。這種想法跟上面的API Gateway有些相似,都是把服務(wù)集中起來提供粗顆粒(Coarse Granular)服務(wù),而不是細(xì)顆粒的服務(wù)(Fine Granular)。
但這樣建立的服務(wù)組合可能只適合一個(gè)程序使用,沒有多少共享價(jià)值。因此如果有合適的場(chǎng)景就采用,否側(cè)也不必強(qiáng)求。雖然我們不能降低RPC服務(wù)之間的耦合度,卻可以減少這種緊耦合帶來的影響。
降低緊耦合的影響
什么是緊耦合的主要問題呢?就是客戶端和服務(wù)端的升級(jí)不同步。服務(wù)端總是先升級(jí),客戶端可能有很多,如果要求它們同時(shí)升級(jí)是不現(xiàn)實(shí)的。它們有各自的部署時(shí)間表,一般都會(huì)選擇在下一次部署時(shí)順帶升級(jí)。
一般有兩個(gè)辦法可以解決這個(gè)問題:
同時(shí)支持多個(gè)版本:這個(gè)工作量比較大,因此大多數(shù)公司都不會(huì)采用這種方式。
服務(wù)端向后兼容:這是更通用的方式。例如你要加一個(gè)新功能或有些客戶要求給原來的函數(shù)增加一個(gè)新的參數(shù),但別的客戶不需要這個(gè)參數(shù)。這時(shí)你只好新建一個(gè)函數(shù),跟原來的功能差不多,只是多了一個(gè)參數(shù)。這樣新舊客戶的需求都能滿足。它的好處是向后兼容(當(dāng)然這取決于你使用的協(xié)議)。
它的壞處是當(dāng)以后新的客戶來了,看到兩個(gè)差不多的函數(shù)就糊涂了,不知道該用那個(gè)。而且時(shí)間越長(zhǎng)越嚴(yán)重,你的服務(wù)端可能功能增加的不多,但相似的函數(shù)卻越來越多,無法選擇。
它的解決辦法就是使用一個(gè)支持向后兼容的RPC協(xié)議,現(xiàn)在最好的就是Protobuf gRPC,尤其是在向后兼容上。
它給每個(gè)服務(wù)定義了一個(gè)接口,這個(gè)接口是與編程語言無關(guān)的中性接口,然后你可以用工具生成各個(gè)語言的實(shí)現(xiàn)代碼,供不同語言使用。函數(shù)定義的變量都有編號(hào),變量可以是可選類型的,這樣就比較好地解決了函數(shù)兼容的問題。
就用上面的例子,當(dāng)你要增加一個(gè)可選參數(shù)時(shí),你就定義一個(gè)新的可選變量。由于它是可選的,原來的客戶端不需要提供這個(gè)參數(shù),因此不需要修改程序。
而新的客戶端可以提供這個(gè)參數(shù)。你只要在服務(wù)端能同時(shí)處理這兩種情況就行了。這樣服務(wù)端并沒有增加新的函數(shù),但用戶的新需求滿足了,而且還是向后兼容的。
微服務(wù)的數(shù)量有沒有上限?
總的來說微服務(wù)的數(shù)量不要太多,不然會(huì)有比較重的運(yùn)維負(fù)擔(dān)。有一點(diǎn)需要明確的是微服務(wù)的流行不是因?yàn)榧夹g(shù)上的創(chuàng)新,而是為了滿足管理上的需要。單體程序大了之后,各個(gè)模塊的部署時(shí)間要求不同,對(duì)服務(wù)器的優(yōu)化要求也不同,而且團(tuán)隊(duì)人數(shù)眾多,很難協(xié)調(diào)管理。
把程序拆分成微服務(wù)之后,每個(gè)團(tuán)隊(duì)負(fù)責(zé)幾個(gè)服務(wù),就容易管理了,而且每個(gè)團(tuán)隊(duì)也可以按照自己的節(jié)奏進(jìn)行創(chuàng)新,但它給運(yùn)維帶來了巨大的麻煩。所以在微服務(wù)剛出來時(shí),我一直覺得它是一個(gè)退步,弊大于利。但由于管理上的問題沒有其他解決方案,只有硬著頭皮上了。
值得慶幸的是微服務(wù)帶來的麻煩都是可解的。直到后來,微服務(wù)建立了全套的自動(dòng)化體系,從程序集成到部署,從全鏈路跟蹤到日志,以及服務(wù)檢測(cè),服務(wù)發(fā)現(xiàn)和注冊(cè),這樣才把微服務(wù)的工作量降了下來。
雖然微服務(wù)在技術(shù)上一無是處,但它的流行還是大大推動(dòng)了容器技術(shù),服務(wù)網(wǎng)格(Service Mesh)和全鏈路跟蹤等新技術(shù)的發(fā)展。不過它本身在技術(shù)上還是沒有發(fā)現(xiàn)任何優(yōu)勢(shì)。
直到有一天,我意識(shí)到單體程序其實(shí)性能調(diào)試是很困難的(很難分離出瓶頸點(diǎn)),而微服務(wù)配置了全鏈路跟蹤之后,能很快找到癥結(jié)所在。看來微服務(wù)從技術(shù)來講也不全是缺點(diǎn),總算也有好的地方。但微服務(wù)的顆粒度不宜過細(xì),否則工作量還是太大。
一般規(guī)模的公司十幾個(gè)或幾十個(gè)微服務(wù)都是可以承受的,但如果有幾百個(gè)甚至上千個(gè),那么絕不是一般公司可以管理的。盡管現(xiàn)有的工具已經(jīng)很齊全了,而且與微服務(wù)有關(guān)的整個(gè)流程也已經(jīng)基本上全部自動(dòng)化了,但它還是會(huì)增加很多工作。
Martin Fowler幾年以前建議先從單體程序開始(詳見 MonolithFirst),然后再逐步把功能拆分出去,變成一個(gè)個(gè)的微服務(wù)。但是后來有人反對(duì)這個(gè)建議,他也有些松口了。
如果單體程序不是太大,這是個(gè)好主意??梢杂脭?shù)據(jù)額庫表的數(shù)量來衡量程序的大小,我見過大的單體程序有幾百張表,這就太多了,很難管理。正常情況下,一個(gè)微服務(wù)可以有兩、三張表到五、六張表,一般不超過十張表。但如果要減少微服務(wù)數(shù)量的話,可以把這個(gè)標(biāo)準(zhǔn)放寬到不要超過二十張表。
用這個(gè)做為大致的指標(biāo)來創(chuàng)建微程序,如果使用一段時(shí)間之后還是覺得太大了,那么再逐漸拆分。當(dāng)然,按照這個(gè)標(biāo)準(zhǔn)建立的服務(wù)更像是服務(wù)組合,而不是單個(gè)的微服務(wù)。不過它會(huì)為你減少工作量。只要不影響業(yè)務(wù)部門的創(chuàng)新進(jìn)度,這是一個(gè)不錯(cuò)的方案。
到底應(yīng)不應(yīng)該選擇微服務(wù)呢?如果單體程序已經(jīng)沒法管理了,那么你別無選擇。如果沒有管理上的問題,那么微服務(wù)帶給你的只有問題和麻煩。其實(shí),一般公司都沒有太多選擇,只能采用微服務(wù),不過你可以選擇建立比較少的微服務(wù)。如果還是沒法決定,有一個(gè)折中的方案,“內(nèi)部微服務(wù)設(shè)計(jì)”。
內(nèi)部微服務(wù)設(shè)計(jì)
這種設(shè)計(jì)表面上看起來是一個(gè)單體程序,它只有一個(gè)源代碼存儲(chǔ)倉庫,一個(gè)數(shù)據(jù)庫,一個(gè)部署,但在程序內(nèi)部可以按照微服務(wù)的思想來進(jìn)行設(shè)計(jì)。它可以分成多個(gè)模塊,每個(gè)模塊是一個(gè)微服務(wù),可以由不同的團(tuán)隊(duì)管理。
用這張圖做例子。這個(gè)圖里的每個(gè)圓角方塊大致是一個(gè)微服務(wù),但我們可以把它作為一個(gè)單體程序來設(shè)計(jì),內(nèi)部有五個(gè)微服務(wù)。
每個(gè)模塊都有自己的數(shù)據(jù)庫表,它們都在一個(gè)數(shù)據(jù)庫中,但模塊之間不能跨數(shù)據(jù)庫訪問(不要建立模塊之間數(shù)據(jù)庫表的外鍵)。
“User”(在Conference Management模塊中)是一個(gè)共享的類,但在不同的模塊中的名字不同,含義和用法也不同,成員也不一樣(例如,在“Customer Service”里叫“Customer”)。
DDD(Domain-Driven Design)建議不要共享這個(gè)類,而是在每一個(gè)有界上下文(模塊)中都建一個(gè)新類,并擁有新的名字。
雖然它們的數(shù)據(jù)庫中的數(shù)據(jù)應(yīng)該大致相同,但DDD建議每一個(gè)有界上下文中都建一個(gè)新表,它們之間再進(jìn)行數(shù)據(jù)同步。
這個(gè)所謂的“內(nèi)部微服務(wù)設(shè)計(jì)”其實(shí)就是DDD,但當(dāng)時(shí)還沒有微服務(wù),因此外表看起來是單體程序,但內(nèi)部已經(jīng)是微服務(wù)的設(shè)計(jì)了。
它的書在2003就出版了,當(dāng)時(shí)就很有名。但它更偏重于業(yè)務(wù)邏輯的設(shè)計(jì),踐行起來也比較困難,因此大家談?wù)摰煤芏?,真正用的較少。
直到十年之后,微服務(wù)出來之后,人們發(fā)現(xiàn)它其實(shí)內(nèi)部就是微服務(wù),而且微服務(wù)的設(shè)計(jì)需要用它的思想來指導(dǎo),于是就又重新煥發(fā)了青春,而且這次更猛,已經(jīng)到了每個(gè)談?wù)撐⒎?wù)的人都不得不談?wù)揇DD的地步。不過一本軟件書籍,在十年之后還能指導(dǎo)新技術(shù)的設(shè)計(jì),非常令人欽佩。
這樣設(shè)計(jì)的好處是它是一個(gè)單體程序,省去了多個(gè)微服務(wù)帶來的部署、運(yùn)維的麻煩。但它內(nèi)部是按微服務(wù)設(shè)計(jì)的,如果以后要拆分成微服務(wù)會(huì)比較容易。至于什么時(shí)候拆分不是一個(gè)技術(shù)問題。
如果負(fù)責(zé)這個(gè)單體程序的各個(gè)團(tuán)隊(duì)之間不能在部署時(shí)間表,服務(wù)器優(yōu)化等方面達(dá)成一致,那么就需要拆分了。
當(dāng)然你也要應(yīng)對(duì)隨之而來的各種運(yùn)維麻煩。內(nèi)部微服務(wù)設(shè)計(jì)是一個(gè)折中的方案,如果你想試水微服務(wù),但又不愿意冒太大風(fēng)險(xiǎn)時(shí),這是一個(gè)不錯(cuò)的選擇。
結(jié)論
微服務(wù)之間的調(diào)用有兩種方式,RPC和事件驅(qū)動(dòng)。事件驅(qū)動(dòng)是更好的方式,因?yàn)樗撬神詈系摹5绻麡I(yè)務(wù)邏輯是緊耦合的,RPC方式也是可行的(它的好處是代碼更簡(jiǎn)單),而且你還可以通過選取合適的協(xié)議(Protobuf gRPC)來降低這種緊耦合帶來的危害。
由于事件溯源和事件通知的相似性,很多人把兩者弄混了,但它們實(shí)際上是完全不同的東西。微服務(wù)的數(shù)量不宜太多,可以先創(chuàng)建比較大的微服務(wù)(更像是服務(wù)組合)。
如果你還是不能確定是否采用微服務(wù)架構(gòu),可以先從“內(nèi)部微服務(wù)設(shè)計(jì)”開始,再逐漸拆分。
審核編輯:劉清
-
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11529 -
BPM
+關(guān)注
關(guān)注
0文章
24瀏覽量
8085 -
緊耦合
+關(guān)注
關(guān)注
0文章
4瀏覽量
931
原文標(biāo)題:難住了,微服務(wù)之間的幾種調(diào)用方式哪種最佳?
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論