事件驅(qū)動架構(gòu)風格是一種流行的分布式異步架構(gòu)風格趁窃,用于構(gòu)建高可擴展和高性能的應(yīng)用程序眷细。它的適應(yīng)性也很強立轧,既可以用于小型應(yīng)用,也可以用于大型復雜應(yīng)用空闲。事件驅(qū)動架構(gòu)由異步接收和處理事件的解耦的事件處理組件組成令杈。它可以作為獨立的架構(gòu)風格使用,也可以嵌入到其他架構(gòu)風格中(例如事件驅(qū)動的微服務(wù)架構(gòu))碴倾。
大多數(shù)應(yīng)用程序遵循所謂的基于請求的模型(如圖14-1所示)逗噩。在此模型中,向系統(tǒng)發(fā)出的執(zhí)行某種操作的請求被發(fā)送到請求編排器跌榔。請求編排器通常是一個用戶界面异雁,但也可以通過API層或企業(yè)服務(wù)總線來實現(xiàn)。請求編排器的作用是確定地僧须、同步地將請求定向到不同的請求處理器纲刀。請求處理器處理請求,檢索或更新數(shù)據(jù)庫中的信息担平。
基于請求的模型的一個很好的例子是客戶請求檢索他們過去六個月的訂單歷史記錄示绊。檢索訂單歷史信息是一種數(shù)據(jù)驅(qū)動的锭部、確定性的請求,它是為了獲取特定上下文中的數(shù)據(jù)而向系統(tǒng)發(fā)出的面褐,而不是系統(tǒng)必須響應(yīng)的事件拌禾。
另一方面,基于事件的模型對特定情況作出反應(yīng)展哭,并根據(jù)該事件采取行動湃窍。基于事件的模型的一個例子是在線拍賣中提交對特定物品的出價匪傍。提交報價不是向系統(tǒng)發(fā)出的請求您市,而是在當前要價公布后發(fā)生的事件。系統(tǒng)必須通過比較同一時間收到的其他報價來響應(yīng)此事件役衡,以確定誰是當前的最高出價者茵休。
拓撲結(jié)構(gòu)
事件驅(qū)動架構(gòu)中有兩種主要拓撲結(jié)構(gòu):中介拓撲和代理拓撲。中介拓撲通常用于當你需要控制事件處理的工作流時手蝎,而代理拓撲則用于當你需要對事件處理進行高度響應(yīng)和動態(tài)控制的情況泽篮。由于這兩種拓撲結(jié)構(gòu)的架構(gòu)特性和實現(xiàn)策略是不同的,所以理解每種拓撲結(jié)構(gòu)以知道哪種拓撲結(jié)構(gòu)最適合于特定情況是很重要的柑船。
代理拓撲
代理拓撲與中介拓撲的不同之處在于沒有中央事件中介器。相反泼各,消息流通過輕量級消息代理(如RabbitMQ鞍时、ActiveMQ、HornetQ等)以鏈式廣播方式發(fā)布到多個事件處理器組件上扣蜻。當你具有相對簡單的事件處理流程并且不需要中央事件編排和協(xié)調(diào)時逆巍,此拓撲結(jié)構(gòu)非常有用。代理拓撲中有四個主要的架構(gòu)組件:一個啟動事件莽使、事件代理锐极、一個事件處理器和一個處理事件。啟動事件是啟動整個事件流的初始事件芳肌,可以是在網(wǎng)上拍賣中出價這樣的簡單事件灵再,還可以是在醫(yī)療福利系統(tǒng)中如換工作或結(jié)婚這種更復雜的事件。啟動事件被發(fā)送到事件代理中的事件通道進行處理亿笤。由于在代理拓撲中沒有一個中介組件去管理和控制事件翎迁,因此單個事件處理器從事件代理中接受啟動事件并開始處理該事件。接受啟動事件的事件處理器執(zhí)行與該事件處理相關(guān)聯(lián)的特定任務(wù)净薛,然后通過創(chuàng)建一個“處理事件”來異步地將它所做的事情對系統(tǒng)其余部分進行播報汪榔。然后,如果需要肃拜,該處理事件將被異步發(fā)送到事件代理進行進一步處理痴腌。其他事件處理器監(jiān)聽此處理事件雌团,通過執(zhí)行某些操作對該事件作出反應(yīng),然后通過一個新的處理事件來公布他們所做的事情士聪。這個過程一直持續(xù)直到?jīng)]有人對最后一個事件處理器所做的事情感興趣锦援。圖14-2說明了該事件處理流程。
事件代理組件通常是聯(lián)合的(意味著多個基于領(lǐng)域的集群實例)戚嗅,其中每個聯(lián)合代理包含該特定領(lǐng)域的事件流中使用的所有事件通道雨涛。由于代理拓撲的非耦合、異步懦胞、“即發(fā)即棄”的廣播特性替久,主題(或者在AMQP的情況下是主題交換)通常利用發(fā)布和訂閱消息模型在代理拓撲中使用。
在代理拓撲中躏尉,對于每個事件處理器來說蚯根,向系統(tǒng)的其余部分公布它所做的事情總是一個很好的實踐,而不管其他事件處理器是否關(guān)心該操作是什么胀糜。如果處理該事件需要其他功能颅拦,則此實踐提供了架構(gòu)的可擴展性。例如教藻,假設(shè)作為一件復雜事件過程的一部分距帅,如圖14-3所示,生成一封電子郵件并發(fā)送給客戶括堤,通知他們所采取過的一個特定操作碌秸。Notification事件處理器將生成并發(fā)送電子郵件,然后通過發(fā)送到一個主題的新處理事件將該操作通告給系統(tǒng)的其余部分悄窃。但是讥电,在這種情況下,沒有其他事件處理器在監(jiān)聽有關(guān)該主題的事件轧抗,因此消息就這樣消失了恩敌。
這是架構(gòu)可擴展性的一個很好的例子。雖然發(fā)送被忽略的消息似乎是在浪費資源横媚,但事實并非如此纠炮。假設(shè)有一個新的需求出現(xiàn)來分析發(fā)送給客戶的電子郵件。這個新的事件處理器可以以最小的工作量添加到整個系統(tǒng)中分唾,因為電子郵件信息可以通過電子郵件主題提供給新的分析器抗碰,而不必添加任何額外的基礎(chǔ)設(shè)施或?qū)ζ渌录幚砥髯鞒鋈魏胃摹?/p>
為了說明代理拓撲是如何工作的,請考慮一個典型的零售訂單輸入系統(tǒng)中的處理流程绽乔,如圖14-4所示弧蝇,在這個系統(tǒng)中,為一個項目下訂單(比如,像這樣的一本書)看疗。在本例中沙峻,OrderPlacement事件處理器接收啟動事件(PlaceOrder),在數(shù)據(jù)庫表中插入訂單两芳,并將訂單ID返回給客戶摔寨。然后,它通過order-created處理事件向系統(tǒng)其余部分通告它創(chuàng)建了一個訂單怖辆。請注意是复,有三個事件處理器對該事件感興趣:Notification事件處理器、Payment事件處理器和Inventory事件處理器竖螃。這三個事件處理器都在并行執(zhí)行它們的任務(wù)淑廊。
Notification事件處理器接收order-created處理事件并向客戶發(fā)送電子郵件。然后生成另一個處理事件(email-sent)特咆。請注意季惩,沒有其他事件處理器在監(jiān)聽該事件。這是正常的腻格,并說明了前面描述架構(gòu)可擴展性的一個就緒鉤子的示例画拾,以便其他事件處理器最終可以在需要時訪問該事件源。
Inventory事件處理器還監(jiān)聽order-created的處理事件菜职,并減少該書相應(yīng)的庫存青抛。然后,它通過一個inventory-updated處理事件來通告此操作酬核,繼而由Warehouse事件處理器提取該事件脂凶,以管理倉庫之間的相應(yīng)庫存粥喜,并在供應(yīng)量過低時重新編排物料喉钢。
Payment事件處理器還接收order-created處理事件摆尝,并為剛剛創(chuàng)建的訂單向客戶的信用卡收費。請注意在圖14-4中鹅很,Payment事件處理器所采取的操作會生成兩個事件:一個用于通知系統(tǒng)其余部分已完成支付(payment-applied),另一個處理事件用于通知系統(tǒng)其他部分支付被拒絕(payment-denied)罪帖。請注意促煮,Notification事件處理器對payment-denied處理事件感興趣,因為它必須反過來向客戶發(fā)送電子郵件整袁,通知他們必須更新信用卡信息或選擇其他付款方式菠齿。
OrderFulfillment事件處理器監(jiān)聽payment-applied處理事件,并執(zhí)行訂單揀選和打包坐昙。完成后绳匀,它會通過order-fulfilled處理事件向系統(tǒng)的其他部分通告它完成了訂單。請注意,Notification處理單元和Shipping處理單元都監(jiān)聽此處理事件疾棵。同時戈钢,Notification事件通知客戶訂單已完成并準備好裝運,同時Shipping事件處理器選擇一種裝運方法是尔。Shipping事件處理器發(fā)送訂單并發(fā)送order-shipped處理事件殉了,Notification事件處理器還監(jiān)聽該事件以通知客戶訂單狀態(tài)變更。
在分析前面的示例時拟枚,請注意薪铜,所有的事件處理器都高度解耦并且彼此獨立的。理解代理拓撲的最佳方法是將其視為接力賽恩溅。在一場接力賽中隔箍,賽跑者拿著接力棒(一根木棒)跑一段距離(比如1.5公里),然后把接力棒交給下一個賽跑者暴匠,如此下去鞍恢,直到最后一個賽跑者越過終點線。在接力賽中每窖,一旦一個賽跑者把接力棒交出去帮掉,這個賽跑者就完成了比賽并轉(zhuǎn)向做其他事情。代理拓撲也是如此窒典。一旦一個事件處理器移交事件蟆炊,它就不再參與該特定事件的處理,可以對其他啟動或處理事件作出反應(yīng)瀑志。此外涩搓,每個事件處理器可以獨立于其他處理器進行擴展,以處理該事件中處理的不同負載條件或備份劈猪。這些主題提供了如果一個事件處理器由于某些環(huán)境問題而關(guān)閉或速度減慢時的背壓點昧甘。
雖然性能、響應(yīng)性和可擴展性都是代理拓撲的巨大優(yōu)勢战得,但它也有一些缺點充边。首先,無法控制與啟動事件(在本例中為PlaceOrder事件)關(guān)聯(lián)的整個工作流常侦。根據(jù)各種情況浇冰,它是非常動態(tài)的,系統(tǒng)中沒有人真正知道下單的業(yè)務(wù)事務(wù)何時真正完成聋亡。錯誤處理在代理拓撲中也是一個很大的挑戰(zhàn)肘习。因為沒有中介器監(jiān)視或控制業(yè)務(wù)事務(wù),所以如果發(fā)生故障(例如Payment事件處理器崩潰坡倔,并且沒有完成分配的任務(wù))漂佩,系統(tǒng)中沒有人會知道該崩潰脖含。如果沒有某種自動化或手動干預(yù),業(yè)務(wù)流程就會卡住而無法移動仅仆。此外器赞,所有其他進程都在不考慮錯誤的情況下繼續(xù)前進。例如墓拜,Inventory事件處理器仍會減少庫存港柜,而所有其他事件處理器也都按一切正常的情況作出反應(yīng)。
代理拓撲也不具備支持重新啟動業(yè)務(wù)事務(wù)(可恢復性)的能力咳榜。由于在初始處理啟動事件時已異步執(zhí)行了其他操作夏醉,因此無法重新提交啟動事件。代理拓撲中沒有組件知道甚至不擁有原始業(yè)務(wù)請求的狀態(tài)涌韩,因此在該拓撲中沒有人負責重新啟動業(yè)務(wù)事務(wù)(啟動事件)并知道它是在哪里停止的畔柔。表14-1總結(jié)了代理拓撲的優(yōu)缺點。
中介拓撲
事件驅(qū)動架構(gòu)的中介拓撲解決了前一節(jié)中描述的代理拓撲的一些缺點臣樱。此拓撲的中心是事件中介器靶擦,它管理和控制需要多個事件處理器協(xié)調(diào)的啟動事件工作流。構(gòu)成中介拓撲的架構(gòu)組件包括一個啟動事件雇毫、一個事件隊列玄捕、一個事件中介器、多個事件通道和事件處理器棚放。
與代理拓撲一樣枚粘,啟動事件是開始整個事件過程的事件。與代理拓撲不同飘蚯,啟動事件被發(fā)送到一個由事件中介器接受的啟動事件隊列馍迄。事件中介器只知道處理啟動事件所涉及的步驟,因此生成相應(yīng)的處理事件局骤,這些事件以點到點消息傳遞方式發(fā)送到專用事件通道(通常是隊列)攀圈。然后,事件處理器監(jiān)聽專用的事件通道峦甩,處理接收到的事件并通常向中介器返回他們已經(jīng)完成工作的響應(yīng)量承。與代理拓撲不同,中介拓撲中的事件處理器不會向系統(tǒng)的其余部分公布它們所做的事情穴店。中介拓撲如圖14-5所示。
在中介拓撲的大多數(shù)實現(xiàn)中有多個中介器拿穴,通常與特定的領(lǐng)域或事件組相關(guān)聯(lián)泣洞。這減少了與此種拓撲相關(guān)的單點故障問題,并提高了總體吞吐量和性能默色。例如球凰,可能有一個客戶中介器處理所有與客戶相關(guān)的事件(例如新客戶注冊和檔案更新),另一個中介器負責處理與訂單相關(guān)的活動(例如將項目添加到購物車和簽出)。
事件中介器可以以多種方式實現(xiàn)呕诉,這取決于它正在處理的事件的性質(zhì)和復雜度缘厢。例如,對于需要簡單錯誤處理和編排的事件甩挫,一個中介器(如Apache Camel贴硫、Mule ESB或Spring Integration)通常就足夠了。這些類型的中介器中的消息流和消息路由通常是用編程語言(如Java或C#)自定義編寫的伊者,以控制事件處理的工作流英遭。但是,如果事件工作流需要大量條件處理和具有復雜錯誤處理指令的多個動態(tài)路徑亦渗,那么Apache ODE或Oracle BPEL流程管理器等中介器將是一個不錯的選擇挖诸。這些中介器基于業(yè)務(wù)流程執(zhí)行語言(BPEL),以一種類似于XML的結(jié)構(gòu)法精,描述了處理事件所涉及的步驟多律。BPEL構(gòu)件還包含用于錯誤處理、重定向搂蜓、廣播等的結(jié)構(gòu)化元素狼荞。BPEL是一種功能強大但相對難以學習的語言,因此通常使用產(chǎn)品的BPEL引擎套件中提供的圖形界面工具來創(chuàng)建洛勉。
BPEL適用于復雜和動態(tài)的工作流粘秆,但對于那些需要在整個事件過程中進行人工干預(yù)的長時間運行事務(wù)的事件工作流并不適用。例如收毫,假設(shè)一個交易是通過一個place-trade啟動事件進行的攻走。事件中介器接受此事件,但在處理過程中發(fā)現(xiàn)需要手動批準此再,因為交易超過一定數(shù)量的股份昔搂。在這種情況下,事件調(diào)停者必須停止事件處理输拇,向高級交易者發(fā)送通知以獲得手動批準摘符,并等待批準發(fā)生。在這些情況下策吠,需要一個業(yè)務(wù)流程管理(BPM)引擎逛裤,如jBPM。
為了正確選擇事件中介器的實現(xiàn)方式猴抹,了解將通過中介器處理的事件的類型是很重要的带族。選擇Apache Camel來處理那些復雜和長時間運行的涉及到人機交互的事件將非常難于編寫和維護。出于同樣的原因蟀给,使用BPM引擎處理簡單的事件流需要花費數(shù)月的時間蝙砌,而在Apache Camel中同樣的事情可以在幾天內(nèi)完成阳堕。
考慮到很少有所有事件只有一類復雜度的情況,我們建議將事件分為簡單择克、困難或復雜恬总,并讓每個事件始終通過一個簡單的中介器(如Apache Camel或Mule)。然后肚邢,簡單中介器可以判斷事件的分類壹堰,并基于該分類處理事件本身或?qū)⑵滢D(zhuǎn)發(fā)給另一個處理更復雜事件的事件中介器。通過這種方式道偷,所有類型的事件都可以被該事件所需的中介器類型有效地處理缀旁。圖14-6說明了這種中介器委派模型。
請注意勺鸦,在圖14-6中并巍,當事件工作流很簡單并且可以由簡單中介器處理時,簡單事件中介器生成并發(fā)送一個處理事件换途。但是懊渡,請注意,當進入簡單事件中介器的啟動事件被分類為困難或復雜事件時军拟,它會將原始啟動事件轉(zhuǎn)發(fā)給相應(yīng)的中介器(BPEL或BMP)剃执。簡單事件中介器在截獲了原始事件之后,可能仍然負責知悉該事件何時完成懈息,或者它只是將整個工作流(包括客戶端通知)委托給其他中介器肾档。
為了說明中介拓撲是如何工作的,請考慮前面的代理拓撲部分中描述的零售訂單輸入系統(tǒng)示例辫继,但這次使用的是中介拓撲怒见。在這個示例中,中介器知道處理此特定事件所需的步驟姑宽。這個事件流(中介器組件的內(nèi)部)如圖14-7所示遣耍。
當收到啟動事件,Customer中介器生成一個create-order處理事件炮车,并將此消息發(fā)送到order-placement-queue隊列(見圖14-8)舵变。OrderPlacement事件處理器接受此事件,驗證并創(chuàng)建訂單瘦穆,并將一個確認回復與訂單ID一起返回給中介器纪隙。此時,中介器可能會將該訂單ID發(fā)送回客戶扛或,示意訂單已下單绵咱,或者可能必須繼續(xù)直到所有步驟都完成(這將基于有關(guān)下訂單的特定業(yè)務(wù)規(guī)則)。
現(xiàn)在第1步已經(jīng)完成告喊,中介器現(xiàn)在轉(zhuǎn)到第2步(見圖14-9)麸拄,同時生成三條消息:email-customer、apply-payment和adjust-inventory黔姜。這些處理事件都被發(fā)送到各自的隊列拢切。所有三個事件處理器都接收到這些消息和執(zhí)行各自的任務(wù),并通知中介器處理已完成秆吵。請注意淮椰,中介器必須等到收到來自所有三個并行處理的確認回復之后,才能繼續(xù)轉(zhuǎn)到第3步纳寂。此時主穗,如果某個并行事件處理器中發(fā)生錯誤,中介器可以采取糾正措施來解決問題(這將在本節(jié)后面更詳細地討論)毙芜。
當中介器在步驟2中從所有事件處理器獲得成功的確認忽媒,它就可以繼續(xù)執(zhí)行步驟3來執(zhí)行訂單(見圖14-10)。請再次注意腋粥,這兩個事件(fulfill-order和order-stock)可以同時發(fā)生晦雨。OrderFulfillment和Warehouse事件處理器接受這些事件,執(zhí)行它們的工作隘冲,并向中介器返回一個確認闹瞧。
當這些事件完成后,中介器就轉(zhuǎn)到第4步(見圖14-11)來發(fā)送訂單展辞。此步驟生成另一個email-customer處理事件奥邮,其中包含有關(guān)要執(zhí)行的操作的特定信息(在本例中,通知客戶訂單已準備就緒)以及ship-order事件罗珍。
最后洽腺,中介器轉(zhuǎn)到第5步(見圖14-12),并生成另一個上下文相關(guān)的email_customer事件靡砌,以通知客戶訂單已經(jīng)發(fā)貨已脓。此時,工作流完成通殃,中介器將啟動事件流標記為完成度液,并刪除與啟動事件關(guān)聯(lián)的所有狀態(tài)。
中介器組件擁有對工作流的知識和控制權(quán)画舌,這是代理拓撲所不具備的堕担。因為中介器控制工作流,所以它可以維護事件狀態(tài)并管理錯誤處理曲聂、可恢復性和重啟功能霹购。例如,假設(shè)在前面的示例中朋腋,由于信用卡過期而未完成付款齐疙。在這種情況下膜楷,中介器接收到這個錯誤情況,并且知道在完成付款之前訂單無法完成(步驟3)贞奋,它會停止工作流并在自己的持久化數(shù)據(jù)存儲中記錄請求的狀態(tài)赌厅。一旦最終完成了支付,工作流就可以從停止的地方重新啟動(在本例中轿塔,是步驟3的開始)特愿。
代理中介和中介拓撲之間的另一個內(nèi)在的差異是處理事件的意義和使用方式的不同。在上一節(jié)中的代理拓撲示例中勾缭,處理事件被發(fā)布為系統(tǒng)中發(fā)生的事件(例如order-created揍障、payment-applied和email-sent)。事件處理器執(zhí)行了一些操作俩由,而其他事件處理程序則對該操作作出反應(yīng)毒嫡。然而,在中介拓撲中采驻,處理事件(如place-order审胚、send-email和fulfill-order)是命令(需要發(fā)生的事情),而不是事件(已經(jīng)發(fā)生的事情)礼旅。此外膳叨,在中介拓撲中,處理事件必須被處理(命令)痘系,而在代理拓撲中可以忽略它(反應(yīng))菲嘴。
雖然中介拓撲解決了一些與代理拓撲相關(guān)的問題,但其自身也存在一些缺點汰翠。首先龄坪,很難對復雜事件流中發(fā)生的動態(tài)處理進行聲明式建模。因此复唤,中介器中的許多工作流只處理一般事件健田,而結(jié)合中介和代理拓撲的混合模式用于解決復雜事件處理的動態(tài)特性(例如缺貨情況或其他非典型錯誤)。此外佛纫,盡管事件處理器可以輕松地以與代理拓撲相同的方式進行擴展妓局,但中介器也必須進行擴展,這有時會在整個事件處理流程中產(chǎn)生瓶頸呈宇。最后好爬,事件處理器在中介拓撲中沒有像代理拓撲那樣高度解耦,并且由于中介器控制事件的處理甥啄,性能也不如代理拓撲好存炮。表14-2總結(jié)了這些權(quán)衡情況。
在代理中介和中介拓撲之間的選擇本質(zhì)上歸結(jié)為追求工作流控制和錯誤處理能力與追求高性能和可擴展性之間的權(quán)衡。盡管中介拓撲中的性能和可擴展性仍然很好穆桂,但是它們沒有代理拓撲中的那么高宫盔。
異步能力
事件驅(qū)動的架構(gòu)風格與其他架構(gòu)風格相比具有一個獨特的特性,即它完全依賴于異步通信來進行“即發(fā)即棄“處理(無需響應(yīng))以及請求/應(yīng)答處理(需要來自事件消費者的響應(yīng))享完。異步通信是提高系統(tǒng)整體響應(yīng)能力的強有力的技術(shù)飘言。
考慮圖14-13所示的例子,其中一個用戶在一個網(wǎng)站上發(fā)布一個針對特定產(chǎn)品的評論驼侠。假設(shè)本例中的評論服務(wù)需要3000毫秒來發(fā)布評論,因為它要經(jīng)過幾個解析引擎:一個敏感詞檢查程序來檢查不可接受的單詞谆吴,語法檢查程序來確保句子結(jié)構(gòu)沒有侮辱性的話倒源,最后一個上下文檢查器來確保評論是關(guān)于特別的產(chǎn)品,而不僅僅是政治上的抱怨句狼。請注意笋熬,在圖14-13中,頂端路徑使用一個同步的RESTful調(diào)用來發(fā)布評論:服務(wù)接收發(fā)布的延遲為50毫秒腻菇,發(fā)布評論的延遲為3000毫秒胳螟,而對發(fā)布評論的用戶做出響應(yīng)的網(wǎng)絡(luò)延遲為50毫秒。用戶將花費3100毫秒的響應(yīng)時間來發(fā)布評論〕锿拢現(xiàn)在看看底部的路徑糖耸,注意到使用異步消息傳遞,從最終用戶的角度來看丘薛,在網(wǎng)站上發(fā)布評論的響應(yīng)時間只有25毫秒(而不是3100毫秒)嘉竟。發(fā)布評論仍然需要3025毫秒(25毫秒來接收消息,3000毫秒來發(fā)布評論)洋侨,但是從最終用戶的角度來看這已經(jīng)完成了舍扰。
這是一個很好的例子來說明響應(yīng)能力和性能之間的區(qū)別。當用戶不需要任何信息返回(除了確認或感謝消息)希坚,為什么要讓用戶等待边苹?響應(yīng)能力就是通知用戶操作已被接受并將立即被處理,而性能則是使端到端的處理更快裁僧。請注意評論服務(wù)處理在文本上并沒有做任何優(yōu)化– 兩種情況仍然需要花費3000毫秒个束。解決性能在于優(yōu)化評論服務(wù),在使用緩存和其他類似技術(shù)的同時運行所有文本和語法解析引擎锅知。圖14-13中的底部示例解決了系統(tǒng)的總體響應(yīng)能力播急,而不是系統(tǒng)的性能。
圖14-13中兩個例子的響應(yīng)時間從3100毫秒到25毫秒之間的差異是驚人的售睹。有一點需要說明桩警。在圖表頂部顯示的同步路徑上,用戶可以保證評論已經(jīng)發(fā)布昌妹。然而捶枢,在底部的路徑上只有對發(fā)布的確認握截,并承諾最終會發(fā)布評論。從用戶的角度來看烂叔,評論已經(jīng)發(fā)布谨胞。但是如果用戶在評論中鍵入了一個敏感詞會發(fā)生什么?在這種情況下蒜鸡,評論將被拒絕胯努,但無法把結(jié)果返回到用戶。還有其他辦法嗎逢防?在這個例子中叶沛,假設(shè)用戶在網(wǎng)站上注冊了(必須是注冊了才能發(fā)布評論),那么就可以向用戶發(fā)送一條消息忘朝,指出評論有問題并給出一些修復建議灰署。這是一個簡單的例子。股票的購買是異步進行的(稱為股票交易)局嘁,并且沒有辦法返回給用戶溉箕,對于這種更復雜的例子怎么樣呢?
異步通信的主要問題是錯誤處理悦昵。雖然響應(yīng)能力顯著提高了肴茄,但很難解決錯誤情況,這增加了事件驅(qū)動系統(tǒng)的復雜性但指。下一節(jié)將使用一種稱為工作流事件模式的反應(yīng)式架構(gòu)模式來解決此問題独郎。
錯誤處理
反應(yīng)式架構(gòu)的工作流事件模式是解決異步工作流中與錯誤處理相關(guān)問題的一種方法。這種模式是一種反應(yīng)式架構(gòu)模式枚赡,它同時解決了可恢復性和響應(yīng)性問題氓癌。換句話說,系統(tǒng)在錯誤處理方面具有彈性贫橙,而不會影響響應(yīng)能力贪婉。工作流事件模式通過使用工作流委托來利用委派、控制和修復卢肃,如圖14-14所示疲迂。事件生產(chǎn)者通過消息通道將數(shù)據(jù)異步傳遞給事件消費者。如果事件消費者在處理數(shù)據(jù)時遇到錯誤莫湘,它會立即將該錯誤委托給工作流處理器尤蒿,并繼續(xù)處理事件隊列中的下一條消息。這樣幅垮,總體響應(yīng)性不會受到影響腰池,因為下一條消息將被立即處理。如果事件消費者要花時間試圖弄清錯誤原因,那么它不會讀取隊列中的下一條消息示弓,因此不僅會影響下一條消息的響應(yīng)能力讳侨,還會影響隊列中等待處理的所有其他消息的響應(yīng)。
當工作流處理器接收到錯誤奏属,它將嘗試弄清消息中的錯誤跨跨。這可能是一個靜態(tài)的、不可逆轉(zhuǎn)的錯誤囱皿,或者它可以利用一些機器學習算法來分析消息勇婴,從而在數(shù)據(jù)中發(fā)現(xiàn)一些異常。不管是哪種方式嘱腥,工作流處理器都會以編程方式(無需人工干預(yù))對原始數(shù)據(jù)進行更改并嘗試修復它咆耿,然后將其發(fā)送回原始隊列。事件消費者將此消息視為一條新消息爹橱,并嘗試再次處理它,希望這次能取得成功窄做。當然愧驱,工作流處理程序嘗試很多遍也無法確定消息中的錯誤。在這些情況下椭盏,工作流處理器將消息發(fā)送給另一個隊列组砚。然后在通常稱為“儀表板”(dashboard)的應(yīng)用程序中接收,該應(yīng)用程序看起來類似于Microsoft的Outlook或Apple的郵件掏颊。這個儀表板通常位于重要人物的桌面上糟红,然后他查看消息,對其進行手動修復乌叶,然后將消息重新提交到原始隊列(通常通過一個reply-to消息頭變量)盆偿。
為了說明工作流事件模式,假設(shè)一個國家的某個交易顧問代表該國另一個地區(qū)的一家大型交易公司接受交易訂單(關(guān)于購買什么股票和購買多少股票的指示)准浴。這個顧問把交易訂單打包(通常稱為一籃子)事扭,然后異步地將這些訂單通過經(jīng)紀人發(fā)送給大型交易公司,這樣股票就可以被購買了乐横。為了簡化示例求橄,假設(shè)貿(mào)易指示的合同必須遵守以下格式:
ACCOUNT(String),SIDE(String),SYMBOL(String),SHARES(Long)
假設(shè)大型貿(mào)易公司從交易顧問處收到以下一籃子蘋果公司(AAPL)交易訂單:
12654A87FR4,BUY,AAPL,1254?
87R54E3068U,BUY,AAPL,3122?
6R4NB7609JJ,BUY,AAPL,5433?
2WE35HF6DHF,BUY,AAPL,8756 SHARES?
764980974R2,BUY,AAPL,1211?
1533G658HD8,BUY,AAPL,2654
注意,第四個交易指令(2WE35HF6DHF葡公,BUY罐农,AAPL,8756 SHARES)在交易股數(shù)后有“SHARES”一詞催什。當大型貿(mào)易公司在沒有任何錯誤處理能力的情況下處理這些異步交易訂單時涵亏,交易配售服務(wù)中會發(fā)生以下錯誤:
Exception in thread "main" java.lang.NumberFormatException:?????
????For input string: "8756 SHARES"??
????at java.lang.NumberFormatException.forInputString
????(NumberFormatException.java:65)?
????at java.lang.Long.parseLong(Long.java:589)?????
????at java.lang.Long.(Long.java:965)?
????at trading.TradePlacement.execute(TradePlacement.java:23)????
????at trading.TradePlacement.main(TradePlacement.java:29)
當發(fā)生此異常時,因為這是一個異步請求,交易配售服務(wù)無能為力溯乒,除了可能記錄錯誤條件之外廊营。換句話說旅赢,沒有用戶可以同步響應(yīng)和修復錯誤。
應(yīng)用工作流事件模式可以以編程方式修復此錯誤。由于大型貿(mào)易公司無法控制交易顧問及其發(fā)送的相應(yīng)交易訂單數(shù)據(jù)索守,它必須自己做出反應(yīng)來修復錯誤(如圖14-15所示)。當發(fā)生相同的錯誤(2WE35HF6DHF坤次,BUY驳规,AAPL,8756 SHARES)時艾君,交易配售服務(wù)立即通過異步消息將錯誤委托給Trade Placement Error 服務(wù)進行錯誤處理采够,并傳遞有關(guān)異常的錯誤信息:
Trade Placed: 12654A87FR4,BUY,AAPL,1254?
Trade Placed: 87R54E3068U,BUY,AAPL,3122?
Trade Placed: 6R4NB7609JJ,BUY,AAPL,5433?
Error Placing Trade: "2WE35HF6DHF,BUY,AAPL,8756 SHARES"?
Sending to trade error processor? <-- delegate the error fixing and move on?
Trade Placed: 764980974R2,BUY,AAPL,1211?
...
Trade Placement Error服務(wù)(充當工作流代理)接收錯誤并檢查異常。鑒于“股票數(shù)”字段中的“SHARES”一詞有問題冰垄,Trade Placement Error服務(wù)會刪除“SHARES”一詞蹬癌,并再次提交交易進行重新處理:
Received Trade Order Error: 2WE35HF6DHF,BUY,AAPL,8756 SHARES?
Trade fixed: 2WE35HF6DHF,BUY,AAPL,8756?
Resubmitting Trade For Re-Processing
然后,交易安置服務(wù)會成功處理修復好的交易:
...?
trade placed: 1533G658HD8,BUY,AAPL,2654?
trade placed: 2WE35HF6DHF,BUY,AAPL,8756 <-- this was the original trade in error
應(yīng)用工作流事件模式的一個后果是虹茶,出錯的消息在重新提交時處理順序不正確逝薪。在我們的交易示例中,消息的順序很重要蝴罪,因為給定帳戶中的所有交易都必須按順序處理(例如董济,在同一個經(jīng)紀帳戶中,對IBM的賣出必須在對AAPL的買入之前發(fā)生)要门。盡管并非不可能虏肾,但在給定的上下文(在本例中是經(jīng)紀帳號)中維護消息順序是一項復雜的任務(wù)。解決這個問題的一種方法是通過Trade Placement服務(wù)排隊并存儲錯誤交易的賬號欢搜。任何具有相同帳號的交易都將被存儲在一個臨時隊列中以供后續(xù)處理(按先進先出順序)封豪。一旦最初出錯的交易被修復和處理,Trade Placement服務(wù)將對同一賬戶的剩余交易進行排隊炒瘟,并按順序進行處理撑毛。
防止數(shù)據(jù)丟失
在處理異步通信時,數(shù)據(jù)丟失一直是一個主要的問題唧领。不幸的是藻雌,在事件驅(qū)動架構(gòu)中,有許多地方會發(fā)生數(shù)據(jù)丟失斩个。我們所說的數(shù)據(jù)丟失是指消息被丟棄或永遠無法到達其最終目的地胯杭。幸運的是,在使用異步消息傳遞時受啥,可以利用一些基本的開箱即用的技術(shù)來防止數(shù)據(jù)丟失做个。
為了說明事件驅(qū)動架構(gòu)中與數(shù)據(jù)丟失相關(guān)的問題鸽心,假設(shè)事件處理器A異步地向一個隊列發(fā)送消息。事件處理器B接受消息并將消息中的數(shù)據(jù)插入數(shù)據(jù)庫居暖。如圖14-16所示顽频,在這種典型場景下有三個地方會發(fā)生數(shù)據(jù)丟失:
1.? ? 消息從未從事件處理器A到達隊列;或者即使它到達隊列太闺,在下一個事件處理器檢索到消息之前代理程序發(fā)生故障糯景。
2.? ? 事件處理器B對下一個可用的消息進行出列,并在處理事件之前崩潰省骂。
3.? ? 由于某些數(shù)據(jù)錯誤蟀淮,事件處理器B無法將消息持久化到數(shù)據(jù)庫中。
這些數(shù)據(jù)丟失的每一個方面都可以通過基本的消息傳遞技術(shù)得到緩解钞澳。通過利用持久化消息隊列和所謂的同步發(fā)送怠惶,問題1(消息永遠不會到達隊列)很容易得到解決。持久化消息隊列支持所謂的保證傳遞轧粟。當消息代理接收到消息時策治,它不僅將其存儲在內(nèi)存中以便快速檢索,而且還將消息保存在某種物理數(shù)據(jù)存儲(如文件系統(tǒng)或數(shù)據(jù)庫)中兰吟。如果消息代理發(fā)生故障通惫,消息將物理地存儲在磁盤上,這樣當消息代理恢復時揽祥,消息就可以進行處理。同步發(fā)送在消息生產(chǎn)者中執(zhí)行阻塞等待檩电,直到代理確認消息已被持久化拄丰。有了這兩種基本技術(shù),事件生產(chǎn)者和隊列之間就不會丟失消息俐末,因為消息要么仍在消息生產(chǎn)者中料按,要么持久存儲于隊列中。
問題2(事件處理器B對下一條可用消息進行出列并在處理事件之前崩潰)也可以使用稱為客戶端確認模式的基本消息傳遞技術(shù)來解決卓箫。默認情況下载矿,當消息退出隊列時,它會立即從隊列中移除(稱為自動確認模式)烹卒∶瓶客戶端確認模式將消息保留在隊列中,并將客戶端ID附加到消息上旅急,以便其他消費者無法讀取該消息逢勾。在這種模式下,如果事件處理器B崩潰藐吮,消息仍然保留在隊列中溺拱,防止消息在消息流的這一部分丟失逃贝。
問題3(由于某些數(shù)據(jù)錯誤,事件處理器B無法將消息持久化到數(shù)據(jù)庫)通過利用數(shù)據(jù)庫提交的ACID(原子性迫摔、一致性沐扳、隔離性、持久性)事務(wù)來解決句占。一旦數(shù)據(jù)庫提交發(fā)生沪摄,數(shù)據(jù)就被保證保存在數(shù)據(jù)庫中。利用稱為最后參與者支持(last participant support辖众,LPS)的方法卓起,通過確認處理已完成且消息已持久化,從持久化隊列中刪除消息凹炸。這可以保證消息在從事件處理器A傳輸?shù)綌?shù)據(jù)庫的整個過程中不會丟失戏阅。這些技術(shù)如圖14-17所示。
廣播能力
事件驅(qū)動架構(gòu)的另一個獨特特性是能夠廣播事件啤它,而無需知道誰(如果有人)正在接收消息以及他們?nèi)绾翁幚硐⑥瓤稹_@種技術(shù)如圖14-18所示,表明當一個生產(chǎn)者發(fā)布一條消息時变骡,同一條消息會被多個訂閱者接收离赫。
廣播可能是事件處理器之間解耦的最高級別,因為廣播消息的生產(chǎn)者通常不知道哪個事件處理器將接收廣播消息塌碌,更重要的是渊胸,他們將如何處理消息。廣播功能是實現(xiàn)最終一致性台妆、復雜事件處理(CEP)和許多其他情況下的模式的重要組成部分翎猛。考慮股票市場上交易工具的股票價格的頻繁變化接剩。每一個股票行情(特定股票的當前價格)都可能影響許多事情切厘。然而,發(fā)布最新價格的服務(wù)只是簡單地廣播它懊缺,而不會去了解這些信息將如何使用疫稿。
請求-應(yīng)答
本章到目前為止我們討論了不需要事件消費者立即響應(yīng)的異步請求。但是如果在訂購圖書時需要訂單ID呢鹃两?如果訂機票時需要一個確認編號怎么辦呢遗座?這些是需要某種同步通信的服務(wù)或事件處理器之間通信的示例。
在事件驅(qū)動架構(gòu)中俊扳,同步通信是通過請求-應(yīng)答消息(有時稱為偽同步通信)來實現(xiàn)的员萍。請求-應(yīng)答消息傳遞中的每個事件通道由兩個隊列組成:請求隊列和應(yīng)答隊列。對信息的初始請求被異步地發(fā)送到請求隊列拣度,然后將控制權(quán)返回給消息生產(chǎn)者碎绎。然后消息生產(chǎn)者在應(yīng)答隊列上執(zhí)行阻塞等待螃壤,等待響應(yīng)返回。消息消費者接收并處理消息筋帖,然后將響應(yīng)發(fā)送到應(yīng)答隊列奸晴。然后,事件生產(chǎn)者接收包含響應(yīng)數(shù)據(jù)的消息日麸。這個基本流程如圖14-19所示寄啼。
有兩種主要技術(shù)來實現(xiàn)請求-應(yīng)答消息傳遞。第一種(也是最常見的)技術(shù)是使用消息頭中包含的correlation ID代箭。correlation ID是應(yīng)答消息中的一個字段墩划,通常設(shè)置為原始請求消息的消息ID。如圖14-20所示嗡综,這種技術(shù)的工作原理如下乙帮,消息ID用ID表示,correlation ID用CID表示:
1.? ? 事件生產(chǎn)者向請求隊列發(fā)送消息并記錄唯一的消息ID(在本例中為id=124)极景。注意察净,本例中的correlation ID(CID)為空。
2.? ? 事件生產(chǎn)者現(xiàn)在使用消息過濾器(也稱為消息選擇器)在應(yīng)答隊列上執(zhí)行阻塞等待盼樟,其中消息頭中的correlation ID等于原始消息ID(在本例中為124)氢卡。請注意,在應(yīng)答隊列中有兩條消息:correlation ID為120的消息(ID=855)和correlation ID為122的消息(ID=856)晨缴。這兩條消息都不會被獲取译秦,因為correlation ID與事件消費者正在查找的內(nèi)容不匹配(CID=124)。
3.? ? 事件消費者接收消息(ID=124)并處理請求击碗。
4.? ? 事件消費者創(chuàng)建包含響應(yīng)的應(yīng)答消息筑悴,并將消息頭中的correlation ID(CID)設(shè)置為原始消息ID(124)。
5.? ? 事件消費者將新消息(ID 857)發(fā)送到應(yīng)答隊列延都。
6.? ? 事件生產(chǎn)者接收消息雷猪,因為correlation ID(124)與步驟2中的消息選擇器匹配睛竣。
用于實現(xiàn)請求-應(yīng)答消息傳遞的另一種技術(shù)是使用臨時隊列作為應(yīng)答隊列晰房。臨時隊列專用于特定請求,在請求發(fā)出時創(chuàng)建射沟,在請求結(jié)束時刪除殊者。這種技術(shù)如圖14-21所示,不需要correlation ID验夯,因為臨時隊列是一個專用隊列猖吴,只有事件生產(chǎn)者知道用于特定請求。臨時隊列技術(shù)的工作原理如下:
1.? ? 事件生產(chǎn)者創(chuàng)建一個臨時隊列(或者自動創(chuàng)建一個隊列挥转,具體取決于消息代理)并向請求隊列發(fā)送一條消息海蔽,在reply-to頭中傳遞臨時隊列的名稱(或消息頭中的其他一些約定的自定義屬性)共屈。
2.? ? 事件生產(chǎn)者對臨時應(yīng)答隊列執(zhí)行阻塞等待。這里不需要消息選擇器党窜,因為發(fā)送到此隊列的任何消息都只屬于最初發(fā)送消息的事件生產(chǎn)者拗引。
3.? ? 事件消費者接收消息并處理請求,然后將響應(yīng)消息發(fā)送到reply-to頭中指定的應(yīng)答隊列幌衣。
4.? ? 事件處理器接收消息并刪除臨時隊列矾削。
雖然臨時隊列技術(shù)要簡單得多,但是消息代理必須為每個請求創(chuàng)建一個臨時隊列豁护,之后立即將其刪除哼凯。大量消息傳遞會顯著降低消息代理的速度,并影響整體性能和響應(yīng)能力楚里。因此断部,我們通常建議使用correlation ID技術(shù)。
在基于請求和基于事件之間進行選擇
基于請求的模型和基于事件的模型都是設(shè)計軟件系統(tǒng)的可行方法腻豌。然而家坎,選擇正確的模型對整個系統(tǒng)的成功至關(guān)重要。當需要對工作流進行確定性和控制時吝梅,我們建議為結(jié)構(gòu)良好虱疏、數(shù)據(jù)驅(qū)動的請求(例如檢索客戶概要數(shù)據(jù))選擇基于請求的模型。我們建議為靈活的苏携、基于行為的事件選擇基于事件的模型做瞪,這些事件需要高級別的響應(yīng)性和規(guī)模,并具有復雜和動態(tài)的用戶處理右冻。
了解基于事件的模型的權(quán)衡也有助于決定哪種模型最適合装蓬。表14-3列出了事件驅(qū)動架構(gòu)的基于事件模型的優(yōu)缺點。
混合事件驅(qū)動架構(gòu)
雖然許多應(yīng)用利用事件驅(qū)動架構(gòu)風格作為主要的總體架構(gòu)纱扭,但在許多情況下牍帚,事件驅(qū)動架構(gòu)與其他架構(gòu)風格結(jié)合使用,形成了所謂的混合架構(gòu)乳蛾。一些常見的架構(gòu)風格利用事件驅(qū)動架構(gòu)作為另一種架構(gòu)風格的一部分暗赶,包括微服務(wù)和基于空間的架構(gòu)。其他可能的混合體包括事件驅(qū)動的微內(nèi)核架構(gòu)和事件驅(qū)動的管道架構(gòu)肃叶。
向任何架構(gòu)風格中添加事件驅(qū)動架構(gòu)有助于消除瓶頸蹂随,在備份事件請求時提供一個背壓點,并提供在其他架構(gòu)風格中達不到的用戶響應(yīng)級別因惭。微服務(wù)和基于空間的架構(gòu)都利用消息傳遞作為數(shù)據(jù)泵岳锁,將數(shù)據(jù)異步發(fā)送到另一個處理器,后者反過來更新數(shù)據(jù)庫中的數(shù)據(jù)蹦魔。當使用消息傳遞進行服務(wù)間通信時激率,兩者都利用事件驅(qū)動架構(gòu)為微服務(wù)架構(gòu)中的服務(wù)和基于空間的架構(gòu)中的處理單元提供一定程度的可編程擴展性咳燕。
架構(gòu)特性評級
特性評級表中的一星級評級(如圖14-22所示)意味著特定的架構(gòu)特性在某種架構(gòu)中沒有得到很好的支持,而五星評級意味著架構(gòu)特性是某種架構(gòu)風格中最強大的特性之一乒躺。記分卡中確定的每個特性的定義見第4章迟郎。
事件驅(qū)動架構(gòu)主要是一種技術(shù)上分區(qū)的體系結(jié)構(gòu),任何特定的領(lǐng)域都分布在多個事件處理器上聪蘸,并通過中介器宪肖、隊列和主題連接在一起。對某個領(lǐng)域的變更通常會影響許多事件處理器健爬、中介器和其他消息傳遞構(gòu)件控乾,因此事件驅(qū)動架構(gòu)不是按照領(lǐng)域劃分的。
事件驅(qū)動架構(gòu)中的量子數(shù)可以從一個量子到多個量子娜遵,這通惩珊猓基于每個事件處理器和請求-應(yīng)答處理中的數(shù)據(jù)庫交互。盡管事件驅(qū)動架構(gòu)中的所有通信都是異步的设拟,但是如果多個事件處理器共享一個數(shù)據(jù)庫實例慨仿,它們都將包含在同一個架構(gòu)量子中。對于請求-應(yīng)答處理也是如此:即使事件處理器之間的通信仍然是異步的纳胧,但是如果事件消費者立即需要一個請求镰吆,它會將這些事件處理器同步地連接在一起;因此它們屬于同一個量子跑慕。
為了說明這一點万皿,考慮一個事件處理器向另一個事件處理器發(fā)送請求來下一個訂單的例子。第一個事件處理器必須等待另一個事件處理器的訂單ID來繼續(xù)核行。如果下訂單并生成訂單ID的第二個事件處理器崩潰牢硅,則第一個事件處理器無法繼續(xù)。因此芝雪,它們是同一個架構(gòu)量子的一部分并共享相同的架構(gòu)特性减余,盡管它們都是發(fā)送和接收異步消息。
事件驅(qū)動架構(gòu)在性能惩系、可擴展性和容錯性方面獲得了五顆星評級位岔,這是這種架構(gòu)風格的主要優(yōu)勢。高性能是通過異步通信與高度并行處理相結(jié)合來實現(xiàn)的蛆挫。高可擴展性是通過事件處理器(也稱為競爭消費者)的編程負載均衡實現(xiàn)的赃承。隨著請求負載的增加妙黍,可以通過編程方式添加額外的事件處理器來處理增加的請求悴侵。容錯性是通過高度解耦的異步事件處理器實現(xiàn)的,這些處理器提供了事件工作流的最終一致性和最終處理拭嫁。如果用戶界面或發(fā)出請求的事件處理器不需要立即響應(yīng)可免,如果其他下游處理器不可用則可以利用保證處理的承諾在往后的時間來處理事件抓于。
事件驅(qū)動架構(gòu)的總體簡單性和可測試性相對較低,這主要是由于這種架構(gòu)風格中通常存在的不確定性和動態(tài)事件流浇借。雖然基于請求的模型中的確定流相對容易測試捉撮,因為路徑和結(jié)果通常是已知的,但事件驅(qū)動模型不是這樣妇垢。有時不知道事件處理器將如何響應(yīng)動態(tài)事件巾遭,以及它們可能生成哪些消息。這些“事件樹圖”可能非常復雜闯估,可以生成數(shù)百到數(shù)千個場景灼舍,這使得管理和測試非常困難。
最后涨薪,事件驅(qū)動架構(gòu)是高度進化的骑素,因此被評為五星級。通過現(xiàn)有或新的事件處理器添加新特性相對簡單刚夺,特別是在代理拓撲中献丑。在代理拓撲中通過發(fā)布的消息提供鉤子,數(shù)據(jù)已經(jīng)可以使用侠姑,因此不需要在基礎(chǔ)設(shè)施或現(xiàn)有事件處理器中進行更改來添加新功能创橄。