本文旨在向大家分享有道精品課數據中臺的架構演進過程,以及 Doris 作爲一個 MPP 分析型數據庫是如何爲不斷增長的業務體量提供有效支撐並進行數據賦能的。內容分享邏輯首先從實時數倉選型的經驗爲切入點,進一步着重分享使用 Doris 過程中遇到的問題以及 Doris 技術團隊針對這些問題所做出的調整和優化。
1、背景
1.1 業務場景
根據業務需求,目前有道精品課的數據層架構上可分爲離線和實時兩部分。
離線系統主要處理埋點相關數據,採用批處理的方式定時計算。
而實時流數據主要來源於各個業務系統實時產生的數據流以及數據庫的變更日誌,需要考慮數據的準確性、實時性和時序特徵,處理過程非常複雜。
有道精品課數據中臺團隊依託於其實時計算能力在整個數據架構中主要承擔了實時數據處理的角色,同時爲下游離線數倉提供實時數據同步服務。
數據中臺主要服務的用戶角色和對應的數據需求如下:
-
運營/策略/負責人主要查看學生的整體情況,查詢數據中臺的一些課程維度實時聚合數據
-
輔導/銷售主要關注所服務學生的各種實時明細數據
-
品控主要查看課程/老師/輔導各維度整體數據,通過 T+1 的離線報表進行查看
-
數據分析師對數據中臺 T+1 同步到離線數倉的數據進行交互式分析
1.2 數據中臺前期系統架構及業務痛點
如上圖所示,在數據中臺 1.0 架構中我們的實時數據存儲主要依託於 Elasticsearch,遇到了以下幾個問題:
-
聚合查詢效率不高
-
數據壓縮空間低
-
不支持多索引的 join,在業務設計上我們只能設置很多大寬表來解決問題
-
不支持標準 SQL,查詢成本較高
2、實時數倉選型
基於上面的業務痛點,我們開始對實時數倉進行調研。當時調研了 Doris, ClickHouse, TiDB+TiFlash, Druid, Kylin。
OLAP引擎 | 優勢 | 劣勢 |
Doris | 1. 兼容MySQL協議 2. 支持Online Schema Change 3. 支持更新 4. 集羣擴縮容自動化 5. 支持基於時間分區,冷熱數據分離 | 1. 開源較晚,目前還在孵化中 |
ClickHouse | 1. 單機性能強勁 2. 向量化引擎 3. 數據壓縮空間大 | 1. 不支持標準SQL 2. 集羣擴縮容不能自動Rebalance 3. 對更新支持不好 4. 運維成本較高 |
TiDB+TiFlash | 1. 兼容MySQL協議 2. 向量化引擎 3. 業務數據和分析數據同步方便(內部Raft同步) | 1. TiFlash不開源 2. 落地公司較少 3. 架構主要面向TP場景 |
Druid | 1. 基於時間分區,聚合數據查詢較快 2. 支持冷熱數據分離 | 1. 不支持明細數據存儲 2. 不支持標準SQL |
Kylin | 1. 支持標準SQL查詢 2. 支持預聚合 3. 社區發展較好 | 1. 依賴較多 2. 明細查詢支持較弱 3. 資源消耗較多 |
於起初我們數據中臺只有兩名開發,而且存儲相關的東西需要自行運維,所以我們對運維的成本是比較敏感的,在這一方面我們首先淘汰了 Kylin 和 ClickHouse。
在查詢方面,我們的場景大多爲明細+聚合多維度的分析,所以 Druid 也被排除。
最後我們對聚合分析的效率方面進行對比,由於 Doris 支持 Bitmap 和 RollUp,而 TiDB+TiFlash 不支持,所以我們最終選擇了 Doris 來作爲我們數據中臺的主存儲。
3、基於 Apache Doris 的數據中臺 2.0
3.1 架構升級
在完成了實時數倉的選型後,我們針對 Doris 做了一些架構上的改變以發揮它最大的作用,主要分爲以下幾個方面:
-
Flink 雙寫
將所有 Flink Job 改寫,在寫入 Elasticsearch 的時候旁路輸出一份數據到 Kafka,並對複雜嵌套數據創建下游任務進行轉化發送到 Kafka,Doris 使用 Routine Load 導入數據。
-
Doris on ES
由於之前我們的實時數倉只有 ES,所以在使用 Doris 的初期,我們選擇了通過 Doris 創建 ES 外表的方式來完善我們的 Doris 數倉底表。同時也降低了查詢成本,業務方可以無感知地使用數倉底表。
具體查詢 Demo 如下所示,我們通過學生的基礎信息 Join 各種練習信息,對學生數據進行補齊。
-
數據同步
原來我們使用 ES 的時候,由於很多表沒有數據寫入時間,數據分析師需要每天掃全表導出全量數據到 Hive,這對我們的集羣有很大壓力,並且也會導致數據延遲上升,我們在引入了 Doris 後,對所有數倉表都添加 eventStamp, updateStamp, deleted 這三個字段。
-
eventStamp:事件發生時間
-
updateStamp:Doris 數據更新時間,在 Routine Load 中生成
-
deleted:數據是否刪除,由於我們很多實時數倉需要定時同步到離線數倉,所以數據需要採取軟刪除的模式
數據對下游同步時可以靈活的選擇 eventStamp 或者 updateStamp 進行增量同步。
數據同步我們採用了多種方式,通過 Hive 表名後綴來決定不同同步場景:
-
_f:每天/每小時全量同步,基於 Doris Export 全量導出
-
_i:每天/每小時增量同步,基於 Doris Export 按分區導出/網易易數掃表導出
-
_d:每天鏡像同步,基於 Doris Export 全量導出
-
指標域劃分/數據分層
將 Elasticsearch 中的數據進行整理並結合後續的業務場景,我們劃分出了如下四個指標域:
根據上面的指標域,我們基於星型模型開始構建實時數倉,在 Doris 中構建了 20 餘張數倉底表以及 10 餘張維表,通過網易易數構建了完整的指標系統。
-
微批生成 DWS/ADS 層
由於我們多數場景都是明細+聚合數據的分析,所以我們基於 Doris insert into select 的導入方式,實現了一套定時根據 DWD 層數據生成 DWS/ADS 層數據的邏輯,延遲最低可以支持到分鐘級,整體的多層數倉表計算流程如下圖:
對於明細數據在 TiDB 或者 ES 的,我們選擇了在 Flink 中進行窗口聚合寫入到下游 Doris 或者 ES 中。而對於明細數據只在 Doris 單獨存在的數據,由於我們大部分使用了異步寫入的方式,所以數據無法立即可讀,我們在外圍構建了支持模版化配置的定時執行引擎,支持分鐘/小時級別的掃描明細表變更寫入下游聚合表,具體模版配置如下圖:
需要對監聽的源表以及變更字段進行配置,在配置的 interval 時間窗口內多個源表進行掃描,然後將結果進行 merge 後生成參數,根據配置的 threshold 對參數進行拆分後傳入多個 insert sql 中,並在每天凌晨進行 T+1 的全量聚合,修復微批計算的錯誤數據。
具體的計算觸發邏輯如下圖:
-
數據血緣
我們基於拉取 Routine Load 和 Flink 數據以及服務上報的方式實現了數據中臺完善的數據血緣,供數據開發/數據分析師進行查詢。
由於我們的 Flink 開發模式爲提交 jar 的形式,爲了獲取到任務的血緣,我們對每個算子的命名進行了格式化封裝,血緣服務定時的拉取/v1/jobs/overview 數據進行解析,我們將不同算子的格式命名封裝爲以下幾種:
-
Source:sourceTypeName [address] [attr]
-
Sink:sinkTypeName [address] [attr]
具體的血緣服務邏輯如下圖所示:
通過血緣服務內部的解析後,批量地將血緣數據拆分成了 Node 與 Edge 存儲到了 NebulaGraph 中,前臺服務進行查詢即可獲得如下圖所示的一條完整血緣:
3.2 數據中臺 2.0 架構
基於圍繞 Doris 的系統架構調整,我們完成了數據中臺 2.0 架構
-
使用網易易數數據運河替換 Canal,擁有了更完善的數據訂閱監控
-
Flink 計算層引入 Redis/Tidb 來做臨時/持久化緩存
-
複雜業務邏輯拆分至 Grpc 服務,減輕 Flink 中的業務邏輯
-
數據適配層新增 Restful 服務,實現一些 case by case 的複雜指標獲取需求
-
通過網易易數離線調度跑通了實時到離線的數據同步
-
新增了數據報表/自助分析系統兩個數據出口
-
4、Doris 帶來的收益
1. 數據導入方式簡單,我們針對不同業務場景使用了三種導入方式
-
Routine Load:實時異步數據導入
-
Broker Load:定時同步離線數倉數據,用於查詢加速
-
Insert into:定時通過 DWD 層數倉表生成 DWS/ADS 層數倉表
2. 數據佔用空間降低,由原來 Es 中的 1T 左右降低到了 200G 左右
3. 數倉使用成本降低
-
Doris 支持 MySQL 協議,數據分析師可以直接進行自助取數,一些臨時分析需求不需要再將 Elasticsearch 數據同步到 Hive 供分析師進行查詢。
-
一些在 ES 中的明細表我們通過 Doris 外表的方式暴露查詢,大大降低了業務方的查詢成本。
-
同時因爲 Doris 支持 Join,原來一些需要查詢多個 Index 再從內存中計算的邏輯可以直接下推到 Doris 中,提升了查詢服務的穩定性,加快了響應時間。
-
聚合計算速度通過物化視圖和列存優勢獲得了較大提升。
5、上線表現
目前已經上線了幾十個實時數據報表,在線集羣的 P99 穩定在 1s 左右。同時也上線了一些長耗時分析型查詢,離線集羣的 P99 穩定在 1min 左右。