VOP消息倉庫演進之路 | 如何設(shè)計一個億級企業(yè)消息平臺
作者:京東零售 李孟冬
VOP作為京東企業(yè)業(yè)務(wù)對外的API對接采購供應(yīng)鏈解決方案平臺,一直致力于從企業(yè)采購數(shù)字化領(lǐng)域出發(fā),發(fā)揮京東數(shù)智化供應(yīng)鏈能力,通過產(chǎn)業(yè)鏈上下游耦合與鏈接,有效助力企業(yè)客戶的成本優(yōu)化與資產(chǎn)效能提升。本文將介紹VOP如何通過億級消息倉庫系統(tǒng)來保障上千家企業(yè)KA客戶與京東的數(shù)據(jù)交互。
引言
消息(倉庫)作為電商業(yè)務(wù)場景必不可少的核心功能,自VOP上線以來,就開始了建設(shè)和演進迭代之路。截止目前,VOP消息倉庫已接入200+內(nèi)部消息端,對外提供80+消息,服務(wù)3000+企業(yè)客戶,覆蓋商品、地址、發(fā)票、訂單、售后、物流等VOP所有業(yè)務(wù)場景。
消息系統(tǒng)中,一般有兩種消費模式:服務(wù)端推送和客戶端拉取。本文除了對于消息倉庫的技術(shù)架構(gòu)演進做對應(yīng)敘述,重點介紹當(dāng)前客戶端拉取的消息倉庫建設(shè)實踐經(jīng)驗。
客戶調(diào)用場景
以商品消息為例,京東企業(yè)業(yè)務(wù)目前大約有5600W+商品,這些商品涉及基本信息、價格、庫存等的變更,客戶側(cè)會通過消息API主動獲取商品變更消息,并通過查詢實時商品信息接口來獲取對應(yīng)信息,同步本地商品庫,業(yè)務(wù)處理完畢后,刪除這一批商品類消息,定時循環(huán)。其他類消息同理,不多加描述。
消息倉庫V1.0
和我們所了解的系統(tǒng)一樣,隨著業(yè)務(wù)發(fā)展和企業(yè)客戶規(guī)模的增多,消息倉庫整體架構(gòu)和底層存儲系統(tǒng)都逐漸出現(xiàn)瓶頸。特別在于數(shù)據(jù)庫方面,畢竟在高并發(fā)讀寫的場景下很大一部分工作是圍繞數(shù)據(jù)庫展開的,所以前期兩次的升級迭代主要需要解決的問題也是如何提升數(shù)據(jù)庫容量。
雖然最初我們也通過讀寫分離等手段來有效降低數(shù)據(jù)庫的負載,提升系統(tǒng)容量和穩(wěn)定性,但是其缺點也是極其明顯:主從延遲、從庫數(shù)據(jù)量有限、TPS高 等問題無法妥善解決。
并且,隨著618、1111等各種活動的開展,且VOP側(cè)客戶的不斷增加,消息激增成為我們不得不盡快面對的問題,限流、緩存等手段隨能保證系統(tǒng)的高可用及并發(fā)能力。但是消息大量積壓、消費水平有限、消息同步不及時等問題越發(fā)嚴(yán)重,隨之帶來的就是對業(yè)務(wù)有損,所以我們在評估后,對系統(tǒng)進行升級,通過分析最掣肘我們的核心原因還是在于數(shù)據(jù)庫。(此時消息表行數(shù)億行,容量超過10G)
消息倉庫V2.0
因此在讀寫分離無法不能滿足我們的業(yè)務(wù)需要時(已經(jīng)歷過數(shù)據(jù)歸檔),分庫分表的模式也就需要登上舞臺了。具體如何分庫分表,注意事項等我就不多加贅述了,感興趣推薦翻閱菜鳥積分系統(tǒng)的分庫分表實踐???https://mp.weixin.qq.com/s/uFgSe59XP7RoXLTmm3u0KQ??
分庫新舊流程對比
分庫新舊流程比對切換依據(jù)(供參考)
- 根據(jù) ducc和 clientId決定是否寫入到新庫,ducc(bizMsgTransDbJson)中配置了切換開關(guān)、白名單、黑名單和分流范圍
- 使用新庫寫入時,根據(jù) clientId.hashCode % dbSource.size,得出使用哪個 dbSource
- 客戶讀取時,先從舊庫查出,若無數(shù)據(jù),則再讀取一遍新庫,dbSource選取方式同上
- 客戶刪除時,判斷刪除 ID是否大于一萬億(1000000000000),大于取新庫,小于取舊庫
由于是多master的架構(gòu),分庫分表除了包含讀寫分離模式的所有優(yōu)點外,還可以解決讀寫分離架構(gòu)中無法解決的 TPS 過高的問題,同時分庫分表理論上是可以無限橫向擴展的,也解決了讀寫分離架構(gòu)下從庫數(shù)量有限的問題。
當(dāng)然在實際的工程實踐中一般需要提前預(yù)估好容量,因為數(shù)據(jù)庫是有狀態(tài)的,如果發(fā)現(xiàn)容量不足再擴容是非常麻煩的,應(yīng)該盡量避免。
在分庫分表的模式下可以通過不啟用查詢從庫的方式來避免主從延遲的問題,也就是說讀寫都在主庫,因為在分庫后,每個 master 上的流量只占總流量的 1/N,大部分情況下能扛住業(yè)務(wù)的流量,從庫只作為 master 的備份,在主庫宕機時執(zhí)行主從切換頂替 master 提供服務(wù)使用。
優(yōu)化后的前期情況是美好的,無論從客戶角度還是從內(nèi)部消費水平都得到了大幅提升,其跳點和峰值消息下高TPS影響CPU等問題都得到了解決,整個消息倉庫性能和穩(wěn)定性趨于穩(wěn)定。
為什么說前期情況好,相信大家都有所預(yù)料了,雖然分庫大幅提升了系統(tǒng)整體的吞吐能力和穩(wěn)定性,但是由于前期的容量評估問題(業(yè)務(wù)增長加?。┘氨旧憩F(xiàn)有架構(gòu)的局限性(單體應(yīng)用),在倉庫穩(wěn)定運行一年左右,又出現(xiàn)了一些顯而易見的痛點問題:
痛點問題
- 海量數(shù)據(jù):19年客戶量及商品品類(商品量級)的大幅增加,及最初分庫時提升了消息數(shù)據(jù)的存儲時長由2-3天提升至7天(原因:考量政府、銀行等客戶重保期間不消費消息的空檔期,但是后期驗證空檔期長達月維度),消息倉庫的流量出現(xiàn)了頻繁翻倍的增長,數(shù)據(jù)不均衡的情況也逐漸顯現(xiàn)出來;
- 字段擴展:隨著業(yè)務(wù)不斷的演進,消息內(nèi)容也逐漸復(fù)雜(如售后消息 會附帶各環(huán)節(jié)信息,整個JSON消息體較大),入庫或存在字段長度限制,調(diào)整字段較難;
- 高可用&擴展性:原有單體架構(gòu)的情況,會有熱點數(shù)據(jù)的沖擊及熱點商品類消息數(shù)據(jù)對訂單類、對賬類消息數(shù)據(jù)的寫入和同步帶來嚴(yán)重的時延問題及服務(wù)性能跳點問題。
- 運維成本高:由于面向廣大開發(fā)者,因此系統(tǒng)必須兼顧各種各樣的網(wǎng)絡(luò)環(huán)境問題,開發(fā)者能力問題等。企業(yè)對接客戶常常來咨詢消息量及消息消費情況,內(nèi)部無對應(yīng)的審計數(shù)據(jù)可供參考。
目標(biāo)
不破不立,為避免消息問題長期以來的頻繁影響及其他系統(tǒng)雷同的消息需求,我們急需打造一套可復(fù)用可擴展的企業(yè)消息中心,在滿足業(yè)務(wù)的同時,還需綜合考慮可用性、低成本、高吞吐和強擴展性,并且在遷移過程中保證消息不丟失和客戶無感知。
方案分析
經(jīng)過多方調(diào)研和排查之后,初步選取了2種存儲方案:Mysql+es和MongoDB。
我們在存儲成本、開發(fā)運維成本、性能對比三個方面進行評估Mysql+es和MongoDB的方案。(僅供參考,具體仍需根據(jù)自身業(yè)務(wù)評估)
- 存儲成本:MongoDB存儲優(yōu)勢明顯——數(shù)據(jù)壓縮和無冗余存儲,相比Mysql+es會減少50%以上的總數(shù)據(jù)容量。
- 開發(fā)運維成本:MongoDB不需要數(shù)據(jù)同步,減少開發(fā)和運維難度;字段調(diào)整方面Mysql+es的架構(gòu)下對于業(yè)務(wù)附帶抖動風(fēng)險,DDL相關(guān)問題風(fēng)險高,易出錯;MongoDB開發(fā)維護成本,存儲架構(gòu)簡單,無數(shù)據(jù)一致性壓力;擴容方面,MongoDB支持隨時動態(tài)無腦擴容,基本不存在上限問題,但是Mysql的擴容需要保證hash一致,遷移數(shù)據(jù)灰度等情況,周期長且高概率存在對業(yè)務(wù)影響。
- 性能對比:經(jīng)過壓測,同樣的4C8G的機器配置下,MySQL和MongoDB在大數(shù)據(jù)量下寫性能基本一致。MySQL的讀性單分片約6000QPS左右,ES的性能只有800QPS左右。而 MongoDB 單分片地讀性能在3萬QPS左右,遠高于MySQL和 ES 的性能。
消息倉庫V3.0
沒有完美的架構(gòu),只有剛好的架構(gòu),沒有滿足一切的架構(gòu),只有滿足目標(biāo)的架構(gòu)
綜上分析,MongoDB不僅完全滿足業(yè)務(wù)需求,同時在其他方面也優(yōu)于其他方案,因此最終選用MongoDB分片集群作為了最底層的數(shù)據(jù)存儲方式,并對系統(tǒng)架構(gòu)重新梳理,分為四個階段:消息接收階段,消息中轉(zhuǎn)階段,消息寫入階段 ,消息可視化階段,主要職責(zé)如下:
- 消息接收階段(vop-worker):該系統(tǒng)僅關(guān)注不同消息源的接入,當(dāng)前已接入中臺近百個消息源,且依賴BTE任務(wù)平臺、訂單&商品池&主數(shù)據(jù)&消息中心等服務(wù),通過過濾,清洗,封裝等手段封裝需入庫的業(yè)務(wù)消息數(shù)據(jù)中轉(zhuǎn)發(fā)出。
- 消息中轉(zhuǎn)階段(JMQ集群):將消息中轉(zhuǎn)出來,分級管控,當(dāng)前分為四級,以此解決核心消息消費不及時,部分時段CPU內(nèi)存飆升的問題。分級別設(shè)置消費線程數(shù),低級別消息不影響高級別消息消費。低級別消息具備降級能力。
- 消息寫入階段(vop-msg-store):消息寫入階段,批量雙寫,MongoDB+ES(支持多維度的運維審計查詢及數(shù)據(jù)導(dǎo)出)。MongoDB解決tps10000+、數(shù)據(jù)量日均5億+、多查詢條件和數(shù)據(jù)分布不均勻的問題,解決數(shù)據(jù)庫無法支撐租戶數(shù)據(jù)均勻和消息內(nèi)容可擴展的問題;創(chuàng)建mongo表,設(shè)置租戶id和事件id索引、設(shè)置租戶id的分片規(guī)則、設(shè)置唯一索引和超時時間45天。ES解決消息運維過程中,審計、核查等問題。
- 消息可視化階段(vop-support-platform):解決對客戶生產(chǎn)/消費能力無認知、全局消息不可控和消息可視化的問題。并且數(shù)據(jù)可視化的不斷完善又會反哺架構(gòu)的可用性提升,為后續(xù)我們設(shè)立的優(yōu)化專題打下堅實的數(shù)據(jù)基礎(chǔ)。
補充:MongoDB分片集群無單點故障的原因——當(dāng) MongoDB 被部署為一個分片集群時,應(yīng)用程序通過驅(qū)動,訪問路由節(jié)點, 也就是 Mongos 節(jié)點 Mongos 節(jié)點會根據(jù)讀寫操作中的片鍵值,把讀寫操作分發(fā)的特定的分片執(zhí)行,然后把分片的執(zhí)行結(jié)果合并,返回給應(yīng)用程序。那集群中的數(shù)據(jù)是如何分布的呢?這些元數(shù)據(jù)記錄在 Config Server 中,這也是一個高可用的復(fù)制集。每個分片管理集群中整體數(shù)據(jù)的一部分,也是一個高可用復(fù)制集。此外,路由節(jié)點,也就是 Mongos 節(jié)點在生產(chǎn)環(huán)境通常部署多個。這樣,整個分片集群沒有任何單點故障。
消息倉庫V3.0給我們帶來的成果也是十分顯著,高標(biāo)準(zhǔn)達到了預(yù)期的目標(biāo):
- 支撐日均消息寫入量5億,現(xiàn)支持6wTPS和1wQPS
- TP99從100ms提升至40ms,在高吞吐量情況下性能表現(xiàn)平穩(wěn)
- 新架構(gòu)邊界清晰,新需求不涉及核心系統(tǒng)的改造
- 數(shù)據(jù)有效期7天提升至45天
- It成本0增長
- 消息可視化方面大幅提升運維效率,已全面開放技術(shù)客服使用
消息倉庫V3.0+(回首往事)
之前我們一直鉚足勁的往前追趕,現(xiàn)在系統(tǒng)穩(wěn)定,為實現(xiàn)未來客戶和商品的增量對消息倉庫無影響&穩(wěn)定運行3年+的目標(biāo),我們決定在限制資源有限性的情況下,轉(zhuǎn)換角度思考問題和優(yōu)化目標(biāo)。隨即我們針對消息數(shù)據(jù)開展了幾個專題的治理,核心圍繞流量治理、系統(tǒng)穩(wěn)定性建設(shè)、降低成本三個方面出發(fā)。
鎖定目標(biāo)定后,剩下的只是邁步朝它慢慢走下去。
流量治理(峰值情況下裁剪億級消息量)
1)優(yōu)化業(yè)務(wù)場景,從源頭減少調(diào)用量,梳理系統(tǒng)流程,優(yōu)化無效數(shù)據(jù)源的接入,歷史空跑邏輯等。
2)a、無效客戶管控(LoadingCache),由于其他端外界客戶接入VOP,存在部分不消費消息的無效客戶,需進行主動屏蔽,以此解決無效客戶消息中轉(zhuǎn)存儲的問題。b、緩存,減少耗時操作等等。
3)消息過濾器(jimdb),通過防重控制+時間窗口對客戶未消費且重復(fù)sku進行去重,以此解決客戶消息消費延遲,客戶消息量大,重復(fù)消息多,客戶系統(tǒng)重啟后消息量巨大的問題,并大幅減少我側(cè)MongoDB存儲數(shù)據(jù)量。
這里補充一個小插曲,在流量治理過程中,我們也在數(shù)據(jù)中發(fā)現(xiàn)了一些問題,并作為指導(dǎo)我們產(chǎn)品優(yōu)化的數(shù)據(jù)支撐,通過技術(shù)手段進行優(yōu)化和處理。**如:通過數(shù)據(jù)分析,我們在整個消費過程中,部分客戶(如:聯(lián)通)消費較慢或者無效消費導(dǎo)致信息同步不及時的問題,因此從技術(shù)角度出發(fā)與客戶技術(shù)側(cè)溝通,通過建立自動補推功能,來提升客戶與京東的同步率,即通過自助補推功能,來輔助客戶同步異常情況下二次同步,以價格變更為例,通過客戶下單價格不一致,來自助補推價格變更消息,以此挽回由于客戶同步異常導(dǎo)致異常的訂單,提升客戶成單率, 進一步提升整體GMV產(chǎn)出。
這里也給我?guī)硭伎?,無論引入還是自研,無論架構(gòu)還是工具,落到實處,真實解決業(yè)務(wù)中的問題,在降本增效中帶來價值,不論大小,均為創(chuàng)新。
系統(tǒng)穩(wěn)定性(解決cpu毛刺及分片熱點問題)
1)提高資源利用率:優(yōu)化部分代碼結(jié)構(gòu),如:通過list.contains()轉(zhuǎn)化為set.contains()將其時間復(fù)雜度由O(n)降至O(1)、比較耗時或者不必放在主流程中執(zhí)行的任務(wù)異步處理、單個寫轉(zhuǎn)化為批量寫、減少傳統(tǒng)重量級鎖使用操作系統(tǒng)互斥量帶來的性能損耗等等,以此解決大流量下,機器 cpu飆升影響整體性能的情況。
2)a、主動降級隊列:前面有提到MongoDB設(shè)置租戶id的分片規(guī)則,所以在單客戶頻繁進行大量商品池操作時,會發(fā)出該客戶的大量商品出入池消息,由于當(dāng)前整個系統(tǒng)吞吐性能極佳,所以在寫入MongoDB時,會造成單分片的熱點寫問題,所以設(shè)定主動降級隊列。具體實現(xiàn)為 在消息倉庫多租戶場景下,不影響整體客戶的情況下,配置化(某客戶+配置詳消息類型)的進行異??蛻舻倪^載流量隔離,來保證底層存儲介質(zhì)的服務(wù)質(zhì)量,即異常流量超過閾值則進入降級隊列。 b、JMQ消費線程調(diào)優(yōu)等
降低成本(非活動期間,白天消息量級相對晚上較少)
serverless自動擴縮:采用秒級消息接收量閾值和機器CPU閾值來觸發(fā)自動擴縮策略,通過調(diào)優(yōu)后非大促期間消息倉庫整體資源成本下降52%。
小結(jié)
目前的消息倉庫從正式服役到通過不斷的迭代和更新已踏入V3.0+版本,成功經(jīng)歷了四次大促,系統(tǒng)各項性能指標(biāo)穩(wěn)定。以最近的大促為例,22年雙十一開門紅,消息相關(guān)接口性能穩(wěn)定,MongoDB整體寫入QPS 2w ,查詢QPS 4.3w。 并且通過評估能完全應(yīng)對接下來獨立場切換帶來的消息增長情況。
在消息倉庫整體架構(gòu)演進升級的過程中,雖然基礎(chǔ)中間件給我們提供了各種高可用的能力,但可用性最終還是要回歸我們業(yè)務(wù)架構(gòu)本身。業(yè)務(wù)系統(tǒng)需要根據(jù)各平臺業(yè)務(wù)特性盡可能選擇最優(yōu)的可用性方案,并在系統(tǒng)架構(gòu)中遵循一些原則,如最大限度減少關(guān)鍵依賴;消除擴容瓶頸;預(yù)防和緩解流量峰值;過載時做好優(yōu)雅降級等等。而且更重要的一點是,我們需要時刻思考架構(gòu)如何支撐業(yè)務(wù)的長期增長。
后續(xù)有時間也可以給大家同步一下我們另一個數(shù)據(jù)推送平臺。(一鍵三連催更)
展望
- 保持工匠精神,精益求精:在保證系統(tǒng)穩(wěn)定性和擴展性的同時,以業(yè)務(wù)為重點,持續(xù)踐行數(shù)據(jù)驅(qū)動的實踐方法,進一步提升客戶和VOP雙方系統(tǒng)的各類消息同步率,通過技術(shù)手段不斷優(yōu)化產(chǎn)品,提升客戶搜索體驗及下單成功率。
- 消息數(shù)據(jù)治理:無論消息推送還是消息拉取方面都有一個極其明顯的特征,在客戶系統(tǒng)消費水平足夠好的情況下,大部分數(shù)據(jù)是會在幾秒內(nèi)進行寫刪各一次,兩次操作完成這條數(shù)據(jù)就失去了意義。(以前天為例,有3000W+消息數(shù)據(jù)生產(chǎn)消費幾乎同速率)在這種場景,使用任何存儲介質(zhì)本身就不合理,就像是在存儲介質(zhì)中插入一條幾乎不會去讀的數(shù)據(jù)。這樣生命周期極短的數(shù)據(jù)放在存儲介質(zhì)中,不僅資源浪費,也造成存儲介質(zhì)成為系統(tǒng)未來的瓶頸。 考慮服務(wù)器本身的成本問題,可以針對升級過濾器或者參考計算機三級存儲體系結(jié)構(gòu)的思路,未來將大量的此類消息事務(wù)在Memory內(nèi)完成,其他消息按照原有方式進行操作,該方式下千萬級消息事務(wù)在Memory內(nèi)完成,節(jié)省大量服務(wù)器資源。
- 推送方式標(biāo)準(zhǔn)化:輪詢狀態(tài)下,數(shù)據(jù)的實時性終究依賴于客戶應(yīng)用的輪詢間隔時間,該方式下,API調(diào)用效率低且浪費機器資源,如何結(jié)合業(yè)務(wù)側(cè)推動數(shù)據(jù)推送標(biāo)準(zhǔn)化,給客戶提供實時可靠的雙向數(shù)據(jù)交換通道,大大提升API調(diào)用效率也是我們后續(xù)著重考慮的方向。
本次就寫到這,零零散散,很多細節(jié)點(如:如何線程調(diào)優(yōu)提升吞如,大流量消息下的數(shù)據(jù)埋點及分析等等)無法完全描繪,如有問題,歡迎交流。希望文章中的消息倉庫的演進經(jīng)驗,給大家?guī)硪恍┦斋@,或者說,大家不妨思考一下你們會采用何種技術(shù)方案和手段來解決演進中遇到的問題。