隨着蘇寧業務的高速發展,大數據平臺對海量的業務數據分析愈來愈具備挑戰,尤爲是在精確去重、複雜 JOIN 場景下,如用戶畫像、UV、新老買家、留存、流失用戶等。算法
圖片來自 Pexelssql
隨着蘇寧業務的高速發展,大數據平臺對海量的業務數據分析愈來愈具備挑戰,尤爲是在精確去重、複雜 JOIN 場景下,如用戶畫像、UV、新老買家、留存、流失用戶等。數據庫
蘇寧大數據平臺目前 OLAP 分析的整體架構是將時序化的數據採用 Druid+ClickHouse、非時序化採用 PostGreSQL、固化場景採用 Hbase+phoenix、明細數據採用 Elasticsearch 分析。數組
基於 Druid 咱們有 HyperLogLog 非精確去重算法,具備很是優異的空間複雜度 O(m log2log2N),空間的佔用隨着基數的增加變化不大,但統計存在必定的誤差。網絡
基於其餘引擎咱們經常使用的精確去重算法通常是 GROUP BY 後 count distinct 操做,GROUP BY 會帶來大量的 shuffle 操做,佔用大量的磁盤和 IO,其性能較爲低下。架構
下面將爲你們揭開蘇寧如何整合 RoaringBitmap 進行高效精確去重架構方案的神祕面紗。併發
RoaringBitmap 在蘇寧的應用實踐app
爲什麼選擇 RoaringBitmap分佈式
首先簡單爲你們介紹下 RoaringBitmap,32 位的 RoaringBitmap 的是由高 16 位的 Key 和低 16 位的 Value 組成,Key 和 Value 經過下標一一對應。ide
Key 數組保持有序存儲在 roaring_array_t 中,方便二分查找。低 16 位的 Value 存儲在 Container 中,Container 共有三種。
RoaringBitmap 對建立何種 Container 有本身的優化策略,在默認建立或元素個數小於 4096 的時候建立的是 Array Container。
它是動態擴容的數組,適合存放稀疏數據,超過最大容量 4096 時,會自動轉換爲 Bitmap Container。
當元素超過 4096 時 Array Container 的大小佔用是會線性增加,可是 Bitmap Container 的內存空間並不會增加,始終仍是佔用 8 K。
還有一種是 Run Container,只有在調用 runOptimize() 方法纔會觸發,會和 ArrayContainer、BitmapContainer 比較空間佔用大小,而後選擇是否轉換。
Run Container 佔用的存儲大小看數據的連續性,上下限範圍 [4 Bytes, 128 KB]。
近年來,大數據技術獲得了快速的發展,各類開源技術給大數據開發人員帶來了很大的便利,在衆多的技術中之因此選擇 RoaringBitmap,是由於它的存儲空間低和運算效率高。
RoaringBitmap 的存儲是經過 bit 來標識狀態,通過壓縮後存儲,據估算蘇寧 6 億會員若是是常規的數組來存儲佔用空間約爲 2.2G,而 RoaringBitmap 存儲僅須要 66MB,大大下降的存儲的空間,下降企業的成本。
RoaringBitmap 是經過位運算(如 AND、OR、ANDNOT 等)進行的,在計算能力上也至關驚人。
咱們在基於 PostGresql+Citus 作過與 count distinct 的對比測試,發現 RoaringBitmap 的統計耗時是 count distinct 的近 1/50。
原生的 RoaringBitmap 只存儲整形數據,32 位的 RoaringBitmap 最大的數據存儲量是 2147483647。
對於會員之類的能夠採用,像訂單、流量這樣的數據量能夠採用 64 位的 RoaringBitmap,在性能上 32 位的效率在同等條件下要優於 64 位。
蘇寧擁有海量的業務數據,天天都有大量的離線和實時計算任務,採用 RoaringBitmap 技術不只大大節約了存儲的成本,計算的效率也獲得了顯著的改善。
應用場景
①會員相關指標計算
RoaringBitmap 在會員相關指標的分析中有着許多重要的應用場景,好比會員的新、老買家、留存、復購、活躍這些指標均要用到精確去重的統計方式。
蘇寧目前有 6 億會員,像新、老買家這樣的指標計算都是拿當前的買家與全量的歷史買家進行比對,如何快速的精確的分析出計算結果,在沒有引入 RoaringBitmap 以前是一個較大的挑戰。
②精確營銷
給目標用戶羣推送優惠的商品提升公司的銷售額已是電商公司採用廣泛的精準營銷手段。
可是如何在海量的用戶行爲日誌中第一時間進行人羣構建、客羣洞察、再到精準地廣告投放是有必定難度的。
若是採用離線計算方案其時效性不能保障,可能在這期間就丟失了目標客戶,在此場景下,高效、精確的計算出目標人羣尤其重要。
在引入 RoaringBitmap 後,在海量的數據中對受衆人羣進行全面深刻的畫像,精準投放廣告,最終幫助企業構建了完整的數字化營銷閉環。
基於 PostgreSQL 實現的 RoaringBitmap
蘇寧在對非時序化的海量數據進行分析的場景,採用的是分佈式 HTAP 數據庫 PostgreSQL+Citus 的架構方案。
咱們將 RoaringBitmap 與 Citus 作了整合,將 RoaringBitmap 融合進了 Citus 集羣,而具體的體現就是表中的一個 bitmap 列,以下圖所示:
下面簡單介紹下以 PostgreSQL+Citus +RoaringBitmap 的技術架構方案來實現會員新、老買家的場景。
數據字典
在進行 RoaringBitmap 的存儲和計算的以前,咱們首先要構建一個全局字典表,此表就是將要轉化的維度維值跟 int 或 long 進行一個映射關係。
將這個映射關係存儲在全局字典表中,RoaringBitmap 的 32 位和 64 位選擇根據實際的數據量進行抉擇。
流程設計
總體的設計流程可分爲三步:
模型建立
數據攝入
數據模型建立流程圖
①模型的建立、數據初始化、以及查詢咱們採用的基於 Citus 的存儲過程實現方式,經測試基於存儲過程的方式比經常使用的 SQL 方式在性能方面有所提高。
②分片表設計:模型中的元素是有維度、指標、bitmap 組成,Citus 目前支持三種類型的表,分別爲本地表、參考表以及分片表,分別應用在不一樣的場景。
Citus 支持 Hash 和 Append 的方式進行分片,以新老買家爲例,咱們以會員的 member_id 進行 Hash 分片。
分片表設計的不只解決了 T 級別的數據存儲問題,也能夠根據分片進行並行計算最後再彙總,提升計算效率。
③Cube_bitmap 表的建立是基於模型的,在後臺咱們有收集用戶的查詢方式,根據採集的樣本數據咱們會根據 Cost 自動的建立 Cube 用於加速。
④數據分析的數據源咱們會根據 Cost 計算從預計算結果、Cube_bitmap 或模型 bitmap 表中獲取。
數據攝入流程圖
數據攝入流程如上圖:
①數據字典同步:全量和增量的模型攝入時候須要同步更新全局字典表。
②模型 bitmap 表邏輯處理(以會員爲例):
第一步:模型表和字典表經過設置的業務主鍵 Key 進行關聯。
第二步:獲取模型增量維度對應的會員 bitmap 數據信息,可根據 rb_or_agg(rb_build(ARRAY [b.id :: INT])) 獲取 。
第三步:將模型 bitmap 表裏當天的 (flag=1) 和前一天 (flag=2) 統計的 bitmap 數據進行 rb_or_agg(bitmap) 操做,數據整合後做爲當天的 flag=2 數據插入到 bitmap 表中。
第四步:日全量統計表只有 flag+statis_date+bitmap 字段,主要統計當天的用戶和歷史用戶 bitmap 狀況,統計 flag=1 的當天 bitmap 數據。
模型 bitmap 表與會員表進行關聯 bitmap 取 rb_or_agg(rb_build(ARRAY[b.id :: INT]))。
第五步:日全量統計表統計 flag=2 的當天 bitmap 數據,從自身表中獲取當天 flag=1 和昨天統計的 flag=2 的數據而後作 rb_or_agg(bitmap)。
③Cube_bitmap、預聚合結果表的源來自於數據模型表,在此基礎上作加速處理。
數據查詢流程圖
數據分析如上圖:
①根據要查詢的維度進行 Cost 分析判斷,最終路由到預計算結果表、Cube_bitmap 表、模型表進行數據分析。
②從模型 bitmap 表或 cube_bitmap 表獲取 bitmap_cur 和 bitmap_sum,從全量 bitmap 表中獲取 bitmap_all 數據(flag=2 而且日期是查詢日期的前一天)。
後續的 bitmap 位運算可在 bitmap_cur、bitmap_sum 和 bitmap_all 中進行。
應用舉例
業務場景以下圖:
第一步:將買家的 ID 做爲數據字典的信息,與對應的 int 或 long 造成關係映射存入全局字典表。
第二步:統計天天的線上、線下的新老買家,統計維度根據渠道(線上和線下)+tag(1 當天 2 歷史)+日期。
天天有兩條統計信息,一個是當天的用戶買家 bitmap 集合,一個是歷史的用戶買家 bitmap 集合。
次日統計基於第一天統計的集合和當天的集合作 rb_or_agg,造成一個新的當天曆史 bitmap 集合(結果存儲在 Bitmap_Table_A)。
第三步:基於統計維度(品類+渠道)+tag+日期來統計新老買家狀況,天天也會有兩條統計信息,一個是當天的一個是歷史的,當天統計的是全部的品類和渠道作的 group by 統計,統計 bitmap 集合打上標籤爲 flag=1,歷史 flag=2 是基於前一天曆史加上當天統計的集合作 rb_or_agg,造成一個新的當天曆史 bitmap 集合(結果存儲在 Bitmap_Table_B)。
場景一:0428 線上新買家
統計 0428 線上新買家實則就是 bitmap 集合 {A,D} 和 bitmap 集合 {A,C} 進行 rb_andnot_cardinality 位運算,結果爲 {D},新買家的數量爲 1。
場景二:0428 線上空調新買家
統計 0428 線上空調新買家則就是 bitmap 集合 {C ,A} 和 bitmap 集合 {C} 進行 rb_andnot_cardinality 位運算,結果爲 {A},新買家的數量爲 1。
0428 線上冰洗新買家則是 bitmap 集合 {D} 和 bitmap 空集合作 rb_andnot_cardinality 位運算,結果爲 {D},數量爲 1。
場景三:0428 線上空調新買家中有多少是線上新買家
統計則根據和 Bitmap_Table_A 和 Bitmap_Table_B 作 rb_and_cardinality 操做,則拿 bitmap 集合 {A} 和 bitmap 集合 {{A,C}} 進行 rb_andnot_cardinality 位運算,結果爲空集,數量爲 0。
0428 線上冰洗新買家則根據 bitmap 集合 {D} 和 bitmap 集合 {A,C} 進行 rb_andnot_cardinality 位運算,運算結果 bitmap 集合爲 {D},數量爲 1。
0428 線上新買家品類分佈即爲:基於 Bitmap_Table_B 表,0428 線上品類有冰洗 {D} 和空調 {A},基於 Bitmap_Table_A 表統計線上歷史買家爲 {A,C}。
線上新買家冰洗則拿 {D} 和 {A,C} 作 rb_andnot_cardinality 後的集合爲 {D},數量爲 1。
線上新買家空調則是拿 {A} 和 {A,C} 作 rb_andnot_cardinality 後的集合爲空集,數量爲 0。
不足與挑戰
基於 PostgreSQL+Citus 的 RoaringBitmap 技術方案,bitmap 集合之間的位運算性能表現的較爲卓越,但在不少業務場景須要高基數的 bitmap 集合進行位運算。
基於 Citus 咱們分析發現,在位運算的時候 CPU 利用率處於低位,後期咱們也針對基於 Citus 作了優化。
如 bitmap 下壓到 Work 運算下降 CN 運算量,建立 cube 下降基數,在必定的程度了提升了效率,然在 Ctius 下的 CPU 始終沒有獲得充分利用。
ClickHouse 的併發 MPP+SMP 這種執行方式能夠很充分地利用機器的集成資源,但當時看了 ClickHouse 尚未提供 bitmap 相關的接口,不能直接加以應用,如何將 RoaringBitmap 融合到 ClickHouse 是一個挑戰。
RoaringBitmap 與 ClickHouse 的整合
在計算引擎中 ClickHouse 算是後起之秀,是一個列導向數據庫,原生的向量化執行引擎,其存儲是採用 Wired Tiger 的 LSM 引擎。
目前蘇寧的大數據已將 ClickHouse 引入並改造,開發了相關的 RoaringBitmap 接口, 用來支撐業務交互式查詢。
基於 ClickHouse 的 RoaringBitmap 方案計算過程大幅簡化,查詢時候的 IO、CPU、MEM、網絡資源都顯著下降,而且不隨着數據規模而現行增長。
基於 ClickHouse 咱們開發了 RoaringBitmap 相關的接口,其支持的 Function 函數有:
bitmapBuild
bitmapToArray
bitmapMax
bitmapMin
bitmapAnd
bitmapOr
bitmapXor
bitmapAndnot
bitmapCardinality
bitmapAndCardinality
bitmapOrCardinality
bitmapAndnotCardinality 等
它們用於支撐各類場景的運算,其相關的接口開發還在不斷的完善中。
將來展望
爲了將基於 ClickHouse 的 RoaringBitmap 方案推廣到公司的更多業務和場景中,咱們在作不斷優化和完善。
目前正着手於如下的嘗試:
ClickHouse 目前不支持 64 位的 bitmap,正在嘗試按 hash 值進行分區,每一個分區單獨計算,可輕易將分區進行橫向疊加支持到 long 長度。
全局字典表在高基數下構建成本較大,佔用較多資源也耗時較大,後續可根據業務場景將數據字典表最大程度複用,同時考慮在無需跨 segment 聚合時候,適用這個列的 segment 字典替代。
全鏈路監控的完善,可根據 query_id 進行各個環節的耗時分析,便於優化和問題的定位。