每個(gè)開發(fā)人員都應(yīng)該知道的基本概念
我們關(guān)于微服務(wù)簡(jiǎn)介的第一篇文章談到了服務(wù)的粒度和確保松散耦合的必要性。據(jù)說服務(wù)應(yīng)該是自治的乘粒,完全擁有它們的依賴關(guān)系豌注,并盡量減少同步通信。今天灯萍,我們將探討松散耦合的含義轧铁,并探索一個(gè)在微服務(wù)社區(qū)中似乎越來越受歡迎的交易技巧——事件驅(qū)動(dòng)架構(gòu)。
一個(gè)簡(jiǎn)單的定義
事件驅(qū)動(dòng)架構(gòu)(EDA)是一種促進(jìn)事件生產(chǎn)和消費(fèi)的軟件架構(gòu)范式旦棉。
事件代表具有重大意義的行動(dòng)齿风。通常,事件對(duì)應(yīng)于某個(gè)實(shí)體的創(chuàng)建或狀態(tài)更改绑洛。例如救斑,在電子商務(wù)應(yīng)用程序中提出訂單就是一個(gè)事件。由于較早的訂單而發(fā)送產(chǎn)品也是一個(gè)事件真屯×澈颍客戶為收到的產(chǎn)品提交評(píng)論 - 你猜對(duì)了 - 一個(gè)事件。
從未發(fā)生的事件
事件的特殊之處在于它們沒有明確地傳達(dá)給可能關(guān)心它們的特定方绑蔫。事件“剛剛發(fā)生”运沦。至關(guān)重要的是,無論某些方面是否對(duì)它們感興趣配深,它們都會(huì)發(fā)生茶袒。這聽起來像是經(jīng)常被引用的哲學(xué)思想實(shí)驗(yàn):“如果一棵樹倒在森林里,而周圍沒有人聽到它凉馆,它會(huì)發(fā)出聲音嗎薪寓?” . 但這正是事件如此強(qiáng)大的原因——事件轉(zhuǎn)化為事件發(fā)生的自包含記錄這一事實(shí)意味著事件以及它們的發(fā)射器從根本上與它們的處理程序分離亡资。事實(shí)上,事件記錄的生產(chǎn)者通常不知道消費(fèi)者是誰**可能是向叉,消費(fèi)者是否存在锥腻。
記錄通常包含描述事件所需的信息。在我們之前的訂單示例中母谎,相應(yīng)的事件可能由一個(gè)簡(jiǎn)單的 JSON 文檔描述瘦黑,該文檔可能如下所示:
“orderId”:“760b5301-295f-4fec-95f8-6b303a3b824a”,
“customerId”:28623823奇唤,
“productId”:31334幸斥,
“數(shù)量”:1,
“時(shí)間戳”:“2021-02-09T11:12:17+ 0000"
注意:盡管存在細(xì)微差別咬扇,但記錄和事件通臣自幔可以互換使用;即懈贺,術(shù)語“事件”用于表示該事件的“記錄”经窖。為了讓事情變得更容易,我們將允許自己從現(xiàn)在開始享有同樣的自由梭灿。
誠然画侣,上面的示例可能是對(duì)訂單的過度簡(jiǎn)化,但它就足夠了堡妒。提出訂單的應(yīng)用程序(例如配乱,購物車服務(wù))不知道誰將處理訂單、何時(shí)皮迟、如何甚至為什么搬泥。生產(chǎn)者確保捕獲潛在消費(fèi)者處理事件所需的一切。也就是說万栅,訂單記錄并不嚴(yán)格需要包括其履行所需的每一個(gè)屬性。例如西疤,產(chǎn)品的尺寸烦粒,其庫存位置和客戶的送貨地址不是直接指定的,而是可以通過在訂單記錄中捕獲的ID來解決代赁。您可能從關(guān)系數(shù)據(jù)庫中熟悉的外鍵概念也適用于事件扰她。
引導(dǎo)事件
如果事件的生產(chǎn)者和消費(fèi)者彼此不知道,他們?nèi)绾螠贤ǎ?/p>
線索就在“記錄”一詞中芭碍。事件通常保存在一個(gè)眾所周知的位置徒役,稱為日志。(有時(shí)窖壕,可以使用術(shù)語賬本忧勿。)日志是低級(jí)的杉女、僅附加的數(shù)據(jù)結(jié)構(gòu),允許生產(chǎn)者將事件保存在其他方(稱為消費(fèi)者)以后可以訪問它的位置鸳吸。對(duì)日志的所有操作都由代理(位于生產(chǎn)者和消費(fèi)者之間的持久中間件)提供便利熏挎。發(fā)布事件后,任何人和每個(gè)人都可以使用該事件晌砾。
在處理事件驅(qū)動(dòng)系統(tǒng)時(shí)坎拐,我們經(jīng)常使用術(shù)語流來描述一個(gè)或多個(gè)日志的接口。雖然日志是一個(gè)物理概念(使用文件實(shí)現(xiàn))养匈,但流是一種邏輯結(jié)構(gòu)哼勇,它將事件表示為無限的記錄序列,受某些排序約束呕乎。不同的事件流平臺(tái)可能使用專有名稱來指代流积担。Apache Kafka——迄今為止最流行的事件流平臺(tái)——根據(jù)主題和分區(qū)來描述流。
以下參考模型描述了生產(chǎn)者楣嘁、消費(fèi)者和流之間的關(guān)系磅轻。
幫助鞏固我們理解的快速檢查點(diǎn):
- 事件是在離散時(shí)間點(diǎn)發(fā)生的感興趣的動(dòng)作,可以從外部觀察和描述逐虚。
- 事件作為記錄保存聋溜。事件和記錄盡管是相關(guān)的,但在技術(shù)上是不同的東西叭爱。事件是某事的發(fā)生(例如撮躁,狀態(tài)變化),它本身是無形的买雾。記錄是對(duì)該事件的準(zhǔn)確描述把曼。我們經(jīng)常使用術(shù)語事件來指代它的記錄。
- 生產(chǎn)者是通過將相應(yīng)記錄發(fā)布到流中來檢測(cè)事件的受體漓穿。
- 流是記錄的持久序列嗤军。它們通常由一個(gè)或多個(gè)基于磁盤的日志支持。同樣晃危,流可能由數(shù)據(jù)庫表叙赚、分布式共識(shí)協(xié)議,甚至是區(qū)塊鏈?zhǔn)降娜ブ行幕~本支持僚饭。
- 代理管理對(duì)流的訪問震叮,促進(jìn)讀寫操作,處理消費(fèi)者狀態(tài)并在流上執(zhí)行各種內(nèi)務(wù)管理任務(wù)鳍鸵。例如苇瓣,當(dāng)記錄溢出時(shí),代理可能會(huì)截?cái)嗔鞯膬?nèi)容偿乖。
- 消費(fèi)者從流中讀取數(shù)據(jù)并對(duì)記錄的接收做出反應(yīng)击罪。對(duì)事件的反應(yīng)可能會(huì)帶來一些副作用哲嘲;例如,消費(fèi)者可能會(huì)將一個(gè)條目持久保存到其本地?cái)?shù)據(jù)庫中——從其發(fā)布的“更新”事件中重建遠(yuǎn)程實(shí)體的狀態(tài)外邓。
- 消費(fèi)者和生產(chǎn)者可能重疊撤蚊;例如,對(duì)事件的反應(yīng)可能是產(chǎn)生一個(gè)或多個(gè)衍生事件损话。
通過異步和通用性解耦
回到我們開始的地方侦啸,為什么 EDA 會(huì)導(dǎo)致耦合程度顯著降低?
耦合的一個(gè)實(shí)用定義是組件受其他組件影響的程度丧枪。耦合既存在于空間中——組件在結(jié)構(gòu)上是相關(guān)的光涂,也存在于時(shí)間中——時(shí)間的概念會(huì)影響它們之間關(guān)系的程度。后者的一個(gè)很好的例子是一個(gè)服務(wù)同步調(diào)用另一個(gè)服務(wù)的 REST API拧烦。如果被調(diào)用的服務(wù)宕機(jī)忘闻,被調(diào)用者通常無法繼續(xù)——它在響應(yīng)中被阻塞。如果兩個(gè)服務(wù)必須同時(shí)運(yùn)行恋博,那么它們之間存在一定程度的時(shí)間耦合齐佳。如果組件之間有很強(qiáng)的相互依賴關(guān)系,我們說組件是緊耦合的债沮,否則就是松耦合的炼吴。
EDA 采用雙管齊下的方法來抑制耦合。
- 回想一下疫衩,事件沒有被傳達(dá)硅蹦,它們只是發(fā)生了。引發(fā)事件的組件(通過發(fā)布記錄)不知道可能存在或不存在的其他組件闷煤。因此童芹,如果消費(fèi)者不可用,生產(chǎn)者不會(huì)停止工作——前提是代理可以持久地緩沖事件而不會(huì)對(duì)生產(chǎn)者施加背壓鲤拿。
- 代理上事件記錄的持久性在很大程度上消除了時(shí)間的概念假褪。生產(chǎn)者可能在T1時(shí)間發(fā)布事件,而消費(fèi)者可能在T2讀取它近顷,T1和T2可能以毫秒(如果一切順利)或幾小時(shí)(如果某些消費(fèi)者情緒低落或掙扎)分開生音。
EDA 不是靈丹妙藥。它并沒有完全消除耦合的概念——否則幕庐,系統(tǒng)中的組件將不再共同發(fā)揮作用【米叮現(xiàn)在我們的注意力轉(zhuǎn)向了代理:為了讓生產(chǎn)者和消費(fèi)者有意義地解耦家淤,他們必須轉(zhuǎn)而依賴(因此將自己耦合到)代理异剥。這增加了系統(tǒng)架構(gòu)的復(fù)雜性并引入了另一個(gè)故障點(diǎn)。這就是為什么經(jīng)紀(jì)人必須具有高性能和容錯(cuò)性絮重,否則我們只是將一組問題換成了另一組問題冤寿。
事件處理方式
事件處理通常分為三種名義風(fēng)格歹苦。這些風(fēng)格不是相互排斥的,經(jīng)常一起出現(xiàn)在大型的事件驅(qū)動(dòng)系統(tǒng)中督怜。
離散事件處理
離散事件的處理殴瘦;例如,在社交媒體平臺(tái)上發(fā)布帖子号杠。離散事件處理的特點(diǎn)是存在通常彼此無關(guān)并且可以獨(dú)立處理的事件蚪腋。
事件流處理
處理無限制的相關(guān)事件流,其中事件記錄以某種順序出現(xiàn)姨蟋,并在處理過去事件的一些知識(shí)的情況下進(jìn)行處理屉凯。一個(gè)很好的例子可能是對(duì)業(yè)務(wù)實(shí)體的更改聯(lián)合。消費(fèi)者可以按照生產(chǎn)者規(guī)定的順序應(yīng)用這些更改眼溶,以將實(shí)體的副本保存在其本地?cái)?shù)據(jù)庫中悠砚。由于順序很重要,離散地處理這些更改記錄可能不會(huì)減少它堂飞。消費(fèi)者還需要避免競(jìng)爭(zhēng)條件灌旧,即多個(gè)消費(fèi)者實(shí)例可能會(huì)嘗試同時(shí)對(duì)數(shù)據(jù)庫中的同一條記錄應(yīng)用更改,從而由于無序更新而導(dǎo)致數(shù)據(jù)不一致绰筛。
像 Kafka 這樣的流行事件流平臺(tái)依靠記錄鍵控和分區(qū)來保持更新的順序枢泰。Kafka 還保證對(duì)實(shí)體的所有更改都由一個(gè)消費(fèi)者實(shí)例處理,從而避免多個(gè)消費(fèi)者天真地并行處理事件時(shí)可能導(dǎo)致的并發(fā)競(jìng)爭(zhēng)别智。
復(fù)雜事件處理
復(fù)雜事件處理 (CEP) 從一系列簡(jiǎn)單事件中派生或識(shí)別復(fù)雜事件模式宗苍。CEP 的一個(gè)示例可能是監(jiān)控建筑物中的一組溫度和煙霧傳感器,以推斷火災(zāi)已經(jīng)發(fā)生并跟蹤其進(jìn)展薄榛。個(gè)別溫度變化可能不足以引發(fā)警報(bào)讳窟;然而,溫度峰值的聚集和變化率可能會(huì)提供更有意義的見解敞恋,最終可以挽救生命丽啡。
這種處理通常涉及更多,需要事件處理器跟蹤先前的事件并提供查詢和聚合它們的有效方式硬猫。
何時(shí)使用 EDA
有幾個(gè)用例可以發(fā)揮事件驅(qū)動(dòng)架構(gòu)的優(yōu)勢(shì):
- 不透明的消費(fèi)者生態(tài)系統(tǒng)补箍。生產(chǎn)者通常不了解消費(fèi)者的情況。后者甚至可能是短暫的過程啸蜜,可以在短時(shí)間內(nèi)來去匆匆坑雅!
- 高扇出。一個(gè)事件可能由多個(gè)不同的消費(fèi)者處理的場(chǎng)景衬横。
- 復(fù)雜的模式匹配裹粤。事件可能被串在一起以推斷更復(fù)雜的事件。
- 命令查詢職責(zé)分離蜂林。CQRS 是一種將數(shù)據(jù)存儲(chǔ)的讀取和更新操作分開的模式遥诉。實(shí)施 CQRS 可以提高應(yīng)用程序的可擴(kuò)展性和彈性拇泣,但需要權(quán)衡一些一致性。這種模式通常與 EDA 相關(guān)聯(lián)。
EDA 的好處
- 緩沖和容錯(cuò)。事件可能以與其生產(chǎn)不同的速率被消費(fèi)晨继,生產(chǎn)者不能放慢速度讓消費(fèi)者趕上弯菊。
- 生產(chǎn)者和消費(fèi)者解耦,避免笨拙的點(diǎn)對(duì)點(diǎn)集成。向系統(tǒng)添加新的生產(chǎn)者和消費(fèi)者很容易。只要遵守約束事件記錄的合同/模式,更改生產(chǎn)者和消費(fèi)者的實(shí)現(xiàn)也很容易葱弟。
- 大規(guī)模的可擴(kuò)展性。通巢碌ぃ可以將事件流劃分為不相關(guān)的子流并并行處理這些子流芝加。如果事件積壓增加,我們還可以擴(kuò)展消費(fèi)者數(shù)量以滿足負(fù)載需求射窒。像 Kafka 這樣的平臺(tái)能夠以嚴(yán)格的順序處理事件藏杖,同時(shí)允許跨流的大規(guī)模并行性。
EDA的缺點(diǎn)
- 僅限于異步處理脉顿。雖然 EDA 是一種用于解耦系統(tǒng)的強(qiáng)大模式蝌麸,但它的應(yīng)用僅限于事件的異步處理。EDA 不能很好地替代請(qǐng)求-響應(yīng)交互艾疟,其中發(fā)起者必須等待響應(yīng)才能繼續(xù)来吩。
- 引入了額外的復(fù)雜性。傳統(tǒng)的客戶端-服務(wù)器和請(qǐng)求-響應(yīng)式計(jì)算僅涉及兩方蔽莱,而采用 EDA 則需要第三方——代理來調(diào)解生產(chǎn)者和消費(fèi)者之間的交互弟疆。
- 故障屏蔽。這是一個(gè)特殊的問題盗冷,因?yàn)樗坪跖c解耦系統(tǒng)的本質(zhì)背道而馳怠苔。當(dāng)系統(tǒng)緊密耦合時(shí),一個(gè)系統(tǒng)中的錯(cuò)誤往往會(huì)迅速傳播仪糖,并經(jīng)常以痛苦的方式引起我們的注意柑司。在大多數(shù)情況下,這是我們希望避免的:一個(gè)組件的故障應(yīng)該對(duì)其他組件的影響盡可能小锅劝。故障掩蔽的另一面是它無意中隱藏了本應(yīng)引起我們注意的問題攒驰。這是通過向每個(gè)事件驅(qū)動(dòng)組件添加實(shí)時(shí)監(jiān)控和日志記錄來解決的,但這會(huì)增加復(fù)雜性故爵。
需要注意的事項(xiàng)
EDA 不是靈丹妙藥玻粪,并且像任何強(qiáng)大的工具一樣,它很容易被誤用。下面的列表不應(yīng)該被解讀為 EDA 的直接缺點(diǎn)奶段,而更多的是作為謹(jǐn)慎的開發(fā)人員和架構(gòu)師在設(shè)計(jì)和實(shí)現(xiàn)事件驅(qū)動(dòng)系統(tǒng)時(shí)應(yīng)該注意的一組陷阱。
- 錯(cuò)綜復(fù)雜的編舞剥纷。對(duì)于松散耦合的組件痹籍,人們可能會(huì)陷入架構(gòu)可能類似于 Rube Goldburg 機(jī)器的情況,其中整個(gè)業(yè)務(wù)邏輯被實(shí)現(xiàn)為一系列偽裝成事件的副作用:一個(gè)組件可能引發(fā)一個(gè)觸發(fā)事件的事件另一個(gè)組件中的響應(yīng)引發(fā)另一個(gè)事件晦鞋,觸發(fā)另一個(gè)組件蹲缠,等等。這種組件之間的交互方式很快就會(huì)變得難以理解和推理悠垛。
- 將命令偽裝成事件线定。事件是對(duì)已經(jīng)發(fā)生的事情的純粹描述;它沒有規(guī)定應(yīng)如何處理該事件确买。另一方面斤讥,命令是針對(duì)特定組件的直接指令。因?yàn)槊詈褪录际歉鞣N消息湾趾,所以很容易被帶走并將命令誤認(rèn)為是事件芭商。
- 對(duì)消費(fèi)者保持不可知論。事件應(yīng)該以不限制如何處理這些事件的方式捕獲相關(guān)屬性搀缠。這說起來容易做起來難铛楣。有時(shí)我們可能會(huì)知道更多信息,理論上這些信息可以添加到事件記錄中艺普,但尚不清楚將這些信息添加到記錄中是否有用簸州,或者它是否只會(huì)導(dǎo)致無用的膨脹。
結(jié)論
微服務(wù)架構(gòu)范式是構(gòu)建更具可維護(hù)性歧譬、可擴(kuò)展性和健壯性的軟件系統(tǒng)的更廣泛難題的一部分岸浑。從問題分解的角度來看,微服務(wù)非常棒瑰步,但它們留下了很多棘手的問題助琐;一個(gè)這樣的問題是耦合。與你開始的地方相比面氓,一個(gè)隨意分解成幾個(gè)微服務(wù)的單體實(shí)際上可能會(huì)讓你處于更糟糕的狀態(tài)兵钮。我們甚至有一個(gè)術(shù)語:“分布式單體”。
為了幫助完成這個(gè)難題并解決耦合問題舌界,我們研究了事件驅(qū)動(dòng)架構(gòu)掘譬。
EDA 是一種通過使用生產(chǎn)者、消費(fèi)者呻拌、事件和流的概念對(duì)交互進(jìn)行建模來減少系統(tǒng)組件之間耦合的有效工具葱轩。一個(gè)事件代表一個(gè)感興趣的動(dòng)作,并且可能被甚至不知道彼此存在的組件異步發(fā)布和消費(fèi)。EDA 允許組件獨(dú)立運(yùn)行和發(fā)展靴拱。殺死所有惡魔并不是靈丹妙藥垃喊,但如果 EDA 是一個(gè)合適的選擇,它帶來的好處遠(yuǎn)遠(yuǎn)超過采用它的成本袜炕。有人可能會(huì)說本谜,EDA 是任何成功的微服務(wù)部署的基本要素。