天天看點

知乎 Druid 叢集優化實踐

作者:閃念基因

背景

随着業務的發展,Druid 叢集規模不斷增長;一個美好的傍晚,打開電腦準備看一場球賽,突然,被一陣急促的報警電話,打破甯靜,叢集查詢失敗率飙升。。。此時迅速打開叢集監控大盤,發現此時叢集 Load 已經被跑飛;對于負責叢集的同學來說,在叢集發展到早中期,應該是大家都會遇到的問題。

本篇文章主要講述圍繞知乎在 Druid 叢集建設發展中遇到的問題以及如何提升穩定性為主題展開。通過閱讀文章,可以收獲到叢集從極速發展到不穩定再到穩定運作的治理曆程,踩過的每一個坑,希望對大家有幫助。

先介紹一下 Druid 在知乎的應用,截止目前在知乎 Druid 叢集現狀 節點70+;資料源300+;存儲規模 600TB+。

知乎 Druid 叢集優化實踐

如上圖所示,在知乎 Druid 主要是針對兩種場景,一種是實時接入,滿足實時名額展示及實時分析;另一種是離線導入 Hive 資料,加速Hive資料查詢分析,提高分析師工作效率。主要使用的業務類型為 A/B Testing、管道管理、APM 、資料郵件等業務。

Druid 簡介

本小結主要是針對 Druid 初學者,如果你已經很了解 Druid 架構,可以直接閱讀下一章節内容,略過此章節。

知乎 Druid 叢集優化實踐

Druid 的整體架構如上圖所示,其中主要有 3 條路線:

  1. 實時攝入的過程: 實時資料會首先按行攝入 Real-time Nodes,Real-time Nodes 會先将每行的資料加入到1個 map 中,等達到一定的行數或者大小限制時,Real-time Nodes 就會将記憶體中的 map 持久化到磁盤中,Real-time Nodes 會按照segmentGranularity 将一定時間段内的小檔案 merge 為一個大檔案,生成 Segment,然後将 Segment 上傳到 Deep Storage(HDFS,S3)中,Coordinator 知道有Segment 生成後,會通知相應的 Historical Node 下載下傳對應的 Segment,并負責該Segment 的查詢。
  2. 離線攝入的過程:離線攝入的過程比較簡單,就是直接通過 MR job 生成 Segment,剩下的邏輯和實時攝入相同。
  3. 使用者查詢過程: 使用者的查詢都是直接發送到 Broker Node,Broker Node 會将查詢分發到 Real-time 節點和 Historical 節點,然後将結果合并後傳回給使用者。

各節點的主要職責如下:

Historical Nodes

Historical 節點是整個 Druid 叢集的骨幹,主要負責加載不可變的 Segment,并負責 Segment的查詢(注意,Segment 必須加載到 Historical 的記憶體中才可以提供查詢)。Historical 節點是無狀态的,是以可以輕易的橫向擴充和快速恢複。Historical 節點 load 和 drop Segment 是依賴 ZK 的,但是即使 ZK 挂掉,Historical 依然可以對已經加載的 Segment 提供查詢,隻是不能再 load 新Segment,drop 舊 Segment。

Broker Nodes

Broker 節點是Druid查詢的入口,主要負責查詢的分發和 Merge。 之外,Broker 還會對不可變的 Segment 的查詢結果進行 LRU 緩存。

Coordinator Nodes

Coordinator 節點主要負責 Segment 的管理。Coordinator 節點會通知 Historical 節點加載新Segment,删除舊 Segment,複制 Segment,以及 Historical 間的負載均衡。

Coordinator 節點依賴 ZK 确定 Historical 的存活和叢集 Segment 的分布。

Real-time Node

實時節點主要負責資料的實時攝入,實時資料的查詢,将實時資料轉為 Segment,将Segment 配置設定 給Historical 節點。

Zookeeper

Druid 依賴 ZK 實作服務發現,資料拓撲的感覺,以及 Coordinator 的選主。

Metadata Storage

Metadata storage(Mysql) 主要用來存儲 Segment 和配置的中繼資料。當有新 Segment 生成時,就會将Segment的元資訊寫入 metadata store, Coordinator 節點會監控 Metadata store 進而知道何時 load 新 Segment,何時 drop 舊 Segment。注意,查詢時不會涉及 Metadata store。

Deep Storage

Deep storage (S3 and HDFS) 是作為 Segment 的永久備份,查詢時同樣不會涉及 Deep storage。

知乎 Druid 平台架構演進

平台架構 V1.0

知乎 Druid 叢集優化實踐

早期 Druid 叢集架構如上圖所示,功能上支援資料的實時攝入和離線批量導入:

  1. 實時資料攝入:基于 Traquility 實作了消費 Kafka 資料,實時攝入 Druid,提供實時查詢服務
  2. 資料離線導入:基于内部的離線排程平台,可視化支援數倉工程師建立離線定時導入任務
  3. 資料存儲:資料源所有資料均存儲在大叢集,Historical 節點均為 SSD 磁盤,資料單備份
  4. 監控系統:

随着業務的日益增加,目前技術方案出現了如下痛點:

  1. 業務查詢逾時:随着業務的日益需求的日益增長,查詢業務并發不斷提高,出現高并發下,查詢逾時,業務間互相影響
  2. 資料可用性差:由于此架構下,資料源的 Segment 在 Historical 節點無備份,任何一台 Historical 節點當機,整個叢集中的資料不可查。
  3. 實時資料丢失:基于 Traquility 實作的實時資料攝入,存在超出時間視窗後,資料丢失
  4. 存儲成本增加:由于業務前期,為了提升查詢性能,資料均存儲在 SSD 磁盤中

由于前期重點放在拓展業務上,導緻發展到一定階段後,出現了叢集建設不合理上帶來的痛點;基于以上痛點的問題根源分析,Druid 平台 V2.0 應運而生。

平台架構V2.0

知乎 Druid 叢集優化實踐

平台架構 V2.0 如上圖所示,對比早期叢集,做了如下改進:

  1. 實時資料攝入:采用 Kafka Indexing Service 方案,解決了超出時間視窗丢失資料的問題,實作了 exactly-once ingestion 語意
  2. 業務隔離,資料冷熱分層:

冷熱分層的依據是什麼呢?在确定規則前,對于業務查詢的場景及查詢曆史進行了分析,最近一個月的資料查詢占比 97% 左右;一個月以外的資料查詢占比比較低,使用特點是使用者月末或季度末查詢資料所使用

知乎 Druid 叢集優化實踐

如上圖所示,核心業務和其他業務進行存儲隔離,内部進行冷熱分層,具體存儲規則制定:

  • 采用統一雙副本
  • 一個月内的資料存儲 :Hot Tier 存儲一份;Cold Tier 存儲一份
  • 一年内的資料:Cold Tier 存儲兩份

收益:

  • 資料雙副本:提升了資料可用性,不會因為某個HIstorical節點當機而不可用
  • 業務隔離:提升了查詢響應速度,降低了業務查詢之間的互相影響
  • 冷熱資料分層:降低了資料存儲成本,叢集成本大約降低 40%

3. 監控系統:

主要實作目标是對資料加載和查詢行為進行監控,做到問題前,問題中,問題後均可根據監控發現問題;随着問題的不斷出現,監控大盤不斷進行疊代,到目前為止,現有監控大盤做到了能夠及時發現問題,研判問題,核心監控點如下所述:Coordinator 排程監控

知乎 Druid 叢集優化實踐

作用:資料源離線加載後,長時間無法資料可見查詢;出現問題時此名額會長時間不輸出。

名額: 此監控名額代表着每次 Coordinator 對資料源 Segment 管理排程周期以及 Assign 到 Historical 的 SegmentRouter 查詢并發監控

知乎 Druid 叢集優化實踐

作用:設定并發查詢門檻值報警,及時發現高并發,以免持續高并發影響叢集查詢響應

查詢時間實時監控

知乎 Druid 叢集優化實踐

作用:叢集 CPU Load 持續飙高,影響叢集查詢響應;快速定位是哪個資料源的查詢導緻

其實監控系統在建設之初,一般按照官方文檔,基礎的核心監控名額都會展示,由于篇幅有限,在這裡就沒有展開詳細介紹,隻是選取了随着系統運作,發現問題不斷疊代出的名額,它們具有典型特點:

a. 在問題前做到預警,比如并發查詢監控,能夠早于業務發現問題,提前幹預,降低對業務的影響;

b.在問題中及時止損,比如查詢實時使用CPU時間監控,當叢集 Load 持續飙高,影響業務查詢時,及時定位資料源,人為介入處理。

4. 叢集管理系統:

此系統主要功能是管理 Druid 叢集,記錄節點狀态,展示導入任務記錄,搜尋查詢日志,探測查詢行為等功能,具體功能展示如下圖:

知乎 Druid 叢集優化實踐

收益:極大了提升了叢集管理效率,為問題決策提供了依據

本小結主要講述了 Druid 平台不斷疊代的過程,從 V1.0 随着問題痛點的出現,經過方案調研和具體情況分析,産出 V2.0 叢集建構方案并落地,主要從業務隔離,冷熱資料,監控系統打造,管理系統建設幾個方面優化叢集建設,做到了降低叢集成本,迅速發現并解決問題,極大的提升了叢集穩定性。

穩定性優化

随着完成推動叢集架構的演進,解決了大部分問題,叢集可用性提升到一個水準,但是問題又來了,就是追求叢集的穩定性要穩定在99.9%以上

加載任務 Pending

問題描述:

首先實時加載任務 Segment 一直處于 Hand Off 階段,無法釋放加載 worker 資源,造成新的實時任務無法啟動處于 Pending 狀态;其次,對于離線加載任務,任務狀态顯示成功,但是資料延遲查詢不可見

問題分析:

首先看一下 Segment 生成後,Assign 到 Historical 的整個事件流,如下圖:

知乎 Druid 叢集優化實踐

如上圖所示,關鍵流程描述如下:

  1. Worker 将生成的 Segment 檔案寫入持久化檔案系統 HDFS 或 S3
  2. Worker 将 Segment 元資訊寫入資料庫 Mysql
  3. Coordinator 定期排程擷取 Segment 元資訊和 資料源 的規則Rule
  4. Coordinator 将需要加載或删除的 Segment 消息同步到 ZK
  5. Historical 從 ZK 擷取加載或删除 Segment 的消息
  6. Historical 從持久化檔案系統拉取 Segment 檔案
  7. Historical 将對應的同步消息從 ZK删除

Coordinator 通過 ZK 與 Historical 進行 Segment Load 資訊排程;當 RealTime 實時加載任務所産生的所有 Segment 加載到 Historical 時才能釋放Druid MiddleManager 上的 worker;

根據線上worker 日志已經确定1,2兩步已經完成;需要進一步分析 Coordinator 日志及排程線程模型明确關鍵流程中問題:

Coordinator 中負責 Segment 管理的模型是串行處理模型, 是以問題根源性原因是一個排程周期内,産生了大量的Segment(按照經驗2W+ Segment),導緻此排程周期長時間運作,在此排程周期内産生的 Segment 無法 Assign 到 Historical,最終導緻實時加載任務無法釋放 Worker;造成資料積壓。

解決方案:

規範業務導入資料量,減少 Segment 個數, 每次導入的資料不超過 7 天。

髒查詢治理

還記得開篇中,突然被報警打破的甯靜嗎?經過 Druid 架構調整後,這種問題不會出現了,但是進一步提高了标準,叢集查詢可用性要不低于99.9%,是以10分鐘持續低于SLA标準,也不能接受。

首先描述一下髒查詢的定義:就是某些查詢導緻叢集 Load 高,造成正常的查詢不能響應,影響叢集的SLA;對于 Druid 所謂的髒查詢主要包括:查詢無filter,資料全掃描;javascripe 正則比對查詢; 次周或次月留存等。

問題描述:

偶爾叢集查詢SLA低于 99%,造成部分查詢逾時

問題分析:

叢集查詢 SLA 降低監控

知乎 Druid 叢集優化實踐

CPU Load 監控

知乎 Druid 叢集優化實踐

查詢CPU耗時實時監控

知乎 Druid 叢集優化實踐

從這個監控大盤定位資料源,經過查詢分析,此資料源在進行近一個月内的次月留存分析,這種查詢非常消耗計算資源。

根據監控可以得出結論,是由于某些髒查詢導緻節點資源耗盡,造成叢集查詢SLA下降。

解決方案:

利用叢集管理系統中查詢行為探測,細粒度分析了最近幾個月的查詢行為,發現99.87%的查詢都能夠秒級傳回,髒查詢共同的特點是分鐘級傳回結果;是以制定了一個治理髒查詢的政策:在 Broker 層面統一設定查詢逾時時間,熱資料逾時時間為 1 Min;冷資料逾時時間為 3 Min。

配置:

druid.server.http.maxIdleTime=PT1m
druid.server.http.defaultQueryTimeout=60000
druid.server.http.maxQueryTimeout=60000           

踩坑:如果線上的版本低于0.13.0 需要打 Patch(issues:) defaultQueryTimeout 參數才能生效。

修複 Historical 慢啟動

問題描述

目前冷資料存儲Historical (12*2T SATA HDD)節點存儲超過 50K Segments,大約有10TB+ 的資料規模,每當節點OOM當機或者重新開機服務,會耗費半個小時左右重新開機時間;但帶來的後果是造成資料移動,影響實時加載任務,甚至造成資料丢失。

問題分析

根據(issue:)的問題分析及解決方案,通過日志和源碼分析是Historical 啟動初始化流程,發現Historical 初始化過程需要讀取資料源中 Segment 中每一列的元資訊, Segment 檔案格式如下圖所示:

知乎 Druid 叢集優化實踐

如上圖所示,就是需要去磁盤讀取每一列的 ColumnDescriptor;可以了解當資料節點存儲資料量比較大,又存儲在多列的情況下,會産生很多随機IO操作;HDD 本身100左右 極限 IOPS;是以大量的時間耗費在了随機IO操作上。

在現有的資料量下,重新開機一次耗時在 30 Min以上,其實對于系統的穩定性留下了比較大的隐患。

解決方案

内部版本引入PR ,由于社群中是基于0.13+以上解決的此問題,是以引入此PR 到内部版本,主要解決了接口不相容的問題;并引入到了0.12.1版本,需要此版本Patch 的可以自取 。

解決的核心思想就是典型的 Lazy 加載模式,如下代碼所示:

Map<String, Column> columns = new HashMap<>(); ===》Map<String, Supplier<Column>> columns = new HashMap<>();           

通過 Guava 庫中的Supplier特性來實作 Lazy 加載模式,Supplier<Column> 修飾的Column對象,在第一次get 的時候才被真正的初始化對象

使用配置:

druid.segmentCache.numBootstrapThreads=10
druid.segmentCache.lazyLoadOnStart=true           

收益:重新開機時間由 30Min 縮減到 3Min以内

本小結主要講述了為了叢集可用性 99.9% 的目标所做的工作,圍繞着資料加載Pending問題,查詢穩定性問題,慢啟動造成的資料移動問題進行了原因定位分析,這些問題應該會伴随着叢集規模和使用而出現,為了能夠更好的提供服務,所要踩的坑。

Druid 使用建議

經過踩坑之旅,我們沉澱了對于離線資料導入和查詢的使用建議

Druid 資料導入:

  1. Druid 本身支援實時導入查詢和批量導入兩種模式,實時導入目前依賴 Tranquality 服務管理,批量導入通過 Hadoop MapReduce 導入 Hive 表資料。實時導入可支援實時查詢,批量導入隻有在導入完成時資料可查。
  2. Druid 支援對導入資料按時間粒度預聚合,進而減少資料存儲量,提高查詢性能,是以要求在資料導入時必須設定預聚合時間粒度,粒度大小根據業務查詢的最小時間精度決定。
  3. Druid segment 的 shard 大小應該控制在 500 MB 以下,防止單個 shard 過大導緻 OOM。
  4. 針對批量導入,每次導入資料的 segment 不能過多,每次導入資料量不要超過 7 天

Druid 資料查詢

  1. 查詢主要分為次元查詢(topN/groupby)和非次元查詢 (timeseries)
  2. 次元查詢中,避免 groupby 查詢多個高基數次元(count(distinct(次元列))),會導緻查詢時記憶體爆炸,盡量不要導入高基數次元到 Druid 中,如果無法避免,隻能用 topN 查詢單個次元
  3. 對于大時間範圍的查詢,可以拆分為多個小時間範圍的查詢,并行送出,以充分利用 Druid 多節點并行查詢能力
  4. 資料需要配置多副本,預設為 2 副本,以保障資料高可用,同時需要區分冷熱,預設最近一個月為熱資料(存放在 ssd 上),一個月之前的為冷資料 (存放在普通磁盤上),一年前的資料為冷備資料(存放在 HDFS 中,不可查詢)

總結

首先本文主要圍繞 Druid 叢集在知乎的應用落地過程為主線,講述了 Druid簡介,在知乎的應用場景; 在落地的過程中經曆了快速發展到穩定性痛點挑戰的過程;最終經過平台架構V1.0到平台架構V2.0的架構疊代,解決了發展中遇到的痛點。

其次,本文中介紹的穩定性優化過程,平台有效的監控對于分析問題能夠起到提效作用;問題明确後,積極參與社群讨論,這樣能夠提高問題解決的效率。

最後,希望讀者可以收獲到整個建設過程中遇到的問題和解決方案,對日常工作有所幫助!

作者:Jacky2020

出處:https://zhuanlan.zhihu.com/p/67607200