美团点评:基于Druid的Kylin存储引擎实践
作者:康凱森
作者簡介:美團大數據工程師,Apache Kylin Committer,目前主要負責美團 OLAP 系統(Kylin & Druid & Palo)的平臺化建設。
8月11日,由 Kyligence 主辦、美團點評協辦的 Apache Kylin Meetup@北京,在美團公司總部圓滿落幕。本文整理自當天美團大數據工程師、Apache Kylin Committer 康凱森的演講實錄,全文共6,600字,閱讀時間大約15分鐘。
本次分享會分為三個部分的內容,首先會介紹 Kylin On Hbase 的問題,引入我們為什么要給 Kylin 增加新存儲引擎的問題;接下來介紹Kylin新存儲引擎的過程,以此來說明為什么選擇 Druid 作為我們新的存儲引擎;最后給大家介紹 Kylin On Druid 的核心架構、核心原理、性能和我們的初步成果,以下分享中將 Kylin On Druid 簡稱 KOD。
Kylin 在美團點評的服務現狀
進入正式主題前,我先介紹下Kylin在美團點評的現狀。目前我們線上 Cube 數有近1000個,Cube 單副本存儲近1PB;每天查詢量380多萬次,查詢的 TP50 時延在200ms左右,TP90時延在1.2s左右。目前 Kylin 已經覆蓋了美團點評所有主要業務線。
Kylin on HBase 問題
隨著 Kylin 在我司的大規模使用和我們對Kylin的深入優化改進,我們發現了 Kylin 本身的一些痛點和問題,其中之一便是 Kylin On HBase 的性能問題。如圖:我們用同一 SQL 在同一集群查詢同一 Cube,前后性能相差上千倍。 兩次查詢的唯一不同點就是 Kylin 維度在 HBase 的 RowKey 中位置不同, 耗時90ms的查詢中維度dt和poi_id 在 RowKey 前兩位,耗時100多s的查詢中維度dt和 poi_id 在 RowKey 后兩位。
下面我們來看下Kylin On HBase中前后綴過濾性能相差巨大的原因:如圖所示:Kylin中會將Cuboid+所有維度拼接成HBase的Rowkey,Kylin默認會將所有普通指標拼接成HBase一個Column Family中同一列的Value。HBase只有單一Rowkey索引,所以只有查詢能夠匹配Rowkey的前綴時,查詢性能會十分高效,反之,查詢性能會比較低下,甚至會出現全表Scan。此外,即使只需要查詢一個指標,Kylin在HBase側也需要Scan所有指標列,相比列存性能也會有較大差距。 總的來說,HBase在Kylin的查詢場景下Scan和Filter效率較低下。
對于Kylin On HBase Scan和Filter效率低下的問題,我們比較自然會想到的解法是:用列存加速Scan,用索引來加速Filter。
這里我簡單介紹下列存的優點,主要包含以下3點:
所以,要解決 Kylin On HBase Scan 和 Filter 效率低下的問題, 我們就需要為 Kylin 增加一個列存,有高效索引的存儲引擎。
Kylin 新存儲引擎探索之路
在我們為Kylin新增一個存儲引擎之前,我們自然就需要先了解Kylin的存儲引擎組成。主要有5部分:存儲格式,Cache,計算,調度和元數據。 計算指數據的Scan,過濾,聚合等,調度指文件增刪,復制和負載均衡等,這里的元數據指的是存儲引擎本身的元數據。其中存儲格式對查詢性能影響很大,也是HBase在Kylin查詢場景下的痛點,所以我們決定首先去尋找或改造一個適合Kylin的存儲格式。
Kylin 新存儲引擎實現思路
當時我們主要有兩個思路,一種思路是基于Spark + 存儲格式進行演進: 就是找到一個優秀的存儲格式后,和Spark進行集成。大概思路是將文件Cache到本地,用Spark來實現計算和查詢的調度,整個方案大體上就可以Run起來。大家對這種思路感興趣的話,可以參考TiDB的TiSpark項目,以及Snappydata這個系統。
第二種思路就是找到或自研一個優秀的存儲格式后,再參考HBase, Druid等系統,逐步完善成一個完整的存儲引擎。
所以,無論哪一種思路,我們都需要首先找到或者自研一個優秀的適合Kylin的存儲格式。
我們在調研存儲格式時主要考慮Scan和Filter性能,導入性能,壓縮率,集成難度這4點因素,其中重點關注Scan和Filter性能。
Kylin On Parquet POC
我們首先對Parquet進行了調研。因為Parquet是當前Hadoop生態列式文件的標準,在Hadoop生態中廣泛使用。一個Parquet文件先按行邏輯上水平拆分成row groups,row groups內是列存,每一列是一個Column chunks,Column chunks進一步拆分成Page, Page是數據訪問的最小單位。Parquet 可以通過min,max索引和字典實現row groups粒度的過濾,但是沒有Page粒度的索引。
我們在17年5月份的時候進行了Kylin On Parquet POC,POC的結果也符合我們的理論預期:由于Parquet是列存,所以在Scan部分列時性能優于HBase,但由于存在Tuple重組,也就是列轉行的開銷,Scan性能會隨著訪問列的個數增加而降低,Scan全部列時性能不如HBase。 Filter方面,Parquet在前綴和后綴過濾上性能沒有差別,但是由于當時的Parquet沒有Page粒度的細粒度索引,所以前綴過濾性能明顯比HBase差。
Kylin On CarbonData
由于Parquet過濾性能不足,所以我們就Pass了Kylin On Parquet的方案。 Parquet之后,我們又調研了當時新起的華為開源的存儲格式CarbonData。和Parquet類似,CarbonData首先將數據水平切分成若干個Blocklet,Blocklet內部按列存儲,每列的數據叫做一個Column Chunk。和Parquet不同的是,CarbonData擁有豐富的索引:MDK索引;Min,Max索引;倒排索引。MDK索引是多維度索引,類似Kylin中的維度索引,整個文件會按照多個維度列進行排序,這樣對MDK列中的維度進行前綴過濾就會很高效。
CarbonData的列存+豐富索引的設計的確是我們所期望的,不過CarbonData和Spark耦合較深,且當時的CarbonData沒有OutputFormat,也不是很成熟,所以我們也Pass了Kylin On CarbonData的方案。
Kylin On Lucene POC
Parquet,CarbonData之后。 我們差不多同時進行了Kylin On Lucene POC 和 Kylin On Druid POC。我們先來看下Lucene, Lucene大家應該都很熟悉,是一個被廣泛使用的全文搜索庫,其存儲格式是基于MMAP,支持倒排索引的列存。
Kylin On Lucene POC的結果和我們對倒排索引的理論認知符合:由于倒排索引是用額外的構建成本和存儲成本換取查詢時高效的過濾性能,所以Lucene的構建性能只有HBase的1/3,存儲是HBase的4倍,但是過濾性能明顯優于HBase。
Kylin On Druid POC
下面我們來看下Druid的存儲格式。和Lucene類似,Druid的存儲格式也是基于MMAP,支持倒排索引的列存,區別是Druid的倒排索引是基于Bitmap的,Lucene的倒排索引是基于倒排表的。Druid的存儲格式比較簡單,就是直接按列依次存儲,其中的M1,M2是指標列,D1,D2指維度列.除了數據文件之外,還會有一個meta文件記錄每一列的offset。我們要讀取Druid文件時,就將Druid文件MMap到內存,直接根據offset讀取指定偏移量的數據。
圖中上半部分是Druid Bitmap 倒排索引的實現原理:我們有維度列D2, 共4行數據,包含meituan,dianping兩個值,Druid會首先對維度列進行字典編碼,圖中meituan編碼為0,dianping編碼為1,然后Druid會基于維度值和字典構建基于Bitmap的倒排索引,倒排索引的Key是編碼后的ID,Value是Bitmap,Bitmap的哪個bit位是1,就表示該值出現在哪些行。 下半部分是Druid String 維度存儲的具體內容:包括列的元數據,維度字典,維度編碼后的ID和倒排索引。
圖中是Kylin On Druid POC的結果,我們可以看到Druid的過濾性能明顯優于HBase,Scan性能和Parquet類似,在部分列時也明顯優于HBase。 構建和存儲的話,和Lucene類似,會比HBase差一點。
Why Kylin On Druid
在我們對Parquet,CarbonData,Lucene,Druid的存儲格式和POC情況有了基本了解之后,我們來看下為什么我們當時做出了Kylin On Druid的方案:
- Parquet的索引粒度較粗,過濾性能不足;
- CarbonData與Spark 耦合較深,集成難度較大;
- Lucene 和 Druid相比,存儲膨脹率較高,還有比較重要的一點是,Druid不僅僅是一個存儲格式,也可以作為Kylin完整的存儲引擎。
我們再來看下我們選擇Kylin On Druid的原因:
- 首先是Druid Scan,Filter性能很好
- 其次是Druid不僅僅是一個存儲格式,而是可以作為一個完整的Kylin存儲引擎, 比如Druid Historical節點負責Segment的Cache和 計算,Druid Coordinator節點負責Segment的增刪,副本和負載均衡。這樣我們就不需要基于存儲格式再去演進出一個存儲引擎,我們整個項目的工期會大幅縮短。
- 最后,Kylin和Druid本身就是我們維護的系統,項目即使失敗,我們的付出也會有收獲。
Kylin On Druid
OK,前半部分主要介紹了Kylin On Druid的大背景,回答了Why Kylin On Druid。 下面我們來看下How Kylin On Druid。
Kylin 可插拔架構
在介紹Kylin On Druid之前,我們先來看下Kylin的可插拔架構。Kylin的可插拔架構對數據源,計算引擎, 存儲引擎都有抽象,理論這3個部分都是可以替換的。目前數據源已經支持Hive,Kafka;計算引擎支持Spark,MR; 但是存儲引擎只有HBase。 所謂的Kylin On Druid就是在Kylin的可插拔架構中用Druid替換掉HBase。
Kylin On Druid 架構
圖中是Kylin On Druid 簡化后的架構圖,分為數據導入和數據查詢兩條線。數據導入時,Kylin的JobServer會將Hive中的數據導入到Druid的Historical節點中;數據查詢時,Kylin的Queryserver會通過Druid的Broker節點向Druid發起查詢。
下面我們來詳細看下KOD數據導入和數據查詢的過程,我們首先來看下數據導入過程:
KOD的數據查詢過程,Kylin和Druid如何交互我們可以有兩種方案。 一種是通過直接訪問Broker的方式和Druid集成,一種是通過直接訪問Druid Historical節點的方式和Druid集成。 第一種方式我們只需要將Kylin的SQL翻譯成Druid的json,通過Http向Druid的Broker發起查詢。 第二種方式我們就需要在kylin中實現Druid Broker的功能,好處是性能會比第一種好,因為少一層網絡傳輸,壞處是Kylin和Druid的依賴沖突會更嚴重,實現較復雜。本著侵入性小和簡單可用的原則,我們選擇了第一種方案。
Kylin On Druid 實現細節
在了解了KOD的架構和整個數據導入,數據查詢過程后,我們再具體看下我們做了哪些方面的工作:
其實這4方面的工作也是我們為Kylin增加一個存儲引擎必須要做的工作。
Kylin和Druid的Schema映射是這樣的:Kylin的Cube對應Druid中的DataSource,Kylin的Segment對應Druid中的Segment,維度對維度,指標對指標,需要額外增加一個Cuboid維度列,Druid中沒有的指標需要在Druid中進行擴展。
我們目前在Druid中增加了Kylin的精確去重指標,Kylin的ExtendColumn指標和Decimal指標。無論是Druid還是Kylin,我們新增一種聚合指標所要做的事情都是類似的,區別只是具體的接口和實現方式不同。我們自定義一種聚合指標要做這些事情:
- 定義指標的元數據:指標的名稱參數,返回類型,核心數據結構;
- 定義指標的聚合方式;
- 定義指標序列化和反序列化的方式;
- 注冊指標,讓系統發現新指標。
Cube構建側適配除了之前提到的將Cuboid 轉為 Druid Segment和Load Segemnt這一步,KOD中為了支持Druid層的業務隔離,還增加了Update Druid Tier這一步,允許用戶將不同的Cube存儲到不同的Tier。在Druid Segment生成和Load這兩步,都進行了一定的優化。 生成Druid Segment時,由于需要生成倒排索引,所以相比生成HBase的HFILE,就需要更多的內存,所以這一步進行了較多內存升的優化。 在Druid Load Segment這一步,初期發現多個Segment 并發導入時,Druid Load Segment會很慢。 經過排查發現,主要是Druid 0.10.0版本有bug,默認的并發度配置不生效,所以整個Load是串行,后來是通過升級到Druid 0.11,并將并發度設置為磁盤數的2倍解決的,因為Druid Load Segment的耗時99.9% 都是在從HDFS下載Segment上。
在介紹KOD對Cube查詢側的適配之前,我們先簡單看下Kylin的查詢流程。Kylin的SQL解析,邏輯計劃的生成和優化都是通過Calcite實現的,Kylin會根據Calcite生成的查詢計劃生成HBase的Scan請求,HBase端經過Scan,Filter,Agg后會一次性將結果返回給Kylin的QueryServer,QueryServer會將HBase返回的結果反序列化,對維度根據字典進行解碼生成Obejct數組,再將Obejct數組轉為Tuple,Tuple和Obejct數組的主要區別是Tuple有了類型信息,最后Tuple會交給Calcite的Enumerator進行最終計算。
下面我們來看下KOD對Cube查詢側的適配,我們拿到Calcite生成的查詢計劃后,為了實現謂詞下推,會首先將Kylin的Filter轉為Druid的Filter;其次會進行分區裁剪,避免訪問不必要的Druid Segment;然后會根據查詢的Cube,維度,指標,Filter等生成Druid的查詢Json,通過Http向Druid發起查詢,Druid端經過Scan,Filter,Agg后會按照Http Chunk的方式Pipline地將查詢結果返回給Kylin的QueryServer,下來的過程基本和HBase類似,需要注意的是,由于我們本身在Druid中存儲的就是原始值,所以查詢就不需要加載字典進行維度的解碼。
最后我們來看下運維工具適配方面的工作,主要包括:
- Cube遷移工具
- Storage Cleanup工具
- Cube Purge,Drop,Retention操作等。
這里我就不展開了,因為主要是一些實現和細節上的問題。
Kylin On Druid 成果
現在為止,我們已經清楚了KOD的整體架構和核心原理。 下面我們來看下,KOD的性能到底怎么樣。 首先我們來看下SSB測試的結果,PPT中展示了我們測試的SQL樣例和測試環境。 需要特別注意的是:我們在KOD和Kylin都只計算了Base Cuboid,因為如果KOD和Kylin都充分預計算,測試性能基本上沒有意義,測試的目的就是為了對比KOD和Kylin的現場計算能力。
這是KOD和Kylin只計算Base Cuboid時SSB測試的結果,圖中縱軸是KOD相比Kylin的加速比,千萬量級時KOD的加速比是49,億級時KOD的加速比是130。 總的來說,KOD的現場計算能力相比Kylin有了兩個數量級的提升。
這張圖展示的是我們KOD第一批Cube上線后,線上的真實數據。 我們可以看到,KOD相比Kylin在查詢性能提升的同時,存儲和計算資源也有了明顯的下降
前面提到,KOD的現場計算能力相比Kylin有了兩個數量級的提升,所以對于億級及以下數據規模的數據,我們不需要再進行復雜的優化,只需要構建Base Cuboid就可以,用戶的易用性有了顯著提升。
Kylin On Druid 特性
在介紹完KOD的架構,核心原理,性能后,我們再總結下KOD的特性:
- 和Kylin完全兼容:SQL,Web 等
- 分區預過濾
- 查詢時無需加載字典:相比Kylin On HBase 查詢穩定性更高
- 存儲層支持業務隔離
- 億級及以下數據只需構建Base Cuboid
Kylin On Druid 未來規劃
最后,我們看下KOD的未來規劃:
1 更高效,更精簡的Cube構建流程:由于Druid不依賴字典,所以KOD的Cube構建時,就不需要構建字典,和字典相關的好幾步都可以省去
2 優化高基數列點查詢的場景:之前的POC也體現了這一點,這種場景是倒排索引不適合的場景,卻是HBase的最佳場景。 解決思路:參考linkedin的Pinot對Druid進行優化,Druid中倒排索引作為可選項,添加輕量級的類似Palo,ClickHouse的前綴索引。
3 支持在線Schema變更:目前Kylin的Schema變更也是用戶使用上一大痛點,每次Schema變更都需要全量重刷數據。
本次分享從Kylin On HBase 問題出發,解釋了為什么我們給Kylin新增一種存儲引擎,進而介紹了我們存儲引擎探索的過程,最后介紹了Kylin On Druid的架構和原理。
最后,需要注意的一點是,最新版本的Parquet已經有了PageIndex,點查詢的性能顯著提高,所以Kylin On paquert 或許依然值得嘗試。
FAQ
Q1:為什么不直接用Druid代替Kylin?
Q2:為什么沒有選擇Kylin On Kudu?
- Kudu 并沒有倒排索引或者二級索引
- Kudu 是C++實現的,我們團隊的技術棧主要是Java,我們的存儲團隊也沒有引進Kudu
Q3:為什么沒有選擇去優化改進HBase?
因為要將HBase的Key-Value模型改成列存的話已經不僅僅是優化改進了,需要重新設計整個系統。 可以參考:kudu.apache.org/faq.html#wh…
Q4:Druid 和 Kylin 的應用場景?
在我司,Kylin是主推的離線OLAP引擎,Druid是主推的實時OLAP引擎。
關于 CarbonData 的 DataMap特性
CarbonData在最近的版本實現了DataMap特性: carbondata.apache.org/datamap-dev…
DataMap的主要目的是一份存儲支持多種查詢場景,實現CarbonData設計之初 的愿景;核心思想是一份數據,多種索引,不同場景下的查詢用不同的索引進行加速,查詢時可以自動路由到相應的索引。 目前已經實現了Pre-aggregate DataMap,Timeseries DataMap,Lucene DataMap,BloomFilter DataMap等,個人比較看好CarbonData這個特性。
點擊查看更多 Apache Kylin 技術案例
總結
以上是生活随笔為你收集整理的美团点评:基于Druid的Kylin存储引擎实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搭建阿里云ecs服务器(一:购买)
- 下一篇: 前端基础1:HTML常用标签