驚了!1 萬屬性、100 億數據、10 萬吞吐,這樣的系統架構是怎么扛住的?
有一類業務場景,數據沒有固定的模式(schema)存儲,卻有著海量的數據行數,該如何從架構層面實現這類業務的存儲與檢索呢?比如要處理 1 萬種屬性、100 億條數據,還得支撐每秒 10 萬次的吞吐,今天就和大家聊聊 “電商商品多維度信息業務” 的架構設計實踐。
一、背景與業務介紹
電商平臺的核心數據
電商平臺涵蓋眾多商品品類,像數碼產品、服裝鞋帽、家居用品、美妝護膚、食品飲料等,每個品類又包含諸多子品類。而無論哪個品類,“商品信息” 都是最核心的數據,這就類似一個大型的線上商品集市。
各分類商品信息的特點
經常網購的人很容易發現,這類平臺的商品信息有以下特點:
- 不同品類的屬性差異極大,數碼產品和服裝的屬性完全不同,就連手機和電腦的屬性也不一樣,目前屬性數量已接近一萬種;
- 數據量極為龐大,達到 100 億級別;
- 每個屬性都存在查詢需求,各組合屬性也可能有組合查詢需求,比如手機要查詢品牌、內存、價格范圍,服裝要查詢尺碼、顏色、材質,家居用品要查詢風格、尺寸、功能等;
- 吞吐量很高,每秒能達到幾十萬次。
那該如何解決 100 億數據量、1 萬種屬性、多屬性組合查詢以及每秒 10 萬次并發查詢的技術難題呢?我們一步步來分析。
二、最容易想到的初始方案
每個電商公司都是從小規模發展起來的,先不考慮并發量和數據量,看看如何先解決:
- 屬性擴展性需求;
- 多屬性組合查詢需求。畢竟公司初期并發量和數據量都不大,得先把業務問題解決。
業務存儲需求的滿足方式
最開始,業務只有手機這一個品類,商品表可能是這樣設計的:product(tid, uid, brand, model, memory, price)
多屬性組合查詢需求的滿足方式
最容易想到的是通過組合索引來滿足查詢需求,比如:
- index_1(brand, model)
- index_2(model, memory)
- index_3(brand, price)
業務擴展后的存儲與查詢問題
隨著業務發展,新增了服裝類別,存儲問題可以通過新增若干屬性來解決,此時商品表變成:
product(tid, uid, brand, model, memory, price, size, color, material)其中,brand、model、memory、price是手機類別的屬性,size、color、material是服裝類別的屬性。
對于查詢需求,由于跨業務屬性一般沒有組合查詢需求,只能建立若干組合索引來滿足服裝類別的查詢需求。但想想看,要覆蓋所有兩屬性、三屬性查詢,得建多少索引,這顯然不是長久之計,業務越來越多時,這種方式就難以維系了。
三、垂直拆分的思路與問題
新增屬性是一種擴展方式,新增表也是一種方式,垂直拆分是常見的存儲擴展方案。
可以這樣操作:
- product_phone(tid, uid, brand, model, memory, price)(手機商品表)
- product_clothes(tid, uid, size, color, material, price)(服裝商品表)
垂直拆分帶來的問題
在業務多樣、數據量和吞吐量都很大的情況下,垂直拆分存在諸多問題:這些表以及對應的服務由不同部門維護,看似各業務靈活性強、研發閉環,但這恰恰是問題的開端:
- 商品 ID(tid)如何規范?
- 屬性如何規范?
- 按商家 ID(uid)查詢(查詢商家發布的所有商品)該怎么辦?
- 按時間查詢(最新上架的商品)該怎么辦?
- 跨品類查詢(比如首頁搜索框)該怎么辦?
- 技術范圍擴散,有的用 MongoDB 存儲,有的用 MySQL 存儲,有的自研存儲;
- 重復開發了不少組件;
- 維護成本過高。就像大型電商平臺的商品表,不可能一個類目一個表,電商商品信息表也一樣。
四、行業最佳實踐:三大中心服務
第一:統一商品中心服務
對于平臺型電商公司,可能有多個品類,各品類有很多異構數據的存儲需求,無需糾結是分還是合,統一基礎數據和基礎服務是很好的實踐(這里針對平臺型業務)。
異構數據的統一存儲方式
要將不同品類、異構的數據統一存儲,可采用以下方法:
- 全品類通用屬性統一存儲;
- 單品類特有屬性,通過品類類型與通用屬性的 JSON 來存儲。
更具體的設計是,商品表結構為:product(tid, uid, time, name, cate, subcate, xxid, ext)。其中:
- 一些通用字段被單獨抽取出來存儲;
- 通過cate、subcate、xxid來定義ext的含義;
- 通過ext來存儲不同業務線的個性化需求。
tid | uid | time | cateid | ext |
1 | 1 | 123 | 招聘 | {"job":"driver","salary":8000,"location":"bj"} |
2 | 1 | 456 | 二手 | {"type":"iphone","money":3500} |
例如,手機商品的ext可以是:{"brand":"Apple","model":"iPhone 15","memory":"256G","price":6999};服裝商品的ext可以是:{"size":"L","color":"blue","material":"cotton","price":299}。
商品數據有 100 億條,分 256 個庫,通過ext存儲異構業務數據,使用 MySQL 存儲,上層搭建一個商品中心服務,并用 Redis 做緩存,這樣一個并不復雜的架構,就解決了業務的大問題。這就是電商平臺最核心的商品中心服務 PMC(Product Management Center)。
新問題的出現
解決了海量異構數據的存儲問題后,又出現了新問題:
- 每條記錄的ext內的鍵(key)都需要重復存儲,占用大量空間,能否壓縮存儲?
- 品類 ID(cateid)已不足以描述ext內的內容,品類有不確定的層級,ext能否具備自描述性?
- 能否隨時增加屬性,保證擴展性?
解決完海量異構數據的存儲問題,接下來要解決類目的擴展性問題。
第二:統一類目屬性服務
每個業務有多少屬性、這些屬性的含義、值的約束等,如果耦合到商品服務里顯然不合理,那該怎么辦呢?
可以抽象出一個統一的類目、屬性服務,單獨管理這些信息,并且商品庫ext字段里的 JSON 鍵,統一用數字表示,以減少存儲空間。
商品表只存儲元信息,不涉及業務含義。比如,JSON 里的鍵不再是 “brand”“model”“size” 這樣的長字符串,而是用數字 1、2、3、4 代替。這些數字的含義、所屬子分類、值的校驗約束,統一存儲在類目、屬性服務里。
tid | uid | time | cateid | ext |
1 | 1 | 123 | 招聘 | {"1":"driver","2":8000,"3":"bj"} |
2 | 1 | 456 | 二手 | {"4":"iphone","5":3500} |
類目表存儲業務信息以及約束信息,與商品表解耦。這個表會對商品中心服務里ext字段的數字鍵進行解釋,比如:
圖片
- 數字 1 代表 “brand”,屬于數碼品類下的手機子品類,其值必須是品牌枚舉值;
- 數字 4 代表 “size”,屬于服裝品類下的上衣子品類,其值必須是尺碼枚舉值(S、M、L 等)。
這樣,原來商品表的ext擴展屬性就變成了:{"1":"Apple","2":"iPhone 15","3":"256G","4":6999}和{"5":"L","6":"blue","7":"cotton","8":299}(服裝商品),鍵和值都有了統一約束。
此外,如果ext里某個鍵的值不是通過正則校驗,而是枚舉值,就需要有一個枚舉表來對值進行限定校驗。比如,當ext為{"5":"XL","6":"blue","7":"cotton","8":299}時,因為鍵 5 對應的屬性(服裝、上衣尺碼字段)的值需要是固定枚舉值(S、M、L 等),而 “XL” 不符合,所以這個ext是不合法的,合法的應該是{"5":"L","6":"blue","7":"cotton","8":299}。
另外,類目屬性服務還能記錄類目之間的層級關系,比如:
- 一級類目有數碼、服裝、家居等;
- 數碼下有二級類目手機、電腦等;
- 手機下有三級類目蘋果手機、華為手機、小米手機等。
類目服務解釋了商品數據,描述了品類層級關系,保證了各類目屬性的擴展性,也保證了各屬性值的合理性校驗,這就是電商平臺另一個統一的核心服務 CMC(Category Management Center)。
這就類似于電商系統里的商品屬性擴展服務:
- 品類層級關系對應電商里的類別層級體系;
- 屬性擴展對應電商里各類別商品的屬性;
- 枚舉值校驗對應屬性的枚舉值,比如手機品牌有蘋果、華為、小米等。
通過品類服務,解決了鍵的壓縮、描述、擴展以及值的校驗、品類層級等問題,但還有一個問題沒解決:每個品類下商品的屬性不同,查詢需求也不同,如何解決 100 億數據量、1 萬種屬性的檢索與聯合檢索需求呢?
第三:統一檢索服務
當數據量很大時,不同屬性上的查詢需求,不可能通過組合索引來滿足所有查詢需求,“外置索引,統一檢索服務” 是常用的實踐方法:
- 數據庫提供 “商品 ID” 的正排查詢需求;
- 所有非 “商品 ID” 的個性化檢索需求,統一通過外置索引來滿足。
圖片
元數據與索引數據的操作遵循以下規則:
- 對商品進行商品 ID(tid)正排查詢,直接訪問商品服務;
- 對商品進行修改時,商品服務通知檢索服務,同時修改索引;
- 對商品進行復雜查詢時,通過檢索服務來滿足需求。
這個檢索服務承擔了電商平臺 80% 的請求,不管請求來自 PC 還是 APP,也不管是來自首頁、分類頁、搜索頁、商品列表頁還是詳情頁,最終都會轉化為一個檢索請求。
搜索引擎架構說明
為了應對 100 億級別的數據量、幾十萬級別的吞吐量以及業務線各種復雜的檢索查詢,擴展性是設計的重點:
圖片
- 統一的代理層作為入口,由于其無狀態性,增加機器就能擴充系統性能;
- 統一的結果聚合層,同樣因為無狀態性,增加機器也能擴充系統性能;
- 搜索內核檢索層,服務和索引數據部署在同一臺機器上,服務啟動時可將索引數據加載到內存,請求訪問時從內存中讀取數據,訪問速度很快。為滿足數據容量的擴展性,索引數據進行了水平切分,增加切分份數就能無限擴展性能;為滿足一份數據的性能擴展性,同一份數據進行了冗余,理論上增加機器就能無限擴展性能。系統時延方面,100 億級別商品檢索,包含請求分合、拉鏈求交集等操作,從聚合層可以做到 10 毫秒返回。
在商品業務中,一致性不是主要矛盾,檢索服務會定期全量重建索引,以確保即使數據存在不一致,也不會持續很長時間


























