不管你使用哪種OLAP引擎,都可以這樣搞定查詢優化
知乎作為中文知識內容平臺,業務增長和產品迭代速度很快,如何滿足業務快速擴張中的靈活分析需求,是知乎資料平臺組要面臨的一大挑戰。
知乎資料平臺團隊基於開源的Druid打造的業務自助式的資料分析平臺,經過研發迭代,目前支撐了全業務的資料分析需求,是業務資料分析的重要工具。
目前,平臺主要的能力如下:
-
統一的資料來源管理,支援攝入離線數倉的Hive表和實時數倉的Kafka流。
-
自助式報表配置,支援多維分析報表、留存分析報表。
-
靈活的多維度多指標的組合分析,秒級響應速度,支援巢狀式「與」和「或」條件篩選。
-
自助式儀表盤配置。
-
開發平臺介面,為其他系統提供資料服務。
-
統一的資料許可權管理。
目前,業務使用平臺的資料如下:
-
自助式配置儀表盤數:495個,儀表盤內報表共計:2399張。
-
日請求量3w+。
-
為A/B Testing、渠道管理、APM、資料郵件等系統提供資料API。
一、資料分析平臺架構
知乎實時多維分析平臺架構
二、技術選型-Druid
Druid是一種能對歷史和實時資料提供亞秒級別的查詢的資料儲存。Druid支援低延時的資料攝取,靈活的資料探索分析,高效能的資料聚合,簡便的水平擴充套件。適用於資料量大,可擴充套件能力要求高的分析型查詢系統。
Druid整體架構
三、Druid資料結構和架構簡介
1、Druid資料結構
-
Data Source: Druid的基本資料結構,在邏輯上可以理解為關係型資料庫中的表。它包含時間、維度和指標三列。
-
Segment: Druid用來儲存索引的資料格式,不同的索引按照時間跨度來分割槽,分割槽可通過segmentGranularity(劃分索引的時間粒度)進行配置。
2、查詢服務的相關元件
內部元件
-
Historical: 用於載入和提供Segment檔案供資料查詢。
-
Broker: 提供資料查詢服務,通過路由查詢請求到對應的Historical節點並獲得資料,合併資料後返回給呼叫方。
-
Router: 當Druid叢集到達TB級別的規模時才需要啟用的節點,主要負責將查詢請求路由到不同的Broker節點上。
外部元件
-
Deep Storage: 用於儲存Segment檔案供Historical節點下載。Deep Storage不屬於Druid內部元件,使用者可根據系統規模來自定義配置。單節點可用本地磁碟,分散式可用HDFS。
-
Metastore Storage: 用於儲存Druid的各種元資料資訊,屬於Druid的外部依賴元件,生產環境中可用MySQL。
四、平臺的演進
1、Druid查詢量大
在Druid成為主力查詢引擎之後,我們發現大查詢量的場景下直接查Druid會有一些弊端,其中最大的痛點就是查詢響應慢。
2、快取查詢結果
為了提高整體查詢速度,我們引入了Redis作為快取。
使用Redis來快取查詢結果
最簡單的快取設計,是將Druid的查詢體(Request<body>)作為key,Druid的返回體(Response<body>)作為value。
上述的快取機制的缺點是顯而易見的,只能應對查詢條件完全一致的重複查詢。在實際應用中,查詢條件往往是多變的,尤其是查詢時間的跨度。
舉個例子,在相同指標維度組合下使用者發起兩次查詢,第一次查詢10月1日到10月7日的資料,Druid查出結果並快取到Redis。使用者調整時間跨度到10月2日到10月8日,發起第二次查詢,這條請求的不會命中Redis,又需要Druid來查詢資料。
從例子中,我們發現兩次查詢的時間跨度交集是10月2日到10月7日,但是這部分的快取結果並沒有被複用。這種查詢機制下,查詢延時主要來自於Druid處理重複的請求,快取結果沒有被充分利用。
3、提高快取的複用
為了提高快取複用率,我們需要增加一套新的快取機制:當查詢在Redis沒有直接命中時,先掃描Redis中是否已快取查詢中部分時間跨度的結果提取命中的結果,未命中再查詢Druid。在掃描的過程中,被掃描的物件是單位時間跨度的快取。
為了能獲得到任意一個單位時間跨度內的快取,除了在Redis中快取單條查詢的結果之外,需要進一步按時間粒度將總跨度等分,快取所有單位時間跨度對應的結果(如下圖所示):
Druid結果按時間粒度快取
4、減少Redis IO
從上圖中我們發現,對每個單位時間跨度的結果判斷是否已被快取都需要對Redis進行一次讀操作,當用戶查詢量增大時,這種操作會對Redis叢集造成比較大的負擔,偶爾會出現Redis連線超時的情況。
為了減少對Redis的IO,我們對時間跨度單獨設計了一套快取機制。
基於減少讀操作的想法,我們設計了通過一次讀操作就可得到已經被快取的所有時間跨度,然後再一次性地將所有快取的結果讀出。
-
要實現一次性獲得快取的所有時間跨度,我們需要在每次快取Druid查詢結果後,再快取查詢請求和它覆蓋的時間跨度,在RedisKey-Value規則上,我們先把查詢體(Request<body>)剔除時間跨度,生成一個時間無關的查詢體(Interval Excluded Request<body>)作為快取的key;提取查詢粒度(Granularity),把剔除出來的時間跨度按粒度分隔開(Set<Timestamp>)作為value。
-
要實現一次性讀出所有快取結果,通過Redis的MGET獲得Interval Excluded Request對應的各個時間戳。
-
判斷當前請求的所有單位時間跨度是否命中快取,命中的結果會被直接返回。
優化後快取機制如下圖所示:
Redis讀操作優化
5、Druid查詢時間跨度長
在未命中快取的情況下,設定較長的查詢時間跨度(長時間跨度:2周以上),Druid經常會出現返回速度變慢,甚至阻塞其他查詢請求的現象。
我們測試了長時間跨度的查詢請求對叢集整體的影響,通過對Druid叢集的監控資料的分析,我們發現被長時間跨度查詢命中的Broker節點會出現記憶體消耗過大的問題,並且隨著時間跨度的增大,記憶體消耗跟著提高,甚至出現記憶體不足導致Broker節點無響應的問題。
6、一個Broker處理一個請求
在調研了Druid執行原理以後,我們發現一個查詢請求只會被Router路由到一個Broker節點,經由Broker節點去Historical節點上查詢目標資料在Deep Storage的儲存位置,最後返回的資料也是經過Broker節點來合併返回結果。查詢的時間跨度越長,對Broker的壓力也越大,記憶體消耗越多。
單個Broker處理長時間跨度查詢
7、多個Broker處理一個請求
單個Broker的效能無法滿足長時間跨度的查詢,為了讓提高查詢效能,我們嘗試把一個查詢N天資料的請求,拆分成N個查詢,每個只查詢一天,然後非同步地將這些請求發出,結果這N個請求都被很快的返回了。和拆分前的查詢耗時相比,拆分後的耗時大大減小。
多個Broker處理長時間跨度查詢
從Broker節點的監控來看,當一個長時間的查詢請求被多個Broker一起處理,可以減少單個Broker記憶體消耗,並且加快了整體的查詢速度。提速程度請參考下圖的測試比對,測試用例採用平臺一天的所有查詢,測試方式是在不命中Redis的情況下非同步地「回放」這些查詢到Druid。
benchmark
根據上述Broker在查詢過程中的工作原理,想達到長時間跨度查詢的提速,我們需要在使用者發起查詢之後把請求拆分。
拆分的機制是根據每個查詢請求的查詢時間粒度而定,例如上述中的一個N天跨度的天粒度請求,在查詢到達Druid叢集之前,我們嘗試把它拆分成N個1天跨度的天級別粒度請求。
整個查詢從拆分到命中Druid的過程如下圖所示(在Druid內部的工作細節請參考上文)。
按時間粒度拆分使用者查詢請求
8、快取結果過期
前兩步的演進完成了從高負載下查詢效能低、查詢時間跨度長而速度慢、Redis複用率低,到查詢效能高、Redis IO穩定。
分析平臺的資料來源來自於離線數倉的Hive和實時數倉的Kafka。重新攝入上游資料到Druid後,對應時間列的Segment檔案會進行重建索引。
在Segment檔案索引重建之後,對應的Druid查詢結果也會發生改變。當這個情況發生時,使用者從Redis獲取到的結果並沒有及時得到更新,這時就會出現資料不一致的情況。因此一套平臺使用者無感的快取自動失效機制就顯得尤為重要。
9、快取自動失效
在Druid查詢鏈路下,資料來源的最近成功攝入的時間可以被抽象為它的最新版本號,利用這一思想,我們可以給每個資料來源都打上資料版本的標籤,在資料更新後,給更新的資料來源替換新的標籤。這樣一來,每次校驗Druid查詢結果是否過期時就有了參照物件。
Druid支援MySQL儲存元資料資訊(Metastore Storage),元資料中的時間戳就恰好可以作為資料版本。在使用者查詢請求發起後,先後取出Redis快取結果中攜帶的時間戳和MySQL元資料版本,然後比較兩個時間。
-
Redis快取的時間較新的話說明快取未過期,可以直接返回快取結果。
-
反之,說明Redis快取資料已經過期,這一對Key-Value會被直接刪除,然後去查詢Druid。
在添加了資料版本校驗後,一個請求的整個生命週期如下圖所示:
快取自動失效機制
五、總結
本文重點介紹了知乎資料分析平臺對Druid的查詢優化。
通過自研的一整套快取機制和查詢改造,該平臺目前在較長的時間內,滿足了業務固化的指標需求和靈活的分析需求,減少了資料開發者的開發成本。
資料分析平臺在上線後,提供了非常靈活的能力。在實踐中,我們發現過度的自由未必是使用者想要的。適當的流程約束,有助於降低使用者的學習成本,以及大幅度改善業務在該平臺上的查詢體驗。 早期我們對資料的攝入並沒有做過多的約束,在整個資料穩定性提升過程中,通過和數倉團隊的大力配合,我們對攝入的資料來源做了優化,包括限制高基數維度等治理的工作。
本文的快取思想不僅僅可以用在Druid上,同樣可以用在ClickHouse,Impala等其他的OLAP引擎的查詢優化上。
參考