作者 | 林暉
一、業(yè)務(wù)背景
電商平臺供應(yīng)鏈的業(yè)務(wù)場景非常復(fù)雜,技術(shù)中臺需要支持非常復(fù)雜且不斷變化的業(yè)務(wù)需求,構(gòu)建了數(shù)量繁多且緊密耦合的業(yè)務(wù)鏈路,為技術(shù)架構(gòu)的維護(hù)帶來了壓力。
1. 問題描述
上圖是一個典型的業(yè)務(wù)架構(gòu),A域是上游域,B域和C域是下游域。A域在收到外部調(diào)用請求時,首先同步調(diào)用B域的服務(wù)接口完成同步業(yè)務(wù)邏輯,然后發(fā)送消息通知到MQ。C域異步消費(fèi)消息后,反向調(diào)用A域的接口查詢詳細(xì)信息,完成異步業(yè)務(wù)邏輯。
這種架構(gòu)的問題包括:
(1) A域強(qiáng)依賴B域的接口,B域接口變動會導(dǎo)致A域調(diào)用失敗,而A域無法管控B域的接口變動;
(2). C域收到消息后需要反查A域的接口,對A域形成了雙重依賴,A域接口和消息格式的任何變動及不穩(wěn)定性都會影響C域;
(3) A域的消息和接口都是瞬時數(shù)據(jù),兩者由于時間差可能不一致,增加了C域處理的復(fù)雜度(例如:C域收到的消息是單據(jù)已創(chuàng)建,調(diào)用接口時查到該單據(jù)已完結(jié));
(4) A域需要保證同步調(diào)用和消息通知的一致性,包括MQ不可用等情況發(fā)生時的容災(zāi)處理面對這些問題,我們希望應(yīng)用事件驅(qū)動架構(gòu)的特性來解耦子域,降低業(yè)務(wù)鏈路復(fù)雜度,構(gòu)建穩(wěn)定并向前兼容的事件契約,從而提升全域的穩(wěn)定性。
2. 事件驅(qū)動架構(gòu)的應(yīng)用過程
(1)重新梳理全鏈路業(yè)務(wù)流和業(yè)務(wù)活動,建立統(tǒng)一的標(biāo)準(zhǔn)語言;
(2)定義標(biāo)準(zhǔn)的事件格式和通用基礎(chǔ)字段;
(3) 各域定義包含完整業(yè)務(wù)語義、自閉包、多租戶的領(lǐng)域事件;
(4) 開發(fā)并接入一套適應(yīng)供應(yīng)鏈業(yè)務(wù)特點(diǎn)的事件系統(tǒng)(NBF事件中心);
3. 關(guān)于NBF
NBF[1] 是阿里巴巴供應(yīng)鏈中臺的基礎(chǔ)技術(shù)團(tuán)隊(duì)打造的一個技術(shù)PaaS平臺,全稱是New-Retail Business Factory,她提供了微服務(wù)FaaS框架,低代碼平臺和中臺基礎(chǔ)設(shè)施等一系列的PaaS產(chǎn)品,旨在幫助業(yè)務(wù)伙伴快速復(fù)用和擴(kuò)展中臺能力,提升研發(fā)效能和對外的商業(yè)化輸出。事件中心就是NBF系列技術(shù)產(chǎn)品中的一員。
本文首先介紹事件驅(qū)動架構(gòu)的概念及適用場景,然后會介紹事件中心產(chǎn)品的設(shè)計(jì)和實(shí)現(xiàn)。
二、什么是事件驅(qū)動架構(gòu)(EDA)
1. 領(lǐng)域事件
很多同學(xué)會將事件和消息混淆。在業(yè)務(wù)系統(tǒng)中,事件指的是領(lǐng)域事件,而消息可以是任意數(shù)據(jù)或數(shù)據(jù)片段。領(lǐng)域事件的特點(diǎn)包括:
(1)與服務(wù)接口一樣有完整的schema,并保證schema向前兼容;
(2)是業(yè)務(wù)流程的一部分,由業(yè)務(wù)動作觸發(fā),包含了完整(或部分但有獨(dú)立語義)的業(yè)務(wù)狀態(tài)變化;
(3)事件消費(fèi)者接收到事件后,相應(yīng)修改自身的業(yè)務(wù)狀態(tài),并按需發(fā)出新的事件;消費(fèi)者需要保證所有事件最終消費(fèi)成功,否則會導(dǎo)致業(yè)務(wù)流程不完整;
(4)事件需要持久化保存并長期歸檔,方便業(yè)務(wù)同學(xué)查詢、恢復(fù)中斷的業(yè)務(wù)流程、重新發(fā)起業(yè)務(wù)流程等,也方便風(fēng)控及財務(wù)分析同學(xué)做離線分析。
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īng)一個業(yè)務(wù)流中的具體活動(如采購單建單),而事件就是活動發(fā)生導(dǎo)致的結(jié)果(如采購單建單完成事件),事件內(nèi)容就是活動導(dǎo)致的完整狀態(tài)變化(如采購單+子單列表)。
3. 事件驅(qū)動架構(gòu)的優(yōu)點(diǎn)
在Fundamentals of Software Architecture[3] 以及Microservices Patterns[4]等書中描述了事件驅(qū)動架構(gòu)的一些明顯特點(diǎn),我們總結(jié)為以下幾項(xiàng):
- 高度解耦
- 廣播能力
- 純異步調(diào)用(Fire and Forget)
- 靈活擴(kuò)展
- 高處理性能
4. 事件驅(qū)動架構(gòu)能解決什么實(shí)際問題
下面我們舉幾個例子來描述事件驅(qū)動架構(gòu)的解耦和廣播能力如何幫助解決現(xiàn)實(shí)工作中的問題:
解耦能力
在基于請求/響應(yīng)方式的服務(wù)化架構(gòu)中,上游服務(wù)按照約定的RPC接口調(diào)用下游服務(wù),這樣有一個比較嚴(yán)重的問題:上游服務(wù)作為數(shù)據(jù)(例如業(yè)務(wù)單據(jù))的生產(chǎn)者,強(qiáng)依賴了作為數(shù)據(jù)消費(fèi)方的下游服務(wù)所定義的接口,導(dǎo)致上游服務(wù)自身無法沉淀接口和數(shù)據(jù)標(biāo)準(zhǔn)。
一種更合理的方案是依賴倒置:由上游服務(wù)定義SPI,下游服務(wù)實(shí)現(xiàn)SPI,這樣,上游服務(wù)終于有機(jī)會沉淀出自身的接口和數(shù)據(jù)標(biāo)準(zhǔn),不再需要適配各個下游服務(wù)的接口,而是由下游服務(wù)的開發(fā)者按照接口文檔來做實(shí)現(xiàn)。但這種設(shè)計(jì)仍然無法解決運(yùn)行時上游服務(wù)仍然依賴下游服務(wù)的問題,下游服務(wù)的可用性、一致性、冪等性能力會直接影響上游服務(wù)的相關(guān)指標(biāo)及實(shí)現(xiàn)方式,需要上下游服務(wù)開發(fā)者一起對齊方案,在出問題時一起解決。
使用事件驅(qū)動設(shè)計(jì)可以實(shí)現(xiàn)契約定義和運(yùn)行時的全面解耦:上游服務(wù)可以沉淀自己的事件契約,在運(yùn)行時無論是上游服務(wù)還是下游服務(wù)都只依賴事件Broker,下游服務(wù)的可用性和一致性等問題由事件Broker來保障。
廣播能力
在供應(yīng)鏈中臺這樣復(fù)雜的微服務(wù)架構(gòu)中,關(guān)鍵的上游服務(wù)往往有多個下游服務(wù),上游服務(wù)一般需要順序或并發(fā)調(diào)用所有的下游服務(wù)來完成一次完整的調(diào)用。
上游服務(wù)的開發(fā)者會面臨多個難題:
- 服務(wù)的可用性會被下游服務(wù)影響;
- 服務(wù)的RT自己無法控制;
- 下游服務(wù)之間的一致性如何保障;
- 如何實(shí)現(xiàn)一套可靠的重試機(jī)制;
而下游服務(wù)的開發(fā)者也有自己的問題:
- 每接入一個上游服務(wù)都需要跟服務(wù)開發(fā)者排期:誰來答疑,什么時候聯(lián)調(diào),什么時候上線;
- 上游流量如何做過濾,高峰流量是否能抗得住;
- 如何滿足上游服務(wù)的可用性及RT要求;
使用事件驅(qū)動架構(gòu)天然可以避免上述問題:
- 上下游完全解耦,上游服務(wù)只要保證將事件成功發(fā)送到Broker,無論有幾個下游消費(fèi)者,都不會影響自身的RT,也不需要考慮下游服務(wù)之間的一致性;
- 下游服務(wù)在接入新的事件時,只需要在事件管理服務(wù)中走完訂閱審批流,不需要等待事件發(fā)布者排期和聯(lián)調(diào);
- 通過事件Broker提供的事件過濾能力,下游服務(wù)只需要消費(fèi)與自身相關(guān)的事件流量(例如:天貓超市的計(jì)費(fèi)服務(wù)只需要消費(fèi)tenantId為天貓超市的采購單創(chuàng)建事件,而不需要消費(fèi)銀泰租戶的采購單創(chuàng)建事件);
- 通過事件Broker提供的事件存儲能力和重投能力,即使上游服務(wù)發(fā)送的事件流量超過了下游服務(wù)的處理能力,也只會影響下游服務(wù)的消費(fèi)延遲,不會導(dǎo)致大量請求失敗的情況。
5. 事件驅(qū)動架構(gòu)不適合什么場景
- 強(qiáng)依賴Response的場景,例如單據(jù)查詢、商品查詢;
- 對全局處理延遲敏感的場景,例如游戲、搜索;
- 要求服務(wù)之間保持強(qiáng)一致性的場景;
三、事件中心的功能設(shè)計(jì)
作為面向中臺的事件中間件,事件中心集成了消息中間件MetaQ(RocketMQ),初始使用體感也與MQ很像,但事件中心有很多不同的功能設(shè)計(jì):
(1)完善的權(quán)限控制;
(2) 支持事件契約定義以及運(yùn)行時合法性校驗(yàn);
(3) 支持大事件發(fā)送和消費(fèi)(10MB或更高);
(4)支持長期的事件歷史查詢、事件索引查詢(如單據(jù)編號、sku)、事件重投;
(5) 支持消費(fèi)周期很長的事件(如需要幾個月才能完結(jié)的入庫單);
(6)所有事件及消費(fèi)記錄的完整歸檔;
(7)以O(shè)penAPI的形式開放了事件查詢、事件重投等運(yùn)維態(tài)的功能,方便被其他系統(tǒng)集成。
四、事件中心的運(yùn)行時架構(gòu)
事件中心運(yùn)行態(tài)主要由以下部分組成:
- 事件中心服務(wù)/SDK
a) SDK:包含事件收發(fā)的主要邏輯,支持事務(wù)發(fā)送和普通發(fā)送,支持事件校驗(yàn)、壓縮、本地備份;
b) Tunnel Service:一層很薄的數(shù)據(jù)庫代理服務(wù),支持按應(yīng)用、事件、場景、IO維度的限流,支持?jǐn)?shù)據(jù)庫快速靈活擴(kuò)容;
c) Index Service:事件索引服務(wù),通過精衛(wèi)(DataX)獲取Binlog,解析為索引后寫入索引表(Lindorm)。
- 阿里中間件
a) Diamond(Nacos):包含應(yīng)用相關(guān)的全部配置信息,如發(fā)送、訂閱關(guān)系、事件定義、中間件配置等;
b) SchedulerX:調(diào)度SDK執(zhí)行事件重新發(fā)送、重新消費(fèi)、事務(wù)異常狀態(tài)問詢;
c) MetaQ:主要的事件收發(fā)管道;
d) TDDL(RDS):事件內(nèi)容及消費(fèi)記錄存儲;
e) 精衛(wèi):用于生成索引、計(jì)算延遲等異步處理邏輯;
f) Lindrom(serverless):用于存放事件外部索引,serverless模式支持按量付費(fèi)和彈性擴(kuò)容,性能比較穩(wěn)定。
下圖為簡化的運(yùn)行時架構(gòu)圖,圖中藍(lán)色線條表示事件的正常收發(fā)鏈路(事務(wù)發(fā)送),紅色線條表示事件的異常處理鏈路。
1. 事件發(fā)送與消費(fèi)流程
事件結(jié)構(gòu)
運(yùn)行時的一條事件實(shí)例由三部分組成:
(1)事件ID:全局唯一,格式為“邏輯庫編號_月內(nèi)發(fā)送日期_uuid”,例如01_11_f75ec4fb347c49c4bc3e93xxxxxxxx,其中邏輯庫編號用于邏輯庫路由,日期用于事件清理;
(2) 事件Head:包含事件元信息,如trace信息、發(fā)送者信息、事件大小、MetaQ信息等,參考示例:
(3)事件Body:JSON格式,包含由用戶已定義的事件內(nèi)容,事件內(nèi)容要符合事件定義契約,否則會被拒絕發(fā)送。
運(yùn)行時的事件可能有多個消費(fèi)方,每個消費(fèi)方會產(chǎn)生一條消費(fèi)記錄,消費(fèi)記錄包含:
- 事件ID
- 消費(fèi)信息:消費(fèi)狀態(tài)、消費(fèi)次數(shù)、下次消費(fèi)時間等
事件發(fā)送流程
事件中心支持事務(wù)發(fā)送和非事務(wù)發(fā)送兩種模式,使用狀態(tài)機(jī)驅(qū)動,API設(shè)計(jì)與MetaQ的API基本一致。以下以事務(wù)發(fā)送為例介紹發(fā)送流程,由于非事務(wù)發(fā)送的流程更簡單,所以不再詳細(xì)介紹。
1)事務(wù)發(fā)送狀態(tài)機(jī)
2)事務(wù)發(fā)送時序圖
3)異常狀態(tài)事務(wù)問詢
事件消費(fèi)流程
事件消費(fèi)流程也使用狀態(tài)機(jī)驅(qū)動,API相比MetaQ有一些不同:
(1)不需要再調(diào)用subscribe topic;
(2)新增消費(fèi)過濾器EventFilter,支持按照租戶、業(yè)務(wù)流、事件維度做過濾;
(3)支持不同的事件使用不同的Listener消費(fèi);
1)事件消費(fèi)狀態(tài)機(jī)
2)重試周期
事件進(jìn)入消費(fèi)失敗狀態(tài)后,事件中心會周期調(diào)用用戶Listener重新消費(fèi),消費(fèi)周期以5s起始指數(shù)增加,最多重試15次,最大為5 * 214 = 81920秒(約22小時)。
3)事件消費(fèi)時序圖
2 事件存儲
數(shù)據(jù)表
事件中心使用了32分庫的TDDL,按照HASH(事件ID)做分庫,每個庫上有以下幾張表:
(1)事件主表,包含發(fā)送者信息、事件信息以及普通事件的事件體;
(2) 事件消費(fèi)記錄主表,包含消費(fèi)者信息、消費(fèi)狀態(tài)以及重新消費(fèi)信息,與事件主表通過事件ID關(guān)聯(lián);
(3)大事件主表,包含大事件體,與事件主表通過事件ID關(guān)聯(lián);
(4) 事件天表,表結(jié)構(gòu)與事件主表相同,存放消費(fèi)完畢的事件;
(5)消費(fèi)記錄天表;
(6)大事件天表;
事件生命周期
(1)新寫入的事件和消費(fèi)記錄會進(jìn)入主表;
(2)當(dāng)事件寫入超過1天,且事件的所有消費(fèi)方都消費(fèi)成功后,事件及所有消費(fèi)記錄會從主表移動到天表中;
(3)當(dāng)事件某個消費(fèi)方需要重新消費(fèi)之前消費(fèi)成功的事件時,事件及所有消費(fèi)記錄會從天表移回到主表中;
(4) 每天的某個時間,事件清理服務(wù)會將7天前的那張?zhí)毂砬蹇?,例如今天?月11號,那么就會清空2月4號的所有天表。
3. 外部索引
事件發(fā)送歷史列表、事件索引查詢和事件重投是事件中心運(yùn)維平臺的主要功能。其中索引查詢功能的查詢速度快、查詢結(jié)果準(zhǔn)確,用戶反饋一直比較好。
索引配置
用戶在修改事件定義時,可以為其中任意基礎(chǔ)類型字段配置為“查詢字段”,事件中心會在運(yùn)行時解析該字段的值,并創(chuàng)建索引;一個事件中的每個查詢字段都會對應(yī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;
查詢性能
通過目前事件中心運(yùn)維平臺99%的查詢都可以在毫秒級別返回結(jié)果,Lindorm索引行數(shù)在十億級別。
五、總結(jié)
本文介紹了事件驅(qū)動架構(gòu)在供應(yīng)鏈執(zhí)行鏈路的應(yīng)用背景和實(shí)踐過程,并介紹了NBF事件中心產(chǎn)品的設(shè)計(jì)和部分實(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/