EDA事件驅(qū)動架構(gòu)具有什么特點和優(yōu)缺點?
隨著互聯(lián)網(wǎng)和移動互聯(lián)網(wǎng)的快速發(fā)展,越來越多的應用程序需要處理海量數(shù)據(jù)和大量的并發(fā)請求。傳統(tǒng)的軟件架構(gòu)已經(jīng)無法滿足這些需求,因此,一些新的架構(gòu)模式開始受到關(guān)注。其中,事件驅(qū)動架構(gòu)(Event-Driven Architecture,EDA)因其高效、可擴展和靈活等優(yōu)點,逐漸成為熱門的選擇。本文將探討事件驅(qū)動架構(gòu)在實際應用中的優(yōu)勢和挑戰(zhàn)。
一、事件驅(qū)動架構(gòu)的概述
事件驅(qū)動架構(gòu)是一種基于事件和消息的軟件架構(gòu)模式,其核心思想是將應用程序設計為響應事件的系統(tǒng)。在這種架構(gòu)中,事件是系統(tǒng)中發(fā)生的某種事情,可以是用戶行為、設備狀態(tài)變化等。當事件發(fā)生時,系統(tǒng)會產(chǎn)生相應的消息,該消息會被傳遞給感興趣的組件進行處理。組件可以是其他應用程序、服務或者處理器等,它們通過訂閱消息的方式來接收事件。
事件驅(qū)動架構(gòu)與傳統(tǒng)的請求響應模式相比,最大的不同在于處理方式。傳統(tǒng)的請求響應模式是基于事務的,即客戶端發(fā)起請求,服務端進行處理并返回響應。而事件驅(qū)動架構(gòu)是基于消息的,即系統(tǒng)會在事件發(fā)生時異步地發(fā)送消息給感興趣的組件進行處理。這種異步的處理方式可以使系統(tǒng)更加靈活、可擴展和高效。
二、事件驅(qū)動架構(gòu)的優(yōu)勢
1. 可擴展性
事件驅(qū)動架構(gòu)是一種高度可擴展的架構(gòu)模式。由于事件是異步的,不同的事件可以由不同的組件進行處理。因此,可以根據(jù)應用程序的需求,動態(tài)地添加或刪除組件,以實現(xiàn)系統(tǒng)的可擴展性。
2. 高效性
事件驅(qū)動架構(gòu)可以提高系統(tǒng)的處理效率。由于事件是異步的,組件可以在事件發(fā)生時立即進行處理,而不需要等待請求的響應。這種異步的處理方式可以提高系統(tǒng)的響應速度和吞吐量。
當類或組件之間內(nèi)聚性很高,它們的耦合度應該很低,也就是說當組件需要相互協(xié)作調(diào)用時,比如我們假設一個組件“A”需要觸發(fā)組件“B”中的一些邏輯,自然的方式是直接讓組件A調(diào)用組件B中的一個方法。但前提是A必須知道B的存在,這樣它們之間就是耦合的,A必須依賴于B了,這會使得系統(tǒng)更難以改變和維護。因此,這里可以使用事件來防止這種直接調(diào)用的耦合。
此外,使用事件實現(xiàn)組件解耦也有其另外的,如果我們有一個只負責組件B的工作團隊,那么他們則可能不需要與負責組件A的團隊進行交流,直接針對組件A中的邏輯改變在組件B中做出相對反應。兩個組件團隊可以獨立發(fā)展(banq注:微服務特點之一), 我們的應用系統(tǒng)變得更靈活。
即使在同一個組件團隊中,有時候我們不需要在同一請求/響應中立即執(zhí)行一個動作的結(jié)果,只要異步執(zhí)行這個動作,比如發(fā)送電子郵件。在這種情況下,我們可以立即向用戶返回響應,并以異步方式發(fā)送電子郵件,并避免讓用戶等待發(fā)送電子郵件。
不過,如果我們不加區(qū)別地使用它,也有危險。我們會遇到邏輯流程的風險,這些邏輯流程在概念上是高度凝聚力的,但是通過采取脫鉤機制的事件連接在一起。換句話說,應該在一起的代碼將被分開,并且難以跟蹤它的流程(類似于goto語句),不易于理解:可能是意大利面一樣混在一起!
為了防止將我們的代碼庫變成一大堆意大利面條,我們應該將事件的使用限制在明確的情況下。根據(jù)我的經(jīng)驗,有三種使用事件的情況:
(1)去耦組件
(2)執(zhí)行異步任務
(3)跟蹤狀態(tài)變化(審核日志)
1.去耦組件(微服務)
當組件A執(zhí)行的邏輯需要觸發(fā)組件B的邏輯時,不要直接調(diào)用它,我們可以將觸發(fā)事件發(fā)送到事件分派器。組件B將偵聽調(diào)度程序中的特定事件,并在事件發(fā)生時執(zhí)行操作。
這意味著A和B都將取決于調(diào)度器和事件,但他們之間將不會知道對方存在,它們將被解耦。
理想情況下,調(diào)度員和事件都不應該在兩個組件之間存在:
(1)調(diào)度員應該是完全獨立于我們應用程序的庫,因此使用依賴管理系統(tǒng)安裝在通用位置。在PHP世界中,這是使用Composer等安裝在vendor文件夾中的東西。
(2) 事件是我們的應用程序的一部分,應該在兩個組件之間生存,組件之間通過事件進行通訊(結(jié)構(gòu)上解耦,行為上耦合)。事件在組件之間共享,它是應用程序的核心部分。事件在DDD中屬于共享內(nèi)核Shared Kernel的一部分。這樣,兩個組件都將依賴于共享內(nèi)核,但彼此不會意識到。然而在單體Monolithic應用程序中,為方便起見,可以將其放在觸發(fā)事件的組件中。
DDD共享內(nèi)核
[。..]明確界定指定團隊同意分享的領(lǐng)域模型的一些子集。保持這個內(nèi)核很小。[。..]這個明確共享的東西有特殊的地位,如果沒有與其他團隊協(xié)商,不應該改變。
Eric Evans 2014, 領(lǐng)域驅(qū)動設計參考
2.執(zhí)行異步任務
有時候我們有一個我們想要執(zhí)行的邏輯,但它可能需要相當長的時間來執(zhí)行,我們不想讓用戶等待它完成。在這種情況下,希望將其作為異步工作運行,并立即返回給用戶的消息,通知他請求將在以后異步執(zhí)行。
例如,在網(wǎng)上商店下訂單可以同步完成,但發(fā)送通知用戶的電子郵件可以進行異步。
在這種情況下,我們可以做的是觸發(fā)一個將被排隊的事件,直到一個工作任務可以獲得這個事件并執(zhí)行它,只要系統(tǒng)有資源。
在這些情況下,相關(guān)聯(lián)的邏輯是否在相同的有界環(huán)境中并不重要,無論哪種方式,邏輯都是去耦的。
3.跟蹤狀態(tài)變化(審計日志)
以傳統(tǒng)的數(shù)據(jù)存儲方式,我們擁有一些數(shù)據(jù)的實體。當這些實體中的數(shù)據(jù)發(fā)生變化時,我們只需更新數(shù)據(jù)庫表行以反映新值。
這里的問題是,我們并不存儲這些值為什么改變且什么時候改變。
我們可以將這些改變的事件存儲在審計日志中。
更多關(guān)于這個進一步的前景,在關(guān)于事件溯源的解釋。
事件模式
Martin Fowler確定了三種不同類型的事件模式:
(1)事件通知
(2)事件執(zhí)行狀態(tài)轉(zhuǎn)移
(3)事件溯源Event Sourcing
所有這些模式共享相同的關(guān)鍵概念:
(1)事件是代表發(fā)生了一些事情(發(fā)生在某事之后);
(2)事件被廣播到正在監(jiān)聽的任何代碼(代碼可以對事件做出反應)。
一。 事件通知
假設我們有一個具有明確定義的組件作為應用程序核心。理想情況下,這些組件是完全相互分離的,但是它們的一些功能需要在其他組件中執(zhí)行一些邏輯。
最典型的情況如前所述:當組件A執(zhí)行的邏輯需要觸發(fā)組件B的邏輯時,A不是直接去調(diào)用B,而是觸發(fā)事件將且發(fā)送到事件調(diào)度程序。組件B將偵聽調(diào)度程序中的特定事件,并在事件發(fā)生時執(zhí)行操作。
重要的是,這種模式的一個特征是事件攜帶最少的數(shù)據(jù)。它只為聽眾提供足夠的數(shù)據(jù),以便知道發(fā)生了什么并執(zhí)行其代碼,通常只是實體ID,也可能是事件創(chuàng)建的日期和時間。
優(yōu)點
(1)彈性更大:將事件排隊后,發(fā)送方組件可以繼續(xù)執(zhí)行其自己邏輯,即使由于錯誤發(fā)生,因為它們排隊等候,它們可以在錯誤被修復時被執(zhí)行。
(2)降低延遲,如果事件排隊,用戶不需要等待該邏輯執(zhí)行;
團隊可以獨立發(fā)展組件,使他們的工作更輕松,更快,更容易出現(xiàn)問題,更靈活;
缺點
(1)如果沒有使用標準,有可能變成一堆意大利面條代碼。
二。 事件執(zhí)行狀態(tài)轉(zhuǎn)移
讓我們再次看看前面例子,一個具有明確定義的組件作為應用程序核心。如果A組件一些功能需要來自其他組件的數(shù)據(jù)。獲得該數(shù)據(jù)的最自然的方法是詢問其他組件,但這意味著被查詢組件必須提供查詢方法以供查詢組件使用,一次兩次修改增加無所謂,如果頻繁要求被查詢組件提供新的查詢方法,說明這兩個組件彼此耦合!
在組件之間共享數(shù)據(jù)的另一種方法是:擁有數(shù)據(jù)的組件觸發(fā)的更改事件時,該事件將攜帶全新更改后的數(shù)據(jù)。對該數(shù)據(jù)感興趣的組件將會監(jiān)聽這些事件,從事件中獲得數(shù)據(jù)并存儲該數(shù)據(jù)的本地副本,然后進一步對這些全新數(shù)據(jù)做出反應。這樣,當他們需要外部數(shù)據(jù)時,他們其實在本地已經(jīng)擁有它們,它們將不需要查詢其他組件,也不需要其他組件提供對應的查詢方法。
單體架構(gòu)
如果使用最早的單體架構(gòu)部署,作為最傳統(tǒng)的應用部署模式,是將整個應用程序作為一個單一的、緊密耦合的單元進行開發(fā)和部署。
在這種情況下,MP4文件轉(zhuǎn)換為WMV格式的功能將作為應用程序的一部分實現(xiàn)。整個應用程序在一個部署單元中運行,包括處理用戶界面、業(yè)務邏輯和數(shù)據(jù)訪問等功能。
遇到問題
使用這種模式,問題非常明顯,雖然部署簡單,但會導致代碼和功能之間的緊密耦合,遇到局部bug會影響整個應用功能運行,可伸縮性受限,整個應用程序需要按照最高負載需求進行伸縮,而不僅僅是轉(zhuǎn)換文件的功能。這可能導致資源浪費和低效的資源利用,另外也會存在單點故障問題,如果應用程序的某個組件出現(xiàn)故障,整個應用程序都會受到影響。
在單體架構(gòu)中,所有的功能模塊都被打包在一起,共享同一個數(shù)據(jù)庫和用戶界面。單體架構(gòu)在早期的軟件開發(fā)中非常常見,因為它簡單、易于理解和實現(xiàn),但受限于可擴展、可維護性、高可用性等問題,單體架構(gòu)逐漸被時代淘汰。
容器技術(shù)/微服務
為了解決單體架構(gòu)存在問題,容器技術(shù)孕育而生,直至docker容器技術(shù)出現(xiàn),行業(yè)改變了以往的架構(gòu)模式。
從過去以物理機和虛擬機為主體的開發(fā)運維環(huán)境,向以容器為核心的基礎(chǔ)設施的轉(zhuǎn)變過程,這并不是一次溫和的改革,而是涵蓋了對網(wǎng)絡、存儲、調(diào)度、操作系統(tǒng)、分布式原理等各個方面的容器化理解和改造。
容器技術(shù)發(fā)展徹底釋放了微服務天性,在單體架構(gòu)部署存在的問題,在微服務架構(gòu)中,應用程序被拆分為多個小型、獨立部署的服務,每個服務專注于一個特定的功能。對于MP4文件轉(zhuǎn)換為WMV格式的過程,可以將其作為一個單獨的轉(zhuǎn)換服務實現(xiàn),甚至可以根據(jù)轉(zhuǎn)換過程進一步拆分,實現(xiàn)更精細化管理,每個服務可以獨立部署和伸縮,提供更好的靈活性和可擴展性,服務之間都是獨立的,可以獨立開發(fā)、測試、擴展和部署。
遇到問題
隨之而來地系統(tǒng)復雜性增加,微服務架構(gòu)引入了分布式系統(tǒng)的復雜性,包括服務之間的通信、數(shù)據(jù)一致性和故障處理等方面。
部署和管理成本增加,管理多個服務的部署和運維需要更多的工作和資源。
企業(yè)需求
對于企業(yè),如果希望既要實現(xiàn)高效、可伸縮、高可用等功能,同時提高團隊人員效率,開發(fā)者可以專注于編寫業(yè)務邏輯,而無需擔心服務器的配置、擴展或管理。
還是之前“MP4文件轉(zhuǎn)換為WMV格式存儲到數(shù)據(jù)”例子中,如何實現(xiàn)開發(fā)者可以專注于編寫業(yè)務邏輯,而無需擔心服務器的配置、擴展或管理問題?
可以通過兩個步驟來了解:
1、Serverless服務
2、事件驅(qū)動架構(gòu)(EDA)
Serverless在基礎(chǔ)設施端解決運維困擾
在Serverless計算服務中,開發(fā)人員只需關(guān)注應用程序的業(yè)務邏輯,云服務提供商將負責管理和調(diào)配計算資源,平臺根據(jù)需求自動擴展計算資源,以適應變化的工作負載,同時按使用付費,根據(jù)實際使用的計算資源付費,避免了長期維護和不必要的成本。
EDA讓開發(fā)者可以專注于編寫業(yè)務邏輯使用事件驅(qū)動架構(gòu)
使用EDA架構(gòu)來實現(xiàn)將MP4文件轉(zhuǎn)換為WMV格式的過程可以提供一種高度可擴展和靈活的方式。