OLAP引擎——Kylin介紹,olap引擎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還提供了增量計算的功能,雖然達不到Realtime Compute的需求,但是基本上可以滿足資料分析的需求。 查詢解析過程主要是使用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介紹 在一個Cube合中,維度個數決定著維度之間可能的組合數,而每一個維度中成員集合的大小決定著每一個可能的組合的個數,例如有三個普通的維度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-kylin1、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、product、year}、{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的,以及執行查詢的流程。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。