蘇寧6億會(huì)員是如何做到快速精確分析的?
原創(chuàng)圖片來自 Pexels
【51CTO.com原創(chuàng)稿件】隨著蘇寧業(yè)務(wù)的高速發(fā)展,大數(shù)據(jù)平臺(tái)對(duì)海量的業(yè)務(wù)數(shù)據(jù)分析越來越具有挑戰(zhàn),尤其是在精確去重、復(fù)雜 JOIN 場(chǎng)景下,如用戶畫像、UV、新老買家、留存、流失用戶等。
蘇寧大數(shù)據(jù)平臺(tái)目前 OLAP 分析的總體架構(gòu)是將時(shí)序化的數(shù)據(jù)采用 Druid+ClickHouse、非時(shí)序化采用 PostGreSQL、固化場(chǎng)景采用 Hbase+phoenix、明細(xì)數(shù)據(jù)采用 Elasticsearch 分析。
基于 Druid 我們有 HyperLogLog 非精確去重算法,具有非常優(yōu)異的空間復(fù)雜度 O(m log2log2N),空間的占用隨著基數(shù)的增長(zhǎng)變化不大,但統(tǒng)計(jì)存在一定的偏差。
基于其他引擎我們常用的精確去重算法一般是 GROUP BY 后 count distinct 操作,GROUP BY 會(huì)帶來大量的 shuffle 操作,占用大量的磁盤和 IO,其性能較為低下。
下面將為大家揭開蘇寧如何整合 RoaringBitmap 進(jìn)行高效精確去重架構(gòu)方案的神秘面紗。
RoaringBitmap 在蘇寧的應(yīng)用實(shí)踐
為何選擇 RoaringBitmap
首先簡(jiǎn)單為大家介紹下 RoaringBitmap,32 位的 RoaringBitmap 的是由高 16 位的 Key 和低 16 位的 Value 組成,Key 和 Value 通過下標(biāo)一一對(duì)應(yīng)。
Key 數(shù)組保持有序存儲(chǔ)在 roaring_array_t 中,方便二分查找。低 16 位的 Value 存儲(chǔ)在 Container 中,Container 共有三種。
RoaringBitmap 對(duì)創(chuàng)建何種 Container 有自己的優(yōu)化策略,在默認(rèn)創(chuàng)建或元素個(gè)數(shù)小于 4096 的時(shí)候創(chuàng)建的是 Array Container。
它是動(dòng)態(tài)擴(kuò)容的數(shù)組,適合存放稀疏數(shù)據(jù),超過最大容量 4096 時(shí),會(huì)自動(dòng)轉(zhuǎn)換為 Bitmap Container。
當(dāng)元素超過 4096 時(shí) Array Container 的大小占用是會(huì)線性增長(zhǎng),但是 Bitmap Container 的內(nèi)存空間并不會(huì)增長(zhǎng),始終還是占用 8 K。
還有一種是 Run Container,只有在調(diào)用 runOptimize() 方法才會(huì)觸發(fā),會(huì)和 ArrayContainer、BitmapContainer 比較空間占用大小,然后選擇是否轉(zhuǎn)換。
Run Container 占用的存儲(chǔ)大小看數(shù)據(jù)的連續(xù)性,上下限范圍 [4 Bytes, 128 KB]。
近年來,大數(shù)據(jù)技術(shù)得到了快速的發(fā)展,各種開源技術(shù)給大數(shù)據(jù)開發(fā)人員帶來了很大的便利,在眾多的技術(shù)中之所以選擇 RoaringBitmap,是因?yàn)樗拇鎯?chǔ)空間低和運(yùn)算效率高。
RoaringBitmap 的存儲(chǔ)是通過 bit 來標(biāo)識(shí)狀態(tài),經(jīng)過壓縮后存儲(chǔ),據(jù)估算蘇寧 6 億會(huì)員如果是常規(guī)的數(shù)組來存儲(chǔ)占用空間約為 2.2G,而 RoaringBitmap 存儲(chǔ)僅需要 66MB,大大降低的存儲(chǔ)的空間,降低企業(yè)的成本。
RoaringBitmap 是通過位運(yùn)算(如 AND、OR、ANDNOT 等)進(jìn)行的,在計(jì)算能力上也相當(dāng)驚人。
我們?cè)诨?PostGresql+Citus 做過與 count distinct 的對(duì)比測(cè)試,發(fā)現(xiàn) RoaringBitmap 的統(tǒng)計(jì)耗時(shí)是 count distinct 的近 1/50。
原生的 RoaringBitmap 只存儲(chǔ)整形數(shù)據(jù),32 位的 RoaringBitmap 最大的數(shù)據(jù)存儲(chǔ)量是 2147483647。
對(duì)于會(huì)員之類的可以采用,像訂單、流量這樣的數(shù)據(jù)量可以采用 64 位的 RoaringBitmap,在性能上 32 位的效率在同等條件下要優(yōu)于 64 位。
蘇寧擁有海量的業(yè)務(wù)數(shù)據(jù),每天都有大量的離線和實(shí)時(shí)計(jì)算任務(wù),采用 RoaringBitmap 技術(shù)不僅大大節(jié)約了存儲(chǔ)的成本,計(jì)算的效率也得到了顯著的改善。
應(yīng)用場(chǎng)景
①會(huì)員相關(guān)指標(biāo)計(jì)算
RoaringBitmap 在會(huì)員相關(guān)指標(biāo)的分析中有著許多重要的應(yīng)用場(chǎng)景,比如會(huì)員的新、老買家、留存、復(fù)購(gòu)、活躍這些指標(biāo)均要用到精確去重的統(tǒng)計(jì)方式。
蘇寧目前有 6 億會(huì)員,像新、老買家這樣的指標(biāo)計(jì)算都是拿當(dāng)前的買家與全量的歷史買家進(jìn)行比對(duì),如何快速的精確的分析出計(jì)算結(jié)果,在沒有引入 RoaringBitmap 之前是一個(gè)較大的挑戰(zhàn)。
②精確營(yíng)銷
給目標(biāo)用戶群推送優(yōu)惠的商品提高公司的銷售額已經(jīng)是電商公司采用普遍的精準(zhǔn)營(yíng)銷手段。
但是如何在海量的用戶行為日志中第一時(shí)間進(jìn)行人群構(gòu)建、客群洞察、再到精準(zhǔn)地廣告投放是有一定難度的。
如果采用離線計(jì)算方案其時(shí)效性不能保障,可能在這期間就丟失了目標(biāo)客戶,在此場(chǎng)景下,高效、精確的計(jì)算出目標(biāo)人群尤為重要。
在引入 RoaringBitmap 后,在海量的數(shù)據(jù)中對(duì)受眾人群進(jìn)行全面深入的畫像,精準(zhǔn)投放廣告,最終幫助企業(yè)構(gòu)建了完整的數(shù)字化營(yíng)銷閉環(huán)。
基于 PostgreSQL 實(shí)現(xiàn)的 RoaringBitmap
蘇寧在對(duì)非時(shí)序化的海量數(shù)據(jù)進(jìn)行分析的場(chǎng)景,采用的是分布式 HTAP 數(shù)據(jù)庫(kù) PostgreSQL+Citus 的架構(gòu)方案。
我們將 RoaringBitmap 與 Citus 做了整合,將 RoaringBitmap 融合進(jìn)了 Citus 集群,而具體的體現(xiàn)就是表中的一個(gè) bitmap 列,如下圖所示:
下面簡(jiǎn)單介紹下以 PostgreSQL+Citus +RoaringBitmap 的技術(shù)架構(gòu)方案來實(shí)現(xiàn)會(huì)員新、老買家的場(chǎng)景。
數(shù)據(jù)字典
在進(jìn)行 RoaringBitmap 的存儲(chǔ)和計(jì)算的之前,我們首先要構(gòu)建一個(gè)全局字典表,此表就是將要轉(zhuǎn)化的維度維值跟 int 或 long 進(jìn)行一個(gè)映射關(guān)系。
將這個(gè)映射關(guān)系存儲(chǔ)在全局字典表中,RoaringBitmap 的 32 位和 64 位選擇根據(jù)實(shí)際的數(shù)據(jù)量進(jìn)行抉擇。
流程設(shè)計(jì)
整體的設(shè)計(jì)流程可分為三步:
- 模型創(chuàng)建
- 數(shù)據(jù)攝入
- 數(shù)據(jù)分析
數(shù)據(jù)模型創(chuàng)建流程圖
模型創(chuàng)建流程如上圖:
①模型的創(chuàng)建、數(shù)據(jù)初始化、以及查詢我們采用的基于 Citus 的存儲(chǔ)過程實(shí)現(xiàn)方式,經(jīng)測(cè)試基于存儲(chǔ)過程的方式比常用的 SQL 方式在性能方面有所提升。
②分片表設(shè)計(jì):模型中的元素是有維度、指標(biāo)、bitmap 組成,Citus 目前支持三種類型的表,分別為本地表、參考表以及分片表,分別應(yīng)用在不同的場(chǎng)景。
Citus 支持 Hash 和 Append 的方式進(jìn)行分片,以新老買家為例,我們以會(huì)員的 member_id 進(jìn)行 Hash 分片。
分片表設(shè)計(jì)的不僅解決了 T 級(jí)別的數(shù)據(jù)存儲(chǔ)問題,也可以根據(jù)分片進(jìn)行并行計(jì)算最后再匯總,提高計(jì)算效率。
③Cube_bitmap 表的創(chuàng)建是基于模型的,在后臺(tái)我們有收集用戶的查詢方式,根據(jù)采集的樣本數(shù)據(jù)我們會(huì)根據(jù) Cost 自動(dòng)的創(chuàng)建 Cube 用于加速。
④數(shù)據(jù)分析的數(shù)據(jù)源我們會(huì)根據(jù) Cost 計(jì)算從預(yù)計(jì)算結(jié)果、Cube_bitmap 或模型 bitmap 表中獲取。
數(shù)據(jù)攝入流程圖
數(shù)據(jù)攝入流程如上圖:
①數(shù)據(jù)字典同步:全量和增量的模型攝入時(shí)候需要同步更新全局字典表。
②模型 bitmap 表邏輯處理(以會(huì)員為例):
第一步:模型表和字典表通過設(shè)置的業(yè)務(wù)主鍵 Key 進(jìn)行關(guān)聯(lián)。
第二步:獲取模型增量維度對(duì)應(yīng)的會(huì)員 bitmap 數(shù)據(jù)信息,可根據(jù) rb_or_agg(rb_build(ARRAY [b.id :: INT])) 獲取 。
第三步:將模型 bitmap 表里當(dāng)天的 (flag=1) 和前一天 (flag=2) 統(tǒng)計(jì)的 bitmap 數(shù)據(jù)進(jìn)行 rb_or_agg(bitmap) 操作,數(shù)據(jù)整合后作為當(dāng)天的 flag=2 數(shù)據(jù)插入到 bitmap 表中。
第四步:日全量統(tǒng)計(jì)表只有 flag+statis_date+bitmap 字段,主要統(tǒng)計(jì)當(dāng)天的用戶和歷史用戶 bitmap 情況,統(tǒng)計(jì) flag=1 的當(dāng)天 bitmap 數(shù)據(jù)。
模型 bitmap 表與會(huì)員表進(jìn)行關(guān)聯(lián) bitmap 取 rb_or_agg(rb_build(ARRAY[b.id :: INT]))。
第五步:日全量統(tǒng)計(jì)表統(tǒng)計(jì) flag=2 的當(dāng)天 bitmap 數(shù)據(jù),從自身表中獲取當(dāng)天 flag=1 和昨天統(tǒng)計(jì)的 flag=2 的數(shù)據(jù)然后做 rb_or_agg(bitmap)。
③Cube_bitmap、預(yù)聚合結(jié)果表的源來自于數(shù)據(jù)模型表,在此基礎(chǔ)上做加速處理。
數(shù)據(jù)查詢流程圖
數(shù)據(jù)分析如上圖:
①根據(jù)要查詢的維度進(jìn)行 Cost 分析判斷,最終路由到預(yù)計(jì)算結(jié)果表、Cube_bitmap 表、模型表進(jìn)行數(shù)據(jù)分析。
②從模型 bitmap 表或 cube_bitmap 表獲取 bitmap_cur 和 bitmap_sum,從全量 bitmap 表中獲取 bitmap_all 數(shù)據(jù)(flag=2 并且日期是查詢?nèi)掌诘那耙惶?。
后續(xù)的 bitmap 位運(yùn)算可在 bitmap_cur、bitmap_sum 和 bitmap_all 中進(jìn)行。
應(yīng)用舉例
①業(yè)務(wù)場(chǎng)景
業(yè)務(wù)場(chǎng)景如下圖:
②設(shè)計(jì)方案
第一步:將買家的 ID 作為數(shù)據(jù)字典的信息,與對(duì)應(yīng)的 int 或 long 形成關(guān)系映射存入全局字典表。
第二步:統(tǒng)計(jì)每天的線上、線下的新老買家,統(tǒng)計(jì)維度根據(jù)渠道(線上和線下)+tag(1 當(dāng)天 2 歷史)+日期。
每天有兩條統(tǒng)計(jì)信息,一個(gè)是當(dāng)天的用戶買家 bitmap 集合,一個(gè)是歷史的用戶買家 bitmap 集合。
第二天統(tǒng)計(jì)基于第一天統(tǒng)計(jì)的集合和當(dāng)天的集合做 rb_or_agg,形成一個(gè)新的當(dāng)天歷史 bitmap 集合(結(jié)果存儲(chǔ)在 Bitmap_Table_A)。
第三步:基于統(tǒng)計(jì)維度(品類+渠道)+tag+日期來統(tǒng)計(jì)新老買家情況,每天也會(huì)有兩條統(tǒng)計(jì)信息,一個(gè)是當(dāng)天的一個(gè)是歷史的,當(dāng)天統(tǒng)計(jì)的是所有的品類和渠道做的 group by 統(tǒng)計(jì),統(tǒng)計(jì) bitmap 集合打上標(biāo)簽為 flag=1,歷史 flag=2 是基于前一天歷史加上當(dāng)天統(tǒng)計(jì)的集合做 rb_or_agg,形成一個(gè)新的當(dāng)天歷史 bitmap 集合(結(jié)果存儲(chǔ)在 Bitmap_Table_B)。
③場(chǎng)景分析
場(chǎng)景一:0428 線上新買家
統(tǒng)計(jì) 0428 線上新買家實(shí)則就是 bitmap 集合 {A,D} 和 bitmap 集合 {A,C} 進(jìn)行 rb_andnot_cardinality 位運(yùn)算,結(jié)果為 {D},新買家的數(shù)量為 1。
場(chǎng)景二:0428 線上空調(diào)新買家
統(tǒng)計(jì) 0428 線上空調(diào)新買家則就是 bitmap 集合 {C ,A} 和 bitmap 集合 {C} 進(jìn)行 rb_andnot_cardinality 位運(yùn)算,結(jié)果為 {A},新買家的數(shù)量為 1。
0428 線上冰洗新買家則是 bitmap 集合 {D} 和 bitmap 空集合做 rb_andnot_cardinality 位運(yùn)算,結(jié)果為 {D},數(shù)量為 1。
場(chǎng)景三:0428 線上空調(diào)新買家中有多少是線上新買家
統(tǒng)計(jì)則根據(jù)和 Bitmap_Table_A 和 Bitmap_Table_B 做 rb_and_cardinality 操作,則拿 bitmap 集合 {A} 和 bitmap 集合 {{A,C}} 進(jìn)行 rb_andnot_cardinality 位運(yùn)算,結(jié)果為空集,數(shù)量為 0。
0428 線上冰洗新買家則根據(jù) bitmap 集合 {D} 和 bitmap 集合 {A,C} 進(jìn)行 rb_andnot_cardinality 位運(yùn)算,運(yùn)算結(jié)果 bitmap 集合為 {D},數(shù)量為 1。
0428 線上新買家品類分布即為:基于 Bitmap_Table_B 表,0428 線上品類有冰洗 {D} 和空調(diào) {A},基于 Bitmap_Table_A 表統(tǒng)計(jì)線上歷史買家為 {A,C}。
線上新買家冰洗則拿 {D} 和 {A,C} 做 rb_andnot_cardinality 后的集合為 {D},數(shù)量為 1。
線上新買家空調(diào)則是拿 {A} 和 {A,C} 做 rb_andnot_cardinality 后的集合為空集,數(shù)量為 0。
不足與挑戰(zhàn)
基于 PostgreSQL+Citus 的 RoaringBitmap 技術(shù)方案,bitmap 集合之間的位運(yùn)算性能表現(xiàn)的較為卓越,但在很多業(yè)務(wù)場(chǎng)景需要高基數(shù)的 bitmap 集合進(jìn)行位運(yùn)算。
基于 Citus 我們分析發(fā)現(xiàn),在位運(yùn)算的時(shí)候 CPU 利用率處于低位,后期我們也針對(duì)基于 Citus 做了優(yōu)化。
如 bitmap 下壓到 Work 運(yùn)算降低 CN 運(yùn)算量,創(chuàng)建 cube 降低基數(shù),在一定的程度了提高了效率,然在 Ctius 下的 CPU 始終沒有得到充分利用。
ClickHouse 的并發(fā) MPP+SMP 這種執(zhí)行方式可以很充分地利用機(jī)器的集成資源,但當(dāng)時(shí)看了 ClickHouse 還沒有提供 bitmap 相關(guān)的接口,不能直接加以應(yīng)用,如何將 RoaringBitmap 融合到 ClickHouse 是一個(gè)挑戰(zhàn)。
RoaringBitmap 與 ClickHouse 的整合
在計(jì)算引擎中 ClickHouse 算是后起之秀,是一個(gè)列導(dǎo)向數(shù)據(jù)庫(kù),原生的向量化執(zhí)行引擎,其存儲(chǔ)是采用 Wired Tiger 的 LSM 引擎。
目前蘇寧的大數(shù)據(jù)已將 ClickHouse 引入并改造,開發(fā)了相關(guān)的 RoaringBitmap 接口, 用來支撐業(yè)務(wù)交互式查詢。
基于 ClickHouse 的 RoaringBitmap 方案計(jì)算過程大幅簡(jiǎn)化,查詢時(shí)候的 IO、CPU、MEM、網(wǎng)絡(luò)資源都顯著降低,并且不隨著數(shù)據(jù)規(guī)模而現(xiàn)行增加。
基于 ClickHouse 我們開發(fā)了 RoaringBitmap 相關(guān)的接口,其支持的 Function 函數(shù)有:
- bitmapBuild
- bitmapToArray
- bitmapMax
- bitmapMin
- bitmapAnd
- bitmapOr
- bitmapXor
- bitmapAndnot
- bitmapCardinality
- bitmapAndCardinality
- bitmapOrCardinality
- bitmapAndnotCardinality 等
它們用于支撐各種場(chǎng)景的運(yùn)算,其相關(guān)的接口開發(fā)還在不斷的完善中。
未來展望
為了將基于 ClickHouse 的 RoaringBitmap 方案推廣到公司的更多業(yè)務(wù)和場(chǎng)景中,我們?cè)谧霾粩鄡?yōu)化和完善。
目前正著手于以下的嘗試:
- ClickHouse 目前不支持 64 位的 bitmap,正在嘗試按 hash 值進(jìn)行分區(qū),每個(gè)分區(qū)單獨(dú)計(jì)算,可輕易將分區(qū)進(jìn)行橫向疊加支持到 long 長(zhǎng)度。
- 全局字典表在高基數(shù)下構(gòu)建成本較大,占用較多資源也耗時(shí)較大,后續(xù)可根據(jù)業(yè)務(wù)場(chǎng)景將數(shù)據(jù)字典表最大程度復(fù)用,同時(shí)考慮在無(wú)需跨 segment 聚合時(shí)候,適用這個(gè)列的 segment 字典替代。
- 全鏈路監(jiān)控的完善,可根據(jù) query_id 進(jìn)行各個(gè)環(huán)節(jié)的耗時(shí)分析,便于優(yōu)化和問題的定位。
作者:范東
簡(jiǎn)介:蘇寧科技集團(tuán)大數(shù)據(jù)中心架構(gòu)師,在 OLAP、OLTP 領(lǐng)域有著深刻的技術(shù)積累。目前主要負(fù)責(zé)數(shù)據(jù)中臺(tái)和數(shù)據(jù)工具平臺(tái)的架構(gòu)及性能調(diào)優(yōu)工作,在數(shù)據(jù)中臺(tái)、數(shù)據(jù)集成開發(fā)工具、數(shù)據(jù)資產(chǎn)、數(shù)據(jù)質(zhì)量和數(shù)據(jù)治理等方面擁有豐富的實(shí)戰(zhàn)經(jīng)驗(yàn)。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】