混合雲中的事件驅動架構

介紹基於事件的架構

譯自: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

回顧一下相關概念:

  • 事件是在離散時間點發生的感興趣的動做:可能從外部進行觀察和描述。
  • 事件持久化爲記錄:事件和記錄儘管是相關的,但在技術上是不一樣的。一個事件表示事情的發生(如狀態變動),自己是無形的。而一條記錄是對該事件的精確描述。咱們一般使用術語"事件"來指代其對應的記錄。
  • 生產者是經過將相應的記錄發佈到流中來檢測事件的接收器。(發佈一條記錄則表示發生了一個事件)
  • 流是持久化的有序的記錄。它們一般由一個或多個基於磁盤的日誌來進行持久化,固然,也可使用數據庫表、布式共識協議,甚至是區塊鏈式的分散帳原本支持持久化。
  • Brokers 負責對流的訪問,方便讀寫操做,處理消費者狀態以及在流上執行各類"內務"。例如,一個broker可能在記錄溢出時對流的內容進行截取。
  • 消費者讀取流,而後對接收到的記錄做出迴應。消費者對事件的迴應可能會伴隨一些額外的操做。例如,一個消費者可能會在本地數據庫中持久化一條表項(經過發佈的"更新"事件來重構遠端實體的狀態)(即更新對遠端實體的描述)。
  • 消費者和生產者可能會重疊。例如,對事件的迴應方,也可能產生一個或多個派生的事件。

經過異步性和通用性進行解耦

爲何EDA可以大大下降耦合度?

對耦合的一種比較務實的定義是:一個組件受其餘組件影響的程度。耦合存在於空間(組件在結構上相關聯)和時間(時間會影響組件之間的關係程度)上。對於後者,一個比較好的例子是,一個服務同步調用其餘服務的REST API。若是被調用的服務down,則該服務將沒法繼續處理(響應被阻塞)。若是兩個服務必須同時運行,則兩者之間會存在必定程度的臨時耦合(temporal coupling)。若是兩個服務高度依賴,則稱之爲強耦合,反之,則稱爲鬆耦合。

Conceptual model of coupling

EDA採用兩種方法來抑制耦合。

  • 回顧一下,事件是不能通訊的,它們只會發生。發起事件的組件(經過發佈記錄)並不知道其餘組件是否存在。所以,即便消費者不可用,生產者也不會中止工做---broker會暫時緩存事件,而不會對生產者施加反向壓力。
  • broker對事件記錄的持久化大大消除了時間觀念。一個生產者可能會在T1時間發佈一個事件,而一個消費者可能會在T2事件纔會讀取該事件,T1T2之間的間隔多是毫秒級別的(全部組件正常)或小時級別的(若是某些消費者down或忙於其餘事情)。

EDA並非銀彈,它沒有一併消除耦合的概念(不然,系統中的組件將再也不共同做用)。如今將關注點轉移到broker上:爲了讓生產者和消費者有意義地進行解耦,它們必須依賴一個broker。這種方式增長了系統架構的複雜度,並引入了其餘故障點。這也是爲何brokers必須是高性能且具備容錯能力,不然,咱們只是將一組問題換成另外一組。

事件處理的方式

時間處理一般分爲三種經常使用的方式。這些方式並不互斥,它們常常會同時存在於一個大型的事件驅動系統中。

離散事件處理

用於處理離散事件:例如在社交媒體平臺上發佈一個帖子。離散事件處理的特徵在於出現的事件之間一般並沒有關聯,能夠獨立處理。

事件流處理

用於處理一系列相關聯的無邊界事件流,事件的記錄以某種順序呈現,並攜帶一些與發生的事件有關的信息。例如,當一個業務實體發生聯合變動時,消費者可能會按照生產者指定的順序進行變動,並在本地數據庫中保存一份該實體的副本。因爲須要關注事件處理的順序,所以不能離散地處理這類事件。消費者須要避免條件競爭,即多個消費者實例可能會同時修改數據庫中的某條記錄,進而因爲亂序更新而致使數據不一致。

比較有名的流事件平臺,如Kafka會依賴記錄的key和partitions來保留更新順序。Kafka同時也保證對一個實體的全部變動會被某個消費者處理,避免多個消費者並行處理事件而致使併發競爭。

復瑣事件處理

復瑣事件處理(CEP)是一種從一系列簡單事件中得出或識別復瑣事件的模式。例如監控一座建築內的溫度和延誤感應器,並於推斷是否發生了火情,並進行持續跟蹤。單獨的溫度變化並不足以引起報警。更具意義的是溫度峯值和變化率聚合而成的羣體事件,進而有可能挽救生命。

一般更多會涉及此類處理,要求事件處理器持續跟蹤先前的事件,並提供一個有效的方式進行請求和聚合。

何時使用EDA

一些場景下可使用事件驅動架構帶來的優點:

  • 不透明的消費者生態系統。這種狀況下,生產者並不瞭解消費者,後者多是一個短暫的過程,可能在短期內來來每每!
  • 高扇出。一個事件可能由多個不一樣的消費者處理的場景。
  • 複雜的模式匹配。可能將事件串在一塊兒來推斷出更復雜的事件。(這類場景可能須要進行聚合,即上面描述的復瑣事件處理)
  • 命令查詢的責任分離。CQRS是一種分離數據存儲區的讀取和更新操做的模式。實現CQRS能夠提升應用的可擴展性和彈性(在數據一致性上進行了取捨)。 這種模式一般與EDA相關。

EDA的好處

  1. 緩存和容錯能力。事件消費的速率可能與生產者不一樣步,生產者不能爲了與消費者保持一致而放慢速率。
  2. 生產者和消費者解耦,避免笨拙的點到點集成。EDA下很容易添加新的生產者和消費者,也很容易修改生產者和消費者的實現(前提是遵照約束事件記錄的合同/方案)。
  3. 大規模擴展。一般會把部分部分事件流切分爲若干不相關的自流,而後並行處理。隨着事件的積壓,咱們也能夠擴展消費者的數量來知足負載需求。像Kafka這樣的平臺會嚴格按序處理事件,並容許跨流進行大規模並行處理。

EDA的缺點

  1. 僅限異步處理。雖然EDA是一種有效的系統解耦模式,但它也將應用限制爲異步事件處理。EDA並不能很好地處理像請求響應這樣的交互(發起者必須等待響應才能繼續處理)。
  2. 引入額外的複雜度。傳統的客戶端-服務器以及請求-響應僅會涉及兩方,在採用EDA以後則引入了第三方-broker,做爲生產者和消費者之間的媒介。
  3. 故障掩蓋。這一點比較奇特,由於它彷佛與解耦系統的本質背道而馳。當系統高度耦合時,一個系統中的錯誤會快速傳遞下去,並引發咱們的關注。大多數場景下,咱們須要避免這種狀況:當一個組件失敗時,儘可能減少它對其餘組件的影響。故障掩蓋的負面影響是,它會在不經意間隱藏本應引發咱們注意的問題。能夠經過爲每一個事件驅動的組件添加實時監控和日誌來解決,但這樣作也帶來了新的複雜度。

須要注意的點

EDA不是萬能藥,與不少強大的工具同樣,它有可能被錯誤地使用。下面列出的內容不該該被認爲是EDA的缺點,而應該做爲開發人員和架構師在設計和實現事件驅動的系統時應注意的一系列陷阱。

  1. 複雜的編排。使用鬆耦合組件,用戶可能會感到困惑,整個架構看起來像是一個Rube Goldburg機器(能夠藉助下圖理解Rube Goldburg),整個業務邏輯也被實現爲一系列(帶有反作用的包裝的)事件:一個組件發起的事件可能觸發另外一個組件發起另外一個事件,而後觸發另外一個組件發起事件,以此類推。這種組件間的交互很快會變得沒法理解。

  2. 將命令和事件混淆。一個事件用於單純地描述發生的事情。它不會指定如何處理事件。而一個命令是針對特定組件的直接指令。因爲命令和事件都是某種類型的消息,很是容易混淆,把命令誤覺得是一個事件。

    命令也能夠放到EDA下,但要分清與事件的區別。命令可能會修改系統狀態,一般會須要回滾方案。

  3. 消費者不可知。事件應該以某種方式捕獲相關的屬性,但並不會限制如何處理這些事件。提及來容易,作起來難。有時咱們可能會沒法得到足夠的信息來限制添加到事件記錄的內容(沒法肯定這些添加到記錄中的信息是否最終有用)。

我的認爲最重要的是上面的第二點,要區分命令和事件。

總結

微服務架構模式是構建更可維護、可擴展、更健壯的軟件系統所涉及的難題之一。從問題分解的角度來看,微服務很是棒,但也帶來了不少棘手的問題,其中一個就是耦合。與一開始相比,隨意將系統拆分爲少數微服務的作法可能會使您處於更糟糕的局面。有一個術語能夠對其進行描述"分佈一體式"。

爲了幫助解決困惑,並定位耦合的問題,咱們引入了事件驅動架構。

EDA是一個能夠幫助下降系統組件間的耦合的有效工具,它是一種使用生產者、消費者、事件和流進行交互的模型。一個事件表示一個感興趣的動做,任何組件均可能異步地發佈和消費事件,而無需感知對方的存在。EDA容許組件獨立操做和演化。但它不是解決全部問題的銀彈。EDA是一個不錯的選擇,它帶來的好處大大超過了採用它的成本。能夠說,EDA是成功部署微服務的必要要素。

相關文章
相關標籤/搜索