i 技術會筆記 | Druid在愛奇藝的實踐和技術演進

- 導讀 -

最近幾年大數據技術在各行各業得到廣泛應用,爲企業的運營決策和各種業務提供支持。隨着數據的增長,業務對數據時效性的要求,給企業的大數據分析帶來了巨大挑戰。針對海量數據的實時分析需求,近年來市場上涌現出衆多OLAP分析引擎。這些OLAP引擎有各自的適用場景和優缺點,如何選擇一款合適的引擎來更快地分析數據、更高效地挖掘數據的潛在價值?

愛奇藝大數據服務團隊評估了市面上主流的OLAP引擎,最終選擇Apache Druid時序數據庫來滿足業務的實時分析需求。本文將介紹Druid在愛奇藝的實踐情況、優化經驗以及平臺化建設的一些思考。

愛奇藝大數據OLAP服務

愛奇藝大數據OLAP服務在2015年前主要以離線分析爲主,主要基於Hive+MySQL、HBase等。2016年起引入Kylin和Impala分別支持固定報表和Ad-hoc查詢。2018年以來引入Kudu和Druid支持實時分析需求。

在引入Druid之前,業務的一些場景無法通過離線分析滿足,如廣告主想要實時基於投放效果調整投放策略、算法工程師調整模型推到線上A/B要隔天離線報表才能看到效果。這些場景都可以歸納爲對海量事件流進行實時分析,經典的解決方案有如下幾種:

  • 離線分析

使用Hive、Impala或者Kylin,它們一個共同的缺點是時效性差,即只能分析一天或者一小時前的數據,Kylin還面臨維度爆炸的問題

  • 實時分析

  • 用ElasticSearch或OpenTSDB,由於數據結構本質是行存儲,聚合分析速度都比較慢;可以通過查詢緩存、OpenTSDB預計算進行優化,但不根本解決問題;

  • 用流任務(Spark/Flink)實時地計算最終結果,存儲在MySQL提供進一步服務;問題是每當需求調整,如維度變更時,則需要寫新的流任務代碼;

  • 使用Kudu和Impala結合能夠做到實時分析。在實踐過程中發現,Kudu受限於內存和單機分區數,支撐海量數據成本很大;

  • Lambda架構:

無論選用哪種實時或離線方案的組合,都會採用Lambda架構,用離線數據校準實時數據。這意味着從攝入、處理、查詢都需要維護兩套架構,新增一個維度,離線和實時均需對應修改,維護困難

以上種種方案的不足,促使我們尋找新的解決方案,最終決定採用Druid。

Apache Druid介紹 

Apache Druid是針對海量事件流進行存儲和實時多維分析的開源系統。它具有如下特性:

  • 實時可見:消息攝入後分鍾級查詢可見

  • 交互查詢:查詢延時在秒級,核心思想爲內存計算和並行計算

  • 維度靈活:支持幾十個維度任意組合,僅在索引時指定的維度查詢可見

  • 易於變更:需求變更後調整索引配置立馬生效;

  • 流批一體:新版本KIS模式可實現Exactly Once語義

上圖爲Druid架構圖,大體分爲幾個模塊:

  • MiddleManager:索引節點,負責實時處理消息,將其轉成列式存儲,並通過Rollup精簡數據量;索引節點定期將內存中數據持久化爲不可修改的文件(Segment),保存至HDFS保證數據不會丟失;

  • Historical:歷史節點,將Segment加載到本地,負責大部分查詢的計算;

  • Broker:查詢節點,將查詢分解爲實時和離線部分,轉發給索引節點和歷史節點,並彙總最終的查詢結果;

  • Overlord:負責索引任務管理;

  • Coordinator:負責負載均衡,確保Segment在歷史節點之間儘量均衡;

Druid在愛奇藝的實踐

Druid很好地填補了愛奇藝在實時OLAP分析領域的空白,隨着業務實時分析需求的增加,Druid集羣和業務規模也在穩步增長。目前集羣規模在數百個結點,每天處理數千億條消息,Rollup效果在10倍以上。平均每分鐘6千條查詢,P99延時一秒內,P90延時在200毫秒內。在建設Druid服務過程中,我們也不斷遇到規模增長帶來的性能瓶頸和穩定性問題。

1.Coordinator瓶頸

當時的挑戰是實時索引任務經常被阻塞。Druid的Handoff總結如下,索引節點將Segment持久化到HDFS,然後Coordinator制定調度策略,將計劃發佈到ZooKeeper。歷史節點從ZooKeeper獲取計劃後異步地加載Segment。當歷史節點加載完Segment索引節點的Handoff過程才結束。這個過程中,由於Coordinator制定計劃是單線程串行的,如果一次觸發了大量Segment加載,執行計劃制定就會很慢,從而會阻塞Handoff過程,進而索引節點所有的Slot均會被用滿。

而以下過程均會觸發大量Segment加載,在解決Coordinator調度性能瓶頸前, 很容易引發故障:

• 歷史節點因硬件故障、GC、主動運維退出

• 調整Segment副本數、保留規則

通過火焰圖對Coordinator進行Profiling最終定位了問題,如下圖所示,將最耗時部分放大出來,是負載均衡策略對每個Segment要選擇一個最佳的服務器。閱讀源碼可知其過程爲,加載Segment X,需要計算它和服務器的每個Segment Y的代價Cost(X, Y),其和爲服務器和Segment X的代價。假設集羣有N個Segment,M個Historical節點,則一個節點宕機,有N/M個Segment需要加載,每個Segment都和剩餘的N個節點計算一次代價,調度耗時和N成平方關係。

一個節點宕機調度耗時 = (N/M)個Segment * 每個Segment調度耗時 = (N/M) * N = O(N^2)

分析清楚原因後,很容易瞭解到Druid新很容易瞭解到Druid新版本提供了新的負載均衡策略(druid.coordinator.balancer.strategy = CachingCostBalancerStrategy),應用後調度性能提升了10000倍,原先一個歷史節點宕機會阻塞Coordinator1小時到2小時,現在30秒內即可完成。

2.Overlord瓶頸

Overlord性能慢,我們發現升級到0.14後Overlord API性能較差,導致的後果是索引任務概率性因調用API超時而失敗。通過Jstack分析,看到大部分的HTTP線程均爲阻塞態,結合代碼分析,定位到API慢的原因,如左圖所示,Tranquility會定期調用Overlord API,獲取所有RunningTasks,Overlord內部維護了和MySQL的連接池,該連接池默認值爲8,該默認值值過小,阻塞了API處理。解決方法是增大dbcp連接池大小。druid.metadata.storage.connector.dbcp.maxTotal = 64

調整後,Overlord性能得到了大幅提升,Overlord頁面打開從幾十秒降低到了幾秒。但意料之外的事情發生了,API處理能力增加帶來了CPU的飆升,如右圖所示,並且隨着Tranquility任務增加CPU逐漸打滿,Overlord頁面性能又逐步降低。通過火焰圖Profile可知,CPU主要花費在getRunningTasks的處理過程,進一步分析Tranquility源碼後得知,Tranquility有一個配置項(druidBeam.overlordPollPeriod)可以控制Tranquility輪詢該API的間隔,增大該間隔後問題得到了暫時緩解,但根本的解決方案還是將任務切換爲KIS模式。 

3.索引成本

Druid索引成本過高。基於Druid官方文檔,一個Druid索引任務需要3個核,一個核用於索引消息,一個核用於處理查詢,一個核用於Handoff過程。我們採用該建議配置索引任務,壓測結果是3核配置下能夠支撐百萬/分鐘的攝入。

在最初,集羣所有的索引任務都是統一配置,但實際使用過程中,大部分的索引任務根本達不到百萬/分鐘的消息量,造成了資源大量浪費。如下圖所示,我們按照索引任務的內存使用量從高到低排序,9 GB爲默認配置,80%的任務利用率低於1/3,即3 GB。我們以3 GB繪製一條橫線,以內存使用最接近的任務繪製一條豎線,定義A爲實際使用的內存,B爲第二象限空白部分,C爲第四象限空白部分,D爲第一象限空白部分,則浪費的資源 = (B+C+D)的面積。

我們思考能否採取索引任務分級的策略,定義一種新的類型索引節點 – Tiny節點。Tiny節點配置改爲1 core\3GB,能夠滿足80%小任務的資源需求,而default節點繼續使用 3 core9 GB的配置,滿足20%大任務的需求,在這種新的配置下,浪費的資源 = (B + C)的面積,D這一大塊被省下來。簡單地計算可知,在不增加機器的情況下,總Slots能夠增加1倍。

默認slot資源需求爲1,Tiny爲1/3,調整後單位任務需要的資源 = 0.2 * 1 + 0.8 * 1/3 = 0.5

在實際操作層面,還需解決一個問題,即如何把Datasource指定給合適的Worker節點。在Druid低版本中,需要通過配置文件將每一個Datasource和Worker節點進行關聯,假設有N個Datasource,M個Worker節點,這種配置的複雜度爲 N * M,且無法較好地處理Worker節點負載均衡,Worker宕機等場景。在Druid 0.17中,引入了節點Category概念,只需將Datasource關聯特定的Category,再將Category和Worker綁定,新的配置方法有2個Category,複雜度 =  2 * N + 2 * M。

4.Tranquility vs KIS

剛使用Druid時,當時主力模式是Tranquility。Tranquility本質上仍然是經典的Lambda架構,實時數據通過Tranquility攝入,離線數據通過HDFS索引覆蓋。通過離線覆蓋的方式解決消息延遲的問題,缺點是維護兩套框架。對於節點失敗的問題,Tranquility的解決方案是鏈路冗餘,即同時在兩個索引節點各起一份索引任務,任一節點失敗仍有一份能夠成功,缺點是浪費了一倍的索引資源。自0.14版本起,Druid官方建議使用KIS模式索引數據,它提供了Exactly Once語義,能夠很好地實現流批一體。

和Tranquility的Push模式不同,KIS採取Pull模式,索引任務從Kafka拉取消息,構建Segment。關鍵點在於最後持久化Segment的時候,KIS任務有一個數據結構記錄了上一次持久化的Offset位置,如圖例左下角所示,記錄了每個Kafka Partition消費的Offset。在持久化時會先檢查Segment的開始Offset和元信息是否一致。如果不一致,則會放棄本次持久化,如果一致,則觸發提交邏輯。提交中,會同時記錄Segment元信息和Kafka Offset,該提交過程爲原子化操作,要麼都成功,要麼都失敗。

KIS如何處理各個節點失敗的情況呢?假設Kafka集羣失敗,由於是Pull模式,Druid在Kafka恢復後繼續從上一個Offset開始消費;假設Druid索引節點失敗,Overlord後臺的Supervisor會監控到相應任務狀態,在新的索引節點啓動KIS任務,由於內存中的狀態丟失,新的KIS任務會讀取元信息,從上一次的Offset開始消費。假設是MySQL或者更新元數據過程失敗,則取決於提交的原子操作是否成功,若成功則KIS從新的Offset開始消費,失敗則從上一次Offset開始消費。

進一步看一下KIS是如何保證Exactly Once語義。其核心是保證Kafka消費的Offset連續,且每個消息都有唯一ID。Exactly Once可以分爲兩個部分,一是At Least Once,由KIS檢查Offset的機制保證,一旦發現缺失了部分Offset,KIS會重新消費歷史數據,該過程相當於傳統的離線補數據,只是現在由Druid自動完成了。另一個是At Most Once,只要保證Offset沒有重疊部分,則每條消息只被處理了一次。

以下是KIS在愛奇藝的一個實例,左下圖爲業務消息量和昨天的對比圖,其中一個小時任務持久化到HDFS失敗了,看到監控曲線有一個缺口。之後Druid後臺啓動了一個新的KIS任務,一段時間後,隨着KIS補錄數據完成,曲線圖恢復到右下圖所示。那麼,如果業務不是一直盯着曲線看,而是定期查看的話,完全感受不到當中發生了異常。

基於Druid的實時分析平臺建設

Druid性能很好,但在初期推廣中卻遇到很大的阻力,主要原因是Druid的易用性差,體現在如下幾個方面:

  1. 數據攝入需要撰寫一個索引配置,除了對數據自身的描述(時間戳、維度和度量),還需要配置Kafka信息、Druid集羣信息、任務優化信息等

  2. 查詢的時候需要撰寫一個JSON格式的查詢,語法爲Druid自定義,學習成本高

  3. 返回結果爲一個JSON格式的數據,用戶需自行將其處理成最終圖表、告警

  4. 報錯信息不友好,上述所有配置均通過JSON撰寫,一個簡單的逗號、格式錯誤都會引起報錯,需花費大量時間排查

爲解決Druid易用性差的問題,愛奇藝自研了實時分析平臺RAP(Realtime  Analysis Platform),屏蔽了Kafka、Druid、查詢的細節,業務只需描述數據格式即可攝入數據,只需描述報表樣式、告警規則,即可配置實時報表和實時告警。

RAP實時分析平臺,主要有六大特性:

  • 全向導配置:業務無需手寫ETL任務

  • 計算存儲透明:業務無需關心底層OLAP選型

  • 豐富報表類型:支持常見的線圖、柱狀圖、餅圖等

  • 數據延時低:從APP數據採集到生成可視化報表的端到端延時在5分鐘內,支持數據分析師、運營等業務實時統計分析UV、VV、在線用戶數等

  •  秒級查詢:大部分查詢都是秒以內

  • 靈活變更:更改維度後重新上線即可生效

RAP實時分析平臺目前已經在愛奇藝會員、推薦、BI等多個業務落地,配置了上千張報表,幫助業務在實時監控報警、實時運營分析、實時AB測試對比等場景提升排障響應速度、運營決策效率。

關於RAP的更多技術細節和業務應用場景,可以閱讀之前分享的技術文章:愛奇藝大數據實時分析平臺的建設與實踐

未來展望

進一步迭代完善Druid及RAP,提升穩定性、服務能力,簡化業務接入成本:

接入愛奇藝自研的Pilot智能SQL引擎,支持異常查詢攔截、限流等功能

運維平臺:包括元信息管理、任務管理、服務健康監測等,提升運維效率

離線索引:支持直接索引Parquet文件,通過Rollup進一步提升查詢效率

支持JOIN:支持更豐富的語義

參考資料

[1] https://druid.apache.org/

[2] 直播回放:Druid在愛奇藝的實踐和技術演進

[3] 愛奇藝大數據實時分析平臺的建設與實踐

[4] 愛奇藝在日誌實時數據監控的探索與實踐

招聘信息

愛奇藝大數據服務團隊誠招Spark/Flink實時計算服務技術專家/架構師。愛奇藝大數據服務團隊負責公司大數據基礎設施的建設工作,提供涵蓋數據採集、數據處理、數據開發、數據應用等整個大數據處理流程的一系列大數據開源服務及相應的開發平臺,具備萬臺以上大規模分佈式服務能力。
簡歷發送[email protected]

郵件主題:大數據實時計算資深研發工程師-${姓名}


掃一掃下方二維碼,更多精彩內容陪伴你!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章