前言
今天事情又比較多,寫得言簡意賅一些,看官勿怪。
Flink和ClickHouse分別是實時計算和(近實時)OLAP領域的翹楚,也是近些年非常火爆的開源框架,很多大廠都在將兩者結合使用來構建各種用途的實時平臺,效果很好。關于兩者的優(yōu)點就不再贅述,本文來簡單介紹筆者團隊在點擊流實時數(shù)倉方面的一點實踐經驗。
點擊流及其維度建模
所謂點擊流(click stream),就是指用戶訪問網站、App等Web前端時在后端留下的軌跡數(shù)據,也是流量分析(traffic analysis)和用戶行為分析(user behavior analysis)的基礎。點擊流數(shù)據一般以訪問日志和埋點日志的形式存儲,其特點是量大、維度豐富。以我們一個中等體量的普通電商平臺為例,每天產生200+GB、十億條左右的原始日志,埋點事件100+個,涉及50+個維度。
按照Kimball的維度建模理論,點擊流數(shù)倉遵循典型的星形模型,簡圖如下。

點擊流數(shù)倉分層設計
點擊流實時數(shù)倉的分層設計仍然可以借鑒傳統(tǒng)數(shù)倉的方案,以扁平為上策,盡量減少數(shù)據傳輸中途的延遲。簡圖如下。

- DIM層:維度層,MySQL鏡像庫,存儲所有維度數(shù)據。
- ODS層:貼源層,原始數(shù)據由Flume直接進入Kafka的對應topic。
- DWD層:明細層,通過Flink將Kafka中數(shù)據進行必要的ETL與實時維度join操作,形成規(guī)范的明細數(shù)據,并寫回Kafka以便下游與其他業(yè)務使用。再通過Flink將明細數(shù)據分別寫入ClickHouse和Hive打成大寬表,前者作為查詢與分析的核心,后者作為備份和數(shù)據質量保證(對數(shù)、補數(shù)等)。
- DWS層:服務層,部分指標通過Flink實時匯總至Redis,供大屏類業(yè)務使用。更多的指標則通過ClickHouse物化視圖等機制周期性匯總,形成報表與頁面熱力圖。特別地,部分明細數(shù)據也在此層開放,方便高級BI人員進行漏斗、留存、用戶路徑等靈活的ad-hoc查詢,這些也是ClickHouse遠超過其他OLAP引擎的強大之處。
要點與注意事項
Flink實時維度關聯(lián)
Flink框架的異步I/O機制為用戶在流式作業(yè)中訪問外部存儲提供了很大的便利。針對我們的情況,有以下三點需要注意:
- 使用異步MySQL客戶端,如Vert.x MySQL Client。
- AsyncFunction內添加內存緩存(如Guava Cache、Caffeine等),并設定合理的緩存驅逐機制,避免頻繁請求MySQL庫。
- 實時維度關聯(lián)僅適用于緩慢變化維度,如地理位置信息、商品及分類信息等??焖僮兓S度(如用戶信息)則不太適合打進寬表,我們采用MySQL表引擎將快變維度表直接映射到ClickHouse中,而ClickHouse支持異構查詢,也能夠支撐規(guī)模較小的維表join場景。未來則考慮使用MaterializedMySQL引擎(當前仍未正式發(fā)布)將部分維度表通過binlog鏡像到ClickHouse。
Flink-ClickHouse Sink設計
可以通過JDBC(flink-connector-jdbc)方式來直接寫入ClickHouse,但靈活性欠佳。好在clickhouse-jdbc項目提供了適配ClickHouse集群的BalancedClickhouseDataSource組件,我們基于它設計了Flink-ClickHouse Sink,要點有三:
- 寫入本地表,而非分布式表,老生常談了。
- 按數(shù)據批次大小以及批次間隔兩個條件控制寫入頻率,在part merge壓力和數(shù)據實時性兩方面取得平衡。目前我們采用10000條的批次大小與15秒的間隔,只要滿足其一則觸發(fā)寫入。
- BalancedClickhouseDataSource通過隨機路由保證了各ClickHouse實例的負載均衡,但是只是通過周期性ping來探活,并屏蔽掉當前不能訪問的實例,而沒有故障轉移——亦即一旦試圖寫入已經失敗的節(jié)點,就會丟失數(shù)據。為此我們設計了重試機制,重試次數(shù)和間隔均可配置,如果當重試機會耗盡后仍然無法成功寫入,就將該批次數(shù)據轉存至配置好的路徑下,并報警要求及時檢查與回填。
當前我們僅實現(xiàn)了DataStream API風格的Flink-ClickHouse Sink,隨著Flink作業(yè)SQL化的大潮,在未來還計劃實現(xiàn)SQL風格的ClickHouse Sink,打磨健壯后會適時回饋給社區(qū)。另外,除了隨機路由,我們也計劃加入輪詢和sharding key hash等更靈活的路由方式。
還有一點就是,ClickHouse并不支持事務,所以也不必費心考慮2PC Sink等保證exactly once語義的操作。如果Flink到ClickHouse的鏈路出現(xiàn)問題導致作業(yè)重啟,作業(yè)會直接從最新的位點(即Kafka的latest offset)開始消費,丟失的數(shù)據再經由Hive進行回填即可。
ClickHouse數(shù)據重平衡
ClickHouse集群擴容之后,數(shù)據的重平衡(reshard)是一件麻煩事,因為不存在類似HDFS Balancer這種開箱即用的工具。一種比較簡單粗暴的思路是修改ClickHouse配置文件中的shard weight,使新加入的shard多寫入數(shù)據,直到所有節(jié)點近似平衡之后再調整回來。但是這會造成明顯的熱點問題,并且僅對直接寫入分布式表才有效,并不可取。
因此,我們采用了一種比較曲折的方法:將原表重命名,在所有節(jié)點上建立與原表schema相同的新表,將實時數(shù)據寫入新表,同時用clickhouse-copier工具將歷史數(shù)據整體遷移到新表上來,再刪除原表。當然在遷移期間,被重平衡的表是無法提供服務的,仍然不那么優(yōu)雅。如果大佬們有更好的方案,歡迎交流。
The End
關于Flink和ClickHouse等組件的配置、調優(yōu)、延遲監(jiān)控、權限管理等知識,筆者在之前的博客中多少講到過(傳送門:Flink文集、ClickHouse文集),不再廢話了。
民那晚安晚安。