大部分時(shí)候,微服務(wù)都是建立在一種基于請(qǐng)求和響應(yīng)的協(xié)議之上。比如,REST等。這種方式是自然的。我們只需要調(diào)用另外一個(gè)模塊就是了,然后等待響應(yīng)返回,然后繼續(xù)。這樣的方式確實(shí)也滿足了我們的很多的場(chǎng)景:用戶通過(guò)點(diǎn)擊頁(yè)面的一個(gè)按鈕然后希望發(fā)生一些事情。
但是,當(dāng)我們開(kāi)始接觸許多獨(dú)立的service的時(shí)候,事情就發(fā)生改變了。隨著service數(shù)量急速的增長(zhǎng),同步交互比例也隨著service在急速增長(zhǎng)。這時(shí)候,我們的service就會(huì)遇到很多的瓶頸。
于是,不幸的ops工程師們就被我們坑了,他們疲憊的奔波于一個(gè)又一個(gè)的service,拼湊在一起的二手信息片段,誰(shuí)說(shuō)了什么,去往哪里,什么時(shí)候發(fā)生?等等。。。
這是一個(gè)非常典型的問(wèn)題。市面上也有一些解決方案。一種方案就是確保您的個(gè)人服務(wù)具有比您的系統(tǒng)更高的SLA。 Google提供了這樣做的協(xié)議。另一種方法是簡(jiǎn)單地分解將服務(wù)綁定在一起的同步關(guān)系。
上面的做法都沒(méi)有從模式上根本解決問(wèn)題。我們可以使用異步機(jī)制來(lái)解決這個(gè)問(wèn)題。比如,電商網(wǎng)站中你會(huì)發(fā)現(xiàn)這樣的同步接口,比如getImage()或者processOrder(),也許你感覺(jué)蠻正常。調(diào)用了然后希望馬上有一個(gè)響應(yīng)。但當(dāng)用戶點(diǎn)擊了“購(gòu)買(mǎi)”后,觸發(fā)了一個(gè)復(fù)雜且異步的處理過(guò)程。這個(gè)過(guò)程涉及到購(gòu)買(mǎi)、送貨上門(mén)給用戶,這一切都是發(fā)生在當(dāng)初的那一次的按鈕點(diǎn)擊。所以把一個(gè)程序處理邏輯切分成多個(gè)異步的處理,是我們需要解決的問(wèn)題。這也正符合我們的真實(shí)的世界,真實(shí)世界本來(lái)就是異步的,擁抱異步吧。
在實(shí)際情況下,我們其實(shí)已經(jīng)自動(dòng)擁抱了異步了。我們發(fā)現(xiàn)自己會(huì)定時(shí)輪詢數(shù)據(jù)庫(kù)表來(lái)更改又或者通過(guò)cron定時(shí)job來(lái)實(shí)現(xiàn)一些更新。這些方法都是一些打破同步的方式,但是這種做法總讓人感覺(jué)有種黑客范兒,感覺(jué)像是黑客行為,怪怪的。
在本文中,我們將會(huì)討論一種完全不同的架構(gòu):不是把service們通過(guò)命令鏈揉到一塊,而是通過(guò)事件流(stream of events)來(lái)做。這是一個(gè)不錯(cuò)的方式。這種方式也是我們之后要討論的一系列的一個(gè)基礎(chǔ)。
當(dāng)我們進(jìn)入正式的例子之前,我們需要先普及三個(gè)簡(jiǎn)單的概念。一個(gè)service與另外一個(gè)service有三種交互方式:命令(Commands)、事件(Events)以及查詢(Queries)。
事件的美妙之處在于“外部數(shù)據(jù)”可以被系統(tǒng)中的任何service所重用。
而且從service的角度來(lái)說(shuō),事件要比命令和查詢都要解耦。這個(gè)很重要。
服務(wù)之間的交互有三種機(jī)制:
Commands 。命令是一個(gè)操作。希望在另一個(gè)服務(wù)中執(zhí)行某些操作的一個(gè)請(qǐng)求。 會(huì)改變系統(tǒng)狀態(tài)的東西。 命令期待有響應(yīng)。
Events 。事件既是一個(gè)事實(shí)也是一個(gè)觸發(fā)器。 發(fā)生了一些事情,表示為通知。
Queries 。查詢是一個(gè)請(qǐng)求,是一個(gè)查找一些東西的請(qǐng)求(request)。重要的是,查詢不會(huì)使得系統(tǒng)狀態(tài)發(fā)生改變。
一個(gè)簡(jiǎn)單事件驅(qū)動(dòng)流程
讓我們開(kāi)始一個(gè)簡(jiǎn)單的例子:用戶購(gòu)買(mǎi)一個(gè)小東西。那么接下來(lái)要發(fā)生兩件事情:
支付。
系統(tǒng)檢查是否還有更多的商品需要被訂購(gòu)。
在請(qǐng)求驅(qū)動(dòng)(request-approach)的架構(gòu)中,這兩個(gè)行為被表現(xiàn)為一個(gè)命令鏈條。交互就像下面這樣:
首先要注意的問(wèn)題是“購(gòu)買(mǎi)更多”的這個(gè)業(yè)務(wù)流程是隨著訂單服務(wù)(Order Service)一塊被初始化的。這就使得責(zé)任不獨(dú)立,責(zé)任跨了兩個(gè)service。理想情況下,我們希望separation of concerns,也就是關(guān)注隔離。
現(xiàn)在如果我們使用事件驅(qū)動(dòng),而不是請(qǐng)求驅(qū)動(dòng)的方式的話,那么事情就會(huì)變得好一些。
在返回給用戶之前,UI service 發(fā)布一個(gè)OrderRequested事件,然后等待OrderConfirmed(或者Rejected)。
訂單服務(wù)(Orders Service)和庫(kù)存服務(wù)(Stock Service) react這個(gè)事件。
仔細(xì)看這里,UI service和Orders Service并沒(méi)有改變很多,而是通過(guò)事件來(lái)通信,而不是直接調(diào)用另一個(gè)。
這個(gè)Stock service(庫(kù)存服務(wù))很有趣。Order Service告訴他要做什么。然后StockService自己決定是否參與本次交互,這是事件驅(qū)動(dòng)架構(gòu)非常重要的屬性,也就是:Reciver Driven Flow Control,接收者驅(qū)動(dòng)流程控制。一下子控制反轉(zhuǎn)了。
這種控制反轉(zhuǎn)給接收者,很好的解耦了服務(wù)之間的交互,這就為架構(gòu)提供了可插拔性。組件們可以輕松的被插入和替換掉,優(yōu)雅!
隨著架構(gòu)變得越來(lái)越復(fù)雜,這種可插拔性的因素變得更加重要。舉個(gè)例子,我們要添加一個(gè)實(shí)時(shí)管理定價(jià)的service,根據(jù)供需調(diào)整產(chǎn)品的價(jià)格。在一個(gè)命令驅(qū)動(dòng)的世界里,我們就需要引入一個(gè)可以由庫(kù)存服務(wù)(Stock Service)和訂單服務(wù)(Orders Service)調(diào)用的類(lèi)似updatePrice()這樣的方法。
但是在事件驅(qū)動(dòng)(event-driven)世界更新價(jià)格的話,service只需要訂閱共享的stream就是了,當(dāng)相應(yīng)的條件符合時(shí),就去執(zhí)行更新價(jià)格的操作。
事件(Events)和查詢(Queries)的混合
上面的例子只是命令和事件。并沒(méi)有說(shuō)到查詢。別忘了,我們之前可是說(shuō)到了三個(gè)概念。現(xiàn)在我們開(kāi)始說(shuō)查詢。我們擴(kuò)展上面的例子,讓訂單服務(wù)(Orders Service)在支付之前檢查是否有足夠的庫(kù)存。
在請(qǐng)求驅(qū)動(dòng)(request-driven)的架構(gòu)中,我們可能會(huì)向庫(kù)存服務(wù)(Stock Service)發(fā)送一個(gè)查詢請(qǐng)求然后獲取到當(dāng)前的庫(kù)存數(shù)量。這就導(dǎo)致了模型混合,事件流純粹被用作通知,允許任何的service加入flow,但查詢卻是通過(guò)請(qǐng)求驅(qū)動(dòng)的方式直接訪問(wèn)源。
對(duì)于服務(wù)(service)需要獨(dú)立發(fā)展的較大的生態(tài)系統(tǒng),遠(yuǎn)程查詢要涉及到很多關(guān)聯(lián),耦合很?chē)?yán)重,要把很多服務(wù)捆綁在一起。我們可以通過(guò)“內(nèi)部化”來(lái)避免這種涉及多個(gè)上下文交叉的查詢。而事件流可以被用于在每個(gè)service中緩存數(shù)據(jù)集,這樣我們就可以在本地來(lái)完成查詢。
所以,增加這個(gè)庫(kù)存檢查,訂單服務(wù)(Order Service)可以訂閱庫(kù)存服務(wù)(Stock Service)的事件流,庫(kù)存一有更新,訂單服務(wù)就會(huì)收到通知,然后把更新存儲(chǔ)到本地的數(shù)據(jù)庫(kù)。這樣接下來(lái)就可以查詢本地這個(gè)“視圖(view)”來(lái)檢查是否有足夠的庫(kù)存。