壹個Cube(多維立方體)其實就是壹個多維數組,例如定義壹個三維數組int array[M][N][K],每壹個維度分別有M、N和K個成員,同樣對於壹個Cube而言,也可以有三個維度,假設分別為time、location和product,每壹個維度的distinct值(稱為維度的cardinality)分別是M、N和K個,而數組中的每壹個值則是每壹個維度取壹個值對應的聚合結果,例如array[0][1][2],它就相當於time取第壹個值(假設為2016-01-01),location取第二個值(假設為HangZhou)、product取第三個值(假設為Food)對應的壹個聚合結果,假設這裏的聚合函數為COUNT(1),那麽求得的值相當於執行了SELECT COUNT(1) from table where time = ‘2016-01-01’ and location = ‘HangZhou’ and product = ‘Food’,這樣當我們有多個聚合函數呢,那麽就相當於數組中的每壹個元素是壹個包含多個值的結構體:
#
如上圖,每壹個data cell就保存了壹個類似的結構體,因此只要能夠計算和保存這樣的壹個多維數組,我們所有的查詢就都可以直接定位到多維數組中的壹個或者壹批值,然後進行過濾得到結果。
但是壹個多維數組和Cube模型還是有壹些區別,例如在多維數組中我們必須在每壹維指定壹個值(下標)才能對應得到壹個確定的值,但是在Cube中,雖然定義了三個維度,但是我可以只指定兩個維度,甚至壹個維度都不指定而進行查詢,例如執行SELECT COUNT(1) from table where time = ‘2016-01-01’ and location = ‘HangZhou’,這就相當於求數組中array[0][1]的值,但這個返回的是壹個數組,而SQL返回的是壹個值,因此需要將這個數組中的每壹個data cell取出來再進行聚合運算(例如計數、相加等)得到的值才是真正的結果。好了,到這裏可以看出有兩種方案計算上面的SQL:
那麽在這個二維數組上指定time和location就能夠對應壹個data cell值了。由於可以通過方案1進行再聚合計算,所以理論上如果保存了所有維度的組合(假設N個維度,那麽就是壹個N為的數組),那麽所有的N-1、N-2、…、0維的任何值都是可以通過這個N維數組進行再聚合計算出來的,但是這勢必就影響查詢的性能。所以這其實就是時間和空間的壹個博弈,那麽在實踐中到底應該如何進行權衡呢,答案是:看需求!也就是建立哪些數組(N個維度,第K層有C(N,K)個K維數組)就要看妳真正要執行的查詢有哪些了,對經常在壹塊進行組合查詢的維度簡歷壹個數組是再適合不過的了,例如time和location這兩個維度老是在壹個SQL中出現,那麽建立壹個這樣的二維數組是需要的,而product和location這兩個不會在壹個SQL中出現,那麽這個二維數組就不需要預計算了。那麽在Kylin裏面是如何決定預計算哪些數組呢?
這裏的壹切就要從Kylin創建Cube開始說起,在Kylin中創建壹個Cube需要以下幾步:
1、設置Cube名、描述信息等
2、設置Cube依賴的表模型(星狀模型,壹個事實表和可選的多個維度表)
3、設置維度(維度有幾種類型這裏不再討論,創建完之後就可以暫時性的忽略這幾種不同的類型,都把它當做普通的維度就可以了)
4、設置度量(每壹個度量包括列和聚合函數,列只能是事實表上的列)
5、設置filter條件(用於對表中的數據進行過濾)
6、設置增量更新的信息(設置增量列和起始時間,該列必須是時間格式列)
7、高級設置(設置維度組、RowKey等)
前面6步的設置比較淺顯易懂,那麽對於Cube的優化主要通過“高級設置”這壹步實現的,這裏設置的主要有以下幾種:
1、設置Rowkey
2、設置維度組
3、設置Cube Size
在進入到設置RowKey的時候會看到每壹個維度的設置(Derived維度看到的是外鍵列而不是Derived的列),每壹個維度可以設置ID(通過拖拽可以改變每壹個維度的ID)、Mandatory、Dictionary和Length。
#Mandatory維度
首先看壹下Mandatory維度,需要設置為Mandatory的維度是哪些在大多數SQL中都會出現的維度,例如time這個維度,如果每次查詢都需要帶上它進行過濾或者group by,那麽就可以把它設置為mandatory。
#維度順序
其次,ID決定了這個維度在數組中執行查找時該維度對應的第壹個維度,例如在上例中time的ID就是1,location對應的ID就是2,product對應的ID為3,這個順序是非常重要的,壹般情況我們會將mandatory維度放置在rowkey的最前面,而其它的維度需要將經常出現在過濾條件中的維度放置在靠前的位置,假設在上例的三維數組中,我們經常使用time進行過濾,但是我把time的ID設置為3(location的ID=1,product的ID=2),這時候如果從數組中查找time大於’2016-01-01’並且小於’2016-01-31’,這樣的查詢就需要從這樣的最小的key=<min(location)、min(product)、‘2016-01-01’>掃描到最大的key=<max(location)、max(product)、‘2016-01-31’>,但是如果把time的ID設置為1,掃描的區間就會變成key=<‘2016-01-01’、min(location)、min(product)>到key=<‘2016-01-31’、max(location)、max(product)>,Kylin在實現時需要將Cube的數組存儲在Hbase中,然後按照hbase中的rowkey進行掃描,假設min(location)=’BeiJing’、max(location)=’ZhengZhou’, min(product)=’aaaa’,max(product)=’zzzz’,這樣在第壹種情況下hbase中需要掃描的rowkey範圍是[BeiJing-aaaa-2016-01-01, ZhengZhou-zzzz-2016-01-31],而第二種情況需要掃描的rowkey範圍是[2016-01-01-BeiJing-aaaa, 2016-01-31-ZhengZhou-zzzz].可以看出第二種情況可以減少掃面的rowkey,查詢的性能也就更好了。但是在kylin中並不會存儲原始的成員值(例如HangZhou、2016-01-01這樣的值),而是需要對它們進行編碼,是否需要編碼則有後面兩個設置項決定。
#維度字典
Dictionary可以設置為true和false,設置為true表示需要為這個維度建立字典樹,如果設置為false則表示不需要設置,而需要設置Length,而Length則意味著在實際存儲到hbase的rowkey時使用該維度的前Length個字符作為它的值(剪切每壹個成員值只保留前Length個字符),壹般情況下是不建議設置Length的,而是設置Dcitionary為true,只有當cardinality比較大時並且只需要取前N個字節就可以表示這個維度時才建議設置Length=N,因為每壹個維度的dictionary都會保存在內存中,如果字典樹占用很大的內存會影響kylin的使用甚至導致OOM,對於dictionary的編碼使用的是字典樹,它的原理實際上是為每壹個維度成員賦予壹個整數的id,實際存儲的時候存儲的是這個id的二進制值(使用int最多占用4個字節),並且保證每壹個id的順序和維度成員的順序相同的,例如aaa的id=1,aab的id=2,aac的id=3,這樣在查詢的時候就可以直接根據column>aaa轉換成id>1,方便hbase coprocessor的處理。
#維度組
設置完了RowKey接下來要設置維度組,維度組的設置主要是為了讓不出現在壹個查詢中的兩個維度不計算cuboid(通過劃分到兩個不同的維度組中),這其實相當於把壹個cube的樹結構劃分成多個不同的樹,可以在不降低查詢性能的情況下減少cuboid的計算量,目前在Kylin-1.x版本中cuboid的算法有壹點的問題,可以參考我對這個算法的改進那篇博文。
#Cube Size
最後設置CubeSize,該項的設置會對cuboid轉換成hfile這壹步的計算產生影響,並且影響hbase中表的分區大小,可選值為SMALL、MEDIUM和LARGE,在kylin-1.1版本之後可以在配置文件可以設置這三個配置的分區大小,默認情況下SMALL=10GB,MEDIUM=20GB,LARGE=100GB,在計算完全部的cuboid之後會統計所有cuboid文件中key和value的大小,然後根據這個大小和用戶的CubeSize配置決定劃分多少region,然後執行壹個MR任務計算每壹個region的hfile,由於kylin在創建hfile的時候都是通過預分區的方式(通過計算出每壹個分區臨界值的key),然後批量load到htable的,所以不會導致region的分裂和合並,所以我們還是建議將CubeSize設置為SMALL,並且配置中將small的配置設置為5GB,這樣可以提高生成hfile這壹步的速度(每壹個region負責壹個region,減小分區的大小會增加reducer的個數)。
好了,本文主要介紹了kylin中創建cube的過程,其中還主要介紹了cube模型的概念,最後詳細介紹了kylin在創建cube中的高級設置的優化方案,如果有什麽錯誤的地方,還希望多多指正。