知乎如何基於開源 Druid 打造下一代數據分析平臺?

知乎如何基於開源 Druid 打造下一代數據分析平臺?

出處丨AI前線

本文重點介紹了知乎數據分析平臺對 Druid 的查詢優化。通過自研的一整套緩存機制和查詢改造,該平臺目前在較長的時間內,滿足了業務固化的指標需求和靈活的分析需求,減少了數據開發者的開發成本。

背 景

知乎作為知名中文知識內容平臺,業務增長和產品迭代速度很快,如何滿足業務快速擴張中的靈活分析需求,是知乎數據平臺組要面臨的一大挑戰。

知乎數據平臺團隊基於開源的 Druid 打造的業務自助式的數據分析平臺,經過研發迭代,目前支撐了全業務的數據分析需求,是業務數據分析的重要工具。

目前,平臺主要的能力如下:

  1. 統一的數據源管理,支持攝入離線數倉的 Hive 表和實時數倉的 Kafka 流
  2. 自助式報表配置,支持多維分析報表、留存分析報表
  3. 靈活的多維度多指標的組合分析,秒級響應速度,支持嵌套式「與」和「或」條件篩選
  4. 自助式儀表盤配置
  5. 開發平臺接口,為其他系統提供數據服務
  6. 統一的數據權限管理

目前,業務使用平臺的數據如下:

  1. 自助式配置儀表盤數:495 個,儀表盤內報表共計:2399 張
  2. 日請求量 3w+
  3. 為 A/B Testing、渠道管理、APM 、數據郵件等系統提供數據 API

數據分析平臺架構

知乎如何基於開源 Druid 打造下一代數據分析平臺?


知乎實時多維分析平臺架構

技術選型 - Druid

Druid 是一種能對歷史和實時數據提供亞秒級別的查詢的數據存儲。

Druid 支持低延時的數據攝取,靈活的數據探索分析,高性能的數據聚合,簡便的水平擴展。適用於數據量大,可擴展能力要求高的分析型查詢系統。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


Druid 整體架構

Druid 數據結構和架構簡介

Druid 數據結構

  • DataSource:Druid 的基本數據結構,在邏輯上可以理解為關係型數據庫中的表。它包含時間、維度和指標三列。
  • Segment:Druid 用來存儲索引的數據格式,不同的索引按照時間跨度來分區,分區可通過 segmentGranularity(劃分索引的時間粒度)進行配置。

查詢服務的相關組件

內部組件

  • Historical:用於加載和提供 Segment 文件供數據查詢。
  • Broker:提供數據查詢服務,通過路由查詢請求到對應的 Historical 節點並獲得數據,合併數據後返回給調用方。
  • Router:當 Druid 集群到達 TB 級別的規模時才需要啟用的節點,主要負責將查詢請求路由到不同的 Broker 節點上。

外部組件

  • Deep Storage:用於存儲 Segment 文件供 Historical 節點下載。Deep Storage 不屬於 Druid 內部組件,用戶可根據系統規模來自定義配置。單節點可用本地磁盤,分佈式可用 HDFS。
  • Metastore Storage:用於存儲 Druid 的各種元數據信息,屬於 Druid 的外部依賴組件,生產環境中可用 MySQL。

平臺的演進

Druid 查詢量大

在 Druid 成為主力查詢引擎之後,我們發現大查詢量的場景下直接查 Druid 會有一些弊端,其中最大的痛點就是查詢響應慢。

緩存查詢結果

為了提高整體查詢速度,我們引入了 Redis 作為緩存。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


使用 Redis 來緩存查詢結果

最簡單的緩存設計,是將 Druid 的查詢體(Request)作為 key,Druid 的返回體(Response)作為 value。上述的緩存機制的缺點是顯而易見的,只能應對查詢條件完全一致的重複查詢。在實際應用中,查詢條件往往是多變的,尤其是查詢時間的跨度。

舉個例子,在相同指標維度組合下用戶發起兩次查詢,第一次查詢 10 月 1 日到 10 月 7 日的數據,Druid 查出結果並緩存到 Redis。用戶調整時間跨度到 10 月 2 日到 10 月 8 日,發起第二次查詢,這條請求的不會命中 Redis,又需要 Druid 來查詢數據。

從例子中,我們發現兩次查詢的時間跨度交集是 10 月 2 日到 10 月 7 日,但是這部分的緩存結果並沒有被複用。這種查詢機制下,查詢延時主要來自於 Druid 處理重複的請求,緩存結果沒有被充分利用。

提高緩存的複用

為了提高緩存複用率,我們需要增加一套新的緩存機制:當查詢在 Redis 沒有直接命中時,先掃描 Redis 中是否已緩存查詢中部分時間跨度的結果提取命中的結果,未命中再查詢 Druid。在掃描的過程中,被掃描的對象是單位時間跨度的緩存。

為了能獲得到任意一個單位時間跨度內的緩存,除了在 Redis 中緩存單條查詢的結果之外,需要進一步按時間粒度將總跨度等分,緩存所有單位時間跨度對應的結果(如下圖所示)。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


Druid 結果按時間粒度緩存

減少 Redis IO

從上圖中我們發現,對每個單位時間跨度的結果判斷是否已被緩存都需要對 Redis 進行一次讀操作,當用戶查詢量增大時,這種操作會對 Redis 集群造成比較大的負擔,偶爾會出現 Redis 連接超時的情況。為了減少對 Redis 的 IO,我們對時間跨度單獨設計了一套緩存機制。基於減少讀操作的想法,我們設計了通過一次讀操作就可得到已經被緩存的所有時間跨度,然後再一次性地將所有緩存的結果讀出。

  1. 要實現一次性獲得緩存的所有時間跨度,我們需要在每次緩存 Druid 查詢結果後,再緩存查詢請求和它覆蓋的時間跨度,在 Redis Key-Value 規則上,我們先把查詢體(Request)剔除時間跨度,生成一個時間無關的查詢體(IntervalExcludedRequest)作為緩存的 key;提取查詢粒度(Granularity),把剔除出來的時間跨度按粒度分隔開(Set)作為 value。
  2. 要實現一次性讀出所有緩存結果,通過 Redis 的 MGET 獲得 IntervalExcludedRequest 對應的各個時間戳。
  3. 判斷當前請求的所有單位時間跨度是否命中緩存,命中的結果會被直接返回。優化後緩存機制如下圖所示:


知乎如何基於開源 Druid 打造下一代數據分析平臺?


Redis 讀操作優化

Druid 查詢時間跨度長

在未命中緩存的情況下,設置較長的查詢時間跨度(長時間跨度:2 周以上),Druid 經常會出現返回速度變慢,甚至阻塞其他查詢請求的現象。

我們測試了長時間跨度的查詢請求對集群整體的影響,通過對 Druid 集群的監控數據的分析,我們發現被長時間跨度查詢命中的 Broker 節點會出現內存消耗過大的問題,並且隨著時間跨度的增大,內存消耗跟著提高,甚至出現內存不足導致 Broker 節點無響應的問題。

一個 Broker 處理一個請求

在調研了 Druid 執行原理以後,我們發現一個查詢請求只會被 Router 路由到一個 Broker 節點,經由 Broker 節點去 Historical 節點上查找目標數據在 Deep Storage 的存儲位置,最後返回的數據也是經過 Broker 節點來合併返回結果。查詢的時間跨度越長,對 Broker 的壓力也越大,內存消耗越多。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


單個 Broker 處理長時間跨度查詢

多個 Broker 處理一個請求

單個 Broker 的性能無法滿足長時間跨度的查詢,為了讓提高查詢性能,我們嘗試把一個查詢 N 天數據的請求,拆分成 N 個查詢,每個只查詢一天,然後異步地將這些請求發出,結果這 N 個請求都被很快得返回了。和拆分前的查詢耗時相比,拆分後的耗時大大減小。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


多個 Broker 處理長時間跨度查詢

從 Broker 節點的監控來看,當一個長時間的查詢請求被多個 Broker 一起處理,可以減少單個 Broker 內存消耗,並且加快了整體的查詢速度 。提速程度請參考下圖的測試比對,測試用例採用平臺一天的所有查詢,測試方式是在不命中 Redis 的情況下異步地「回放」這些查詢到 Druid。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


benchmark

根據上述 Broker 在查詢過程中的工作原理,想達到長時間跨度查詢的提速,我們需要在用戶發起查詢之後把請求拆分。拆分的機制是根據每個查詢請求的查詢時間粒度而定,例如上述中的一個 N 天跨度的天粒度請求,在查詢到達 Druid 集群之前,我們嘗試把它拆分成 N 個 1 天跨度的天級別粒度請求。整個查詢從拆分到命中 Druid 的過程如下圖所示(在 Druid 內部的工作細節請參考上文)。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


按時間粒度拆分用戶查詢請求

緩存結果過期

前兩步的演進完成了從高負載下查詢性能低、查詢時間跨度長而速度慢、Redis 複用率低,到查詢性能高、Redis IO 穩定。

分析平臺的數據源來自於離線數倉的 Hive 和實時數倉的 Kafka。重新攝入上游數據到 Druid 後, 對應時間列的 Segment 文件會進行重建索引。在 Segment 文件索引重建之後,對應的 Druid 查詢結果也會發生改變。當這個情況發生時,用戶從 Redis 獲取到的結果並沒有及時得到更新,這時就會出現數據不一致的情況。因此一套平臺用戶無感的緩存自動失效機制就顯得尤為重要。

緩存自動失效

在 Druid 查詢鏈路下,數據源的最近成功攝入的時間可以被抽象為它的最新版本號,利用這一的思想,我們可以給每個數據源都打上數據版本的標籤,在數據更新後,給更新的數據源替換新的標籤。這樣一來,每次校驗 Druid 查詢結果是否過期時就有了參照對象。

Druid 支持 MySQL 存儲元數據信息(Metastore Storage),元數據中的時間戳就恰好可以作為數據版本。在用戶查詢請求發起後,先後取出 Redis 緩存結果中攜帶的時間戳和 MySQL 元數據版本,然後比較兩個時間。Redis 緩存的時間較新的話說明緩存未過期,可以直接返回緩存結果;反之,說明 Redis 緩存數據已經過期,這一對 Key-Value 會被直接刪除,然後去查詢 Druid。

在添加了數據版本校驗後,一個請求的整個生命週期如下圖所示。

知乎如何基於開源 Druid 打造下一代數據分析平臺?


緩存自動失效機制

總 結

本文重點介紹了知乎數據分析平臺對 Druid 的查詢優化。通過自研的一整套緩存機制和查詢改造,該平臺目前在較長的時間內,滿足了業務固化的指標需求和靈活的分析需求,減少了數據開發者的開發成本。

數據分析平臺在上線後,提供了非常靈活的能力。在實踐中,我們發現過度的自由未必是用戶想要的。適當的流程約束,有助於降低用戶的學習成本,以及大幅度改善業務在該平臺上的查詢體驗。早期我們對數據的攝入並沒有做過多的約束,在整個數據穩定性提升過程中,通過和數倉團隊的大力配合,我們對攝入的數據源做了優化,包括限制高基數維度等治理的工作。本文的緩存思想不僅僅可以用在 Druid 上,同樣可以用在 ClickHouse,Impala 等其他的 OLAP 引擎的查詢優化上。

團隊介紹

知乎的大數據平臺團隊,屬於知乎的技術中臺。面對業務的多元化發展和精細化運營,數據的需求變得越來越多,大數據平臺團隊主要負責:

  • 搭建公司級可視化分析系統和數據服務
  • 維護全端數據的採集、集成和數據倉庫,對接業務系統
  • 管理數據生命週期,提供一站式的數據開發、數據權限、元信息管理和任務調度平臺
  • 提供 AB Testing 實驗平臺,系統化集成實驗分析框架,推動業務增長

隨著知乎業務規模的快速增長,以及業務複雜度的持續增加,我們團隊面臨的技術挑戰也越來越大,歡迎對技術感興趣、渴望技術挑戰的小夥伴加入我們,一起建設知乎的數據平臺。

參考引用 [1] http://druid.io/docs/latest/design/


分享到:


相關文章: