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