什么是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" : "123", "id" : "A234-1234-1234", "time" : "2018-04-05T17:31:00Z", "comexampleextension1" : "value", "comexampleothervalue" : 5, "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的接入成本。