作者 | 林暉
一、業(yè)務(wù)背景
電商平臺供應鏈的業(yè)務(wù)場景非常復雜,技術(shù)中臺需要支持非常復雜且不斷變化的業(yè)務(wù)需求,構(gòu)建了數(shù)量繁多且緊密耦合的業(yè)務(wù)鏈路,為技術(shù)架構(gòu)的維護帶來了壓力。
1. 問題描述

上圖是一個典型的業(yè)務(wù)架構(gòu),A域是上游域,B域和C域是下游域。A域在收到外部調(diào)用請求時,首先同步調(diào)用B域的服務(wù)接口完成同步業(yè)務(wù)邏輯,然后發(fā)送消息通知到MQ。C域異步消費消息后,反向調(diào)用A域的接口查詢詳細信息,完成異步業(yè)務(wù)邏輯。
這種架構(gòu)的問題包括:
(1) A域強依賴B域的接口,B域接口變動會導致A域調(diào)用失敗,而A域無法管控B域的接口變動;
(2). C域收到消息后需要反查A域的接口,對A域形成了雙重依賴,A域接口和消息格式的任何變動及不穩(wěn)定性都會影響C域;
(3) A域的消息和接口都是瞬時數(shù)據(jù),兩者由于時間差可能不一致,增加了C域處理的復雜度(例如:C域收到的消息是單據(jù)已創(chuàng)建,調(diào)用接口時查到該單據(jù)已完結(jié));
(4) A域需要保證同步調(diào)用和消息通知的一致性,包括MQ不可用等情況發(fā)生時的容災處理面對這些問題,我們希望應用事件驅(qū)動架構(gòu)的特性來解耦子域,降低業(yè)務(wù)鏈路復雜度,構(gòu)建穩(wěn)定并向前兼容的事件契約,從而提升全域的穩(wěn)定性。
2. 事件驅(qū)動架構(gòu)的應用過程
(1)重新梳理全鏈路業(yè)務(wù)流和業(yè)務(wù)活動,建立統(tǒng)一的標準語言;
(2)定義標準的事件格式和通用基礎(chǔ)字段;
(3) 各域定義包含完整業(yè)務(wù)語義、自閉包、多租戶的領(lǐng)域事件;
(4) 開發(fā)并接入一套適應供應鏈業(yè)務(wù)特點的事件系統(tǒng)(NBF事件中心);
3. 關(guān)于NBF
NBF[1] 是阿里巴巴供應鏈中臺的基礎(chǔ)技術(shù)團隊打造的一個技術(shù)PaaS平臺,全稱是New-Retail Business Factory,她提供了微服務(wù)FaaS框架,低代碼平臺和中臺基礎(chǔ)設(shè)施等一系列的PaaS產(chǎn)品,旨在幫助業(yè)務(wù)伙伴快速復用和擴展中臺能力,提升研發(fā)效能和對外的商業(yè)化輸出。事件中心就是NBF系列技術(shù)產(chǎn)品中的一員。
本文首先介紹事件驅(qū)動架構(gòu)的概念及適用場景,然后會介紹事件中心產(chǎn)品的設(shè)計和實現(xiàn)。
二、什么是事件驅(qū)動架構(gòu)(EDA)
1. 領(lǐng)域事件
很多同學會將事件和消息混淆。在業(yè)務(wù)系統(tǒng)中,事件指的是領(lǐng)域事件,而消息可以是任意數(shù)據(jù)或數(shù)據(jù)片段。領(lǐng)域事件的特點包括:
(1)與服務(wù)接口一樣有完整的schema,并保證schema向前兼容;
(2)是業(yè)務(wù)流程的一部分,由業(yè)務(wù)動作觸發(fā),包含了完整(或部分但有獨立語義)的業(yè)務(wù)狀態(tài)變化;
(3)事件消費者接收到事件后,相應修改自身的業(yè)務(wù)狀態(tài),并按需發(fā)出新的事件;消費者需要保證所有事件最終消費成功,否則會導致業(yè)務(wù)流程不完整;
(4)事件需要持久化保存并長期歸檔,方便業(yè)務(wù)同學查詢、恢復中斷的業(yè)務(wù)流程、重新發(fā)起業(yè)務(wù)流程等,也方便風控及財務(wù)分析同學做離線分析。
2. 事件驅(qū)動架構(gòu)的概念
和很多架構(gòu)名詞類似,事件驅(qū)動架構(gòu)并沒有一個明確的定義和能力范圍。Martin Fowler在2017年的文章[2] 中描述了與事件驅(qū)動架構(gòu)相關(guān)的一些主要模式。在本文中,事件驅(qū)動架構(gòu)的概念具象為由領(lǐng)域事件驅(qū)動的業(yè)務(wù)流技術(shù)架構(gòu)。每一個領(lǐng)域事件都對應一個業(yè)務(wù)流中的具體活動(如采購單建單),而事件就是活動發(fā)生導致的結(jié)果(如采購單建單完成事件),事件內(nèi)容就是活動導致的完整狀態(tài)變化(如采購單+子單列表)。
3. 事件驅(qū)動架構(gòu)的優(yōu)點
在Fundamentals of Software Architecture[3] 以及Microservices Patterns[4]等書中描述了事件驅(qū)動架構(gòu)的一些明顯特點,我們總結(jié)為以下幾項:
- 高度解耦
 - 廣播能力
 - 純異步調(diào)用(Fire and Forget)
 - 靈活擴展
 - 高處理性能
 
4. 事件驅(qū)動架構(gòu)能解決什么實際問題
下面我們舉幾個例子來描述事件驅(qū)動架構(gòu)的解耦和廣播能力如何幫助解決現(xiàn)實工作中的問題:
解耦能力

在基于請求/響應方式的服務(wù)化架構(gòu)中,上游服務(wù)按照約定的RPC接口調(diào)用下游服務(wù),這樣有一個比較嚴重的問題:上游服務(wù)作為數(shù)據(jù)(例如業(yè)務(wù)單據(jù))的生產(chǎn)者,強依賴了作為數(shù)據(jù)消費方的下游服務(wù)所定義的接口,導致上游服務(wù)自身無法沉淀接口和數(shù)據(jù)標準。

一種更合理的方案是依賴倒置:由上游服務(wù)定義SPI,下游服務(wù)實現(xiàn)SPI,這樣,上游服務(wù)終于有機會沉淀出自身的接口和數(shù)據(jù)標準,不再需要適配各個下游服務(wù)的接口,而是由下游服務(wù)的開發(fā)者按照接口文檔來做實現(xiàn)。但這種設(shè)計仍然無法解決運行時上游服務(wù)仍然依賴下游服務(wù)的問題,下游服務(wù)的可用性、一致性、冪等性能力會直接影響上游服務(wù)的相關(guān)指標及實現(xiàn)方式,需要上下游服務(wù)開發(fā)者一起對齊方案,在出問題時一起解決。

使用事件驅(qū)動設(shè)計可以實現(xiàn)契約定義和運行時的全面解耦:上游服務(wù)可以沉淀自己的事件契約,在運行時無論是上游服務(wù)還是下游服務(wù)都只依賴事件Broker,下游服務(wù)的可用性和一致性等問題由事件Broker來保障。
廣播能力
在供應鏈中臺這樣復雜的微服務(wù)架構(gòu)中,關(guān)鍵的上游服務(wù)往往有多個下游服務(wù),上游服務(wù)一般需要順序或并發(fā)調(diào)用所有的下游服務(wù)來完成一次完整的調(diào)用。

上游服務(wù)的開發(fā)者會面臨多個難題:
- 服務(wù)的可用性會被下游服務(wù)影響;
 - 服務(wù)的RT自己無法控制;
 - 下游服務(wù)之間的一致性如何保障;
 - 如何實現(xiàn)一套可靠的重試機制;
 
而下游服務(wù)的開發(fā)者也有自己的問題:
- 每接入一個上游服務(wù)都需要跟服務(wù)開發(fā)者排期:誰來答疑,什么時候聯(lián)調(diào),什么時候上線;
 - 上游流量如何做過濾,高峰流量是否能抗得住;
 - 如何滿足上游服務(wù)的可用性及RT要求;
 
使用事件驅(qū)動架構(gòu)天然可以避免上述問題:

- 上下游完全解耦,上游服務(wù)只要保證將事件成功發(fā)送到Broker,無論有幾個下游消費者,都不會影響自身的RT,也不需要考慮下游服務(wù)之間的一致性;
 - 下游服務(wù)在接入新的事件時,只需要在事件管理服務(wù)中走完訂閱審批流,不需要等待事件發(fā)布者排期和聯(lián)調(diào);
 - 通過事件Broker提供的事件過濾能力,下游服務(wù)只需要消費與自身相關(guān)的事件流量(例如:天貓超市的計費服務(wù)只需要消費tenantId為天貓超市的采購單創(chuàng)建事件,而不需要消費銀泰租戶的采購單創(chuàng)建事件);
 - 通過事件Broker提供的事件存儲能力和重投能力,即使上游服務(wù)發(fā)送的事件流量超過了下游服務(wù)的處理能力,也只會影響下游服務(wù)的消費延遲,不會導致大量請求失敗的情況。
 
5. 事件驅(qū)動架構(gòu)不適合什么場景
- 強依賴Response的場景,例如單據(jù)查詢、商品查詢;
 - 對全局處理延遲敏感的場景,例如游戲、搜索;
 - 要求服務(wù)之間保持強一致性的場景;
 
三、事件中心的功能設(shè)計
作為面向中臺的事件中間件,事件中心集成了消息中間件MetaQ(RocketMQ),初始使用體感也與MQ很像,但事件中心有很多不同的功能設(shè)計:
(1)完善的權(quán)限控制;
(2) 支持事件契約定義以及運行時合法性校驗;
(3) 支持大事件發(fā)送和消費(10MB或更高);
(4)支持長期的事件歷史查詢、事件索引查詢(如單據(jù)編號、sku)、事件重投;
(5) 支持消費周期很長的事件(如需要幾個月才能完結(jié)的入庫單);
(6)所有事件及消費記錄的完整歸檔;
(7)以O(shè)penAPI的形式開放了事件查詢、事件重投等運維態(tài)的功能,方便被其他系統(tǒng)集成。
四、事件中心的運行時架構(gòu)
事件中心運行態(tài)主要由以下部分組成:
- 事件中心服務(wù)/SDK
 
a) SDK:包含事件收發(fā)的主要邏輯,支持事務(wù)發(fā)送和普通發(fā)送,支持事件校驗、壓縮、本地備份;
b) Tunnel Service:一層很薄的數(shù)據(jù)庫代理服務(wù),支持按應用、事件、場景、IO維度的限流,支持數(shù)據(jù)庫快速靈活擴容;
c) Index Service:事件索引服務(wù),通過精衛(wèi)(DataX)獲取Binlog,解析為索引后寫入索引表(Lindorm)。
- 阿里中間件
 
a) Diamond(Nacos):包含應用相關(guān)的全部配置信息,如發(fā)送、訂閱關(guān)系、事件定義、中間件配置等;
b) SchedulerX:調(diào)度SDK執(zhí)行事件重新發(fā)送、重新消費、事務(wù)異常狀態(tài)問詢;
c) MetaQ:主要的事件收發(fā)管道;
d) TDDL(RDS):事件內(nèi)容及消費記錄存儲;
e) 精衛(wèi):用于生成索引、計算延遲等異步處理邏輯;
f) Lindrom(serverless):用于存放事件外部索引,serverless模式支持按量付費和彈性擴容,性能比較穩(wěn)定。
下圖為簡化的運行時架構(gòu)圖,圖中藍色線條表示事件的正常收發(fā)鏈路(事務(wù)發(fā)送),紅色線條表示事件的異常處理鏈路。

1. 事件發(fā)送與消費流程
事件結(jié)構(gòu)

運行時的一條事件實例由三部分組成:
(1)事件ID:全局唯一,格式為“邏輯庫編號_月內(nèi)發(fā)送日期_uuid”,例如01_11_f75ec4fb347c49c4bc3e93xxxxxxxx,其中邏輯庫編號用于邏輯庫路由,日期用于事件清理;
(2) 事件Head:包含事件元信息,如trace信息、發(fā)送者信息、事件大小、MetaQ信息等,參考示例:

(3)事件Body:JSON格式,包含由用戶已定義的事件內(nèi)容,事件內(nèi)容要符合事件定義契約,否則會被拒絕發(fā)送。
運行時的事件可能有多個消費方,每個消費方會產(chǎn)生一條消費記錄,消費記錄包含:
- 事件ID
 - 消費信息:消費狀態(tài)、消費次數(shù)、下次消費時間等
 
事件發(fā)送流程
事件中心支持事務(wù)發(fā)送和非事務(wù)發(fā)送兩種模式,使用狀態(tài)機驅(qū)動,API設(shè)計與MetaQ的API基本一致。以下以事務(wù)發(fā)送為例介紹發(fā)送流程,由于非事務(wù)發(fā)送的流程更簡單,所以不再詳細介紹。
1)事務(wù)發(fā)送狀態(tài)機

2)事務(wù)發(fā)送時序圖

3)異常狀態(tài)事務(wù)問詢

事件消費流程
事件消費流程也使用狀態(tài)機驅(qū)動,API相比MetaQ有一些不同:
(1)不需要再調(diào)用subscribe topic;
(2)新增消費過濾器EventFilter,支持按照租戶、業(yè)務(wù)流、事件維度做過濾;
(3)支持不同的事件使用不同的Listener消費;
1)事件消費狀態(tài)機

2)重試周期
事件進入消費失敗狀態(tài)后,事件中心會周期調(diào)用用戶Listener重新消費,消費周期以5s起始指數(shù)增加,最多重試15次,最大為5 * 214 = 81920秒(約22小時)。
3)事件消費時序圖

2 事件存儲
數(shù)據(jù)表
事件中心使用了32分庫的TDDL,按照HASH(事件ID)做分庫,每個庫上有以下幾張表:
(1)事件主表,包含發(fā)送者信息、事件信息以及普通事件的事件體;
(2) 事件消費記錄主表,包含消費者信息、消費狀態(tài)以及重新消費信息,與事件主表通過事件ID關(guān)聯(lián);
(3)大事件主表,包含大事件體,與事件主表通過事件ID關(guān)聯(lián);
(4) 事件天表,表結(jié)構(gòu)與事件主表相同,存放消費完畢的事件;
(5)消費記錄天表;
(6)大事件天表;
事件生命周期
(1)新寫入的事件和消費記錄會進入主表;
(2)當事件寫入超過1天,且事件的所有消費方都消費成功后,事件及所有消費記錄會從主表移動到天表中;
(3)當事件某個消費方需要重新消費之前消費成功的事件時,事件及所有消費記錄會從天表移回到主表中;
(4) 每天的某個時間,事件清理服務(wù)會將7天前的那張?zhí)毂砬蹇?,例如今天?月11號,那么就會清空2月4號的所有天表。
3. 外部索引
事件發(fā)送歷史列表、事件索引查詢和事件重投是事件中心運維平臺的主要功能。其中索引查詢功能的查詢速度快、查詢結(jié)果準確,用戶反饋一直比較好。

索引配置
用戶在修改事件定義時,可以為其中任意基礎(chǔ)類型字段配置為“查詢字段”,事件中心會在運行時解析該字段的值,并創(chuàng)建索引;一個事件中的每個查詢字段都會對應一條索引;即使沒有配置查詢字段,也會生成一條包含時間戳的索引,用于已發(fā)送事件的排序和分頁。

索引結(jié)構(gòu)
事件中心的索引為KV結(jié)構(gòu),使用Lindorm的寬表存儲,按使用場景分為兩種類型:
(1)不包含查詢字段的索引;
(2)Key格式為 HASH(租戶id_事件code)_env_發(fā)送時間差值_事件ID;
(3)Value為事件ID、事件頭;
(4)包含查詢字段的索引;
(5)Key格式為 HASH(租戶id_事件Code_字段路徑_索引值)_env_發(fā)送時間差值_事件ID;
(6) Value為事件ID、事件頭;
其中
(1)發(fā)送時間差值 = Long.MAX_VALUE - 發(fā)送時間毫秒數(shù),用于按發(fā)送時間倒序展示;
(2)字段路徑是json path格式,例如 $.bizNo;
查詢性能
通過目前事件中心運維平臺99%的查詢都可以在毫秒級別返回結(jié)果,Lindorm索引行數(shù)在十億級別。
五、總結(jié)
本文介紹了事件驅(qū)動架構(gòu)在供應鏈執(zhí)行鏈路的應用背景和實踐過程,并介紹了NBF事件中心產(chǎn)品的設(shè)計和部分實現(xiàn)。目前事件中心每日事件發(fā)送量峰值在千萬級別,平穩(wěn)度過了雙11、雙12、年貨節(jié)等流量高峰。
參考鏈接:
[1]https://www.infoq.cn/video/xXxlmqhTH5owSDRSx52p
[2]https://martinfowler.com/articles/201701-event-driven.html
[3]https://book.douban.com/subject/34464806/
[4]https://book.douban.com/subject/26989027/















 
 
 












 
 
 
 