京東APP百億級(jí)的車與商品關(guān)系數(shù)據(jù)檢索實(shí)踐
?導(dǎo)讀
本文主要講解了京東百億級(jí)商品車型適配數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)以及怎樣實(shí)現(xiàn)適配接口的高性能查詢。通過京東百億級(jí)數(shù)據(jù)緩存架構(gòu)設(shè)計(jì)實(shí)踐案例,簡(jiǎn)單剖析了jimdb的位圖(bitmap)函數(shù)和lua腳本應(yīng)用在高性能場(chǎng)景。希望通過本文,讀者可以對(duì)緩存的內(nèi)部結(jié)構(gòu)知識(shí)有一定了解,并且能夠以最小的內(nèi)存使用代價(jià)將位圖(bitmap)靈活應(yīng)用到各個(gè)高性能實(shí)際場(chǎng)景。
名詞解釋:
- jimdb-基于redis改造升級(jí),京東中間件團(tuán)隊(duì)自研的一個(gè)高性能key-value數(shù)據(jù)庫(kù),多數(shù)功能與redis一致。
背景
整個(gè)汽車行業(yè)特殊性,對(duì)于零配件有一個(gè)很強(qiáng)的對(duì)口特性,不同車使用的零配件(例如:輪胎、機(jī)油、三濾、雨刮、火花塞等)規(guī)格型號(hào)不一樣。在售賣汽車零配件的時(shí)候,不能像3C家電、服飾,需要結(jié)合用戶具體車輛信息,推薦適合的配件商品?;诖嗽?,京東自建人車檔案模型并且利用算法清洗出百億級(jí)的車型-零配件的適配關(guān)系數(shù)據(jù),最終形成“人->車-〉貨”關(guān)系鏈路,解決“人不識(shí)貨”的問題。具體使用場(chǎng)景如下圖:
圖1.1京東商詳推薦商品
圖1.2京東加購(gòu)彈窗推薦商品
數(shù)據(jù)模型
“人-> 車->貨”關(guān)系的核心鏈路是由人(京東用戶)、乘用車和SKU這三部分組成。
首先,用戶在京東APP的商搜頁(yè)、商詳頁(yè)多個(gè)位置都可以選擇自己的車型信息進(jìn)行綁定(例如:圖2.1,京東商詳綁車入口位置“+添加愛車”按鈕),建立“人車檔案”數(shù)據(jù)。
圖2.1.京東商詳綁車入口位置
圖2.2.京東商搜綁車入口位置
其次,運(yùn)營(yíng)在后臺(tái)管理系統(tǒng)中將商品與車型進(jìn)行綁定,建立“商品與車型關(guān)系”數(shù)據(jù)(商品與車型的關(guān)系數(shù)據(jù)量級(jí)在百億級(jí)別)。
最終,購(gòu)買商品的時(shí)候,京東推薦系統(tǒng)可以通過用戶自己綁定的車型推薦出適合該車型的商品。具體商品適配車型數(shù)據(jù)模型,見圖2.3。
名詞解釋:
- SKU-Stock Keeping Unit(庫(kù)存量單位),KU是指一款商品,每款都有出現(xiàn)一個(gè)SKU,便于電商品牌識(shí)別商品。例如:商品詳情頁(yè)的每個(gè)型號(hào)/規(guī)格都會(huì)對(duì)應(yīng)一個(gè)SKU。
乘用車-涵蓋了轎車、微型客車以及不超過9座的輕型客車。乘用車下細(xì)分為轎車、MPV和SUV等。
圖2.3京東商品適配車型數(shù)據(jù)模型
緩存結(jié)構(gòu)設(shè)計(jì)
基于前面兩個(gè)部分的介紹,我們可以了解到整個(gè)商品搜索適配推薦存在兩個(gè)最核心問題。第一、百億級(jí)商品適配車型數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)設(shè)計(jì),盡可能的占用資源成本最??;第二、商詳通過用戶車型來搜索適配商品時(shí),必須保證接口性能的TP999位于毫秒級(jí)。最終技術(shù)選型的時(shí)候,采用了jimdb的位圖(bitmap)函數(shù)來進(jìn)行數(shù)據(jù)存儲(chǔ)。
名詞解釋:
- TP999-單位時(shí)間內(nèi),99.9%請(qǐng)求接口響應(yīng)時(shí)間小于等于該值。例如:1秒鐘內(nèi)1000個(gè)請(qǐng)求,對(duì)這1000個(gè)請(qǐng)求按照響應(yīng)時(shí)間由低到高排名,第999位的響應(yīng)時(shí)間是20ms。最終TP999就是20ms。
3.1位圖(bitmap)結(jié)構(gòu)
位圖(bitmap)是通過最小的單位bit來進(jìn)行0或者1的設(shè)置,表示某個(gè)元素對(duì)應(yīng)的值或者狀態(tài)。一個(gè)bit的值是0或者1;也就是說一個(gè)bit能存儲(chǔ)的最多信息是2。
- 位(bit):計(jì)算機(jī)內(nèi)部數(shù)據(jù)存儲(chǔ)的最小單位,例如:11001100是一個(gè)八位二進(jìn)制數(shù)。
- 字節(jié)(byte):計(jì)算機(jī)中數(shù)據(jù)處理的基本單位,習(xí)慣上用大寫B(tài)來表示,1B(byte,字節(jié))=8bit。
圖3.1位圖(bitmap)內(nèi)部結(jié)構(gòu)
3.2位圖(bitmap)數(shù)據(jù)寫流程
位圖(bitmap)是基于jimdb的SDS(簡(jiǎn)單動(dòng)態(tài)字符串)類型的一系列位操作,遵循jimdb的SDS特性,例如:位圖(bitmap)最大長(zhǎng)度512M,最大可以存儲(chǔ)232位。以下是“big”字符串的SDS結(jié)構(gòu)示例:
圖3.2.1
SDS(簡(jiǎn)單動(dòng)態(tài)字符串)為了保證性能采用了空間預(yù)分配的策略:空間預(yù)分配用于優(yōu)化SDS的字符串增長(zhǎng)操作。SDS的API對(duì)一個(gè)SDS進(jìn)行修改并且需要對(duì)SDS進(jìn)行空間擴(kuò)展的時(shí)候,程序不僅會(huì)為SDS分配修改所必須要的空間,還會(huì)為SDS分配額外的未使用空。具體預(yù)分配流程圖如下:
圖3.2.2
1)創(chuàng)建SDS簡(jiǎn)單字符串預(yù)分配空間為:偏移量/8+1。
2)剩余空間不足時(shí),預(yù)分配空間流程。
3.3壓縮商品與車關(guān)系緩存
商品適配車型關(guān)系(百億級(jí)數(shù)據(jù)量)
商品與車關(guān)系緩存存儲(chǔ)過程中,采用了商品SKU作為KEY,全量車型ID的偏移量(采用偏移量是為降低內(nèi)存消耗)作為VALUE值來進(jìn)行存儲(chǔ)。
全量車型ID大約有幾十萬(wàn)的數(shù)據(jù)量,極限情況下一個(gè)商品SKU可以適配幾十萬(wàn)輛車,很容易造成緩存大KEY的問題,為此我們進(jìn)行了偏移量(全量車型ID對(duì)應(yīng)的自增ID)的分段處理。具體是按照:SKU作為緩存KEY的基礎(chǔ)上,追加一個(gè)分段標(biāo)記數(shù)字作為新KEY,每個(gè)偏移量都會(huì)按照分段范圍對(duì)應(yīng)一個(gè)分段標(biāo)記數(shù)字。例如:偏移量1?50000,對(duì)應(yīng)緩存KEY為SKU+0;偏移量50001?100000,對(duì)應(yīng)緩存KEY為SKU+1,其它偏移量以此類推,這樣就保證了一個(gè)SKU即使適配所有車輛也不會(huì)出現(xiàn)緩存大KEY的情況。
BitMap緩存結(jié)構(gòu)底層使用SDS簡(jiǎn)單字符串,為了保證性能采用了預(yù)分配空間的策略(如圖3.2.2,“緩存BitMap內(nèi)部存儲(chǔ)流程圖”的2)中虛線框圈選),這樣在緩存商品與車關(guān)系的時(shí)候浪費(fèi)了大量的緩存空間。為此我們調(diào)整了偏移量存儲(chǔ)順序,首先獲取到需要緩存的車型內(nèi)最大的偏移量,保證同一個(gè)緩存KEY第1次創(chuàng)建SDS簡(jiǎn)單字符串(如圖3.2.2,“緩存BitMap內(nèi)部存儲(chǔ)流程圖”的1)中虛線框圈選)后,不再進(jìn)行第2次空間擴(kuò)容,這樣來最大限度的提升緩存利用率,起到壓縮空間目的。緩存數(shù)據(jù)關(guān)系流程如下:
3)設(shè)置分段最大的偏移量,保證后續(xù)新增偏移量不再擴(kuò)容空間。
4)設(shè)置分段較小的偏移量。
全量車型ID是定長(zhǎng)7位的數(shù)字,如果用它作為偏移量將消耗內(nèi)存巨大,所以采用對(duì)應(yīng)自增ID作為偏移量。最終在bitmap緩存的商品SKU與車的適配關(guān)系緩存結(jié)構(gòu)如下圖:
3.3商品與車緩存結(jié)構(gòu)圖
5)spuId用{}括起來表示緩存路由(Lua腳本中同一次請(qǐng)求,數(shù)據(jù)必須在緩存同一個(gè)分片上,否則會(huì)丟失數(shù)據(jù))。POP商品spuId是SKU的產(chǎn)品ID,自營(yíng)商品spuId是SKU的MainSkuId。
備注:
- 自營(yíng)商品MainSkuId可能發(fā)生變化,所以我們接入了商品變化MQ消息,實(shí)時(shí)調(diào)整SKU與車適配關(guān)系的存儲(chǔ)位置。
- 京東商詳頁(yè)面中每個(gè)不同的規(guī)格/型號(hào)分別對(duì)應(yīng)不同的SKU,但是它們都對(duì)應(yīng)同一個(gè)SpuId或者M(jìn)ainSkuId。
緩存架構(gòu)設(shè)計(jì)
商品與車的關(guān)系數(shù)據(jù)量每天都在不斷增長(zhǎng),要求緩存架構(gòu)設(shè)計(jì),需要支持集群橫向/縱向擴(kuò)容和來滿足業(yè)務(wù)發(fā)展以及高可用性。整個(gè)緩存架構(gòu)體系主要有前端、京東養(yǎng)車商品與車關(guān)系層和存儲(chǔ)三部分組成。
“商品與車關(guān)系緩存架構(gòu)”層核心包括:1、“集群路由”層,實(shí)現(xiàn)了集群橫向擴(kuò)容,保證數(shù)據(jù)量增長(zhǎng)的時(shí)候,緩存容量也能跟上。2、“分片路由”層,保證搜索的底層數(shù)據(jù)的分片相同,避免數(shù)據(jù)丟失。
“存儲(chǔ)”層核心包括:1、實(shí)現(xiàn)了緩存壓縮,參見3.3壓縮商品與車關(guān)系緩存。2、單元化實(shí)現(xiàn)跨區(qū)域?yàn)?zāi)備,保障大促系統(tǒng)穩(wěn)定性。
具體商品與車關(guān)系緩存架構(gòu)如下:
商品與車關(guān)系緩存架構(gòu)圖
6)集群路由,通過商品類型或者商品編號(hào)(POP商品)路由到不同緩存集群,便于橫向擴(kuò)展,每個(gè)集群?jiǎn)畏制拗疲鉀Q分片超過限制問題。
7)分片路由,保障Lua腳本搜索數(shù)據(jù)的底層數(shù)據(jù)集群分片相同,避免數(shù)據(jù)丟失。其中自營(yíng)商品和POP商品的路由分別是main_sku_id和product_id。
8)自營(yíng)商品緩存集群,單元化實(shí)現(xiàn)跨區(qū)域?yàn)?zāi)備,采用自研DRC(Data Replication Center)數(shù)據(jù)同步機(jī)制。
9)POP商品緩存集群,通過商家編號(hào)拆分為兩個(gè)子集群。
高性能搜索
基于BitMap(位圖)緩存的商品與車關(guān)系數(shù)據(jù),商詳調(diào)用接口的內(nèi)部實(shí)現(xiàn)采用了Lua腳本來降低網(wǎng)絡(luò)開銷,保障整個(gè)接口的性能。以下是搜索接口的流程圖:
5.1商詳搜索商品與車適配關(guān)系流程圖
10)商詳調(diào)用接口的時(shí)候,要傳兩個(gè)參數(shù)。第1個(gè)參數(shù)是全量車型ID列表,大約5個(gè)全量車型ID。第2個(gè)參數(shù)是商品SKU列表,SKU的數(shù)量極限超過200個(gè)。最后全量車型ID與商品SKU組合為上千個(gè)商品與車的關(guān)系后,再到百億級(jí)適配關(guān)系去搜索看是否匹配的。如果不匹配返回適配商品,反之則返回不適配。
5.2商詳搜索商品與車適配關(guān)系Lua代碼
Lua腳本減少了應(yīng)用服務(wù)器與緩存服務(wù)器的交互,降低了網(wǎng)絡(luò)開銷的時(shí)間,達(dá)到提升搜索服務(wù)的性能。以下是Lua腳本具體代碼:
5.3商詳搜索商品與車適配關(guān)系接口性能
基于以上緩存設(shè)計(jì)和Lua腳本的使用,整個(gè)接口T999小于13ms。具體的接口性能監(jiān)控如下圖:
總結(jié)
整個(gè)緩存結(jié)構(gòu)設(shè)計(jì)的時(shí)候,使用BitMap(位圖)來存儲(chǔ)數(shù)據(jù)。解析SDS的內(nèi)部存儲(chǔ)流程,通過存儲(chǔ)流程機(jī)制避開預(yù)分配空間節(jié)點(diǎn),最大限度的利用緩存空間,避免資源浪費(fèi)。采用Lua腳本來實(shí)現(xiàn)數(shù)據(jù)的適配搜索,降低網(wǎng)絡(luò)開銷,進(jìn)一步提升接口的性能。希望此文對(duì)大家后續(xù)設(shè)計(jì)類似場(chǎng)景有一定的幫助和啟發(fā)。