作者|楊鷖 資深大資料開發工程師
編輯整理|SelectDB
領健是健康科技行業 SaaS 軟體的引領者,專注於消費醫療口腔和醫美行業,為口腔診所、醫美機構、生美機構提供經營管理一體化系統,提供了覆蓋單店管理、連鎖管理、健康檔案/電子病歷、客戶關係管理、智慧營銷、B2B交易平臺、進銷存、保險支付、影像整合、BI商業智慧等覆蓋機構業務全流程的一體化SaaS軟體。 同時透過開放平臺連線產業上下游,與優質的第三方平臺合作,為機構提供完整配套的一站式服務。 截止當前,領健已經在全國設立了 20 餘個分支機構,超過 30000 多家中高階以及連鎖機構正在使用其服務。
Doris 在領健的演進歷程
在進入正文之前,簡單瞭解一下 Doris 在領健的發展歷程。最初 Doris 替代 ClickHouse 被應用到資料服務專案中,該專案是領健為旗下客戶提供的增值報表服務;後在專案服務中發掘出 Doris 查詢效能優異、簡單易用、部署成本低等諸多優勢,在 2021 年10月,我們決定擴大 Doris 應用範圍,將 Doris 引入到公司的數倉中,在 Doris 社群及 SelectDB 專業技術團隊的支援下,業務逐步從 Kudu 遷移到 Doris,並在最近升級到 1。1。4 向量化版本。我們將透過本文為大家詳細介紹領健基於 Doris 的演進實踐及數倉構建的經驗。
資料服務架構演進
專案需求
領健致力於為醫療行業客戶提供精細化門店運營平臺,為客戶提供了資料報表工具,該工具可實現自助式拖拽設計圖表、支援多種自帶函式自建、資料實時更新等功能,可以支援門店訂單查詢、客戶管理、收入分析等,以推動門店數字化轉型,輔助門店科學決策。為更好實現以上功能,資料報表工具需滿足以下特點:
支援複雜查詢:客戶進行自助拖拽設計圖表時,將生成一段複雜的 SQL 查詢語句直查資料庫,且語句複雜度未知,這將對資料庫帶來不小的壓力,從而影響查詢效能。
高併發低延時:至少可以支撐
100 個
併發,並在
1 秒內
得到查詢結果;
資料實時同步: 報表資料來源自於 SaaS 系統,當客戶對系統中的歷史資料進行修改後,報表資料也要進行同步更改,保持一致,這就要求報表資料要與系統實現實時同步。
低成本易部署:SaaS 業務存在私有云客戶,為降低私有化部署的人員及成本投入,這要求
架構部署及運維要足夠簡單。
ClickHouse 遭遇併發宕機
最初專案選用 ClickHouse 來提供資料查詢服務,但在執行過程中 ClickHouse 遭遇了嚴重的併發問題,即 10 個併發就會導致 ClickHouse 宕機,這使其無法正常為客戶提供服務,這是迫使我們尋找可以替代 ClickHouse 產品的關鍵因素。
除此之外還有幾個較為棘手的問題:
雲上 ClickHouse 服務成本非常高,且 ClickHouse 元件依賴性較高,資料同步時 ClickHouse 和 Zookeeper 的頻繁互動,會對穩定性產生較大的壓力。
如何進行無縫遷移,不影響客戶正常使用。
技術選型
針對存在的問題及需求,我們決定進行技術選型,分別對 Doris(0。14)、Clickhous、Kudu 這 3 個產品展開的調研測試。
如上表所示,我們對這 3 個產品進行了橫向比較,可以看出 Doris 在多方面表現優異:
高併發:Doris 併發性好,可支援上百甚至上千併發,輕鬆解決 10 併發導致 ClickHouse 宕機問題。
查詢效能:Doris 可實現毫秒級查詢響應,在單表查詢中,雖 Doris 與 ClickHouse 查詢效能基本持平,但在多表查詢中,Doris 遠勝於 ClickHouse ,Doris 可以實現在較高的併發下,QPS 不下降。
資料更新:Doris 的資料模型可以滿足我們對資料更新的需求,以保障系統資料和業務資料的一致性,下文將詳細介紹。
使用成本:Doris 架構簡單,整體部署簡單快速,具有完備的匯入功能,很好的彈性伸縮能力;同時, Doris 內部可以自動做副本平衡,運維成本極低。而 ClickHouse 及 Kudu 對元件依賴較高,在使用上需要做許多準備工作,這就要求具備一支專業的運維支援團隊來處理大量的日常運維工作。
標準 SQL:Doris 相容 MySQL 協議,使用標準 SQL,開發人員上手簡單,不需要付出額外的學習成本。
分散式 Join :Doris 支援分散式 Join,而 ClickHouse 由於 Join 查詢限制、函式侷限性、以及可維護性較差等原因,不滿足我們當前的業務需求。
社群活躍:Apache Doris 是國內自研資料庫,開源社群相當活躍,同時 SelectDB 為 Doris 社群提供了專業且全職團隊做技術支援,遇到問題可以直接與社群聯絡溝通,並能得到快速解決,這對於國外的專案,很大的降低與社群溝通的語言與時間成本。
從以上調研中可以發現,Doris 各方面能力優秀,十分符合我們對選型產品的需求,因此我們使用 Doris 替代了 ClickHouse ,很好解決了ClickHouse 併發效能差、宕機等問題,很好的支撐了資料報表查詢服務。
數倉架構演進
在資料報表的使用過程中,我們逐漸發掘出 Doris 諸多優勢,因此決定擴大 Doris 應用範圍,將 Doris 引入到公司的數倉中來。
接下來將為大家介紹公司數倉從 Kudu 到 Doris 的演進歷程,以及在搭建過程中的最佳化實踐分享。
早期公司數倉架構
早期的公司數倉架構使用 Kudu、Impala 來作為運算儲存引擎,整體架構如下圖所示。
從上圖可知,資料透過 Kafka Consumer 進入 ODS 層;透過 Kudu 層滿足資料更新需要;運用 Impala 來執行資料運算和查詢;透過自研平臺 DMEP 進行任務排程。 在 ETL 程式碼中會使用大量的 Upsert 對資料進行 Merge 操作,那麼引入 Doris 的首要問題就是要如何實現 Merge 操作,支援業務資料更新,下文中將進行介紹。
基於 Doris 的新數倉架構設計
如上圖所示,在新架構設計中使用 Apache Doris 負責數倉儲存及資料運算;實時資料、 ODS資料的同步從 Kafka Consumter 改為 Flink ;流計算平臺使用團隊自研的 Duckula;任務排程則引入最新 的 DolphinSchedular,Dolphin schedule 幾乎涵蓋了自研 DMEP 的大部分功能,同時可以很方便拓展 ETL 的方式,可排程很多不同的任務。
最佳化實踐
資料模型選擇
上文中提到,當客戶對系統中的歷史資料修改後,報表資料也要進行同步更改,同時,客戶有時只更改某一列的數值,這要求我們需要選擇合適的 Doris 模型來滿足這些需求。我們在測試中發現,
透過 Aggregate 聚合模型Replace_if_not_null 方式進行資料更新時,可以實現單獨更新一列,程式碼如下:
drop table test。expamle_tbl2CREATE TABLE IF NOT EXISTS test。expamle_tbl2( `user_id` LARGEINT NOT NULL COMMENT “使用者id”, `date` DATE NOT NULL COMMENT “資料灌入日期時間”, `city` VARCHAR(20) COMMENT “使用者所在城市”, `age` SMALLINT COMMENT “使用者年齡”, `sex` TINYINT COMMENT “使用者性別”, `last_visit_date` DATETIME REPLACE_IF_NOT_NULL COMMENT “使用者最後一次訪問時間”, `cost` BIGINT REPLACE_IF_NOT_NULL COMMENT “使用者總消費”, `max_dwell_time` INT REPLACE_IF_NOT_NULL COMMENT “使用者最大停留時間”, `min_dwell_time` INT REPLACE_IF_NOT_NULL COMMENT “使用者最小停留時間”)AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)DISTRIBUTED BY HASH(`user_id`) BUCKETS 1PROPERTIES (“replication_allocation” = “tag。location。default: 1”);insert into test。expamle_tbl2 values(10000,‘2017-10-01’,‘北京’,20,0,‘017-10-01 06:00:00’,20,10,10);select * from test。expamle_tbl ;insert into test。expamle_tbl2 (user_id,date,city,age,sex,cost)values(10000,‘2017-10-01’,‘北京’,20,0,50);select * from test。expamle_tbl ;
如下圖所示,當寫 50 進去,可以實現只覆蓋
Cost
列,其他列保持不變。
Doris Compaction 最佳化
當 Flink 抽取業務庫全量資料、持續不斷高頻寫入 Doris 時,將產生了大量資料版本,Doris 的 Compaction 合併版本速度跟不上新版本生成速度,從而造成資料版本堆積。從下圖可看出,BE Compaction Score 分數很高,最高可以達到 400,而健康狀態分數應在 100 以下。
針對以上我們做了以下調整:
全量資料不使用實時寫入的方式,先匯出到 CSV,再透過 Stream Load 寫入 Doris;
降低 Flink 寫入頻率,增大 Flink 單一批次資料量;該調整會降低資料的實時性,需與業務側進行溝通,根據業務方對實時性的要求調整相關數值,最大程度的降低寫入壓力。
調節 Doris BE 引數,使更多 CPU 資源參與到 Compaction 操作中;
compaction_task_num_per_disk
單磁碟 Compaction 任務執行緒數預設值 2,提升後會大量佔用CPU資源,阿里雲 16 核,提升 1 個執行緒多佔用 6% 左右 CPU。
max_compaction_threads compaction
執行緒總數預設為10。
max_cumulative_compaction_num_singleton_deltas
引數控制一個 CC 任務最多合併 1000 個數據版本,適當改小後單個 Compaction 任務的執行時間變短,執行頻率變高,叢集整體版本數會更加穩定。
透過調整叢集, Compaction Score 穩定在了
50-100,有效解決了版本堆積問題
。
值得關注的是,在 Doris 1。1 版本中對 Compaction 進行了一系列的最佳化,
在任務排程層面
,增加了主動觸發式的 Compaction 任務排程,結合原有的被動式掃描,高效的感知資料版本的變化,主動觸發Compaction。
在任務執行層面
,對資源進行隔離,將重量級的 Base Compaction 和輕量級的Cumulative Compaction 進行了物理分離,防止任務的互相影響。同時,
針對高頻小檔案的匯入
,最佳化檔案合併策略,採用梯度合併的方式,保證單次合併的檔案都屬於同一數量級,逐漸有層次的進行資料合併,減少單個檔案參與合併的次數,大幅節省系統消耗。
負載隔離
最初我們只有 1 個 Doris 叢集,Doris 叢集要同時支援高頻實時寫、 高併發查詢、ETL 處理以及Adhoc 查詢等功能。其中高頻實時寫對 CPU 的佔用很高,而 CPU 的上限決定高併發查詢的能力,另外 Adhoc 查詢無法預知 SQL 的複雜度,當複雜度過高時也會佔用較高的記憶體資源。這就導致了資源競爭,業務之前互相影響的問題。為解決這些問題,我們進行了以下探索最佳化。
Doris 叢集拆分
最初我們嘗試對 Doris 叢集進行拆分,我們把 1 個叢集拆分為 3 個叢集,分別為 ODS 叢集、DW 叢集、ADS 叢集。我們將 CPU 負載最高的 ODS 層分離出去, ETL 時,透過 Doris 外表連線另一個 Doris 叢集抽取資料;同時也將 BI 應用訪問的叢集分離出去,獨立為業務提供資料查詢。如下所示為各叢集負責的任務:
ODS 叢集:數倉 ODS 層,Flink 寫資料集中在此層進行。
DW 叢集:數倉 DW 層,DIM 層,主要負責 ETL 處理,Adhoc 查詢任務。
ADS 叢集:數倉 ADS 層,主要支援 Web 應用的資料查詢
透過叢集拆分,有效降低各個資源間的相互影響,保證每個業務運轉都有較充足的資源。但是叢集的拆分也存在叢集之間資料同步 ETL 時間較長、從 ADS 到 ODS 跨3個叢集的資料校驗複雜度較高等問題。直到 Doris 0。15 釋出後,這些問題也得到了相對有效的解決。
資源隔離最佳化叢集資源
Doris 0。15 版本新增了資源標籤功能以及查詢 Block 功能,資源標籤功能允許 Doris 叢集實現資源隔離,該功能有效減少叢集之間同步資料的時間,降低了跨叢集資料校驗複雜度。其次查詢 Block 功能的上線,可以對 SQL 進行查詢審計,阻塞簡單/不合規的查詢語句,降低資源佔用率,提升查詢效能。除此之外,透過資源隔離的方式,我們將 3 個叢集合併成 1 個叢集,被合併的 6 個入口節點 FE 被釋放掉,將節省的資源加到核心的運算節點上來。
升級到 Doris 0。15 後,我們將 ODS 表的副本修改為
group_ods
3份,
default
3份。 Flink 寫入時只寫
group_ods
資源組的節點,資料寫入後,得益於 Doris 內部的副本同步機制,資料會自動實時同步到
default
資源組。ETL 則可以使用
default
資源組的節點資源取用 ODS 資料,進行查詢和資料處理。同理 ADS 也做了相同處理,原先需要透過外表進行資料抽取同步的表,均被做成了副本跨資源組的形式。此方式有效縮短了跨叢集資料同步的 ETL 時長 。
離線 ETL 記憶體高
我們使用的是離線 ETL 方式直接在 Doris 上做 ETL 操作,在 Join 時,如果右表資料量比較大的情況下會消耗大量的記憶體,從而造成 OOM。在 1。0 版本之前記憶體跟蹤能力較弱,容易造成 BE 節點超出 Linux 限制,導致程序被關閉 ,這時候會收到以下報錯資訊:
Host is down
或者
Fail to initialize storage reader
。在1。0 及更高版本中, Doris 由於優化了記憶體跟蹤,則容易見到以下報錯:
Memory exceed limit。 Used: XXXX ,Limit XXXX。
針對記憶體受限問題,我們開始尋找最佳化方案,另外由於公司內部資源受限,最佳化方案必須在不增加叢集成本的情況下把超出叢集負荷的任務跑通。這裡為大家介紹 2 個解決方法:
最佳化調整 Join 的方式:
Doris 內部 Join 分為 4 種,其記憶體開銷以及優先順序如下圖所示:
從上圖可知,Join 型別優先順序從左往右依次變低,Shuffle 的優先順序最低,排在 Broadcast 之後。值得注意的是, Broadcast 記憶體開銷非常大,它將右表廣播到所有 BE 節點,這相當於每個 BE 節點會消耗一個右表的記憶體,這將造成很大的記憶體開銷。
針對 Broadcast 比較大的記憶體開銷,我們透過 Hint 條件強制 Join 型別的方式,使 Join 語句跳過 Broadcast 到 Shuffle Join ,從而降低記憶體消耗
。
select * from a join [shuffle] b on a。k1 = b。k1;
資料分批處理
我們嘗試將資料按照時間分批,每批涵蓋某一個或某幾個時間段的資料,分批進行 ETL,有效降低記憶體消耗,避免 OOM 。分批須知:需要將分批的標記列放在主鍵中,最大程度提升搜尋資料的效率;注意分桶和分割槽的設定方式,保證每個分割槽的資料量都比較均衡,避免個別分割槽記憶體佔用較高的問題。
總結
新架構收益:
基於 Doris 的新數倉架構不再依賴 Hadoop 生態元件,運維簡單,維護成本低。
具有更高效能,使用更少的伺服器資源,提供更強的資料處理能力。
支援高併發,能直接支援 WebApp 的查詢服務。
支援外表,可以很方便的進行資料釋出,將資料推送其他資料庫中。
支援動態擴容,資料自動平衡。
支援多種聯邦查詢方式,支援 Hive、ES、MySQL 等
得益於新架構的優異能力,我們所用叢集從
18 臺 16C128G 減少到 12 臺 16C128G
,叢集資源較之前
節省了33%
,大大降低了投入成本;並且運算效能得到大幅提升,在 Kudu 上
3 小時
即可完成的 ETL 任務, Doris 只需要
1 小時
即可完成 。除此之外,高頻更新的場景下,Kudu 內部資料碎片檔案不能進行自動合併,表的效能會越來越差,需要定期重建;而Doris 內部的 Compaction 機制可以有效避免此問題。
社群寄語:
首先,Doris 的使用成本很低,僅需要 3 臺低配伺服器、甚至是桌上型電腦,就能相對容易的部署一套基於 Doris 的數倉作為資料中臺基礎;我認為對於想要進行數字化,但介於資源投入有限而又不想落後於市場的企業來說,非常建議嘗試使用 Apache Doris ,Doris 可以助力企業低成本跑通整個資料中臺。
其次,Doris 是一款國人自研的的 MPP 架構分析型資料庫,這令我感到很自豪,同時其社群十分活躍、便於溝通,Doris 背後的商業化公司 SelectDB 為社群組建了一支專職技術團隊,任何問題都能在 1 小時內得到響應,近 1 年社群更是在 SelectDB 的持續推動下,推出了一系列十分抗打的新特性。另外社群在版本迭代時會認真考慮中國人的使用習慣,這些會為我們的使用帶來很多便利。
最後,感謝 Doris 社群和 SelectDB 團隊的全力支援,也歡迎開發者以及各企業多多瞭解 Doris、使用 Doris,支援國產資料!
Doris 1。2。0 版本
Apache Doris 於 2022 年 12 月 7 日迎來 1。2。0 Release 版本的正式釋出!新版本中
實現了全面的向量化、實現多場景查詢效能 3-11 倍的提升
,在 Unique Key 模型上
實現了 Merge-on-Write 的資料更新模式、資料高頻更新時查詢效能提升達 3-6 倍
,增加了 Multi-Catalog 多源資料目錄、
提供了無縫接入
Hive
、ES、Hudi、Iceberg 等外部資料來源的能力
,引入了 Light Schema Change 輕量表結構變更、
實現毫秒級的 Schema Change 操作
並可以藉助 Flink CDC
自動同步上游資料庫的 DML 和
DDL
操作
,以 JDBC 外部表替換了過去的 ODBC 外部表,支援了 Java UDF 和 Romote UDF 以及 Array 陣列型別和 JSONB 型別,修復了諸多之前版本的效能和穩定性問題,推薦大家下載和使用!
下載安裝
GitHub下載:https://github。com/apache/doris/releases
官網下載頁:https://doris。apache。org/download
原始碼地址:https://github。com/apache/doris/releases/tag/1。2。0-rc04