譯自:Introduction to Event-Driven Architecture數據庫
後面將引入幾篇與EDA相關的文章,目的在於充分掌握EDA架構的優劣勢。json
在前面的微服務介紹一文中討論了服務的顆粒度,以及保證鬆耦合的必要性。文中提出,服務應該是自治且完整獨立的,並儘可能減小同步通訊。今天,咱們將討論鬆耦合意味着什麼,並探索一種在微服務社區中愈來愈受歡迎的"交易技巧"-事件驅動架構。緩存
事件驅動架構(EDA)是一個促進生產和消費事件的軟件架構規範。服務器
一個事件表示一個感興趣的動做。一般,事件對應一個建立或修改某些實體狀態的動做。例如,在電子商務應用程序中下訂單是一個事件,分發一個已下單的產品也是一個事件。一個消費者提交一個對接收的產品的評論也是一個事件。數據結構
關於事件的奇特之處在於它們不會明確地傳達給可能關心它們的特定服務。事件"只會發生"。更爲重要的是事件只會單純地發生,與是否存在關心這些事件的特定服務無關。這聽起來像是常常被引用的哲學思想:"若是一顆森林中的樹,沒有人聽到它,那麼它會發出聲音嗎?"。但這也是事件之因此強大的緣由--事件會轉換爲一條對某些正在發生的事情的(自包含)記錄,事件及其擴展程序(從根本上講)與它們的處理程序是分離的。實際上,事件記錄的生產者並不知道消費者是誰,甚至不知道是否存在消費者。架構
一條記錄一般包含描述一個事件的信息。在以前的訂單爲例,其對應的事件的JSON描述以下:併發
{ "orderId": "760b5301-295f-4fec-95f8-6b303a3b824a", "customerId": 28623823, "productId": 31334, "quantity": 1, "timestamp": "2021-02-09T11:12:17+0000" }
Node:儘管記錄和事件存在細微的差異,但它們常常能夠互換,即術語"事件"一般指代一個事件的"記錄",爲了簡化描述,本文中將自由地使用這兩個術語。異步
上述是對一個訂單的高度簡化。發起訂單(購物車服務)的應用並不知道誰(如何,以及爲何)處理該訂單。生產者會保證潛在的消費者可以捕獲處理事件所需的一切信息。也就是說,訂單記錄不必定嚴格包含實現訂單所需的每一個屬性。例如,不必定會直接指定產品的尺寸,存放位置以及消費者的送貨地址等信息,但能夠解析經過捕獲的訂單記錄中的ID間接得到這些信息。關係數據庫中的外鍵概念也一樣適用於事件。微服務
若是生產者和消費者都互不感知對方,那麼二者該如何通訊?工具
答案是經過術語"記錄"進行粘合。事件一般被持久化到一個衆所周知的位置,稱爲日誌(有時也會用到術語"帳簿")。日誌是底層的,只能在後續消費者能夠訪問的地方附加生產者保存的事件數據結構。brokers(位於生產者和消費者之間的持久化中間件)負責操做日誌。一旦產生了一個事件,任何人均可以消費該事件。
當處理事件驅動系統時,咱們常常會使用術語"流"來描述一個或多個日誌接口。日誌是物理上的概念(使用文件實現),一條流是邏輯上的概念,表示構成事件的一組沒無邊界的記錄,但記錄要遵照某種特定的順序。不一樣的流平臺可能使用專有名稱指代一條流。Apache Kafka使用topics和partitions來描述流。
生產者、消費者和流的關係以下:
Event-Driven Architecture Reference Model
回顧一下相關概念:
爲何EDA可以大大下降耦合度?
對耦合的一種比較務實的定義是:一個組件受其餘組件影響的程度。耦合存在於空間(組件在結構上相關聯)和時間(時間會影響組件之間的關係程度)上。對於後者,一個比較好的例子是,一個服務同步調用其餘服務的REST API。若是被調用的服務down,則該服務將沒法繼續處理(響應被阻塞)。若是兩個服務必須同時運行,則兩者之間會存在必定程度的臨時耦合(temporal coupling)。若是兩個服務高度依賴,則稱之爲強耦合,反之,則稱爲鬆耦合。
Conceptual model of coupling
EDA採用兩種方法來抑制耦合。
EDA並非銀彈,它沒有一併消除耦合的概念(不然,系統中的組件將再也不共同做用)。如今將關注點轉移到broker上:爲了讓生產者和消費者有意義地進行解耦,它們必須依賴一個broker。這種方式增長了系統架構的複雜度,並引入了其餘故障點。這也是爲何brokers必須是高性能且具備容錯能力,不然,咱們只是將一組問題換成另外一組。
時間處理一般分爲三種經常使用的方式。這些方式並不互斥,它們常常會同時存在於一個大型的事件驅動系統中。
用於處理離散事件:例如在社交媒體平臺上發佈一個帖子。離散事件處理的特徵在於出現的事件之間一般並沒有關聯,能夠獨立處理。
用於處理一系列相關聯的無邊界事件流,事件的記錄以某種順序呈現,並攜帶一些與發生的事件有關的信息。例如,當一個業務實體發生聯合變動時,消費者可能會按照生產者指定的順序進行變動,並在本地數據庫中保存一份該實體的副本。因爲須要關注事件處理的順序,所以不能離散地處理這類事件。消費者須要避免條件競爭,即多個消費者實例可能會同時修改數據庫中的某條記錄,進而因爲亂序更新而致使數據不一致。
比較有名的流事件平臺,如Kafka會依賴記錄的key和partitions來保留更新順序。Kafka同時也保證對一個實體的全部變動會被某個消費者處理,避免多個消費者並行處理事件而致使併發競爭。
復瑣事件處理(CEP)是一種從一系列簡單事件中得出或識別復瑣事件的模式。例如監控一座建築內的溫度和延誤感應器,並於推斷是否發生了火情,並進行持續跟蹤。單獨的溫度變化並不足以引起報警。更具意義的是溫度峯值和變化率聚合而成的羣體事件,進而有可能挽救生命。
一般更多會涉及此類處理,要求事件處理器持續跟蹤先前的事件,並提供一個有效的方式進行請求和聚合。
一些場景下可使用事件驅動架構帶來的優點:
EDA不是萬能藥,與不少強大的工具同樣,它有可能被錯誤地使用。下面列出的內容不該該被認爲是EDA的缺點,而應該做爲開發人員和架構師在設計和實現事件驅動的系統時應注意的一系列陷阱。
複雜的編排。使用鬆耦合組件,用戶可能會感到困惑,整個架構看起來像是一個Rube Goldburg機器(能夠藉助下圖理解Rube Goldburg),整個業務邏輯也被實現爲一系列(帶有反作用的包裝的)事件:一個組件發起的事件可能觸發另外一個組件發起另外一個事件,而後觸發另外一個組件發起事件,以此類推。這種組件間的交互很快會變得沒法理解。
將命令和事件混淆。一個事件用於單純地描述發生的事情。它不會指定如何處理事件。而一個命令是針對特定組件的直接指令。因爲命令和事件都是某種類型的消息,很是容易混淆,把命令誤覺得是一個事件。
命令也能夠放到EDA下,但要分清與事件的區別。命令可能會修改系統狀態,一般會須要回滾方案。
消費者不可知。事件應該以某種方式捕獲相關的屬性,但並不會限制如何處理這些事件。提及來容易,作起來難。有時咱們可能會沒法得到足夠的信息來限制添加到事件記錄的內容(沒法肯定這些添加到記錄中的信息是否最終有用)。
我的認爲最重要的是上面的第二點,要區分命令和事件。
微服務架構模式是構建更可維護、可擴展、更健壯的軟件系統所涉及的難題之一。從問題分解的角度來看,微服務很是棒,但也帶來了不少棘手的問題,其中一個就是耦合。與一開始相比,隨意將系統拆分爲少數微服務的作法可能會使您處於更糟糕的局面。有一個術語能夠對其進行描述"分佈一體式"。
爲了幫助解決困惑,並定位耦合的問題,咱們引入了事件驅動架構。
EDA是一個能夠幫助下降系統組件間的耦合的有效工具,它是一種使用生產者、消費者、事件和流進行交互的模型。一個事件表示一個感興趣的動做,任何組件均可能異步地發佈和消費事件,而無需感知對方的存在。EDA容許組件獨立操做和演化。但它不是解決全部問題的銀彈。EDA是一個不錯的選擇,它帶來的好處大大超過了採用它的成本。能夠說,EDA是成功部署微服務的必要要素。