1. 程式人生 > 實用技巧 >Event-Driven Architecture思考

Event-Driven Architecture思考

什麼是Event?

An event represents a fact, something happened; and it is immutab.

事件代表著事實,代表著過去發生的某件事情,是不可變的。

既然事件代表著在過去某一時刻發生的某個事情,那麼那必然具備一些基本的要素,就像現實生活中發生某件事情也具備時間、地點等幾個要素。事件的基本要素包含time、source、key、header、metadata和payload。

Event vs Message

日常開發中我們接觸到的和事件最接近的應該是訊息,這兩者也比較容易混淆,難以說清楚它們的界限:什麼是事件,而什麼是訊息?

A message is an item of data that is sent to a specific destination. An event is a signal emitted by a component upon reaching a given state.

上面是我覺得的解釋是比較好的,訊息是傳送到特定目標的資料項,而事件是標識某個元件達到了某個狀態。訊息更關注於行為,即“要做一件什麼事情”——要把某一條訊息傳送到服務端;而事件關注於事實,即“發生了什麼事情”。

訊息沒有特定的意圖(no special intent),可以承載任何資料,那麼也可以用訊息來承載事件,所以訊息是事件的超集(事件可以認為是一類加上了一些特定限制的訊息)。

Event vs Command

還有一類日常中使用的遠端呼叫的方式Command,通常我們執行一個RPC呼叫都是執行一個Command。Command和Event的區別在於Command著重於要做什麼,用於傳遞一個要執行某個動作的請求。

因為Command具有以下特性:

  • 意味著行為即將發生,但是還沒發生
  • 可能被拒絕:可能被拒絕執行,或者因為某些原因無法執行
  • 有明確的源(發起者)和目標(執行者)

結合和Message及Command的差異,總結一下Event具備的特徵:

  • 代表著已經發生的事情->已經發生的事情不可改變
  • 事件有特定的一些屬性,表明特定的含義,事件是訊息的子集
  • 事件有明確的源,但沒有明確的目標

CloudEvents

因為事件無處不在,DB中的資料發生了一次變更是一個事件,幾臺機器宕機了也是一個事件,並且每個釋出者對事件的描述是不一致的,導致難以在一個大規模的範圍內使用事件。

  • 沒有統一的標準去描述事件意味著開發者需要為每一個事件源編寫邏輯
  • 沒有統一的標準去描述事件意味著沒有通用的類庫、工具、基礎設定來支援事件的處理、分發
  • 沒有統一的標準去描述事件意味無法進行移植,可能無法跨平臺的去使用

其實在每個特定的系統中我們都會制定事件的標準來解決這個問題,比如CDC(Change Data Capture)的場景中,類似Canal(https://github.com/alibaba/canal)這樣的產品都會將DB中的變更事件封裝成特定的Java物件,而這個物件實際就是事件的標準。面對這個問題,CNCF(Cloud Native Computing Foundation)站在更高的角度來抽象事件,而不是侷限在特定的系統或者領域,制定了CloudEvents標準。

每個符合規範的CloudEvent都需要包含必須(REQUIRED)的屬性,並且可以包括多個非必須(OPTIONAL)的屬性。這些屬性描述了事件,並且獨立於事件的資料進行序列化,這樣就可以在不必進行事件資料反序列化的情況下對事件進行檢查。

REQUIRED Attributes

  • id: String型別,標識Event,必須保證Source+ID能唯一確定一個Event
  • source:URI-reference型別,事件發生的“源”
  • specversion:String型別,標識事件使用的CloudEvents Spec版本
  • type:String型別,描述事件的型別

OPTIONAL Attributes

  • datacontenttype:String型別,Event內容的型別,例如"application/xml"
  • dataschema:URI型別,指明Event內容的Schema資訊
  • subject:String型別,Event的Subject資訊
  • time:Timestamp型別,事件發生的時間

Example

{
"specversion" : "1.0-rc1",
"type" : "com.github.pull.create",
"source" : "https://github.com/cloudevents/spec/pull",
"subject" : "",
"id" : "A234-1234-1234",
"time" : "2018-04-05T17:31:00Z",
"comexampleextension1" : "value",
"comexampleothervalue" : ,
"datacontenttype" : "text/xml",
"data" : "<much wow=\"xml\"/>"
}

什麼是Event-Driven?

在討論Event-Driven之前需要弄清楚Event-Driven的概念,這裡就需要理清楚Event-Driven和Request-Driven的關係。

上面這張圖來源於《Build Services on a Backbone of Events》一文,比較清楚的描述了Request-Driven和Event的區別:

  • Request-Driven包含Command和Query兩種,Command意為執行命令,會導致狀態的變更;而Query僅查詢資料,不會導致狀態變更。Request-Driven是希望執行某段業務邏輯。
  • Event-Driven則是事件驅動,事件在狀態變更後觸發,是業務邏輯執行完後導致的狀態變更的通知。

日常我們使用的RPC服務都可以理解為是Request-Driven,都是請求執行某個命令;而日常使用的訊息中介軟體都是Event-Driven。如果由Event來驅動執行邏輯,那麼稱為Event-Driven的應用。一個應用往往即會處理RPC請求,也會訂閱訊息,那麼它有一部分是Request-Driven的,有一部分是Event-Driven的(除非是Function或者是特定領域的簡單的應用——比如做訊息的轉換,很少會有一個純粹的Event-Driven的應用)。沒有必要過分糾結一個應用是否是純粹的Event-Driven的,更重要的是理解Event-Driven的思想,將它融入到架構的思想中來把業務系統做的更好。

什麼是Event-Driven Architecture?

Event-Driven Architecture是一種用於構建可擴充套件的分散式非同步處理模式,由高度解耦的、單一職責的事件處理器組成。

Event-Driven Architecture模式有兩種主要的結構:Mediator Topology和Broker Topology。Mediator Topology多用於需要有多個編排步驟的事件處理,而Broker Topology用於鏈式的事件處理。

Mediator Topology

Mediator Topology用於Event需要多個步驟進行處理,且需要一些編排能力的場景。比如一個Event進來之後,系統需要決定處理的順序,以及其中哪些步驟可以併發的執行。Mediator Topology中有四個核心的元件:event -queue、event-mediator、event-channel、event-processor。整個流程通過客戶端傳送一個Event到event-queue開始,然後由event-queue將Event傳輸到event-mediator,event-mediator收到初始的Event之後通過非同步的傳送額外的Event到event-channel來完成編排,event-processor從event-channel接收Event並執行特定的業務邏輯來完成處理。

在Event-Driven Architecture中會有成百上千Queue,Event-Driven Architecture並不繫結Queue的實現,它可以由MQ實現,也可以由其他元件實現。

在Mediator模式下有兩類Event,一類是initial event,一類是processing event。initial event是外部輸入的初始Event,processing event是由Mediator產生的,由Event Processor處理的Event。

event-mediator的職責是編排initial event,對於initial event的每個處理步驟event-mediator傳送一個processing event給event-channel,event-mediator並不執行任何真正的業務邏輯。

event-channel用於event-mediator將Event非同步的投遞給event-processor處理,event-channel可以使用message queue或者message topic實現。message topic是更常用的,因為event可以被投遞給多個event processor處理。

event-processor包含了用於處理processing event的業務邏輯。event-processor是應用中用於執行特定任務的、自包含的、獨立的、高度解耦的元件。明確event-processor是單一職責的非常重要,event-processor執行任何一個任務不應該依賴於其他event-processor的處理。

上圖是使用者通過保險公司投保並修改地址的例子。首先event-mediator收到初始事件,然後執行change address操作(傳送Processing Event: change address event給ConsumerProcessor處理),再之後併發的執行RecalcQuote和UpdateClaims事件,接著執行AdjustClaims調整報價,最後執行Insured的通知。

Broker Topology

區別於Mediator Topology,Broker Topology沒有中心式的event-mediator,Event在event-processor之間以一種鏈式的結構流轉,在事件流相對簡單,不需要集中編排的場景下這種模式是非常適用的。

在Broker Topology下只有兩個核心的元件:broker和event-processor。Broker可以是中心式的,也可以是分散式的,包含事件流中所需要的所有event-channel。Broker中的event-channel可以是message queue或者message topic,也可以是它們的組合。

Broker Topology結構如上圖所示,在圖中沒有一個event-mediator來編排event,僅有event-processor來處理event並在處理完成之後產生新的event來表示它剛剛執行的操作。

同樣以Mediator中的例子來看的話,在Broker模式下的處理流程如下:

其中ChangeAddress的Processor直接處理Event,處理完畢後產生一個ChangeAddress完成的事件,然後由QuoteProcess和ClaimsProcess訂閱這個事件並執行各自的處理邏輯(這一步是併發的),之後AdjustProcess會訂閱UpdateClaims的Event,而NotificationProcess會同時訂閱RecalcQuote和UpdateClaims的Event。

總結

Event-Driven Architecture在某些方面是具有天然優勢的,在另一些方面則對現行的開發模式增加了負擔,比如:

  • 敏捷度:EDA模式中event-processor都是解耦的且單一職責的,所以再進行變更時會非常容易,能夠快速的變更完成業務,所以有較好的敏捷性。
  • 易於部署:EDA模式是非常容易部署的,特別是Broker的模式,因為各個元件是相互獨立的。
  • 可測試性:EDA模式的測試相對於REQUEST驅動的模式會複雜很多,因為需要構建各個Event並且都是非同步的。
  • 效能:EDA模式各個元件獨立解耦,且非同步執行,這大大提升了系統的效能。
  • 可伸縮性:EDA進行了元件的解耦,所以天然的具備的非常好的伸縮性,可以根據各個元件的效能執行不同的伸縮策略。
  • 易於開發:EDA模式下,比如Broker模式每個event-processor只需要處理自己關注的事件,並決定是否產出一個新的事件,邏輯開發是簡單的,但是如何將event-processor組合起來完成整個業務是相對複雜的。

Event-Driven Architecture是相對複雜的架構,因為它天然是非同步的、分散式的。當選擇採用Event-Driven來處理業務邏輯時,如何劃分event-processor是非常重要的,因為event-processor是獨立的,需要避免將帶有事務語義的邏輯拆分到多個event-processor中。統一事件標準也是非常重要的,因為event-processor會隨著業務的變化而不斷的增長,使用統一的標準將降低event-processor之間的互動及新event-processor的接入成本。