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

愛奇藝技術產品團隊發表於2020-07-01

- 導讀 -

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

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

愛奇藝大資料OLAP服務

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

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

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

  • 離線分析

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

  • 實時分析

  • 用ElasticSearch或OpenTSDB,由於資料結構本質是行儲存,聚合分析速度都比較慢;可以透過查詢快取、OpenTSDB預計算進行最佳化,但不根本解決問題;
  • 用流任務(Spark/Flink)實時地計算最終結果,儲存在MySQL提供進一步服務;問題是每當需求調整,如維度變更時,則需要寫新的流任務程式碼;
  • 使用Kudu和Impala結合能夠做到實時分析。在實踐過程中發現,Kudu受限於記憶體和單機分割槽數,支撐海量資料成本很大;

  • Lambda架構:

無論選用哪種實時或離線方案的組合,都會採用Lambda架構,用離線資料校準實時資料。這意味著從攝入、處理、查詢都需要維護兩套架構,新增一個維度,離線和實時均需對應修改,維護困難
i 技術會筆記 | Druid在愛奇藝的實踐和技術演進
以上種種方案的不足,促使我們尋找新的解決方案,最終決定採用Druid。

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

  • 實時可見:訊息攝入後分鍾級查詢可見
  • 互動查詢:查詢延時在秒級,核心思想為記憶體計算和平行計算
  • 維度靈活:支援幾十個維度任意組合,僅在索引時指定的維度查詢可見
  • 易於變更:需求變更後調整索引配置立馬生效;
  • 流批一體:新版本KIS模式可實現Exactly Once語義

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

上圖為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秒內即可完成。

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

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
i 技術會筆記 | Druid在愛奇藝的實踐和技術演進
調整後,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
i 技術會筆記 | Druid在愛奇藝的實踐和技術演進
在實際操作層面,還需解決一個問題,即如何把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語義,能夠很好地實現流批一體。


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

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

i 技術會筆記 | Druid在愛奇藝的實踐和技術演進
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補錄資料完成,曲線圖恢復到右下圖所示。那麼,如果業務不是一直盯著曲線看,而是定期檢視的話,完全感受不到當中發生了異常。

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

基於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、線上使用者數等
  •  秒級查詢:大部分查詢都是秒以內
  • 靈活變更:更改維度後重新上線即可生效

i 技術會筆記 | Druid在愛奇藝的實踐和技術演進
RAP實時分析平臺目前已經在愛奇藝會員、推薦、BI等多個業務落地,配置了上千張報表,幫助業務在實時監控報警、實時運營分析、實時AB測試對比等場景提升排障響應速度、運營決策效率。
關於RAP的更多技術細節和業務應用場景,可以閱讀之前分享的技術文章:愛奇藝大資料實時分析平臺的建設與實踐

未來展望

進一步迭代完善Druid及RAP,提升穩定性、服務能力,簡化業務接入成本:
接入愛奇藝自研的Pilot智慧SQL引擎,支援異常查詢攔截、限流等功能
運維平臺:包括元資訊管理、任務管理、服務健康監測等,提升運維效率
離線索引:支援直接索引Parquet檔案,透過Rollup進一步提升查詢效率
支援JOIN:支援更豐富的語義


參考資料

[1]
[2] 直播回放:Druid在愛奇藝的實踐和技術演進
[3] 愛奇藝大資料實時分析平臺的建設與實踐
[4] 愛奇藝在日誌實時資料監控的探索與實踐

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945252/viewspace-2701599/,如需轉載,請註明出處,否則將追究法律責任。

相關文章