難住了,微服務(wù)之間的調(diào)用方式哪種更優(yōu)?

倚天碼農(nóng)
事件通知就是微服務(wù)之間不直接調(diào)用,而是通過(guò)發(fā)消息來(lái)進(jìn)行合作。事件溯源有點(diǎn)像記賬,它把所有的事件都記錄下來(lái),作為永久存儲(chǔ)層,再在它的基礎(chǔ)之上構(gòu)建應(yīng)用程序。

本文來(lái)自IT168網(wǎng)站,作者/倚天碼農(nóng)。

在微服務(wù)架構(gòu)中,需要調(diào)用很多服務(wù)才能完成一項(xiàng)功能。服務(wù)之間如何互相調(diào)用就變成微服務(wù)架構(gòu)中的一個(gè)關(guān)鍵問(wèn)題。

服務(wù)調(diào)用有兩種方式,一種是RPC方式,另一種是事件驅(qū)動(dòng)(Event-driven)方式,也就是發(fā)消息方式。

消息方式是松耦合方式,比緊耦合的RPC方式要優(yōu)越,但RPC方式如果用在適合的場(chǎng)景也有它的一席之地。

我們總在談耦合,那么耦合到底意味著什么呢?

一、耦合的種類(lèi)

1)時(shí)間耦合:客戶端和服務(wù)端必須同時(shí)上線才能工作。發(fā)消息時(shí),接受消息隊(duì)列必須運(yùn)行,但后臺(tái)處理程序暫時(shí)不工作也不影響。

2)容量耦合:客戶端和服務(wù)端的處理容量必須匹配。發(fā)消息時(shí),如果后臺(tái)處理能力不足也不要緊,消息隊(duì)列會(huì)起到緩沖的作用。

3)接口耦合:RPC調(diào)用有函數(shù)標(biāo)簽,而消息隊(duì)列只是一個(gè)消息。例如買(mǎi)了商品之后要調(diào)用發(fā)貨服務(wù),如果是發(fā)消息,那么就只需發(fā)送一個(gè)商品被買(mǎi)消息。

4)發(fā)送方式耦合:RPC是點(diǎn)對(duì)點(diǎn)方式,需要知道對(duì)方是誰(shuí),它的好處是能夠傳回返回值。消息既可以點(diǎn)對(duì)點(diǎn),也可以用廣播的方式,這樣減少了耦合,但也使返回值比較困難。

下面我們來(lái)逐一分析這些耦合的影響。

第一,時(shí)間耦合,對(duì)于多數(shù)應(yīng)用來(lái)講,你希望能馬上得到回答,因此即使使用消息隊(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)用,而是通過(guò)發(fā)消息來(lái)進(jìn)行合作。事件溯源有點(diǎn)像記賬,它把所有的事件都記錄下來(lái),作為永久存儲(chǔ)層,再在它的基礎(chǔ)之上構(gòu)建應(yīng)用程序。

實(shí)際上從應(yīng)用的角度來(lái)講,它們并不應(yīng)該分屬一類(lèi),它們的用途完全不同。事件通知是微服務(wù)的調(diào)用(或集成)方式,應(yīng)該和RPC分在一起。事件溯源是一種存儲(chǔ)數(shù)據(jù)的方式,應(yīng)該和數(shù)據(jù)庫(kù)分在一起。

1、事件通知(Event Notification)

讓我們用具體的例子來(lái)看一下。在下面的例子中,有三個(gè)微服務(wù),“Order Service”,“Customer Service”和“Product Service”。

360截圖16251112669372.png

先說(shuō)讀數(shù)據(jù),假設(shè)要?jiǎng)?chuàng)建一個(gè)“Order”,在這個(gè)過(guò)程中需要讀取“Customer”的數(shù)據(jù)和“Product”數(shù)據(jù)。

如果用事件通知的方式就只能在“Order Service”本地也創(chuàng)建只讀“Customer”和“Product”表,并把數(shù)據(jù)用消息的方式同步過(guò)來(lái)。

再說(shuō)寫(xiě)數(shù)據(jù),如果在創(chuàng)建一個(gè)“Order”時(shí)需要?jiǎng)?chuàng)建一個(gè)新的“Customer”或要修改“Customer”的信息,那么可以在界面上跳轉(zhuǎn)到用戶創(chuàng)建頁(yè)面,然后在“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)行,沒(méi)有綁定關(guān)系。但現(xiàn)在“Order Service”需要等待“Customer Service”創(chuàng)建完了之后才能繼續(xù)運(yùn)行,來(lái)完成整個(gè)創(chuàng)建“Order”的工作。主要是因?yàn)?ldquo;Order”和“Customer”本身從邏輯上來(lái)講就是緊耦合關(guān)系,沒(méi)有“Customer”你是不能創(chuàng)建“Order”的。

在這種緊耦合的情況下,也可以使用RPC。你可以建立一個(gè)更高層級(jí)的管理程序來(lái)管理這些微服務(wù)之間的調(diào)用,這樣“Order Service”就不必直接調(diào)用“Customer Service”了。

當(dāng)然它從本質(zhì)上來(lái)講并沒(méi)有解除耦合,只是把耦合轉(zhuǎn)移到了上一層,但至少現(xiàn)在“order Service”和“Customer Service”可以互不影響了。之所以不能根除這種緊耦合關(guān)系是因?yàn)樗鼈冊(cè)跇I(yè)務(wù)上是緊耦合的。

再舉一個(gè)購(gòu)物的例子。用戶選好商品之后進(jìn)行“Checkout”,生成“Order”,然后需要“payment”,再?gòu)?ldquo;Inventory”取貨,最后由“Shipment”發(fā)貨,它們每一個(gè)都是微服務(wù)。這個(gè)例子用RPC方式和事件通知方式都可以完成。

當(dāng)用RPC方式時(shí),由“Order”服務(wù)調(diào)用其他幾個(gè)服務(wù)來(lái)完成整個(gè)功能。用事件通知方式時(shí),“Checkout”服務(wù)完成之后發(fā)送“Order Placed”消息,“Payment”服務(wù)收到消息,接收用戶付款,發(fā)送“Payment received”消息。

“Inventory”服務(wù)收到消息,從倉(cāng)庫(kù)里取貨,并發(fā)送“Goods fetched”消息。“Shipment”服務(wù)得到消息,發(fā)送貨物,并發(fā)送“Goods shipped”消息。

360截圖16251112669372.png

對(duì)這個(gè)例子來(lái)講,使用事件驅(qū)動(dòng)是一個(gè)不錯(cuò)的選擇,因?yàn)槊總€(gè)服務(wù)發(fā)消息之后它不需要任何反饋,這個(gè)消息由下一個(gè)模塊接收來(lái)完成下一步動(dòng)作,時(shí)間上的要求也比上一個(gè)要寬松。用事件驅(qū)動(dòng)的好處是降低了耦合度,壞處是你現(xiàn)在不能在程序里找到整個(gè)購(gòu)物過(guò)程的步驟。

如果一個(gè)業(yè)務(wù)邏輯有它自己相對(duì)固定的流程和步驟,那么使用RPC或業(yè)務(wù)流程管理(BPM)能夠更方便地管理這些流程。在這種情況下選哪種方案呢?在我看來(lái)好處和壞處是大致相當(dāng)?shù)?。從技術(shù)上來(lái)講要選事件驅(qū)動(dòng),從業(yè)務(wù)上來(lái)講要選RPC。不過(guò)現(xiàn)在越來(lái)越多的人采用事件通知作為微服務(wù)的集成方式,它似乎已經(jīng)成了微服務(wù)之間的標(biāo)椎調(diào)用方式。

2、事件溯源(Event Sourcing)

這是一種具有顛覆性質(zhì)的的設(shè)計(jì),它把系統(tǒng)中所有的數(shù)據(jù)都以事件(Event)的方式記錄下來(lái),它的持久存儲(chǔ)叫Event Store,一般是建立在數(shù)據(jù)庫(kù)或消息隊(duì)列(例如Kafka)基礎(chǔ)之上,并提供了對(duì)事件進(jìn)行操作的接口,例如事件的讀寫(xiě)和查詢。事件溯源是由領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design)提出來(lái)的。

DDD中有一個(gè)很重要的概念,有界上下文(Bounded Context),可以用有界上下文來(lái)劃分微服務(wù),每個(gè)有界上下文都可以是一個(gè)微服務(wù)。下面是有界上下文的示例。下圖中有兩個(gè)服務(wù)“Sales”和“Support”。

有界上下文的一個(gè)關(guān)鍵是如何處理共享成員,在圖中是“Customer”和“Product”。在不同的有界上下文中,共享成員的含義、用法以及他們的對(duì)象屬性都會(huì)有些不同,DDD建議這些共享成員在各自的有界上下文中都分別建自己的類(lèi)(包括數(shù)據(jù)庫(kù)表),而不是共享。可以通過(guò)數(shù)據(jù)同步的手段來(lái)保持?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ù)都通過(guò)向Event Store發(fā)送和接受消息而互相通信。

Event Store內(nèi)部可以分成不同的stream(相當(dāng)于消息隊(duì)列中的Topic),供不同的微服務(wù)中的領(lǐng)域?qū)嶓w(Domain Entity)使用。

事件溯源的一個(gè)短板是數(shù)據(jù)查詢,它有兩種方式來(lái)解決。第一種是直接對(duì)stream進(jìn)行查詢,這只適合stream比較小并且查詢比較簡(jiǎn)單的情況。

查詢復(fù)雜的話,就要采用第二種方式,那就是建立一個(gè)只讀數(shù)據(jù)庫(kù),把需要的數(shù)據(jù)放在庫(kù)中進(jìn)行查詢。數(shù)據(jù)庫(kù)中的數(shù)據(jù)通過(guò)監(jiān)聽(tīng)Event Store中相關(guān)的事件來(lái)更新。

數(shù)據(jù)庫(kù)存儲(chǔ)方式只能保存當(dāng)前狀態(tài),而事件溯源則存儲(chǔ)了所有的歷史狀態(tài),因而能根據(jù)需要回放到歷史上任何一點(diǎn)的狀態(tài),具有很大優(yōu)勢(shì)。但它也不是一點(diǎn)問(wèn)題都沒(méi)有。

第一,它的程序比較復(fù)雜,因?yàn)槭录且坏裙?,你必須把業(yè)務(wù)邏輯按照事件的方式整理出來(lái),然后用事件來(lái)驅(qū)動(dòng)程序。第二,如果你要想修改事件或事件的格式就比較麻煩,因?yàn)榕f的事件已經(jīng)存儲(chǔ)在Event Store里了(事件就像日志,是只讀的),沒(méi)有辦法再改。

由于事件溯源和事件通知表面上看起來(lái)很像,不少人都搞不清楚它們的區(qū)別。事件通知只是微服務(wù)的集成方式,程序內(nèi)部是不使用事件溯源的,內(nèi)部實(shí)現(xiàn)仍然是傳統(tǒng)的數(shù)據(jù)庫(kù)方式。

只有當(dāng)要與其他微服務(wù)集成時(shí)才會(huì)發(fā)消息。而在事件溯源中,事件是一等公民,可以不要數(shù)據(jù)庫(kù),全部數(shù)據(jù)都是按照事件的方式存儲(chǔ)的。

雖然事件溯源的踐行者有不同的意見(jiàn),但有不少人都認(rèn)為事件溯源不是微服務(wù)的集成方式,而是微服務(wù)的一種內(nèi)部實(shí)現(xiàn)方式。因此,在一個(gè)系統(tǒng)中,可以某些微服務(wù)用事件溯源,另外一些微服務(wù)用數(shù)據(jù)庫(kù)。

當(dāng)你要集成這些微服務(wù)時(shí),你可以用事件通知的方式。注意現(xiàn)在有兩種不同的事件需要區(qū)分開(kāi),一種是微服務(wù)的內(nèi)部事件,是顆粒度比較細(xì)的,這種事件只發(fā)送到這個(gè)微服務(wù)的stream中,只被事件溯源使用。

另一種是其他微服務(wù)也關(guān)心的,是顆粒度比較粗的,這種事件會(huì)放到另外一個(gè)或幾個(gè)stream中,被多個(gè)微服務(wù)使用,是用來(lái)做服務(wù)之間集成的。這樣做的好處是限制了事件的作用范圍,減少了不相關(guān)事件對(duì)程序的干擾。詳見(jiàn)"Domain Events vs.Event Sourcing"。

事件溯源出現(xiàn)已經(jīng)很長(zhǎng)時(shí)間了,雖然熱度一直在上升(尤其是這兩年),但總的來(lái)說(shuō)非常緩慢,談?wù)摰娜瞬簧伲a(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的,雖然也支持消息,但成熟度就差多了,因此有不少工作還是要自己來(lái)做。

有意思的是Kafka一直在推動(dòng)它作為事件驅(qū)動(dòng)的工具,也取得了很大的成功,但它卻沒(méi)有得到事件溯源圈內(nèi)的認(rèn)可。

多數(shù)事件溯源都使用一個(gè)叫evenstore的開(kāi)源Event Store,或是基于某個(gè)數(shù)據(jù)庫(kù)的Event Store,只有比較少的人用Kafka做Event Store。

但如果用Kafka實(shí)現(xiàn)事件通知就一點(diǎn)問(wèn)題都沒(méi)有。總的來(lái)說(shuō),對(duì)大多數(shù)公司來(lái)講事件溯源是有一定挑戰(zhàn)的,應(yīng)用時(shí)需要找到合適的場(chǎng)景。如果你要嘗試的話,可以先拿一個(gè)微服務(wù)試水。

雖然現(xiàn)在事件驅(qū)動(dòng)還有些生澀,但從長(zhǎng)遠(yuǎn)來(lái)講,還是很看好它的。像其他全新的技術(shù)一樣,事件溯源需要大規(guī)模的適用場(chǎng)景來(lái)推動(dòng)。例如容器技術(shù)就是因?yàn)槲⒎?wù)的流行和推動(dòng),才走向主流。

360截圖16251112669372.png

事件溯源以前的適用場(chǎng)景只限于記賬和源代碼庫(kù),局限性較大。區(qū)塊鏈可能會(huì)成為它的下一個(gè)機(jī)遇,因?yàn)樗玫囊彩鞘录菰醇夹g(shù)。

另外AI今后會(huì)滲入到具體程序中,使程序具有學(xué)習(xí)功能。而RPC模式注定沒(méi)有自適應(yīng)功能。事件驅(qū)動(dòng)本身就具有對(duì)事件進(jìn)行反應(yīng)的能力,這是自我學(xué)習(xí)的基礎(chǔ)。因此,這項(xiàng)技術(shù)長(zhǎng)遠(yuǎn)來(lái)講定會(huì)大放異彩,但短期內(nèi)(3-5年)大概不會(huì)成為主流。

三、RPC方式

RPC的方式就是遠(yuǎn)程函數(shù)調(diào)用,像RESTFul,gRPC,DUBBO都是這種方式。它一般是同步的,可以馬上得到結(jié)果。在實(shí)際中,大多數(shù)應(yīng)用都要求立刻得到結(jié)果,這時(shí)同步方式更有優(yōu)勢(shì),代碼也更簡(jiǎn)單。

1、服務(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ù)封裝起來(lái),這樣UI就只調(diào)用服務(wù)網(wǎng)關(guān)就可以了,不需要去對(duì)付每一個(gè)微服務(wù)。下面是API Gateway示例圖:

360截圖16251112669372.png

服務(wù)網(wǎng)關(guān)(API Gateway)不是為了解決微服務(wù)之間調(diào)用的緊耦合問(wèn)題,它主要是為了簡(jiǎn)化客戶端的工作。其實(shí)它還可以用來(lái)降低函數(shù)之間的耦合度。

有了API Gateway之后,一旦服務(wù)接口修改,你可能只需要修改API Gateway,而不必修改每個(gè)調(diào)用這個(gè)函數(shù)的客戶端,這樣就減少了程序的耦合性。

2、服務(wù)調(diào)用

可以借鑒API Gateway的思路來(lái)減少RPC調(diào)用的耦合度,例如把多個(gè)微服務(wù)組織起來(lái)形成一個(gè)完整功能的服務(wù)組合,并對(duì)外提供統(tǒng)一的服務(wù)接口。這種想法跟上面的API Gateway有些相似,都是把服務(wù)集中起來(lái)提供粗顆粒(Coarse Granular)服務(wù),而不是細(xì)顆粒的服務(wù)(Fine Granular)。

但這樣建立的服務(wù)組合可能只適合一個(gè)程序使用,沒(méi)有多少共享價(jià)值。因此如果有合適的場(chǎng)景就采用,否側(cè)也不必強(qiáng)求。雖然我們不能降低RPC服務(wù)之間的耦合度,卻可以減少這種緊耦合帶來(lái)的影響。

四、降低緊耦合的影響

什么是緊耦合的主要問(wèn)題呢?就是客戶端和服務(wù)端的升級(jí)不同步。服務(wù)端總是先升級(jí),客戶端可能有很多,如果要求它們同時(shí)升級(jí)是不現(xiàn)實(shí)的。它們有各自的部署時(shí)間表,一般都會(huì)選擇在下一次部署時(shí)順帶升級(jí)。

一般有兩個(gè)辦法可以解決這個(gè)問(wèn)題:

1)同時(shí)支持多個(gè)版本:這個(gè)工作量比較大,因此大多數(shù)公司都不會(huì)采用這種方式。

2)服務(wù)端向后兼容:這是更通用的方式。例如你要加一個(gè)新功能或有些客戶要求給原來(lái)的函數(shù)增加一個(gè)新的參數(shù),但別的客戶不需要這個(gè)參數(shù)。這時(shí)你只好新建一個(gè)函數(shù),跟原來(lái)的功能差不多,只是多了一個(gè)參數(shù)。這樣新舊客戶的需求都能滿足。它的好處是向后兼容(當(dāng)然這取決于你使用的協(xié)議)。

它的壞處是當(dāng)以后新的客戶來(lái)了,看到兩個(gè)差不多的函數(shù)就糊涂了,不知道該用那個(gè)。而且時(shí)間越長(zhǎng)越嚴(yán)重,你的服務(wù)端可能功能增加的不多,但相似的函數(shù)卻越來(lái)越多,無(wú)法選擇。

它的解決辦法就是使用一個(gè)支持向后兼容的RPC協(xié)議,現(xiàn)在最好的就是Protobuf gRPC,尤其是在向后兼容上。

它給每個(gè)服務(wù)定義了一個(gè)接口,這個(gè)接口是與編程語(yǔ)言無(wú)關(guān)的中性接口,然后你可以用工具生成各個(gè)語(yǔ)言的實(shí)現(xiàn)代碼,供不同語(yǔ)言使用。函數(shù)定義的變量都有編號(hào),變量可以是可選類(lèi)型的,這樣就比較好地解決了函數(shù)兼容的問(wèn)題。

就用上面的例子,當(dāng)你要增加一個(gè)可選參數(shù)時(shí),你就定義一個(gè)新的可選變量。由于它是可選的,原來(lái)的客戶端不需要提供這個(gè)參數(shù),因此不需要修改程序。

而新的客戶端可以提供這個(gè)參數(shù)。你只要在服務(wù)端能同時(shí)處理這兩種情況就行了。這樣服務(wù)端并沒(méi)有增加新的函數(shù),但用戶的新需求滿足了,而且還是向后兼容的。

五、微服務(wù)的數(shù)量有沒(méi)有上限?

總的來(lái)說(shuō)微服務(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)維帶來(lái)了巨大的麻煩。所以在微服務(wù)剛出來(lái)時(shí),我一直覺(jué)得它是一個(gè)退步,弊大于利。但由于管理上的問(wèn)題沒(méi)有其他解決方案,只有硬著頭皮上了。

值得慶幸的是微服務(wù)帶來(lái)的麻煩都是可解的。直到后來(lái),微服務(wù)建立了全套的自動(dòng)化體系,從程序集成到部署,從全鏈路跟蹤到日志,以及服務(wù)檢測(cè),服務(wù)發(fā)現(xiàn)和注冊(cè),這樣才把微服務(wù)的工作量降了下來(lái)。

雖然微服務(wù)在技術(shù)上一無(wú)是處,但它的流行還是大大推動(dòng)了容器技術(shù),服務(wù)網(wǎng)格(Service Mesh)和全鏈路跟蹤等新技術(shù)的發(fā)展。不過(guò)它本身在技術(shù)上還是沒(méi)有發(fā)現(xiàn)任何優(yōu)勢(shì)。

直到有一天,我意識(shí)到單體程序其實(shí)性能調(diào)試是很困難的(很難分離出瓶頸點(diǎn)),而微服務(wù)配置了全鏈路跟蹤之后,能很快找到癥結(jié)所在??磥?lái)微服務(wù)從技術(shù)來(lái)講也不全是缺點(diǎn),總算也有好的地方。但微服務(wù)的顆粒度不宜過(guò)細(xì),否則工作量還是太大。

一般規(guī)模的公司十幾個(gè)或幾十個(gè)微服務(wù)都是可以承受的,但如果有幾百個(gè)甚至上千個(gè),那么絕不是一般公司可以管理的。盡管現(xiàn)有的工具已經(jīng)很齊全了,而且與微服務(wù)有關(guān)的整個(gè)流程也已經(jīng)基本上全部自動(dòng)化了,但它還是會(huì)增加很多工作。

Martin Fowler幾年以前建議先從單體程序開(kāi)始(詳見(jiàn)MonolithFirst),然后再逐步把功能拆分出去,變成一個(gè)個(gè)的微服務(wù)。但是后來(lái)有人反對(duì)這個(gè)建議,他也有些松口了。

如果單體程序不是太大,這是個(gè)好主意。可以用數(shù)據(jù)額庫(kù)表的數(shù)量來(lái)衡量程序的大小,我見(jiàn)過(guò)大的單體程序有幾百?gòu)埍恚@就太多了,很難管理。正常情況下,一個(gè)微服務(wù)可以有兩、三張表到五、六張表,一般不超過(guò)十張表。但如果要減少微服務(wù)數(shù)量的話,可以把這個(gè)標(biāo)準(zhǔn)放寬到不要超過(guò)二十張表。

用這個(gè)做為大致的指標(biāo)來(lái)創(chuàng)建微程序,如果使用一段時(shí)間之后還是覺(jué)得太大了,那么再逐漸拆分。當(dāng)然,按照這個(gè)標(biāo)準(zhǔn)建立的服務(wù)更像是服務(wù)組合,而不是單個(gè)的微服務(wù)。不過(guò)它會(huì)為你減少工作量。只要不影響業(yè)務(wù)部門(mén)的創(chuàng)新進(jìn)度,這是一個(gè)不錯(cuò)的方案。

到底應(yīng)不應(yīng)該選擇微服務(wù)呢?如果單體程序已經(jīng)沒(méi)法管理了,那么你別無(wú)選擇。如果沒(méi)有管理上的問(wèn)題,那么微服務(wù)帶給你的只有問(wèn)題和麻煩。其實(shí),一般公司都沒(méi)有太多選擇,只能采用微服務(wù),不過(guò)你可以選擇建立比較少的微服務(wù)。如果還是沒(méi)法決定,有一個(gè)折中的方案,“內(nèi)部微服務(wù)設(shè)計(jì)”。

1、內(nèi)部微服務(wù)設(shè)計(jì)

這種設(shè)計(jì)表面上看起來(lái)是一個(gè)單體程序,它只有一個(gè)源代碼存儲(chǔ)倉(cāng)庫(kù),一個(gè)數(shù)據(jù)庫(kù),一個(gè)部署,但在程序內(nèi)部可以按照微服務(wù)的思想來(lái)進(jìn)行設(shè)計(jì)。它可以分成多個(gè)模塊,每個(gè)模塊是一個(gè)微服務(wù),可以由不同的團(tuán)隊(duì)管理。

360截圖16251112669372.png

用這張圖做例子。這個(gè)圖里的每個(gè)圓角方塊大致是一個(gè)微服務(wù),但我們可以把它作為一個(gè)單體程序來(lái)設(shè)計(jì),內(nèi)部有五個(gè)微服務(wù)。

每個(gè)模塊都有自己的數(shù)據(jù)庫(kù)表,它們都在一個(gè)數(shù)據(jù)庫(kù)中,但模塊之間不能跨數(shù)據(jù)庫(kù)訪問(wèn)(不要建立模塊之間數(shù)據(jù)庫(kù)表的外鍵)。

“User”(在Conference Management模塊中)是一個(gè)共享的類(lèi),但在不同的模塊中的名字不同,含義和用法也不同,成員也不一樣(例如,在“Customer Service”里叫“Customer”)。

DDD(Domain-Driven Design)建議不要共享這個(gè)類(lèi),而是在每一個(gè)有界上下文(模塊)中都建一個(gè)新類(lèi),并擁有新的名字。

雖然它們的數(shù)據(jù)庫(kù)中的數(shù)據(jù)應(yīng)該大致相同,但DDD建議每一個(gè)有界上下文中都建一個(gè)新表,它們之間再進(jìn)行數(shù)據(jù)同步。

這個(gè)所謂的“內(nèi)部微服務(wù)設(shè)計(jì)”其實(shí)就是DDD,但當(dāng)時(shí)還沒(méi)有微服務(wù),因此外表看起來(lái)是單體程序,但內(nèi)部已經(jīng)是微服務(wù)的設(shè)計(jì)了。

它的書(shū)在2003就出版了,當(dāng)時(shí)就很有名。但它更偏重于業(yè)務(wù)邏輯的設(shè)計(jì),踐行起來(lái)也比較困難,因此大家談?wù)摰煤芏?,真正用的較少。

直到十年之后,微服務(wù)出來(lái)之后,人們發(fā)現(xiàn)它其實(shí)內(nèi)部就是微服務(wù),而且微服務(wù)的設(shè)計(jì)需要用它的思想來(lái)指導(dǎo),于是就又重新煥發(fā)了青春,而且這次更猛,已經(jīng)到了每個(gè)談?wù)撐⒎?wù)的人都不得不談?wù)揇DD的地步。不過(guò)一本軟件書(shū)籍,在十年之后還能指導(dǎo)新技術(shù)的設(shè)計(jì),非常令人欽佩。

這樣設(shè)計(jì)的好處是它是一個(gè)單體程序,省去了多個(gè)微服務(wù)帶來(lái)的部署、運(yùn)維的麻煩。但它內(nèi)部是按微服務(wù)設(shè)計(jì)的,如果以后要拆分成微服務(wù)會(huì)比較容易。至于什么時(shí)候拆分不是一個(gè)技術(shù)問(wèn)題。

如果負(fù)責(zé)這個(gè)單體程序的各個(gè)團(tuán)隊(duì)之間不能在部署時(shí)間表,服務(wù)器優(yōu)化等方面達(dá)成一致,那么就需要拆分了。

當(dāng)然你也要應(yīng)對(duì)隨之而來(lái)的各種運(yùn)維麻煩。內(nèi)部微服務(wù)設(shè)計(jì)是一個(gè)折中的方案,如果你想試水微服務(wù),但又不愿意冒太大風(fēng)險(xiǎn)時(shí),這是一個(gè)不錯(cuò)的選擇。

2、結(jié)論

微服務(wù)之間的調(diào)用有兩種方式,RPC和事件驅(qū)動(dòng)。事件驅(qū)動(dòng)是更好的方式,因?yàn)樗撬神詈系?。但如果業(yè)務(wù)邏輯是緊耦合的,RPC方式也是可行的(它的好處是代碼更簡(jiǎn)單),而且你還可以通過(guò)選取合適的協(xié)議(Protobuf gRPC)來(lái)降低這種緊耦合帶來(lái)的危害。

由于事件溯源和事件通知的相似性,很多人把兩者弄混了,但它們實(shí)際上是完全不同的東西。微服務(wù)的數(shù)量不宜太多,可以先創(chuàng)建比較大的微服務(wù)(更像是服務(wù)組合)。

如果你還是不能確定是否采用微服務(wù)架構(gòu),可以先從“內(nèi)部微服務(wù)設(shè)計(jì)”開(kāi)始,再逐漸拆分。

THEEND

最新評(píng)論(評(píng)論僅代表用戶觀點(diǎn))

更多
暫無(wú)評(píng)論