天天看點

【實踐案例分享】美團的Apache Kylin的實踐與優化

【實踐案例分享】美團的Apache Kylin的實踐與優化

總第423篇

2020年 第47篇

【實踐案例分享】美團的Apache Kylin的實踐與優化

從2016年開始,美團到店餐飲技術團隊就開始使用Apache Kylin作為OLAP引擎,但是随着業務的高速發展,在建構和查詢層面都出現了效率問題。于是,技術團隊從原了解讀開始,然後對過程進行層層拆解,并制定了由點及面的實施路線。本文總結了一些經驗和心得,希望能夠幫助業界更多的技術團隊提高資料的産出效率。

背景

銷售業務的特點是規模大、領域多、需求密。美團到店餐飲擎天銷售系統(以下簡稱“擎天”)作為銷售資料支援的主要載體,不僅涉及的範圍較廣,而且面臨的技術場景也非常複雜(多組織層級資料展示及鑒權、超過1/3的名額需要精準去重,峰值查詢已經達到數萬級别)。在這樣的業務背景下,建設穩定高效的OLAP引擎,協助分析人員快速決策,已經成為到餐擎天的核心目标。

Apache Kylin是一個基于Hadoop大資料平台打造的開源OLAP引擎,它采用了多元立方體預計算技術,利用空間換時間的方法,将查詢速度提升至亞秒級别,極大地提高了資料分析的效率,并帶來了便捷、靈活的查詢功能。基于技術與業務比對度,擎天于2016年采用Kylin作為OLAP引擎,接下來的幾年裡,這套系統高效地支撐了我們的資料分析體系。

2020年,美團到餐業務發展較快,資料名額也迅速增加。基于Kylin的這套系統,在建構和查詢上均出現了嚴重的效率問題,進而影響到資料的分析決策,并給使用者體驗優化帶來了很大的阻礙。技術團隊經過半年左右的時間,對Kylin進行一系列的優化疊代,包括次元裁剪、模型設計以及資源适配等等等,幫助銷售業績資料SLA從90%提升至99.99%。基于這次實戰,我們沉澱了一套涵蓋了“原了解讀”、“過程拆解”、“實施路線”的技術方案。希望這些經驗與總結,能夠幫助業界更多的技術團隊提高資料産出與業務決策的效率。

問題與目标

銷售作為銜接平台和商家的橋梁,包含銷售到店和電話拜訪兩種業務模式,以戰區、人力組織架構逐級管理,所有分析均需要按2套組織層級檢視。在名額口徑一緻、資料産出及時等要求下,我們結合Kylin的預計算思想,進行了資料的架構設計。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

而Kylin計算次元組合的公式是2^N(N為次元個數),官方提供次元剪枝的方式,減少次元組合個數。但由于到餐業務的特殊性,單任務不可裁剪的組合個數仍高達1000+。在需求疊代以及人力、戰區組織變動的場景下,需要回溯全部曆史資料,會耗費大量的資源以及超高的建構時長。而基于業務劃分的架構設計,雖能夠極大地保證資料産出的解耦,保證名額口徑的一緻性,但是對Kylin建構産生了很大的壓力,進而導緻資源占用大、耗時長。基于以上業務現狀,我們歸納了Kylin的MOLAP模式下存在的問題,具體如下:

  • 效率問題命中難(實作原理):建構過程步驟多,各步驟之間強關聯,僅從問題的表象很難發現問題的根本原因,無法行之有效地解決問題。
  • 建構引擎未疊代(建構過程):曆史任務仍采用MapReduce作為建構引擎,沒有切換到建構效率更高的Spark。
  • 資源利用不合理(建構過程):資源浪費、資源等待,預設平台動态資源适配方式,導緻小任務申請了大量資源,資料切分不合理,産生了大量的小檔案,進而造成資源浪費、大量任務等待。
  • 核心任務耗時長(實施路線):擎天銷售交易業績資料名額的源表資料量大、次元組合多、膨脹率高,導緻每天建構的時長超過2個小時。
  • SLA品質不達标(實施路線):SLA的整體達成率未能達到預期目标。

在認真分析完問題,并确定提效的大目标後,我們對Kylin的建構過程進行了分類,拆解出在建構過程中能提升效率的核心環節,通過“原了解讀”、“層層拆解”、“由點及面”的手段,達成雙向降低的目标。具體量化目标如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

優化前提-原了解讀

為了解決效率提升定位難、歸因難的問題,我們解讀了Kylin建構原理,包含了預計算思想以及By-layer逐層算法。

預計算

根據次元組合出所有可能的次元,對多元分析可能用到的名額進行預計算,将計算好的結果儲存成Cube。假設我們有4個次元,這個Cube中每個節點(稱作Cuboid)都是這4個次元的不同組合,每個組合定義了一組分析的次元(如group by),名額的聚合結果就儲存在每個Cuboid上。查詢時,我們根據SQL找到對應的Cuboid,讀取名額的值,即可傳回。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

By-layer逐層算法

一個N維的Cube,是由1個N維子立方體、N個(N-1)維子立方體、N*(N-1)/2個(N-2)維子立方體、……N個1維子立方體和1個0維子立方體構成,總共有 2^N個子立方體。在逐層算法中,按照次元數逐層減少來計算,每個層級的計算(除了第一層,由原始資料聚合而來),是基于上一層級的計算結果來計算的。

例如:group by [A,B]的結果,可以基于group by [A,B,C]的結果,通過去掉C後聚合得來的,這樣可以減少重複計算,當0維Cuboid計算出來的時候,整個Cube的計算也就完成了。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

過程分析-層層拆解

在了解完Kylin的底層原理後,我們将優化的方向鎖定在“引擎選擇”、“資料讀取”、“建構字典”、“分層建構”、“檔案轉換”五個環節,再細化各階段的問題、思路及目标後,我們終于做到了在降低計算資源的同時降低了耗時。詳情如下表所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

建構引擎選擇

目前,我們已經将建構引擎已逐漸切換為Spark。擎天早在2016年就使用Kylin作為OLAP引擎,曆史任務沒有切換,僅僅針對MapReduce做了參數優化。其實在2017年,Kylin官網已啟用Spark作為建構引擎(官網啟用Spark建構引擎),建構效率相較MapReduce提升1至3倍,還可通過Cube設計選擇切換,如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

讀取源資料

Kylin以外部表的方式讀取Hive中的源資料,表中的資料檔案(存儲在HDFS)作為下一個子任務的輸入,此過程可能存在小檔案問題。目前,Kylin上遊資料寬表檔案數分布比較合理,無需在上遊設定合并,如果強行合并反而會增加上遊源表資料加工時間。

對于項目需求,要回刷曆史資料或增加次元組合,需要重新建構全部的資料,通常采用按月建構的方式回刷曆史,加載的分區過多出現小檔案問題,導緻此過程執行緩慢。在Kylin級别重寫配置檔案,對小檔案進行合并,減少Map數量,可有效地提升讀取效率。

合并源表小檔案:合并Hive源表中小檔案個數,控制每個Job并行的Task個數。調整參數如下表所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

Kylin級别參數重寫:設定Map讀取過程的檔案大小。調整參數如下表所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

建構字典

Kylin通過計算Hive表出現的次元值,建立次元字典,将次元值映射成編碼,并儲存儲存統計資訊,節約HBase存儲資源。每一種次元組合,稱為一個Cuboid。理論上來說,一個N維的Cube,便有2^N種次元組合。

組合數量檢視

在對次元組合剪枝後,實際計算次元組合難以計算,可通過執行日志(截圖為提取事實表唯一列的步驟中,最後一個Reduce的日志),檢視具體的次元組合數量。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

全局字典依賴

擎天有很多業務場景需要精确去重,當存在多個全局字典列時,可設定列依賴,例如:當同時存在“門店數量”、“線上門店數量”資料名額,可設定列依賴,減少對超高基次元的計算。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

計算資源配置

當名額中存在多個精準去重名額時,可适當增加計算資源,提升對高基次元建構的效率。參數設定如下表所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

分層建構

此過程為Kylin建構的核心,切換Spark引擎後,預設隻采用By-layer逐層算法,不再自動選擇(By-layer逐層算法、快速算法)。Spark在實作By-layer逐層算法的過程中,從最底層的Cuboid一層一層地向上計算,直到計算出最頂層的Cuboid(相當于執行了一個不帶group by的查詢),将各層的結果資料緩存到記憶體中,跳過每次資料的讀取過程,直接依賴上層的緩存資料,大大提高了執行效率。Spark執行過程具體内容如下。

Job階段

Job個數為By-layer算法樹的層數,Spark将每層結果資料的輸出,作為一個Job。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

Stage階段

每個Job對應兩個Stage階段,分為讀取上層緩存資料和緩存該層計算後的結果資料。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

Task并行度設定

Kylin根據預估每層建構Cuboid組合資料的大小(可通過次元剪枝的方式,減少次元組合的數量,降低Cuboid組合資料的大小,提升建構效率,本文暫不詳細介紹)和分割資料的參數值計算出任務并行度。計算公式如下:

  • Task個數計算公式:Min(MapSize/cut-mb ,MaxPartition) ;Max(MapSize/cut-mb ,MinPartition)
    • MapSize:每層建構的Cuboid組合大小,即:Kylin對各層級次元組合大小的預估值。
    • cut-mb:分割資料大小,控制Task任務并行個數,可通過kylin.engine.spark.rdd-partition-cut-mb參數設定。
    • MaxPartition:最大分區,可通過kylin.engine.spark.max-partition參數設定。
    • MinPartition:最小分區,可通過kylin.engine.spark.min-partition參數設定。
  • 輸出檔案個數計算:每個Task任務将執行完成後的結果資料壓縮,寫入HDFS,作為檔案轉換過程的輸入。檔案個數即為:Task任務輸出檔案個數的彙總。

資源申請計算

平台預設采用動态方式申請計算資源,單個Executor的計算能力包含:1個邏輯CPU(以下簡稱CPU)、6GB堆内記憶體、1GB的堆外記憶體。計算公式如下:

  • CPU  =  kylin.engine.spark-conf.spark.executor.cores * 實際申請的Executors個數。
  • 記憶體 =(kylin.engine.spark-conf.spark.executor.memory + spark.yarn.executor.memoryOverhead)* 實際申請的Executors個數。
  • 單個Executor的執行能力 = kylin.engine.spark-conf.spark.executor.memory / kylin.engine.spark-conf.spark.executor.cores,即:1個CPU執行過程中申請的記憶體大小。
  • 最大Executors個數 = kylin.engine.spark-conf.spark.dynamicAllocation.maxExecutors,平台預設動态申請,該參數限制最大申請個數。

在資源充足的情況下,若單個Stage階段申請1000個并行任務,則需要申請資源達到7000GB記憶體和1000個CPU,即:

CPU:1*1000=1000;記憶體:(6+1)*1000=7000GB

資源合理化适配

由于By-layer逐層算法的特性,以及Spark在實際執行過程中的壓縮機制,實際執行的Task任務加載的分區資料遠遠小于參數設定值,進而導緻任務超高并行,占用大量資源,同時産生大量的小檔案,影響下遊檔案轉換過程。是以,合理的切分資料成為優化的關鍵點。通過Kylin建構日志,可檢視各層級的Cuboid組合資料的預估大小,以及切分的分區個數(等于Stage階段實際生成的Task個數)。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

結合Spark UI可檢視實執行情況,調整記憶體的申請,滿足執行所需要的資源即可,減少資源浪費。

1. 整體資源申請最小值大于Stage階段Top1、Top2層級的緩存資料之和,保證緩存資料全部在記憶體。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

計算公式:Stage階段Top1、Top2層級的緩存資料之和 < kylin.engine.spark-conf.spark.executor.memory * kylin.engine.spark-conf.spark.memory.fraction *   spark.memory.storageFraction *最大Executors個數

2. 單個Task實際所需要的記憶體和CPU(1個Task執行使用1個CPU)小于單個Executor的執行能力。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

計算公式:單個Task實際所需要的記憶體 < kylin.engine.spark-conf.spark.executor.memory * kylin.engine.spark-conf.spark.memory.fraction *   spark.memory.st·orageFraction / kylin.engine.spark-conf.spark.executor.cores。參數說明如下表所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

檔案轉換

Kylin将建構之後的Cuboid檔案轉換成HTable格式的Hfile檔案,通過BulkLoad的方式将檔案和HTable進行關聯,大大降低了HBase的負載。此過程通過一個MapReduce任務完成,Map個數為分層建構階段輸出檔案個數。日志如下:

【實踐案例分享】美團的Apache Kylin的實踐與優化

此階段可根據實際輸入的資料檔案大小(可通過MapReduce日志檢視),合理申請計算資源,避免資源浪費。

計算公式:Map階段資源申請 = kylin.job.mr.config.override.mapreduce.map.memory.mb * 分層建構階段輸出檔案個數。具體參數如下表所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

實施路線-由點及面

交易試點實踐

我們通過對Kylin原理的解讀以及建構過程的層層拆解,選取銷售交易核心任務進行試點實踐。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

實踐結果對比

針對銷售交易核心任務進行實踐優化,對比調整前後資源實際使用情況和執行時長,最終達到雙向降低的目标。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

成果展示

資源整體情況

擎天現有20+的Kylin任務,經過半年時間持續優化疊代,對比Kylin資源隊列月均CU使用量和Pending任務CU使用量,在同等任務下資源消耗已明顯降低。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

SLA整體達成率

經過了由點及面的整體優化,擎天于2020年6月SLA達成率達到100%。如下圖所示:

【實踐案例分享】美團的Apache Kylin的實踐與優化

展望

Apache Kylin在2015年11月正式成為Apache基金會的頂級項目。從開源到成為Apache頂級項目,隻花了13個月的時間,而且它也是第一個由中國團隊完整貢獻到Apache的頂級項目。

目前,美團采用比較穩定的V2.0版本,經過近4年的使用與積累,到店餐飲技術團隊在優化查詢性能以及建構效率層面都積累了大量經驗,本文主要闡述了在Spark建構過程的資源适配方法。值得一提的是,Kylin官方在2020年7月釋出了V3.1版本,引入了Flink作為建構引擎,統一使用Flink建構核心過程,包含資料讀取階段、建構字典階段、分層建構階段、檔案轉換階段,以上四部分占整體建構耗時的95%以上。此次版本的更新也大幅度提高了Kylin的建構效率。詳情可檢視:Flink Cube Build Engine。

回顧Kylin建構引擎的更新過程,從MapReduce到Spark,再到如今的Flink,建構工具的疊代始終向更加優秀的主流引擎在靠攏,而且Kylin社群有很多活躍的優秀代碼貢獻者,他們也在幫助擴大Kylin的生态,增加更多的新功能,非常值得大家學習。最後,美團到店餐飲技術團隊再次表達對Apache Kylin項目團隊的感謝。

作者簡介

嶽慶,2019年加入美團,到店餐飲研發中心工程師。

----------  END  ----------

【實踐案例分享】美團的Apache Kylin的實踐與優化