September 18th 2019

本文僅做翻譯
原文地址:https://hackernoon.com/best-practices-for-event-driven-microservice-architecture-e034p21lk
如果你正從事架構(gòu)相關(guān)的工作,那么你可能正在努力實(shí)踐微服務(wù)架構(gòu)。而且,估計(jì)之前是使用 REST 作為通訊層的協(xié)議。現(xiàn)在,越來(lái)越多的項(xiàng)目正遷移到事件驅(qū)動(dòng)的架構(gòu)上來(lái)。讓我們深入了解一下這個(gè)架構(gòu)的優(yōu)缺點(diǎn),以及這個(gè)模式導(dǎo)致的一些關(guān)鍵設(shè)計(jì)決策和常見(jiàn)的反模式。
什么是事件驅(qū)動(dòng)的微服務(wù)架構(gòu)?
在事件驅(qū)動(dòng)架構(gòu)中,當(dāng)一個(gè)服務(wù)執(zhí)行了一個(gè)其他服務(wù)關(guān)注的操作時(shí),這個(gè)服務(wù)就會(huì)產(chǎn)生一個(gè)事件——一個(gè)包含了所執(zhí)行動(dòng)作的記錄。這個(gè)事件的作用就是讓其他作為這個(gè)事件消費(fèi)者的服務(wù)運(yùn)行他們自己的任務(wù)。與 REST 不同,產(chǎn)生事件的服務(wù)不需要知道消費(fèi)這個(gè)時(shí)間的服務(wù)的細(xì)節(jié)。
這里有一個(gè)簡(jiǎn)單的示例:當(dāng)網(wǎng)站受到一個(gè)訂單時(shí),一個(gè)“下單”時(shí)間就產(chǎn)生了,隨后被若干個(gè)微服務(wù)消費(fèi):
- 訂單服務(wù)會(huì)將一個(gè)訂單記錄寫到數(shù)據(jù)庫(kù)中
- 客戶服務(wù)會(huì)創(chuàng)建一條 客戶 記錄
- 支付服務(wù)則會(huì)處理支付的邏輯
事件可以通過(guò)多種方式發(fā)布。例如,它們可以發(fā)布到隊(duì)列中并確保分發(fā)到對(duì)應(yīng)的消費(fèi)者服務(wù),或者它們可以發(fā)布到一個(gè) “發(fā)布/訂閱”模型的處理流中,這樣所有對(duì)這個(gè)事件感興趣的服務(wù)都可以獲取到這個(gè)事件。不管是哪種方式,都是 生產(chǎn)者 發(fā)布事件,然后 消費(fèi)者 接收事件并作出相應(yīng)的處理。注意在某些情況下,這兩個(gè)角色也可能被稱作 發(fā)布者(生產(chǎn)者)和 訂閱者(消費(fèi)者)。
為什么要使用事件驅(qū)動(dòng)架構(gòu)
事件驅(qū)動(dòng)架構(gòu)相對(duì)于 REST 方式提供了一下這些好處:
- 異步處理——基于事件的架構(gòu)是非阻塞的異步處理模式。這種方式保證了一個(gè)原子任務(wù)處理完成資源就可以被釋放去做其他事情,而不用維護(hù)這個(gè)原子任務(wù)與其他任務(wù)的關(guān)系。它還能可以將事件放入隊(duì)列或緩沖區(qū),這樣可以防止因?yàn)橄M(fèi)者的原因?qū)е律a(chǎn)者壓力增大或被阻塞。
- 松耦合——服務(wù)之間不需要(也不應(yīng)該)知道彼此的存在,或者相互依賴。當(dāng)使用事件時(shí),服務(wù)是獨(dú)立運(yùn)行的,服務(wù)本省并不知道其他服務(wù)的存在,包括它們的實(shí)現(xiàn)細(xì)節(jié)以及傳輸協(xié)議。在事件模型下的服務(wù)可以更容易的獨(dú)立的升級(jí)、測(cè)試、部署。
- 易擴(kuò)展——既然在事件驅(qū)動(dòng)的架構(gòu)中服務(wù)是不耦合的,并且服務(wù)都是只運(yùn)行一個(gè)任務(wù),查找哪一個(gè)服務(wù)是瓶頸,并且擴(kuò)展這個(gè)服務(wù)(僅這個(gè)服務(wù))都會(huì)很容易。
- 支持恢復(fù)——一個(gè)使用了隊(duì)列的事件驅(qū)動(dòng)架構(gòu)可以“重發(fā)”丟失的事件。當(dāng)用戶需要恢復(fù)丟失的數(shù)據(jù)時(shí)這是很有價(jià)值的。
當(dāng)然,事件驅(qū)動(dòng)架構(gòu)也會(huì)有它的缺點(diǎn)。在解耦的時(shí)候?qū)^(guò)于簡(jiǎn)單的關(guān)注點(diǎn)進(jìn)行分離會(huì)導(dǎo)致過(guò)度設(shè)計(jì);它可能需要大量的前期投入;并且會(huì)導(dǎo)致基礎(chǔ)設(shè)施,服務(wù)契約,使用多語(yǔ)言構(gòu)建系統(tǒng),依賴關(guān)系圖的復(fù)雜性的額外增加。
或許最重要的瓶頸和挑戰(zhàn)是數(shù)據(jù)和事務(wù)處理的管理。因?yàn)樗麄兲烊皇钱惒降?,事件?qū)動(dòng)架構(gòu)需要小心的處理服務(wù)間數(shù)據(jù)不一致,版本沖突,事件重復(fù),并且通常不支持 ACID(原子性、一致性、隔離性、持久性) 事務(wù),取而代之的是最終一致性 這會(huì)讓定位問(wèn)題和調(diào)試更加的困難。
即使有這些缺點(diǎn)對(duì)于企業(yè)及的微服務(wù)系統(tǒng)來(lái)說(shuō),事件驅(qū)動(dòng)架構(gòu)通常還是一個(gè)更好的選擇。它在擴(kuò)展性、松耦合、對(duì) dev-ops 友好的好處大過(guò)了它的缺點(diǎn)。
何時(shí)使用 REST
在如下的情況下 REST 的方式會(huì)更合適:
- 你需要一個(gè)有時(shí)限的 請(qǐng)求/響應(yīng) 的接口
- 要很方便的支持事務(wù)
- 你的 API 是公開(kāi)的
- 你的項(xiàng)目很小(REST 很容易實(shí)現(xiàn)和部署)
你最重要的設(shè)計(jì)選擇 – 消息框架
一旦你決定了使用消息驅(qū)動(dòng)的架構(gòu),那么你就需要選擇你的時(shí)間框架。事件產(chǎn)生和消費(fèi)的方式是你系統(tǒng)的關(guān)鍵要素。有許多的框架以供選擇,要做出正確的決定需要花一些時(shí)間進(jìn)行研究。
你最基礎(chǔ)的選擇開(kāi)始于對(duì)消息處理或流處理的選擇。
消息處理
對(duì)于傳統(tǒng)的消息處理來(lái)說(shuō),由一個(gè)組件創(chuàng)建消息然后分發(fā)到指定的(通常是單個(gè))目標(biāo)。消息接收組件將會(huì)接收消息并作出相應(yīng)的處理,然后等待新的消息。通常,當(dāng)消息到達(dá),消息組件會(huì)單獨(dú)執(zhí)行一個(gè)進(jìn)程。然后消息就被刪除。
消息處理架構(gòu)的一個(gè)典型例子就是消息隊(duì)列。盡管大多數(shù)新的項(xiàng)目使用流處理(稍后說(shuō)明)架構(gòu),但是使用消息(或者說(shuō)事件)隊(duì)列也是很主流的方式。消息隊(duì)列通常使用一個(gè)通過(guò)代理“存儲(chǔ)并發(fā)送”的系統(tǒng),事件在代理之間進(jìn)行路由,知道他們到達(dá)合適的消費(fèi)者。ActiveMQ 和 RabbitMQ是兩個(gè)主力的消息隊(duì)列框架。這兩個(gè)框架都經(jīng)過(guò)了多年的實(shí)踐并有穩(wěn)定的社區(qū)支持。
流處理
使用流處理是另一種選擇,當(dāng)組件到達(dá)一個(gè)特定的狀態(tài)是會(huì)發(fā)出事件。其他對(duì)這個(gè)事件感興趣的組件會(huì)監(jiān)聽(tīng)事件流并最初相應(yīng)的處理。事件不會(huì)被指定一個(gè)特定的接受者,但是它對(duì)任何想要回去它的組件來(lái)說(shuō)都是可以獲得的。
在流處理的方式中,組件可以同時(shí)接收多個(gè)事件,并且基于多個(gè)流和事件進(jìn)行復(fù)雜的操作。一些流包含了持久化的功能,事件可以根據(jù)需要保存足夠長(zhǎng)的時(shí)間。
使用流處理,系統(tǒng)可以重新生成歷史事件,在事件發(fā)生之后再次產(chǎn)生并觸發(fā)相應(yīng)的處理,甚至進(jìn)行滑動(dòng)窗口計(jì)算(sliding-window computation)。例如,它可以從每秒事件流中計(jì)算出每分鐘的平均CPU使用率。
最流行的時(shí)間處理框架是 Apache Kafka。Kafka 是在很多項(xiàng)目中檢驗(yàn)過(guò)的成熟且穩(wěn)定的解決方案。它可以作為一種具備工業(yè)級(jí)強(qiáng)度的解決方案。Kafka 有大量的用戶群,一個(gè)很好的社區(qū),和一個(gè)不斷更新的工具集。
其他的選擇
也有其他的框架提供消息或流的組合處理或期中一種解決方案。例如, Pulsar, Apache 支持的一個(gè)新框架,是一個(gè)開(kāi)源的發(fā)布/訂閱方式的消息系統(tǒng)同時(shí)支持消息和流兩種方式,并且都具有極高的性能。Pulsar具有豐富的特性,他支持多租戶和跨域發(fā)布,當(dāng)然也相應(yīng)的更為復(fù)雜一些。有人認(rèn)為 Kafka的目標(biāo)是高吞吐,而 Pulsar 的目標(biāo)是低延遲。
NATS 是另一個(gè)可選項(xiàng),它屬于發(fā)布/訂閱模式的消息系統(tǒng),它使用“同步”的隊(duì)列。NATS 被設(shè)計(jì)用戶發(fā)送小的、高頻的消息。它同時(shí)支持高吞吐和低延遲。然而,NATS 認(rèn)為某種程度的數(shù)據(jù)丟失是可以接受的,性能的優(yōu)化優(yōu)先于數(shù)據(jù)分發(fā)的可靠。
其他的設(shè)計(jì)考量
一旦你選定了消息框架,下面這些問(wèn)題就需要被考慮了:
事件源
實(shí)現(xiàn)一個(gè)由多個(gè)松耦合服務(wù)組合的系統(tǒng)是很困難的,需要獨(dú)立數(shù)據(jù)存儲(chǔ),具有原子性的事務(wù)。 Event Sourcing模式對(duì)此會(huì)有幫助。使用事件源,更新和刪除永遠(yuǎn)不會(huì)直接作用于數(shù)據(jù);取而代之的是保存一些列的事件。
CQRS
上邊提到事件源會(huì)引入一個(gè)新的問(wèn)題:既然狀態(tài)是基于一系列的時(shí)間,那么查詢就會(huì)變得慢并且復(fù)雜。 Command Query Responsibility Segregation (CQRS) 是一個(gè)解決方案,它對(duì)插入操作和讀取操作使用不同的模型。
查找事件信息
在事件驅(qū)動(dòng)架構(gòu)中一個(gè)最大的挑戰(zhàn)是對(duì)服務(wù)和事件進(jìn)行分類。你從哪里獲得時(shí)間的說(shuō)明和細(xì)節(jié)呢?事件產(chǎn)生的原因是什么?那個(gè)團(tuán)隊(duì)創(chuàng)建了這個(gè)事件?他們還繼續(xù)維護(hù)這個(gè)事件么?
處理變更
事件的結(jié)構(gòu)會(huì)改變么?你如何改變一個(gè)事件的結(jié)構(gòu)而不影響到其他的服務(wù)?隨著服務(wù)的增加你如何回答這些問(wèn)題會(huì)變得至關(guān)重要。
要成為一個(gè)好的事件消費(fèi)者需要在編碼實(shí)現(xiàn)是考慮到事件結(jié)構(gòu)的變更。成為一個(gè)號(hào)的事件生產(chǎn)者則意味著要意識(shí)到事件結(jié)構(gòu)的變更會(huì)影響到其他的服務(wù),并且對(duì)事件進(jìn)行優(yōu)良的設(shè)計(jì)并編寫清晰的文檔。
私有部署還是托管部署
根據(jù)你的選擇的事件框架,你還需要決定你的服務(wù)是私有部署(消息代理的部署并非一件簡(jiǎn)單的事情,尤其是需要高可用的情況下),或者使用托管的服務(wù),例如Apache Kafka on Heroku.
反模式
和大多數(shù)架構(gòu)一樣,一個(gè)事件驅(qū)動(dòng)的架構(gòu)也有它對(duì)應(yīng)的反模式。下邊就是一些需要小心的反模式。
過(guò)量的好東西
要小心你不要因?yàn)閯?chuàng)建了大量的事件而感到激動(dòng)。創(chuàng)建了過(guò)量的事件將會(huì)導(dǎo)致服務(wù)之間不必要的復(fù)雜性,增加開(kāi)發(fā)者的認(rèn)知負(fù)擔(dān),讓開(kāi)發(fā)和測(cè)試都更困難,并且導(dǎo)致消費(fèi)者數(shù)量激增。不是每個(gè)方法都需要一個(gè)事件。
泛化事件
不要使用泛化的時(shí)間,不管是對(duì)于事件命名還是事件意圖。你需要其他團(tuán)隊(duì)理解為何存在這個(gè)事件,它應(yīng)該被怎樣使用,以及它何時(shí)該被使用。事件應(yīng)當(dāng)具有一個(gè)良好的目的說(shuō)明,并且被恰當(dāng)?shù)拿H绻录幸粋€(gè)泛化的名字,或者使用標(biāo)志位泛化事件都會(huì)讓人困惑,引發(fā)問(wèn)題。
復(fù)雜的依賴關(guān)系
要特別小心服務(wù)依賴于一個(gè)或多個(gè)服務(wù)造成復(fù)雜的依賴關(guān)系,或?qū)е卵h(huán)反饋。每個(gè)網(wǎng)絡(luò)請(qǐng)求節(jié)點(diǎn)都會(huì)給原始請(qǐng)求增加額外的延遲,尤其是數(shù)據(jù)中心之外的 南/北 網(wǎng)絡(luò)流量。
確定順序,分發(fā),副作用
事件是異步的;所以,任何基于順序或重復(fù)的假設(shè)不僅會(huì)增加系統(tǒng)復(fù)雜程度,還會(huì)抵消掉很多事件驅(qū)動(dòng)架構(gòu)的關(guān)鍵優(yōu)勢(shì)。如果你的消費(fèi)者有副作用,例如插入一個(gè)值到數(shù)據(jù)庫(kù),那么你最要不要重復(fù)之前的事件。
過(guò)早的優(yōu)化
大部分產(chǎn)品都由小規(guī)模開(kāi)始,然后隨著時(shí)間的流逝而增長(zhǎng)。雖然你夢(mèng)想著將來(lái)會(huì)擴(kuò)展成大型的復(fù)雜組織,但是如果你的團(tuán)隊(duì)很小,那么事件驅(qū)動(dòng)架構(gòu)帶來(lái)的多余的復(fù)雜性將會(huì)減慢開(kāi)發(fā)速度。相對(duì)的,考慮將你的系統(tǒng)設(shè)計(jì)為一個(gè)簡(jiǎn)單但是分離了關(guān)注點(diǎn)的架構(gòu),將來(lái)隨著它的發(fā)展你可以進(jìn)行切換。
期望事件驅(qū)動(dòng)解決所有問(wèn)題
在較低的技術(shù)水平上,不要指望事件驅(qū)動(dòng)架構(gòu)能夠解決所有的問(wèn)題。雖然這個(gè)架構(gòu)肯定可以在很多方面提供改進(jìn),但是它依然無(wú)法解決很多關(guān)鍵的核心問(wèn)題,例如缺乏自動(dòng)化測(cè)試,缺乏交流,或過(guò)時(shí)的 dev-ops 實(shí)踐。
更多內(nèi)容
理解事前驅(qū)動(dòng)架構(gòu)的優(yōu)缺點(diǎn),以及一些通常的設(shè)計(jì)決策和挑戰(zhàn),讓創(chuàng)建一個(gè)好的設(shè)計(jì)成為可能。
如果你想要學(xué)習(xí)更多的內(nèi)容,可以查看 event-driven reference architecture這是我們?cè)?Heroku 上創(chuàng)建的項(xiàng)目,它讓你通過(guò)一個(gè)點(diǎn)擊就能把應(yīng)用部署到 Heroku上。這個(gè)參考的架構(gòu)創(chuàng)建了一個(gè)虛擬的網(wǎng)上開(kāi)發(fā)銷售商店。