
一 業務背景
電商平臺供應鏈的業務場景非常複雜,技術中臺需要支援非常複雜且不斷變化的業務需求,構建了數量繁多且緊密耦合的業務鏈路,為技術架構的維護帶來了壓力。
1 問題描述

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

在基於請求/響應方式的服務化架構中,上游服務按照約定的RPC介面呼叫下游服務,這樣有一個比較嚴重的問題:上游服務作為資料(例如業務單據)的生產者,強依賴了作為資料消費方的下游服務所定義的介面,導致上游服務自身無法沉澱介面和資料標準。

一種更合理的方案是依賴倒置:由上游服務定義SPI,下游服務實現SPI,這樣,上游服務終於有機會沉澱出自身的介面和資料標準,不再需要適配各個下游服務的介面,而是由下游服務的開發者按照介面文件來做實現。但這種設計仍然無法解決執行時上游服務仍然依賴下游服務的問題,下游服務的可用性、一致性、冪等效能力會直接影響上游服務的相關指標及實現方式,需要上下游服務開發者一起對齊方案,在出問題時一起解決。

使用事件驅動設計可以實現契約定義和執行時的全面解耦:上游服務可以沉澱自己的事件契約,在執行時無論是上游服務還是下游服務都只依賴事件Broker,下游服務的可用性和一致性等問題由事件Broker來保障。
廣播能力
在供應鏈中臺這樣複雜的微服務架構中,關鍵的上游服務往往有多個下游服務,上游服務一般需要順序或併發呼叫所有的下游服務來完成一次完整的呼叫。

上游服務的開發者會面臨多個難題:
1. 服務的可用性會被下游服務影響;
2. 服務的RT自己無法控制;
3. 下游服務之間的一致性如何保障;
4. 如何實現一套可靠的重試機制;
而下游服務的開發者也有自己的問題:
1. 每接入一個上游服務都需要跟服務開發者排期:誰來答疑,什麼時候聯調,什麼時候上線;
2. 上游流量如何做過濾,高峰流量是否能抗得住;
3. 如何滿足上游服務的可用性及RT要求;
使用事件驅動架構天然可以避免上述問題:

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

1 事件傳送與消費流程
事件結構

執行時的一條事件例項由三部分組成:
1. 事件ID:全域性唯一,格式為“邏輯庫編號_月內傳送日期_uuid”,例如01_11_f75ec4fb347c49c4bc3e93xxxxxxxx,其中邏輯庫編號用於邏輯庫路由,日期用於事件清理;
2. 事件Head:包含事件元資訊,如trace資訊、傳送者資訊、事件大小、MetaQ資訊等,參考示例:

3. 事件Body:JSON格式,包含由使用者已定義的事件內容,事件內容要符合事件定義契約,否則會被拒絕傳送。
執行時的事件可能有多個消費方,每個消費方會產生一條消費記錄,消費記錄包含:
-
事件ID
-
消費資訊:消費狀態、消費次數、下次消費時間等
事件傳送流程
事件中心支援事務傳送和非事務傳送兩種模式,使用狀態機驅動,API設計與MetaQ的API基本一致。以下以事務傳送為例介紹傳送流程,由於非事務傳送的流程更簡單,所以不再詳細介紹。
1)事務傳送狀態機

2)事務傳送時序圖

3)異常狀態事務問詢

事件消費流程
事件消費流程也使用狀態機驅動,API相比MetaQ有一些不同:
1. 不需要再呼叫subscribe topic;
2. 新增消費過濾器EventFilter,支援按照租戶、業務流、事件維度做過濾;
3. 支援不同的事件使用不同的Listener消費;
1)事件消費狀態機

2)重試周期
事件進入消費失敗狀態後,事件中心會週期呼叫使用者Listener重新消費,消費週期以5s起始指數增加,最多重試15次,最大為5 * 214 = 81920秒(約22小時)。
3)事件消費時序圖

2 事件儲存
資料表
事件中心使用了32分庫的TDDL,按照HASH(事件ID)做分庫,每個庫上有以下幾張表:
1. 事件主表,包含傳送者資訊、事件資訊以及普通事件的事件體;
2. 事件消費記錄主表,包含消費者資訊、消費狀態以及重新消費資訊,與事件主表透過事件ID關聯;
3. 大事件主表,包含大事件體,與事件主表透過事件ID關聯;
4. 事件天表,表結構與事件主表相同,存放消費完畢的事件;
5. 消費記錄天表;
6. 大事件天表;
事件生命週期
1. 新寫入的事件和消費記錄會進入主表;
2. 當事件寫入超過1天,且事件的所有消費方都消費成功後,事件及所有消費記錄會從主表移動到天表中;
3. 當事件某個消費方需要重新消費之前消費成功的事件時,事件及所有消費記錄會從天表移回到主表中;
4. 每天的某個時間,事件清理服務會將7天前的那張天表清空,例如今天是2月11號,那麼就會清空2月4號的所有天表。
3 外部索引
事件傳送歷史列表、事件索引查詢和事件重投是事件中心運維平臺的主要功能。其中索引查詢功能的查詢速度快、查詢結果準確,使用者反饋一直比較好。

索引配置
使用者在修改事件定義時,可以為其中任意基礎型別欄位配置為“查詢欄位”,事件中心會在執行時解析該欄位的值,並建立索引;一個事件中的每個查詢欄位都會對應一條索引;即使沒有配置查詢欄位,也會生成一條包含時間戳的索引,用於已傳送事件的排序和分頁。

索引結構
事件中心的索引為KV結構,使用Lindorm的寬表儲存,按使用場景分為兩種型別:
1. 不包含查詢欄位的索引;
2. Key格式為 HASH(租戶id_事件code)_env_傳送時間差值_事件ID;
3. Value為事件ID、事件頭;
4. 包含查詢欄位的索引;
5. Key格式為 HASH(租戶id_事件Code_欄位路徑_索引值)_env_傳送時間差值_事件ID;
6. Value為事件ID、事件頭;
其中
1. 傳送時間差值 = Long.MAX_VALUE – 傳送時間毫秒數,用於按傳送時間倒序展示;
2. 欄位路徑是json path格式,例如 $.bizNo;
查詢效能
透過目前事件中心運維平臺99%的查詢都可以在毫秒級別返回結果,Lindorm索引行數在十億級別。
五 總結
本文介紹了事件驅動架構在供應鏈執行鏈路的應用背景和實踐過程,並介紹了NBF事件中心產品的設計和部分實現。目前事件中心每日事件傳送量峰值在千萬級別,平穩度過了雙11、雙12、年貨節等流量高峰。
參考連結:
[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/
資料分析系統之資料管理與資料倉庫
點選閱讀原文檢視詳情
關鍵詞
資料
問題
業務
索引
架構