Kylin介紹


  Kylin是ebay開發的一套OLAP系統,與Mondrian不同的是,它是一個MOLAP系統,主要用於支持大數據生態圈的數據分析業務,它主要是通過預計算的方式將用戶設定的多維立方體緩存到HBase中(目前還僅支持hbase),這段時間對mondrian和kylin都進行了使用,發現這兩個系統是時間和空間的一個權衡吧,mondrian是一個ROLAP系統,所有的查詢可以通過實時的數據庫查詢完成,而不會有任何的預計算,大大節約了存儲空間的要求(但是會有查詢結果的緩存,目前是緩存在程序內存中,很容易導致OOM),而kylin是一個MOLAP系統,通過預計算的方式緩存了所有需要查詢的的數據結果,需要大量的存儲空間(原數據量的10+倍)。一般我們要分析的數據可能存儲在關系數據庫(mysql、oracle,一般是程序內部寫入的一些業務數據,可能存在分表甚至分庫的需求)、HDFS上數據(結構化數據,一般是業務的日志信息,通過hive查詢)、文本文件、excel等。kylin主要是對hive中的數據進行預計算,利用hadoop的mapreduce框架實現。而mondrian理論上可以支持任意的提供SQL接口數據,由於關系數據庫一般會存在索引,所以即使使用mondrian去查詢性能還是可以接受的,當前我們使用的oracle數據庫,千萬條級別的記錄,查詢可以在分鍾級別完成,但是對於hive、
這樣的數據源查詢就太慢了,慢得不可以接受。
 
系統架構
     於是,我們開始嘗試使用kylin,kylin的出現就是為了解決大數據系統中TB級別數據的數據分析需求,而對於關系數據庫中的數據分析進行預計算可能有點不合適了(關系數據庫一般存在索引使得即使數據量很大查詢也不會慢的離譜,除非SQL寫的很爛)。在使用kylin的過程中,也逐漸對kylin有了一定的認識,首先看一下kylin的系統架構:
Kylin系統架構
     kylin由以下幾部分組成:
  · REST Server:提供一些restful接口,例如創建cube、構建cube、刷新cube、合並cube等cube的操作,project、table、cube等元數據管理、用戶訪問權限、系統配置動態修改等。除此之外還可以通過該接口實現SQL的查詢,這些接口一方面可以通過第三方程序的調用,另一方也被kylin的web界面使用。
  · jdbc/odbc接口:kylin提供了jdbc的驅動,驅動的classname為org.apache.kylin.jdbc.Driver,使用的url的前綴jdbc:kylin:,使用jdbc接口的查詢走的流程和使用RESTFul接口查詢走的內部流程是相同的。這類接口也使得kylin很好的兼容tebleau甚至mondrian。
  · Query引擎:kylin使用一個開源的Calcite框架實現SQL的解析,相當於SQL引擎層。
  · Routing:該模塊負責將解析SQL生成的執行計划轉換成cube緩存的查詢,cube是通過預計算緩存在hbase中,這部分查詢是可以再秒級甚至毫秒級完成,而還有一些操作使用過查詢原始數據(存儲在hadoop上通過hive上查詢),這部分查詢的延遲比較高。
  · Metadata:kylin中有大量的元數據信息,包括cube的定義,星狀模型的定義、job的信息、job的輸出信息、維度的directory信息等等,元數據和cube都存儲在hbase中,存儲的格式是json字符串,除此之外,還可以選擇將元數據存儲在本地文件系統。
  · Cube構建引擎:這個模塊是所有模塊的基礎,它負責預計算創建cube,創建的過程是通過hive讀取原始數據然后通過一些mapreduce計算生成Htable然后load到hbase中。
 
關鍵流程
     在kylin中,最關鍵的兩個流程是cube的預計算過程和SQL查詢轉換成cube的過程,cube的構造可以分成cube的構建和cube的合並,首先需要創建一個cube的定義,包括設置cube名、cube的星狀模型結構,dimension信息、measure信息、設置where條件、根據hive中事實表定義的partition設置增量cube,設置rowkey等信息,這些設置在mondrian中也是可以看到的,一個cube包含一些dimension和measure,where條件決定了源數據的大小,在mondrian中可以通過view實現。另外,kylin還提供了增量計算的功能,雖然達不到實時計算的需求,但是基本上可以滿足數據分析的需求。
     查詢解析過程主要是使用Calcite框架將用戶輸入的SQL解析並轉換成對hbase的key-value查詢操作以獲取結果,但是經過使用發現它對SQL的支持是比較差的,所有的SQL不能使用from A,B where xxx之類的join方式,必須使用inner(left、right) join on的方式,否則解析就會出錯,這就會導致mondrian生成的SQL壓根不能使用kylin查詢(因為mondrian生成的SQL是前面一種方式的),另外還有一個局限性就是發現只能對cube相關的表和列進行查詢,例如根據維度進行group by查詢定義的度量信息,而其他的查詢也統統的返回錯誤,這點倒也不算是很大的問題,畢竟cube的模型已經定義,我們不太可能查詢這個模型以外的東西。還有一點有點不能接受的是kylin對於子查詢的支持很弱,測試發現查詢的結果經常返回空(沒有一行),而相同的查詢在hive中是有結果的,這對於一些產品方需求支持不是很好,例如產品方可能需要查詢年銷售額大於xx的地區的每個月的銷售總額。我們一般情況下會寫出這樣的sql:select month, sum(sales) from fact where location in (select location from fact group by year having sum(sales) > 1000) group by month;前一段時間測試發現這種SQL對於關系數據庫簡直是災難,因為in語句會導致后面的子查詢沒有緩存結果,而寫成select month, sum(sales) from fact  as A inner join (select location from fact group by year having sum(sales) > 1000) as B on A.location = B.location group by month;可以提高性能,但是測試發現kylin返回的結果為空,而kylin對於in語句的查詢時非常高效的(畢竟全部走緩存),那么我們就不得不首先執行子查詢得到location集合,然后再寫一個SQL使用where location in xxx(kylin對於使用in子句的查詢支持還是相當棒的)的方式獲得結果,這個應該是需要改進的地方吧。
 
cube模型
     前面介紹了cube在創建過程中需要的設置,這里看一些每一個設置的具體含義吧,首先我們會設置cube名和notification列表,前者需要保證是全局唯一的,后者是一些Email用於通知cube的一些事件的發生。接着我們需要定義一個星狀模型,和一般的數據倉庫模型一樣,需要指定一個事實表和任意多個維度表,如果存在維度表還需要指定事實表和維度表的關聯關系,也就是join方式。接下來是定義dimension,在定義dimension的時候可以選擇dimension的類型,分為Normal、Hierachy以及Derived,這個后面再進行介紹,dimension的定義決定着cube的大小,也需要用戶對原始的表非常了解。
     接下來是定義measure,kylin會為每一個cube創建一個聚合函數為count(1)的度量,它不需要關聯任何列,用戶自定義的度量可以選擇SUM、COUNT、DISTINCT COUNT、MIN、MAX,而每一個度量定義時還可以選擇這些聚合函數的參數,可以選擇常量或者事實表的某一列,一般情況下我們當然選擇某一列。這里我們發現kylin並不提供AVG等相對較復雜的聚合函數(方差、平均差更沒有了),主要是因為它需要基於緩存的cube做增量計算並且合並成新的cube,而這些復雜的聚合函數並不能簡單的對兩個值計算之后得到新的值,例如需要增量合並的兩個cube中某一個key對應的sum值分別為A和B,那么合並之后的則為A+B,而如果此時的聚合函數是AVG,那么我們必須知道這個key的count和sum之后才能做聚合。這就要求使用者必須自己想辦法自己計算了。
     定義完measure之后需要設置where條件,這一步是對原始數據進行過濾,例如我們設定銷售額小於XXX的地區不在於本次分析范圍之內,那么就可以在where條件里設定location in xxx(子查詢),那么生成的cube會過濾掉這些location,這一步其實相當於對無效數據的清洗,但是在kylin中這個是會固化的,不容易改變,例如我今天希望將銷售額小於XX的地區清洗掉,明天可能有想將年消費小於xxx的用戶去除,這就需要每次都創建一個相同的cube,區別僅僅在於where條件,他們之間會有很多的重復緩存數據,也會導致存儲空間的浪費,但這也是MOLAP系統不可避免的,因此當過濾條件變化比較多的時候,更好的方案則是創建一個完整的cube(不設置任何where條件),使用子查詢的方式過濾掉不希望要的一些維度成員。
     接下來的一步是設置增量cube信息,首先需要選擇事實表中的某一個時間類型的分區列(貌似只能是按照天進行分區),然后再指定本次構建的cube的時間范圍(起始時間點和結束時間點),這一步的結果會作為原始數據查詢的where條件,保證本次構建的cube只包含這個閉區間時間內的數據,如果事實表沒有時間類型的分區別或者沒有選擇任何分區則表示數據不會動態更新,也就不可以增量的創建cube了。
     最后一步設置rowkey,這一步的建議是看看就可以了,不要進行修改,除非對kylin內部實現有比較深的理解才能知道怎么去修改。當然這里有一個可以修改的是mandatory dimension,如果一個維度需要在每次查詢的時候都出現,那么可以設置這個dimension為mandatory,可以省去很多存儲空間,另外還可以對所有維度進行划分group,不會組合查詢的dimension可以划分在不同的group中,這樣也會降低存儲空間。
 
Dimension介紹
     在一個多維數據集合中,維度的個數決定着維度之間可能的組合數,而每一個維度中成員集合的大小決定着每一個可能的組合的個數,例如有三個普通的維度A、B、C,他們的不同成員數分別為10/100/1000,那么一個維度的組合有2的3次方個,分別是{空、A、B、C、AB、BC、AC、ABC},每一個成員我們稱為cuboid(維度的組合),而這些集合的成員組合個數分別為1、10、100、1000、10*100、100*1000、10*1000和10*100*1000。我們稱每一個dimension中不同成員個數為cardinatily,我們要盡量避免存儲cardinatily比較高的維度的組合,在上面的例子中我們可以不緩存BC和C這兩個cuboid,可以通過計算的方式通過ABC中成員的值計算出BC或者C中某個成員組合的值,這相當於是時間和空間的一個權衡吧。
     在kylin中存在的四種維度是為了減少cuboid的個數,而不是每一個維度是否緩存的,當前kylin是對所有的cuboid中的所有組合都進行計算和存儲的,對於普通的dimension,從上面的例子中可以看出N個維度的cuboid個數為2的N次方,而kylin中設置了一些維度可以減少cuboid個數,當然,這需要使用者對自己需要的維度十分了解,知道自己可能根據什么進行group by。
     好了,我們先來看一下kylin中的三種特殊的dimension以及它們的作用,這里參考: http://www.slideshare.net/YangLi43/design-cube-in-apache-kylin
1、Mandatory維度
     這種維度意味着每次查詢的group by中都會攜帶的,將某一個dimension設置為mandatory可以將cuboid的個數減少一半,如下圖:
mandatory dimension
這是因為我們確定每一次group by都會攜帶A,那么就可以省去所有不包含A這個維度的cuboid了。
2、hierarchy維度
     這種維度是最常見的,尤其是在mondrian中,我們對於多維數據的操作經常會有上卷下鑽之類的操作,這也就需要要求維度之間有層級關系,例如國家、省、城市,年、季度、月等。有層級關系的維度也可以大大減少cuboid的個數。如下圖:
hierarchy dimension
這里僅僅局限於A/B/C是一個層級,例如A是年份,B是季度、C是月份,那么查詢的時候可能的組合只有年、xx年的季度、xx年xx季度的xx月,這就意味着我們不能再單獨的對季度和月份進行聚合了,例如我們查詢的時候不能使用group by month,而必須使用group by year,quart,month。如果需要單獨的對month進行聚合,那么還需要再使用month列定義一個單獨的普通維度。
3、derived維度
     這類維度的意思是可推導的維度,需要該維度對應的一個或者多個列可以和維度表的主鍵是一對一的,這種維度可以大大減少cuboid個數,如下圖:
derived dimension
例如timeid是時間這個維度表的主鍵,也就是事實表的外檢,時間只精確到天,那么year、month、day三列可以唯一對應着一個time_id,而time_id是事實表的外鍵,那么我們可以指定year、month、day為一個derived維度,實際存儲的時候可以只根據timeid的取值決定維度的組合,但這就要求我們在查詢的時候使用的group by必須指定derived維度集合中的所有列。
     最后,簡單介紹一下如何計算cuboid個數的,假設我們存在兩個普通維度brand、product,存在一個hierarchy,包含四個維度分別為year、quart、month和day,一個derived維度,指定location信息,包含country、province和city列,這相當於一共9個維度,但是根據上面的分析我們並不需要512分cuboid。
第0層的cuboid(不包含任何維度,不包含group by),cuboid的個數為1,這個cuboid的成員個數也為1;
第1層的cuboid包含一個維度,一共有4種組合(分別為brand、product、year、location,因為quart是hierarchy的第二個層級,不能單獨group by,而location的三列可以視為一個整體),成員個數則有每一個維度的cardinality;
第2層的cuboid有7種,分別為{brand、product}、{brand、year}、{brand、location}、{product、year}、{product、location}、{year、location}和{year、quart};
第3層的cuboid有8種,分別為{brand、product、year}、{brand、product、location}、{product、year、location}、{brand、year、location}、{brand、year、quart}、{product、year、quart}、{location、year、quart}、{year、quart、month};
第4層的cuboid有8種,分別為{brand、product、year、location}、{brand、product、year、quart}、{brand、location、year、quart}、{product、location、year、quart}、{brand、year、quart、month}、{product、year、quart、month}、{location、year、quart、month}、{year、quart、month、day}
第5層的cuboid有7種,分別為{brand、product、year、quart、location}、{brand、product、year、quart、momth}、{brand、location、year、quart、month}、{product、location、year、quart、month}、{brand、year、quart、month、day}、{product、year、quart、month、day}、{location、year、quart、month、day}
第6層的cuboid有5種,分別為{brand、product、year、quart、month、location}、{brand、product、year、quart、momth、day}、{brand、location、year、quart、month、day}、{product、location、year、quart、month、day}
第7層的cuboid有1中,為{brand、product、year、quart、month、day、location}
所以一共40個cuboid(kylin計算的是39個,應該沒有把第0層的計算在內)。
 
增量cube
     由於kylin的核心在於預計算緩存數據,那么對於實時的數據查詢的支持就不如mondrian好了,但是一般情況下我們數據分析並沒有完全實時的要求,數據延遲幾個小時甚至一天是可以接受的,kylin提供了增量cube的接口,kylin的實現是一個cube(這里是指邏輯上的cube)中可以包含多個segment,每一個segment對應着一個物理cube,在實際存儲上對應着一個hbase的一個表,用戶定義根據某一個字段進行增量(目前僅支持時間,並且這個字段必須是hive的一個分區字段),在使用的時候首先需要定義好cube的定義,可以指定一個時間的partition字段作為增量cube的依賴字段,其實這個選擇是作為原始數據選擇的條件,例如選擇起始時間A到B的數據那么創建的cube則會只包含這個時間段的數據聚合值,創建完一個cube之后可以再次基於以前的cube進行build,每次build會生成一個新的segment,只不過原始數據不一樣了(根據每次build指定的時間區間),每次查詢的時候會查詢所有的segment聚合之后的值進行返回,有點類似於tablet的存儲方式,但是當segment存在過多的時候查詢效率就會下降,因此需要在存在多個segment的時候將它們進行合並,合並的時候其實是指定了一個時間區間,內部會選擇這個時間區間內的所有segment進行合並,合並完成之后使用新的segment替換被合並的多個segment,合並的執行時非常迅速的,數據不需要再從HDFS中獲取,直接將兩個hbase表中相同key的數據進行聚合就可以了。但是有一點需要注意的是當合並完成之后,被合並的幾個segment所對應的hbase表並沒有被刪除。實際的使用過程中對於增量的cube可以寫個定時任務每天凌晨進行build,當達到一個數目之后進行merge(其實每次build完成之后都進行merge也應該是可以的)。
 
cube的詞典樹
     kylin的cube數據是作為key-value結構存儲在hbase中的,key是每一個維度成員的組合值,不同的cuboid下面的key的結構是不一樣的,例如cuboid={brand,product,year}下面的一個key可能是brand='Nike',product='shoe',year=2015,那么這個key就可以寫成Nike:shoe:2015,但是如果使用這種方式的話會出現很多重復,所以一般情況下我們會把一個維度下的所有成員取出來,然后保存在一個數組里面,使用數組的下標組合成為一個key,這樣可以大大節省key的存儲空間,kylin也使用了相同的方法,只不過使用了字典樹(Trie樹),每一個維度的字典樹作為cube的元數據以二進制的方式存儲在hbase中,內存中也會一直保持一份。
 
總結
     以上介紹了kylin的整體框架以及部分的模塊的流程,由於之前主要是關注cube的一些操作,例如創建、構建、合並等,對於查詢這一塊了解的較少,當然,這一塊是kylin的核心之一。接下來會從源代碼的角度去看kylin是如何構建和mergecube的,以及執行查詢的流程。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM