數據驅動理念已被各行各業所熟知,核心環節包括數據採集、埋點規劃、數據建模、數據分析和指標體系構建。在用戶行為數據領域,對常見的多維數據模型進行信息提煉和模型整合,可以形成一套常見的數據分析方法來發現用戶行為的內在聯繫,能更好洞察用戶的行為習慣和行為規律,幫助企業挖掘用戶數據的商業價值。 行業內... ...
一、背景介紹
數據驅動理念已被各行各業所熟知,核心環節包括數據採集、埋點規劃、數據建模、數據分析和指標體系構建。在用戶行為數據領域,對常見的多維數據模型進行信息提煉和模型整合,可以形成一套常見的數據分析方法來發現用戶行為的內在聯繫,能更好洞察用戶的行為習慣和行為規律,幫助企業挖掘用戶數據的商業價值。
行業內最早可追溯到Google Analytics埋點分析工具,國內較早開始這方面研究的是百度大數據分析平臺;隨著15年後國內大數據興起,神策的用戶行為分析平臺、GrowthingIO的增長平臺等獨立數據分析平臺公司相繼成立;18年後一些發展較快的大廠經過幾年數據積累也有了自己的分析平臺,例如美團點評的Ocean行為分析平臺、位元組的火山引擎增長分析平臺等等。
只有當數據達到一定規模才更適合用科學化的方法來提升數據分析效率,如前面所述,雖然Google和百度在這塊最早探索,但後面一些互聯網公司也是過幾年才有自己的產品,即數據產品的發展需要與實際數據規模和業務發展相符。B站最早從19年開始關註大數據建設,到現在已經有一套較為成熟的數據產品——北極星,可以實現對用戶行為數據進行埋點採集、埋點測試、埋點管理、行為數據分析等功能。行為數據分析平臺主要包括下圖所列功能模塊,本文介紹主要模塊原理和相關技術實現。
二、技術方案演進
北極星用戶行為分析(User Behavior Analysis, UBA)模塊自19年以來主要有三波迭代。
這個階段主要任務是功能實現,根據用戶前端查詢參數,提交Spark Jar作業等待返回結果。不同的分析模塊對應不同的Spark Jar作業,也對應不同加工好的用戶行為模型。數據架構如下圖所示:
雖然在一定程度上可以完成功能實現,但存在明顯弊端:
-
部分模型化:用戶維度信息需要提前加工到模型表中,後面不易變更和運維,且早期分析模型設計不支持私參查詢,即明細數據信息只保留了一部分;
-
資源自適應問題:Spark Jar任務每次啟動都需要通過YARN單獨申請資源,不同查詢條件對應的任務計算複雜度不同,但任務資源參數固定,一方面資源的申請和調配就需要花費較長時間,另一方面不能動態適應任務複雜度,就算維護一個常駐記憶體的 SparkSession供查詢任務調用,也沒法解決根據查詢任務的資源自適應問題;
-
併發受限:同一時間段查詢的請求太多,後面請求一直會等待前面請求對應Spark任務釋放所占用資源,且資源未隔離,會影響其他正常ADHOC查詢。
在實際使用中計算時間太長,單事件分析需要超過3分鐘返回結果,漏斗和路徑分析需要超過30分鐘返回結果,導致產品可用性極低,查詢穩定性和成功率不是很高,使用人數不是很多。這個階段的埋點管理和上報格式未完全規範化,所以重點還是做後者。
ClickHouse是Yandex公司於2016年開源的一個列式資料庫管理系統。Yandex的核心產品是搜索引擎,非常依賴流量和線上廣告業務,因此ClickHouse天生就適合用戶流量分析。B站於2020年開始引入ClickHouse,結合北極星行為分析場景進行重構,如下圖所示:
這裡直接從原始數據開始消費,通過Flink清洗任務將數據直接洗入ClickHouse生成用戶行為明細,可以稱作無模型化明細數據。Redis維表被用來做實時用戶屬性關聯,字典服務被用於把String類型的實體ID轉成Bigint,利用ClickHouse原生的RoaringBitMap函數對參與計算的行為人群交並差集計算。這一代實現了實時埋點效果查看,上線以來北極星產品周活人數提升了300%以上,相對於前代,性能有較大提升:
-
查詢速度極大提升:90%事件分析查詢可以在5秒內返回查詢結果,90%的漏斗查詢可以在30S內返回查詢結果,速度提升達到98%以上;
-
實時性查詢:可以對當天實時的用戶行為數據進行分析,極大的增加了用戶獲取分析結果的及時性。
但本身這種性能提升是以資源消耗為前提的。以移動端日誌為例,Flink消費任務峰值可以達到百萬條每秒,對Redis維表關聯和字典服務處理挑戰很大,計算併發度甚至達到1200core,遇到特殊流量事件往往出現堆積、延遲、斷流,對人工運維成本消耗也較大。此外這種Lambda數據流架構,實時和離線清洗邏輯需要保持一致,否則很容易導致數據解釋成本提升。另外本身實時+離線維護兩套對存儲上也是極大浪費,即Kafka、Hive、CK都需要存儲同一份數據。到21年底,隨著業務發展CK存儲幾經橫向擴充剩下不到10%,而集群的擴展和數據遷移也需要較大精力,本文後面小節會詳細介紹。功能方面,直接對明細數據應用原生CK函數查詢的跨天留存分析、路徑分析需要用時分鐘級,體驗不是很好。
22年開始公司大力推動降本增效,這就要求以儘可能少的資源最大化行為分析產品效能。整體核心思路是全模型化聚合加速,底層流量數據鏈路走kappa架構,不會再用北極星應用數據和流量表不一致的情況,數據小時級產出。這次改造實時資源可以節約1400core,節省Redis記憶體400G、節省Kafka300 Partiton。每天數據量由千億數據降低為百億,通過特定的sharding方式配合下推參數,利用分區、主鍵、索引等手段支持事件分析(平均查詢耗時2.77s)、事件合併去重分析(平均查詢耗時1.65s)、單用戶細查(平均查詢耗時16.2s)、漏斗分析(平均查詢耗時0.58s),留存分析和路徑分析從分鐘級查詢到10s內相應。數據架構如圖所示:
擁有以下特點:
-
全模型聚合:21年中開始我們就設計了一款通用流量聚合模型,可以認為是全信息的hive流量模型結構,除了把時間維度退化外其餘信息基本能完整保留,原來千億級的量級可以壓縮為百億內;
-
BulkLoad出倉:數據按文件批次從HDFS導入到ClickHouse,千億級別的數據一小時內可以導完,其原理後文會有介紹;
-
字典服務升級:我們通過加強版的snowflake+redis+公司自研rockdbKV存儲,大大增強了字典服務性能,壓測可支持40萬QPS;
-
用戶屬性現算模式:不再採用預計算模式,而是通過我們另一套基於CK的標簽平臺所生成的指定用戶標簽人群跨集群關聯現算,這樣可以靈活指定想要分析的用戶屬性。
到22年中,隨著數據湖的興起,我們將hive流量聚合模型遷移到Iceberg上,日常事件查詢可以在10s內完成,可以作為CK數據的備用鏈路。這條鏈路不光降低了緊急事件運維成本,提升數據可用性保障,還可以支持用戶日常流量關聯其他業務定製化查詢取數。通用的模型結構除了支持流量行為日誌外,通過映射管理可以快速接入其他服務端日誌,擴展其使用的場景。下圖為22年12月份最近一周各功能模塊使用情況:
從發展歷程來看,用戶行為數據分析經歷了從強離線引擎驅動到強OLAP驅動,離不開業界大數據技術不斷發展和進步,北極星行為數據底層明細後面也會切換到Hudi,可以滿足更加實時的數據消費,讓專業的工具做專業的事。
三、事件、留存分析
事件分析是指對具體的行為事件進行相關指標統計、屬性分組、運算、條件篩選等操作,本質上是分析埋點事件的用戶觸發情況以及埋點事件的分析統計情況。留存分析可以根據業務場景以及產品階段的不同,自定義起始行為和後續行為做留存計算,協助分析用戶使用產品的粘性,根據留存分析結果有針對性地調整策略,引導用戶發現產品價值,留住用戶,實現用戶真實的增長。
過去北極星分析平臺的分析模塊大多以B站的千億明細行為數據為基礎,通過ClickHouse查詢引擎的指標函數例如uniq(),可以支持單個事件分析、多個事件的對比分析以及多個事件的複合指標運算,支持指定時間內的行為留存分析(參與後續行為的用戶占參與初始行為用戶的比值),通過篩選、分組等組件滿足多樣化分析需求。但是過去的北極星事件分析是基於明細數據,B站行為數據每天增量千億級別,存儲日增10T以上資源消耗巨大,明細數據分析查詢比較慢,每天用戶慢查詢平均30s~50s體驗較差,而且其功能比較單薄,只能支持30天的查詢視窗,用戶留存、用戶分群等複雜分析模塊很難實現。而且海量行為數據分析也面臨許多挑戰,每天千億行為數據,高峰期寫入QPS百萬以上。如何實現既滿足時效性又滿足海量數據壓力的計算方式?如何滿足複雜分析場景的同時,壓縮存儲提升查詢效率?如何簡化數據鏈路,模塊化插件化降低接入成本,提升擴展性?如何打通標簽、ABTest等其他業務系統將北極星的行為分析能力標準化?
北極星事件分析:
為瞭解決以上痛點和海量數據分析的挑戰,新的事件、留存分析通過準實時方式建模分層,用戶、事件、時間等粒度的預聚合壓縮,不僅統一了離線口徑,而且自研拉寬匯聚spark腳本可以承載千億數據壓力,搭配多種聚合模型實現豐富的分析模塊。同時釋放實時資源離線小時任務保證時效性,維表壓力採用join離線維表+屬性字典維度服務的方式解決,並且早於平臺自研可指定shard的BulkLoad出倉工具,配合下推參數可加速查詢,數據鏈路可擴展易運維。相比較以往的處理千億明細數據,準實時在DWB層實現了對數據的壓縮,將每天千億數據壓縮到每天百億級別。OLAP層也通過彙總後的數據替代了原先的明細數據,大大縮小存儲的同時也提高了查詢性能,每天用戶慢查詢可降到10s以內,時間視窗可擴大到45天甚至更長。並且對高複雜的查詢比如用戶留存,用戶分群等分析場景可以更好的支持。
事件分析數據開發流程:
具體實現包括以下核心部分:
1、流量聚合模型創建。首先準實時清洗DWD層B站千億明細行為數據,流量數據都是分為私有參數和公有參數,其中公有參數在用戶粒度下是不會經常改變的,我們會用一般聚合函數取一定時間內指定設備和行為事件下最新保留的不變公有參數,而將同等粒度下變化比較頻繁的私有參數維度名寫入Array結構,利用map索引原理,把私參維度值組合通過spark自定義邏輯計數併入map的key中,map的value則用來寫入各種公共指標聚合結果,整個過程均通過spark腳本實現,最終寫入到Iceberg引擎中。因為Iceberg可以關聯其他任何已有hive表,通過快速業務表關聯也可以支持到其他多項業務應用,也可以作為不出倉的北極星降級備用方案支持大部分查詢分析功能。
流量聚合模型數據方案:
2、流量聚合模型在iceberg下查詢。 如下圖所示,聚合之後的數據形成DWB層落地到iceberg表(即圖中iceberg_bdp.dwb_flow_ubt_app_group_buvid_eventid_v1_l_hr),可以在hive和spark上計算大部分查詢維度下的指標。利用Trino基於連接器實現了存儲與計算分離,通過map_filter、array_position等trino條件函數和map_values、reduce等trino指標函數可以實現一系列複雜事件分析,當然我們也配套開發了一些簡單易用的UDF可以繞開較複雜的trino函數組合供用戶查詢使用,性能上相差不大。
3、公參和私參篩選器創建。接下來我們利用BulkLoad出倉腳本將iceberg數據導入ClickHouse表(即圖中polagrou.polaris_dwb_flow_ubt_group_buvid_eventid_pro_i_d_v1),即保證了時效性又相容了特殊的數據結構。從ClickHouse表結構設計上支持了SAMPLE BY murmurHash3_64(buvid)的抽樣功能,由於buvid(設備id)分shard寫入可以保證單節點的數據隨機分配,只要在單節點上做抽樣配合ReplicatedReplacingMergeTree引擎就可以實現了ck to ck的物化篩選器,直接為北極星分析平臺提供公參維度聚合、私參枚舉排序的維度篩選功能。整個過程直接在可支持調度的python腳本上實現,可支持到近小時更新。
4、流量聚合模型在ClickHouse下查詢。在ClickHouse查詢上設計特定的CK-UDF來解析嵌套map結構,保證複雜分析場景的同時用於加速了查詢,相比用ClickHouse原生多個函數組合解析要快30%左右,比原先明細模型的查詢要快更多。而且通過腳本實現了多維度的ClickHouse小時級別的機器人監控告警,早於平臺對此定製化監控告警的支持。
目前北極星分析平臺平均查詢耗時3.4s,通過通用聚合模型,下游可以對行為人群進行交並計算實現標簽畫像和人群圈選等轉化分析功能,也可以利用Retention函數實現了N日的事件留存分析。最終相比前代方案節省計算資源1400C、節省存儲資源40%,提升查詢效率60%以上,利用RBM實現了北極星、標簽、ABTest等多業務打通。
四、漏斗、路徑分析
流量業務分析場景上會查看一群用戶在客戶端或者網頁上的路徑流轉信息,路徑分析將用戶在產品中的使用路徑用桑吉圖呈現,展現用戶在頁面與頁面流轉中的流量走向。通過路徑分析可以幫助驗證產品運營策略,優化產品設計思路。漏斗是用戶在產品使用中完成的一系列行為轉化。漏斗分析可以幫助瞭解用戶在行為步驟中的轉化或流失情況,進而通過優化產品或者開展運營活動提升轉化率,達成業務目標。
在業務日益增長的情況下,對用戶漏斗、路徑精細化分析訴求逐漸增加,為此北極星分析平臺增加此類型支持,用於分析一群用戶在某一頁面、某一模塊前後的流量流轉變化。漏斗分析業界常見解決此類場景利用ClickHouse提供了一個名叫windowFunnel的函數來實現對明細數據的漏斗分析。而路徑分析技術一般分為兩種,一種為明細數據結合sequenceCount(pattern)(timestamp, cond1, cond2, ...)做簡單的路徑分析,而複雜的路徑分析又叫智能路徑分析可以通過ClickHouse提供的高階數組函數進行曲線救國。
路徑分析背景挑戰:
但是過去的流量漏斗、路徑分析都是基於明細數據進行的。存儲資源消耗大、分析查詢慢、功能比較單薄等。為瞭解決以上痛點,新的漏斗、路徑分析通過離線方式的建模分層、用戶路徑粒度的預聚合、存儲引擎ClickHouse的RBM物化視圖等技術,將每天千億數據壓縮到每天幾十億。查詢效率也從分鐘級優化到秒級,更是通過關聯標簽和人群支持到了各種轉化查詢分析。大大縮小存儲的同時查詢性能大大提升,最終實現了關聯標簽和人群圈選等功能。
路徑分析功能頁面:
具體實現包括以下核心部分:
1、路徑聚合DWB模型創建。首先離線處理B站的千億明細行為數據,經過維度裁剪變化比較頻繁的私有參數,保留用戶粒度下的公有參數,並且通過buvid(設備id)粒度進行聚合,將同一個buvid的所有事件根據時間線串聯聚合到一個欄位中,聚合之後的數據形成DWB層落地到hive表。
路徑分析數據方案:
2、路徑聚合DWS模型創建。在上一步的基礎上,對DWB層的數據進行路徑的彙總,將同一個路徑的buvid(設備id)彙總聚合到數組結構中,這個過程出現很多干擾事件,比如某些路徑會頻繁出現,會亂序而干擾真正的用戶行為,所以我們會通過去重等手段進行干擾事件過濾路徑補位拼接形成桑基圖節點,當然我們還引入了RBM數據結構存儲聚合後的設備編碼,最終落到hive表。整個過程都是通過spark腳本利用代碼和演算法實現的。
漏斗分析查詢方案:
3、路徑聚合模型Clickhouse表設計。接下來我們利用平臺工具將hive數據出倉到ClickHouse,在ClickHouse表結構設計上,採用了ClickHouse的物化視圖技術和RBM數據結構,進一步壓縮buvid(設備id)集合為RBM編碼,利用數組物化RBM的方式大大壓縮了存儲,可通過Bitmap交並計算路徑相關指標,千億數據壓縮到幾十億做到了秒級查詢。
路徑分析數據協議:
數據結構形成的樹型圖:
4、路徑聚合模型漏斗分析查詢。在功能上漏斗分析通過windowFunnel函數進行計算,將計算周期內每個用戶的行為明細按時間順序聚合為對應事件鏈,然後搜索滑動時間視窗滿足漏斗條件的事件鏈,並計算從鏈中發生的最大事件數level,最後對各級level計算uv獲得結果。
右側節點上的數字表示從中心事件e0至自身的路徑uv:
在樹型圖中的對應關係:表示路徑e0->e4→e1→e3→e2在視窗期內的總uv為1。左側同理,方向相反。
5、路徑聚合模型路徑分析查詢。同理路徑分析在ClickHouse數據基礎上利用數據協議和複雜sql繪製出路徑樹狀圖進而拼接出桑基圖,可直觀的展現用戶主流流程,幫助確定轉化漏斗中的關鍵步驟,迅速發現被用戶忽略的產品價值點,修正價值點曝光方式併發現用戶的流失點,同時通過Bitmap的交並計算實現了標簽畫像和人群圈選等轉化分析功能。
五、標簽、人群圈選
B站的北極星行為分析平臺、標簽畫像平臺、AB實驗人群包都是基於ClickHouse的RBM(RoaringBitMap)實現,此外RBM還有其他多項應用,比如事件分析標簽人群圈選、預計算的路徑分析、創建用戶行為的用戶分群等,具體可查看之前文章[1]。
下圖是基於北極星CK底層數據生成一個滿足指定行為結果的人群包邏輯:
RBM固然好用,但是只支持int或者long類型,如果去重欄位不是int或者long怎麼辦呢?海量數據應用層的維度服務如何做到高可用高併發?依賴的鏈路出問題如何快速恢復,數據如何保障?
屬性字典維度服務就是可解碼編碼多業務屬性、可輸出管理多業務維度,具有分散式、高可用、高併發等特性的服務系統,通過屬性字典維度服務可實現多維度管理多業務打通,為海量數據應用層定製化提供技術支持。
屬性字典維度服務架構設計:
高可用方面Grpc+LoadCache+Redis+公司自研rockdbKV存儲,多級緩存分散式架構支持平滑擴容和滾動發佈,可做到日常緩存命中率70%以上,底層ID生成演算法基於Leaf-SnowFlake快速生成,壓測可支持50w以上QPS高併發。所有請求通過公司的日誌傳輸通道可以小時級同步到hive做備份,事故情況下配合BulkLoad讀寫分離可40分鐘內恢復20億+屬性字典。
最終利用屬性字典對buvid(設備id)等業務屬性編碼和解碼,對用戶標簽和AB人群進行創建,並且通過RBM交並計算實現了北極星分析平臺、用戶畫像平臺、AB實驗平臺的多業務打通。
人群圈選sql示例:
六、ClickHouse數據導入方案演進
如上文所述,北極星是基於ClickHouse構建的一套海量UBA技術解決方案,底層ClickHouse集群的穩定性 、讀寫性能、資源使用率均會影響上層業務的使用體驗。與此同時,海量數據如何導入ClickHouse,以及數據導入過程的穩定性、導入效率、資源消耗在很大程度上決定了ClickHouse集群的整體穩定性和使用效率。所以,一個穩定高效的數據導入方案對於一套UBA解決方案來說是必不可少的。
在B站,UBA場景的數據導入方案大致經歷了三個階段的演進:
1、 JDBC寫入方案
在B站內部,針對數據寫入到各個資料庫/引擎主要有兩套pipeline:一套是基於Spark的離線導入鏈路,大部分數據來源於Hive;另一套是基於FLink的實時導入鏈路,大部分數據源來源於kafka。這兩套鏈路都支持clickhouse作為data sink,UBA場景最開始也是基於這兩套鏈路來做數據導入的,主要使用的是實時導入鏈路,在歷史數據初始導入和故障補數等少數情況下也用到離線導入鏈路。
如上圖所示,離線和實時導入最終都使用ClickHouse JDBC向ClickHouse發送數據,這種寫入方式實現起來比較簡單,使用開源的ClickHouse JDBC Driver就可以使用標準JDBC介面向ClickHouse寫入數據。同時,flink實時寫入的數據延遲比較低,端到端延遲可控制在秒級。但這個方案存在以下問題:
ClickHouse Server端的資源消耗比較大(因為數據的排序,索引生成,數據壓縮等步驟均是在server端完成),在高峰時會影響查詢性能。
實時任務寫入頻次較高,數據會在寫入後觸發大量merge操作,造成“寫放大”,消耗更多的磁碟IO和CPU資源,可能導致too many parts錯誤。
實時Flink任務需要長時間占用大量資源,且在故障情況下容易出現數據堆積、延遲、斷流等問題,運維成本較高。
以上問題在資源充沛的情況下不會影響業務使用,但當集群資源接近瓶頸時,查詢性能受寫入影響,寫入性能和穩定性受merge影響,最終導致集群整體穩定性下降,影響業務使用。
2、基於中間存儲的BulkLoad導入方案
UBA場景的多個分析模塊對數據延遲要求不盡相同,大部分數據實時性要求並不高,小時級延遲在大部分模塊下是可接受的。因此,為瞭解決上述JDBC寫入方案的問題,我們針對大部分對時效性要求不高的數據導入需求,構建了一套基於中間存儲的BulkLoad導入方案:
首先,將clickhouse格式的data part文件的生成過程轉移到Spark Application中完成,這樣就可以利用Yarn集群的資源來完成數據排序,索引生成,數據壓縮等步驟。
data part文件的生成我們藉助clickhouse-local工具實現,在Spark Executor中調用clickhouse-local寫入數據到本地磁碟,生成clickhouse data part文件。
然後,將Spark Executor生成的data part文件上傳到HDFS文件系統的特定目錄中。
接著,從Spark Executor端發送 "ALTER TABLE ... FETCH PART/PARTITION" SQL語句到clickhouse server執行。
最後,ClickHouse Server執行 "ALTER TABLE ... FETCH PART/PARTITION",從HDFS拉取data part文件並完成attach操作。其中,我們對ClickHouse代碼做了一些改造,使得FETCH語句支持從HDFS拉取文件。
由於Bulkload導入將數據寫入data part文件這個過程移到了Spark端執行,大大降低了ClickHouse Server數據寫入對資源的消耗。與此同時,由於在Spark端數據批量寫入之前已經完成了repartition和攢批,到達ClickHouse Server的data part數量相較JDBC寫入要少很多,所以clickhouse的merge壓力也大幅降低。該方案上線後,數據寫入對clickhouse查詢的影響基本消除,集群穩定性得到大幅提升。
但這個方案依然存在一些問題:
以HDFS作為文件傳輸的中間存儲,增加了數據傳輸的耗時和網路開銷,同時會占用HDFS的存儲資源。
HDFS的負載情況可能影響ClickHouse Bulkload數據導入的性能與穩定性。
3、直達ClickHouse的BulkLoad導入方案
為了進一步優化數據導入的性能和穩定性,我們參照ClickHouse副本間數據同步的DataExchange服務,開發了ClickHouse的DataReceive服務,以支持Spark Executor直接將data part文件傳輸到ClickHouse Server,繞開HDFS中間存儲。
DataReceive服務允許使用HTTP客戶端直接將數據文件發送到ClickHouse,ClickHouse端會進行鑒權、數據校驗、流量控制、併發控制、磁碟負載均衡等操作。該方案相較於基於HDFS中間存儲的Bulkload方案,大致有一倍的性能提升。
七、ClickHouse數據重平衡
B站每天的用戶行為數據量達數千億行,UBA場景需要分析最近半年以上的歷史數據,所以底層ClickHouse需要存儲PB級的已壓縮數據。同時,隨著B站活躍用戶日益增長,需要存儲的數據量也在不斷增長,所以集群擴容的需求是必不可少的。
然而,由於受限於存算一體的架構設計,ClickHouse集群目前無法做到彈性擴容,數據需要在新集群中完成重分配。因此,ClickHouse如何高效穩定地完成數據重平衡(Data Rebalance)是ClickHouse集群管理人員必須面對和解決的的問題。
我們在UBA場景集群擴容的準備和實施過程中,經歷了從手動化,到半自動化,再到服務化的演進。在此期間,我們將在海量數據重平衡實踐過程中遇到的問題與解決方法轉化成為了一套自動化工具服務。下麵,我們就來介紹一下這套工具服務的功能與實現原理。
1、平衡度
集群中表的大小差異很大,有些達到幾百TB, 有些只有幾GB,如何度量數據的平衡程度,篩選出需要平衡的表?我們引入了一些數學公式來解決這個問題。
變異繫數:當需要比較兩組數據離散程度大小的時候,如果兩組數據的測量尺度相差太大,或者數據量綱的不同,直接使用標準差來進行比較不合適,此時就應當消除測量尺度和量綱的影響,而變異繫數可以做到這一點,它是原始數據標準差與原始數據平均數的比,取值範圍0~1,值越小,離散程度越小。
表的平衡度 = 變異繫數(取值範圍0~1,值越大,表越不平衡)
舉例:表A的平衡度
集群共有4個節點,表A在不同節點的大小分別為4GB, 10GB, 5GB, 3GB
平均值: (4 + 10 + 5 + 3) / 4 = 5.5
方差: (x - 平均值) ^ 2 / 4 = 7.25
標準差: root(方差) = 2.69
變異繫數: 標準差 / 平均值 = 0.49
表A的平衡度 = 0.49
2、平衡演算法
對於待平衡的表,有些業務期望最大程度的平衡,提升並行度,發揮集群的最大算力,而有些表容量過大,業務期望以最小的遷移成本,快速平衡數據,達到相對較優的平衡。
對於不同的業務需求,提供了兩種平衡演算法,裝箱演算法和貪心演算法。
期望達到極致的均衡,數據量較小時,推薦使用裝箱演算法。期望以最小的遷移成本,達到較優的均衡,推薦使用貪心演算法。
(1)裝箱演算法
演算法整體採用Best Fit(最優裝箱演算法) + AVL樹的設計。每個ClickHouse節點即為一個Node,每個Node有初始閾值capacity,代表ClickHouse節點的容納量。將準備平衡的part按照大小順序排序,並根據Best Fit演算法依次填充到Node中,Node根據remain_capacity(剩餘容量),左旋右旋組成一棵AVL樹,以此提升查詢效率,方便快速完成平衡。
設計如下圖所示。
裝箱演算法細節在此不做贅述,感興趣的讀者可參考這裡[2]。
(2)貪心演算法
演算法整體採用不斷輪詢 + 局部最優的設計。將ClickHouse節點按照大小排序,找出最大和最小的節點,如果將某個part從最大的節點搬遷至最小的節點,遷出的節點仍然大於遷入節點,則搬遷該part,直到最大節點無法遷出。依此類推,繼續按照大小排序ClickHouse節點,每次找到最大最小節點,平衡part至局部最優,直到輪詢ClickHouse節點結束。
設計如下圖所示:
3、平衡計劃
根據平衡演算法,可以得出集群中節點計劃的遷入、遷出情況。平衡單位為表級別,遷移粒度到part,可以理解為表內部part平衡。
如下圖所示,可以看到表平衡前後的平衡度,以及節點1計劃的遷入、遷出情況。平衡計劃生成完成後,可以根據需要選擇執行特定的平衡計劃。
4、重平衡執行流程
在執行平衡計劃的過程中,如何準確、高效地將part遷入和遷出?如何保證原子性,避免數據出現丟失或重覆的問題?如何限流,避免因平衡占用過多的資源,影響集群的穩定性?
經過不斷的測試、調整,最終制定了一套比較健壯的平衡方案,整體流程為:預判斷(是否merge) + fetch(遷入節點) + detach(遷出節點) + attach(遷入節點) + detached(遷出節點) + drop detached(遷出節點)。
平衡期間對於不同階段的異常,添加了相應的重試和回滾機制,以此來覆蓋網路抖動、zookeeper重連接等問題,從而保證了平衡的原子性,數據一致性。
平衡期間通過限流配置(max_replicated_fetches_network_bandwidth),來控制平衡速度,保障了集群的穩定性,避免影響其他業務的正常查詢。
整體設計如下圖所示。
八、ClickHouse應用優化實踐
在支持UBA場景各項功能模塊的過程中,我們針對ClickHouse的查詢,存儲等方面做了大量應用優化工作。下麵選取其中幾個優化點做簡單介紹。
1、查詢下推
ClickHouse中的針對分散式表的查詢會被改寫成對local表的查詢併發送到集群各個shard執行,然後將各個shard的中間計算結果收集到查詢節點做合併。當中間計算結果很大時,比如countDistinct、 windowFunnel函數等,查詢節點的數據收集和數據合併可能成為整個查詢的性能瓶頸。
查詢下推的思路就是儘量將計算都下推到各個shard執行,查詢節點僅收集合併少量的最終計算結果。不過,也不是所有查詢都適合做下推優化,滿足以下兩個條件的查詢可以考慮做下推優化:
數據已經按照計算需求做好sharding:比如,UBA場景的數據已按user id做好了sharding,所以針對用戶的漏斗分析,UV等計算可以下推到各個shard執行。否則,下推後的計算結果是不准確的。
計算的中間結果較大:sum,count等計算是無需下推的,因為其中間結果很小,合併計算很簡單,下推並不能帶來性能提升。
下麵,我們以上文中提到的漏斗分析為例,闡述一下如何做查詢下推。
上圖是用windowFunnel函數實現漏斗分析的一個SQL,如圖中“執行步驟”所示,該查詢需要從各shard收集大量數據併在查詢節點完成計算,會產生大量數據傳輸和單點計算量。
我們先使用配置distributed_group_by_no_merge做了一版下推優化:
優化SQL-V1將windowFunnel的計算下推到各個shard執行,僅在查詢節點對windowFunnel的最終結果做聚合計算。在我們的場景下,該版本較上一版本性能提升了5倍以上。
為了更進一步做查詢下推,我們利用cluster + view的函數組合,將聚合查詢進一步下推:
優化SQL-V2的性能較優化SQL-V1進一步提升30+%.
2、Array和Map的跳數索引支持
UBA場景中的事件數據有很多公共屬性和私有屬性,公共屬性被設計為表的固定欄位,而私有屬性因為各個事件不盡相同,所以採用Array/Map來存儲。最初的設計是採用兩個數組分別存儲屬性名和屬性值,ClickHouse支持Map結構後,則在後續模塊中採用Map來滿足類似需求。無論是Array還是Map,最初都不支持創建跳數索引,所以在其他索引欄位過濾效果有限的情況下,針對Array和Map的操作可能會成為查詢的性能瓶頸。
針對這個問題,我們給Array和Map加上了Bloom filter等跳數索引支持,針對Map僅對其key構建索引。在某些出現頻率較低的私有屬性過濾場景下,Array/Map的跳數索引可以收穫數倍的性能提升。
3、壓縮演算法優化
ClickHouse常用的數據壓縮方式有三種,分別為LZ4、LZ4HC以及ZSTD。針對不同的數據類型,數據分佈方式來使用特定的編碼方式可以大大提高數據壓縮率,以減少存儲成本。
針對UBA場景,我們測試了不同壓縮演算法的壓縮率,寫入性能,查詢性能。相較預設的LZ4,ZSTD(1)在壓縮率上普遍可以節省30%以上的存儲空間,查詢性能方面未見明顯差異,不過寫入性能在某些場景下有20%左右的下降。由於UBA場景數據存儲壓力較大,同時對數據時效性要求不是很高,因此我們最終選擇了ZSTD(1)作為主要的壓縮方式。
九、下一步工作
1、多業務通用模型支持
UBA場景的泛化形態實際是人+內容+行為,例如用戶可以在觀看場景產出彈幕行為或者點贊行為,這類數據不同於傳統的SDK日誌數據具有通用的埋點格式,但我們可以通過抽象映射到通用行為聚合模型上來,來實現對服務端日誌的行為分析。目前我們正在對社區服務端日誌和其他非埋點規範的業務SDK日誌進行泛化支持,儘可能復用已有能力提高用戶查詢和分析效率。
2、Clickhouse增強多維過濾場景支持
在UBA場景下,同一張表可能在多個模塊中使用到,比如,用戶行為事件數據在事件分析等分析模塊中使用,同時在單用戶行為明細查詢中會使用到。這兩種使用場景下對錶的查詢是基於不同過濾維度的,但clickhouse目前的主鍵索引很難同時對多個維度過濾都有較好過濾效果,因此很難同時滿足多個場景下的查詢性能要求。我們已經完成了ZOrder索引的開發,目前正在開發相應的編碼類型,使得UBA場景下的數據可以使用ZOrder index同時支持多個維度的高效查詢。
本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Application-practice-of-massive-user-behavior-analysis-based-on-ClickHouse-in-Bilibili.html