實(shí)戰(zhàn):預(yù)計(jì)算OLAP引擎讓大數(shù)據(jù)秒級(jí)查詢)
先說(shuō)一個(gè)深夜加班的場(chǎng)景業(yè)務(wù)方甩過(guò)來(lái)一張報(bào)表要求按天、按城市、按品類統(tǒng)計(jì)最近三個(gè)月的銷量而且要能自由組合篩選。你在Hive里跑一條聚合SQL兩千萬(wàn)行明細(xì)掃完二十多分鐘過(guò)去結(jié)果還沒(méi)出來(lái)。旁邊那個(gè)用Kylin的同事同樣的需求幾秒鐘給出數(shù)字還能順手把維度切到渠道、周粒度再看看。這就是Kylin這類預(yù)計(jì)算OLAP引擎的價(jià)值所在——數(shù)據(jù)越大、查詢模式越固定它的優(yōu)勢(shì)越明顯。Apache Kylin是一款開(kāi)源的分布式分析引擎核心思想是把多維分析里最耗時(shí)的聚合計(jì)算提前做好真正查詢時(shí)只做“查結(jié)果”所以能把百億級(jí)數(shù)據(jù)集的交互式查詢壓到秒級(jí)。對(duì)數(shù)據(jù)分析師、數(shù)據(jù)研發(fā)、剛接觸大數(shù)據(jù)OLAP的同學(xué)來(lái)說(shuō)想體驗(yàn)“一條慢查詢秒變快”的爽感它確實(shí)是個(gè)非常值得入手的項(xiàng)目。這篇文章不聊太深的源碼就是把我實(shí)際跑通一遍Kylin的過(guò)程、步驟、踩過(guò)的坑都攤開(kāi)來(lái)講照著做就能玩起來(lái)。1. Kylin為什么是“快”的先想清楚這個(gè)問(wèn)題再動(dòng)手1.1 大數(shù)據(jù)里的查詢慢慢在哪里很多剛?cè)腴T(mén)的朋友會(huì)有個(gè)疑惑Hive、Spark SQL也能做聚合分析為什么非要多一個(gè)Kylin我舉個(gè)例子你就明白了。比如一張訂單事實(shí)表幾千萬(wàn)行你要算“每個(gè)城市每個(gè)品類的總銷售額”Hive的執(zhí)行方式是先掃描全表明細(xì)再按城市和品類做Shuffle、Group By。如果這張表還要和幾張維度表Join那計(jì)算量更上一層樓。今天跑一次要二十分鐘明天業(yè)務(wù)方改了兩個(gè)維度換個(gè)指標(biāo)又要二十分鐘。數(shù)據(jù)的增長(zhǎng)永遠(yuǎn)比硬件升級(jí)快這類固定模式的聚合查詢?nèi)绻看味既繏呙璩杀痉浅8?。Kylin的思路是完全反過(guò)來(lái)的。它不在查詢發(fā)生時(shí)去現(xiàn)算而是提前把各種維度組合下的聚合結(jié)果算好、存下來(lái)。等查詢來(lái)了它只需要判斷你要查哪幾種維度的組合然后直接去存儲(chǔ)里把已經(jīng)算好的結(jié)果撈出來(lái)。這就是典型的“空間換時(shí)間”——用預(yù)計(jì)算的存儲(chǔ)成本換取查詢時(shí)的高性能。1.2 預(yù)計(jì)算的核心Cube、Cuboid與SegmentKylin里最核心的三個(gè)概念是Cube、Cuboid和Segment。Cube就是根據(jù)你的模型和維度定義構(gòu)建出來(lái)的一份完整的多維預(yù)計(jì)算結(jié)果集合。但一個(gè)Cube不是一塊鐵板它內(nèi)部會(huì)按照維度組合拆分。比如你有城市、品類、渠道三個(gè)維度Groub By城市、Group By城市品類、Group By品類渠道這些不同的組合各自算出來(lái)的結(jié)果在Kylin里就叫一個(gè)Cuboid。查詢時(shí)Kylin會(huì)把SQL解析成對(duì)應(yīng)的維度組合精確命中某個(gè)Cuboid。Segment則更像是Cube在時(shí)間上的“切片”。Kylin構(gòu)建Cube時(shí)一般會(huì)按時(shí)間字段分批構(gòu)建比如一天一個(gè)Segment或一周一個(gè)Segment。查詢時(shí)如果帶時(shí)間范圍它只需要掃描命中的幾個(gè)Segment而不是整個(gè)Cube。這個(gè)設(shè)計(jì)對(duì)增量數(shù)據(jù)非常友好——每天新數(shù)據(jù)只需要構(gòu)建當(dāng)天對(duì)應(yīng)的Segment舊數(shù)據(jù)完全不用動(dòng)。1.3 Kylin適合什么不適合什么這個(gè)一定要先搞清楚不然容易用錯(cuò)方向。Kylin適合的場(chǎng)景是數(shù)據(jù)量在千萬(wàn)到百億級(jí)、查詢模式相對(duì)固定、要求秒級(jí)響應(yīng)、并且主要用于多維統(tǒng)計(jì)分析的項(xiàng)目。比如電商經(jīng)營(yíng)分析、流量日志分析、BI報(bào)表平臺(tái)底層加速這些都是它的主場(chǎng)。它不適合的場(chǎng)景也很明顯第一明細(xì)級(jí)查詢比如“把某用戶最近十筆訂單都查出來(lái)”這種活是OLTP或明細(xì)查詢引擎該做的Kylin根本不存明細(xì)第二維度基數(shù)特別高的場(chǎng)景比如幾億個(gè)用戶ID全做成維度Cuboid數(shù)量會(huì)爆炸構(gòu)建時(shí)間和存儲(chǔ)成本都扛不住第三查詢模式完全無(wú)法預(yù)判、每次都是新的維度組合那預(yù)計(jì)算的價(jià)值就大打折扣。某開(kāi)發(fā)者曾在一套模擬項(xiàng)目X上試過(guò)把訂單明細(xì)直接塞進(jìn)Kylin結(jié)果Cube構(gòu)建時(shí)間比查詢節(jié)省的時(shí)間還多后來(lái)把方案改成“Kylin算匯總明細(xì)走另一套存儲(chǔ)”才合理。1.4 版本怎么選現(xiàn)在Kylin主流有4.x和5.x兩個(gè)系列我更推薦入門(mén)直接用4.x。原因很簡(jiǎn)單4.x把構(gòu)建引擎統(tǒng)一到了Spark上存儲(chǔ)從之前依賴HBase換成了Parquet部署復(fù)雜度比老版本低了不少。Kylin 4.x也支持標(biāo)準(zhǔn)ANSI SQLWeb界面里寫(xiě)SQL和看構(gòu)建Job都很方便。如果你所在環(huán)境里Hadoop生態(tài)比較老那可以根據(jù)實(shí)際情況選帶對(duì)應(yīng)依賴構(gòu)建的版本我當(dāng)時(shí)用的是Kylin 4.0搭配Hadoop 3.x、Hive 2.3.x、Spark 2.4.x、Zookeeper 3.4.x組合比較穩(wěn)。提示版本匹配是大坑。Kylin對(duì)Hive和Spark的版本比較敏感建議以官網(wǎng)文檔里列出的兼容矩陣為準(zhǔn)別為了趕新把Hive換成3.x大版本結(jié)果Kylin啟動(dòng)時(shí)元數(shù)據(jù)操作報(bào)一堆錯(cuò)排查起來(lái)很痛苦。2. 部署實(shí)操?gòu)牧惆岩粋€(gè)Kylin環(huán)境跑起來(lái)2.1 前置依賴與環(huán)境準(zhǔn)備Kylin不是單機(jī)玩具它需要跑在已有的大數(shù)據(jù)環(huán)境之上。理論上你要先有Hadoop集群至少要有HDFS和YARN、Hive用來(lái)同步元數(shù)據(jù)和創(chuàng)建中間表、Spark用來(lái)執(zhí)行構(gòu)建Job、Zookeeper用于分布式協(xié)調(diào)。入門(mén)階段不用真的搭一套高可用集群我建議在一臺(tái)內(nèi)存不低于16G的機(jī)器上搭個(gè)偽分布式環(huán)境把上述組件全部配好再裝Kylin這樣出問(wèn)題也好定位。如果你用的是某云平臺(tái)的大數(shù)據(jù)產(chǎn)品里面自帶了Hadoop/Hive那反而省事只需要確保Kylin能訪問(wèn)到Hive Metastore和HDFS即可。安裝Kylin本身不復(fù)雜核心是配置文件。把編譯好的tar包解壓到某個(gè)目錄比如/opt/apache-kylin-4.0.0-bin記住這個(gè)路徑后面環(huán)境變量要用。export KYLIN_HOME/opt/apache-kylin-4.0.0-bin export PATH$KYLIN_HOME/bin:$PATH然后把集群里的core-site.xml、hdfs-site.xml、hive-site.xml、yarn-site.xml、spark-conf等配置文件復(fù)制到Kylin的conf目錄下或者直接在kylin.properties里顯式指定依賴組件的配置目錄。這一步很容易被忽略但Kylin啟動(dòng)時(shí)需要識(shí)別HDFS地址、Hive連接信息漏一個(gè)就會(huì)報(bào)“無(wú)法訪問(wèn)Hive”或“找不到HDFS NameNode”。2.2 啟動(dòng)順序和第一眼日志依賴組件都正常之后啟動(dòng)順序也有講究HDFS和YARN先起然后是Zookeeper再是Hive Metastore服務(wù)最后才啟動(dòng)Kylin。bin/kylin.sh start第一次啟動(dòng)時(shí)Kylin會(huì)在HDFS上創(chuàng)建自己的工作目錄對(duì)應(yīng)kylin.env.hdfs-tmp-dir配置并初始化一些元數(shù)據(jù)。啟動(dòng)過(guò)程大概一兩分鐘日志里會(huì)出現(xiàn)“Kylin service started”之類的字樣??吹竭@個(gè)先別急著高興驗(yàn)證一下Web界面能不能打開(kāi)。瀏覽器訪問(wèn)http://你的主機(jī)名:7070默認(rèn)賬號(hào)ADMIN、默認(rèn)密碼KYLIN。我在實(shí)際部署過(guò)程中遇到過(guò)好幾次Web界面打不開(kāi)的情況十有八九是端口沒(méi)監(jiān)聽(tīng)成功。直接在服務(wù)器上檢查端口netstat -tlnp | grep 7070如果端口沒(méi)起來(lái)最常犯的毛病是kylin.properties里的kylin.env.hdfs-tmp-dir指向的HDFS目錄權(quán)限不對(duì)或者當(dāng)前啟動(dòng)Kylin的系統(tǒng)用戶對(duì)HDFS的home目錄沒(méi)有寫(xiě)權(quán)限。另一個(gè)容易忽視的是主機(jī)名解析——Kylin會(huì)把主機(jī)名寫(xiě)進(jìn)一些運(yùn)行配置如果/etc/hosts里沒(méi)有正確映射它明明啟動(dòng)了你卻訪問(wèn)不到。2.3 快速驗(yàn)證能不能連上HiveWeb界面能打開(kāi)并不代表Kylin已經(jīng)能正確操作Hive表。我習(xí)慣先做一個(gè)最基礎(chǔ)的驗(yàn)證進(jìn)到Data Source頁(yè)面填一個(gè)Hive中的庫(kù)名例如default點(diǎn)擊加載。如果表列表正常出現(xiàn)說(shuō)明Kylin和Hive Metastore之間的連接是通的。這一步能過(guò)濾掉大部分“Kylin起來(lái)了但用不了”的隱患。如果這里就報(bào)錯(cuò)重點(diǎn)檢查kylin.properties配置文件里的這幾個(gè)參數(shù)kylin.designer.hive-conf-folder/opt/apache-hive/conf kylin.source.hive.working-dir/tmp/kylin kylin.env.hdfs-tmp-dir/tmp/kylin注意working-dir和hdfs-tmp-dir建議獨(dú)立設(shè)置不要和Hive默認(rèn)的臨時(shí)目錄混在一起否則運(yùn)行過(guò)程中清理臨時(shí)文件的Job可能互相干擾。我當(dāng)時(shí)就是偷懶沒(méi)單獨(dú)設(shè)目錄某次Kylin的中間結(jié)果被YARN清掉導(dǎo)致構(gòu)建任務(wù)一直失敗。3. 建模實(shí)操?gòu)臄?shù)據(jù)表到Cube的完整流程3.1 前提準(zhǔn)備造一份能練手的數(shù)據(jù)入門(mén)學(xué)Kylin沒(méi)必要上來(lái)就接生產(chǎn)大表。我建議用一個(gè)模擬的“電商銷售分析”數(shù)據(jù)集來(lái)練手核心是三張表事實(shí)表sim_sales_order_fact存儲(chǔ)訂單級(jí)明細(xì)包含訂單ID、下單日期、城市ID、商品類目ID、銷售金額、商品數(shù)量維度表sim_dim_city存城市信息維度表sim_dim_category存商品類目信息。事實(shí)表造個(gè)幾百萬(wàn)行就夠體驗(yàn)了關(guān)鍵是要讓維度表和事實(shí)表能通過(guò)外鍵關(guān)聯(lián)上。造數(shù)據(jù)時(shí)有個(gè)建議把日期字段不要造得太連續(xù)只造近幾個(gè)月的數(shù)據(jù)并且讓分區(qū)字段就是日期。這樣后面構(gòu)建Segment和測(cè)試增量構(gòu)建時(shí)你才能直觀感受到“只構(gòu)建某一天的數(shù)據(jù)”是什么意思。日期字段類型最好用字符串比如2024-06-01避免后面在Kylin界面里設(shè)置分區(qū)列時(shí)還要做類型轉(zhuǎn)換凈添麻煩。3.2 三步建模Project、Model、Cube在Kylin Web界面里建模路徑非常清晰一步步走就行。第一步創(chuàng)建Project。相當(dāng)于一個(gè)邏輯隔離空間不同業(yè)務(wù)線的Cube放在不同Project里避免相互影響。我建的項(xiàng)目名字就叫demo_sales簡(jiǎn)單直白。第二步在Data Source里加載Hive表。把上面的三張表同步進(jìn)來(lái)Kylin會(huì)讀取表的Schema。我看到很多新手在這一步會(huì)漏掉維度表只把事實(shí)表同步進(jìn)來(lái)結(jié)果后面建Model時(shí)發(fā)現(xiàn)關(guān)聯(lián)不出維度又要回頭去補(bǔ)同步。建議一次性把事實(shí)表和維度表都加載出來(lái)。第三步創(chuàng)建Model。Model定義了“事實(shí)表維度表”的關(guān)聯(lián)結(jié)構(gòu)相當(dāng)于告訴Kylin這兩張表靠什么字段Join。我建Model時(shí)選了sim_sales_order_fact作為事實(shí)表把sim_dim_city和sim_dim_category加進(jìn)來(lái)分別設(shè)置關(guān)聯(lián)關(guān)系事實(shí)表的city_id對(duì)維度表的city_id事實(shí)表的category_id對(duì)維度表的category_id然后要指定Model的維度和度量。維度就是查詢時(shí)想按哪些字段分組我選了日期、城市名、類目名。度量是查詢時(shí)要算的指標(biāo)我選了SUM(sale_amount)和COUNT(DISTINCT customer_id)。注意COUNT(DISTINCT ...)在Kylin里屬于特殊度量它的預(yù)計(jì)算和存儲(chǔ)開(kāi)銷比普通SUM要大得多入門(mén)階段可以先加一個(gè)感受一下精度和資源占用之間的權(quán)衡。第四步創(chuàng)建Cube。Cube是建立在一個(gè)Model之上的你選好Model之后界面會(huì)把Model里定義好的維度、度量自動(dòng)帶出來(lái)你要做的是進(jìn)一步裁剪。比如日期維度如果查詢時(shí)只會(huì)按天、周、月看那就沒(méi)必要把“年-季度-月”拆成三個(gè)獨(dú)立維度可以直接把order_date的時(shí)間層級(jí)配置成層級(jí)維度讓Kylin自動(dòng)生成對(duì)應(yīng)的組合結(jié)果。最后點(diǎn)一下Build按鈕選擇起始時(shí)間Kylin就會(huì)開(kāi)始構(gòu)建Cube。在Jobs頁(yè)面能看到構(gòu)建任務(wù)的進(jìn)度整個(gè)構(gòu)建過(guò)程會(huì)經(jīng)歷創(chuàng)建中間Hive表、用Spark計(jì)算各Cuboid、寫(xiě)出結(jié)果等階段。等Job狀態(tài)變成SuccessCube就能查了。3.3 Cuboid數(shù)量是怎么算的為什么它決定構(gòu)建成敗很多新手遇到的第一個(gè)構(gòu)建失敗原因都指向同一個(gè)詞Cuboid爆炸。Cube的維度數(shù)直接影響Cuboid數(shù)量如果有10個(gè)維度所有維度自由組合Cuboid數(shù)量就是2的10次方1024個(gè)20個(gè)維度就是1048576個(gè)。這還沒(méi)算上每個(gè)Cuboid要存儲(chǔ)數(shù)據(jù)構(gòu)建Job要計(jì)算多少內(nèi)容存儲(chǔ)和計(jì)算成本是成指數(shù)上漲的。所以建模時(shí)一定要學(xué)會(huì)做裁剪。我常用的幾種手段聚合組把維度分組。比如A組放城市、類目、渠道B組放日期、時(shí)段。不同組的維度之間不會(huì)任意組合出新的Cuboid能大幅減少數(shù)量。強(qiáng)制維度每次查詢都必須包含的維度。比如業(yè)務(wù)上所有報(bào)表都離不開(kāi)日期那把日期設(shè)為強(qiáng)制維度Cuboid數(shù)量直接減半。層級(jí)維度像年-月-日這種天然有上下級(jí)關(guān)系的維度你只需要層級(jí)組合沒(méi)必要讓年和月單獨(dú)交叉。必要維度具體查詢時(shí)組合范圍比較受限的維度。類比一下這就像給數(shù)據(jù)蓋樓。不裁剪時(shí)相當(dāng)于給每個(gè)可能的家具擺放方式都預(yù)留一個(gè)房間樓自然蓋得又高又貴裁剪之后只給實(shí)際會(huì)住的戶型蓋房間成本直線下降。實(shí)操心得先小規(guī)模試跑。我通常會(huì)把維度控制在6到8個(gè)以內(nèi)跑一次構(gòu)建觀察Cuboid數(shù)量與存儲(chǔ)量再?zèng)Q定要不要加維度。不要第一次建Cube就把所有可能的維度全放進(jìn)去你看著維度列表很爽構(gòu)建Job能跑到你懷疑人生。3.4 維度表變化了怎么辦真實(shí)業(yè)務(wù)里維度表不是一成不變的城市表可能新增城市類目表可能調(diào)整層級(jí)。但Kylin的預(yù)計(jì)算結(jié)果在構(gòu)建時(shí)就已經(jīng)固定維度表變化并不會(huì)自動(dòng)更新到Cube里。所以建Model時(shí)盡量把維度字段做成“穩(wěn)定鏡像”。我建議的常規(guī)做法是在Hive里把維度表做成拉鏈表保留每個(gè)維度的歷史版本或者把需要Join的維度屬性直接冗余到事實(shí)表里比如把城市名冗余到事實(shí)表查詢時(shí)就不必每次都關(guān)聯(lián)維度表Cube構(gòu)建也更簡(jiǎn)單。這雖然不符合數(shù)據(jù)庫(kù)三范式但OLAP場(chǎng)景本來(lái)就大量使用星型模型和冗余字段用空間換查詢性能是刻意為之的選擇。4. SQL查詢與性能實(shí)測(cè)最讓人上癮的一刻4.1 用標(biāo)準(zhǔn)SQL直接查和查普通表沒(méi)區(qū)別Cube構(gòu)建完成后查詢方式非常簡(jiǎn)單。進(jìn)入Kylin的Insight頁(yè)面直接寫(xiě)標(biāo)準(zhǔn)ANSI SQL就行。比如我想看最近一個(gè)月、每天、每個(gè)城市的銷售額和訂單數(shù)SELECT dt, city_name, SUM(sale_amount) AS total_amount, COUNT(*) AS order_cnt FROM sim_sales_order_fact WHERE dt 2024-05-01 AND dt 2024-05-31 GROUP BY dt, city_name ORDER BY dt, city_name;Kylin會(huì)自動(dòng)把這條SQL和已經(jīng)構(gòu)建的Cube做匹配。只要SQL里的維度、度量在Cube范圍內(nèi)它就會(huì)命中預(yù)計(jì)算結(jié)果。如果SQL里出現(xiàn)Cube沒(méi)有的維度或度量Kylin會(huì)明確提示無(wú)法匹配而不會(huì)給你一個(gè)錯(cuò)的結(jié)果。這個(gè)“寧可不答也不答錯(cuò)”的特性讓我放心很多——對(duì)比一些其他方案查詢對(duì)不上模型時(shí)容易語(yǔ)義跑偏Kylin的約束反而清晰。它不僅支持Web界面查詢還支持標(biāo)準(zhǔn)JDBC和REST API。也就是說(shuō)你的報(bào)表系統(tǒng)、數(shù)據(jù)分析平臺(tái)都可以直接用JDBC連Kylin查詢體驗(yàn)和連MySQL簡(jiǎn)直沒(méi)差別這對(duì)BI系統(tǒng)的改造非常友好。4.2 實(shí)測(cè)感受同樣的SQL量級(jí)完全不一樣我在模擬項(xiàng)目X里造了大約五百萬(wàn)行訂單明細(xì)分別用Hive和Kylin跑了兩條完全相同的聚合SQL。Hive那條在YARN上用兩個(gè)Container跑了將近十分鐘才出結(jié)果而Kylin在Cube構(gòu)建完成之后冷查詢大概三秒左右返回?zé)狳c(diǎn)查詢基本一秒以內(nèi)。差距是數(shù)量級(jí)的。為什么差這么多因?yàn)镠ive每次都在掃描五百萬(wàn)行明細(xì)然后實(shí)時(shí)聚合Kylin在構(gòu)建Cube時(shí)已經(jīng)把這五百萬(wàn)行按照各維度組合算好查詢時(shí)讀的只是幾十萬(wàn)行甚至更少的預(yù)計(jì)算結(jié)果。數(shù)據(jù)量越大預(yù)計(jì)算的優(yōu)勢(shì)越明顯。不過(guò)這里也得潑一盆冷水Kylin快的前提是命中預(yù)計(jì)算結(jié)果。如果你查的是Cube里完全沒(méi)有的組合再寬的表也救不了你查詢速度會(huì)掉回Hive的水平甚至因?yàn)檫€要做匹配檢查而更尷尬。所以Kylin適合的是“查詢模式穩(wěn)定”的場(chǎng)景適合把它放在報(bào)表和固定分析鏈路上。4.3 通過(guò)REST API查了解一下底層對(duì)接方式除了Web界面Kylin的REST API也很關(guān)鍵因?yàn)樽罱K你可能會(huì)讓程序去調(diào)。curl -u ADMIN:KYLIN -H Content-Type: application/json -d { project: demo_sales, sql: SELECT dt, SUM(sale_amount) FROM sim_sales_order_fact WHERE dt \2024-05-01\ GROUP BY dt, limit: 20 } http://localhost:7070/kylin/api/query這個(gè)接口返回JSON結(jié)果做數(shù)據(jù)應(yīng)用的開(kāi)發(fā)者可以直接封裝成查詢服務(wù)。Kylin在API層面做的事情就是解析SQL、匹配Cube、取數(shù)、返回。你要注意這里傳入SQL里如果包含不想被外部任意執(zhí)行的邏輯服務(wù)端接口要做好權(quán)限校驗(yàn)不能直接暴露到公網(wǎng)。4.4 為什么“查詢不命中”比“查詢慢”更值得重視普通關(guān)系型數(shù)據(jù)庫(kù)里一條慢SQL最多是性能問(wèn)題Kylin里一條“不命中Cube”的SQL代表的可能是建模時(shí)漏掉了某個(gè)查詢維度或者是業(yè)務(wù)方突然提了一個(gè)從沒(méi)預(yù)見(jiàn)過(guò)的分析角度。這類情況往往不是調(diào)參數(shù)能解決的而是要回到Model或Cube層面去改模型、重新構(gòu)建。我在實(shí)際項(xiàng)目中就遇到過(guò)某創(chuàng)作者團(tuán)隊(duì)臨時(shí)想按“用戶地域”來(lái)分析轉(zhuǎn)化率但Cube設(shè)計(jì)時(shí)根本沒(méi)把用戶地域設(shè)計(jì)成維度于是查詢一直報(bào)無(wú)法匹配。后來(lái)處理辦法是重新建模新增維度字段再構(gòu)建增量Segment。所以Kylin的建模過(guò)程本質(zhì)上是數(shù)據(jù)分析需求的“前置評(píng)審”——能想到的查詢模式最好提前想到否則后續(xù)迭代成本不低。提示Kylin查詢里盡量不要寫(xiě)SELECT *。一是語(yǔ)義上可能沒(méi)法命中預(yù)聚合列二是返回大量明細(xì)字段沒(méi)有意義。按需取列既是SQL習(xí)慣也是讓Kylin高效工作的前提。5. 踩坑記錄與問(wèn)題排查實(shí)錄5.1 構(gòu)建Job失敗的四個(gè)高頻原因Cube構(gòu)建是最容易出問(wèn)題的環(huán)節(jié)我總結(jié)下來(lái)新手階段碰到的失敗大多可以歸到這四類。第一類是Hive表本身有問(wèn)題。比如事實(shí)表的字段類型和維度表的關(guān)聯(lián)字段不一致或者Hive分區(qū)字段的值格式和Cube里設(shè)置的分區(qū)時(shí)間格式對(duì)不上。這類報(bào)錯(cuò)一般在Job日志里會(huì)出現(xiàn)“ParseException”或“SemanticException”之類的關(guān)鍵詞排查時(shí)先看日志里能不能定位到具體SQL或表名。第二類是Spark資源不足。Kylin 4.x用Spark執(zhí)行Cuboid計(jì)算如果YARN隊(duì)列資源不夠或者Kylin傳給Spark的executor內(nèi)存配置過(guò)小Job會(huì)因?yàn)槿萜鞣磸?fù)被殺掉而失敗。我當(dāng)時(shí)遇到的典型表現(xiàn)是構(gòu)建進(jìn)度條卡在某個(gè)比例然后一直重試。解決辦法是調(diào)大kylin.engine.spark-conf.extra里的spark.executor.memoryProcess邏輯上可以類比一下Spark容器就像是工地上的吊車吊車太少或太小蓋Cube這棟樓自然蓋不動(dòng)。第三類是HDFS存儲(chǔ)空間不夠。Cube構(gòu)建中間結(jié)果、最終結(jié)果都在HDFS上幾百萬(wàn)行數(shù)據(jù)可能看不出什么但維度一多、Cuboid一多占用就是成倍增長(zhǎng)。空間不夠時(shí)構(gòu)建會(huì)直接中斷而且Kylin不一定能及時(shí)回收失敗Job產(chǎn)生的中間文件。我養(yǎng)成了一個(gè)習(xí)慣定期用hdfs dfs -du -h檢查Kylin工作目錄的大小手動(dòng)清理廢棄的中間目錄。第四類是權(quán)限問(wèn)題。Kylin運(yùn)行用戶對(duì)HDFS某些目錄沒(méi)有寫(xiě)權(quán)限或者Hive表對(duì)Kylin用戶沒(méi)有讀寫(xiě)權(quán)限這種問(wèn)題在部署在別的部門(mén)共享集群時(shí)特別常見(jiàn)。排查時(shí)直接切到Kylin啟動(dòng)用戶試一下訪問(wèn)HDFS和Hive表很多權(quán)限問(wèn)題立刻現(xiàn)出原形。5.2 查詢結(jié)果為空或者少數(shù)據(jù)排查思路順序很重要Cube構(gòu)建成功但查詢結(jié)果總比預(yù)期少這種bug特別讓人頭大。我排查的順序是這樣第一步看時(shí)間范圍。Kylin查詢?nèi)绻粠r(shí)間條件它只會(huì)在已有的Segment范圍內(nèi)取數(shù)。如果你構(gòu)建Cube時(shí)只構(gòu)建了5月1日到5月3日SQL查詢5月全月數(shù)據(jù)結(jié)果自然只有3天。這不算錯(cuò)誤但非常容易讓剛接觸的人產(chǎn)生“是不是數(shù)據(jù)丟了”的錯(cuò)覺(jué)。第二步看模型關(guān)聯(lián)。維度表和事實(shí)表的關(guān)聯(lián)字段如果設(shè)反了或者Join類型設(shè)成了內(nèi)連接而事實(shí)表里有些記錄在維度表里沒(méi)有對(duì)應(yīng)項(xiàng)那些記錄會(huì)被直接過(guò)濾掉。這種情況Hive里查得好好的Kylin查出來(lái)卻少了往往就是這個(gè)原因。第三步看度量類型。COUNT和SUM在Kylin里都有嚴(yán)格的聚合語(yǔ)義如果你在Cube里定義了COUNT(字段)而這個(gè)字段在部分記錄里是NULL統(tǒng)計(jì)結(jié)果就會(huì)有明顯差異。度量類型選錯(cuò)查詢結(jié)果看起來(lái)沒(méi)什么“報(bào)錯(cuò)”但數(shù)據(jù)語(yǔ)義實(shí)際上已經(jīng)錯(cuò)了。第四步看是否命中Cube。如果查詢結(jié)果能出來(lái)但響應(yīng)時(shí)間異常緩慢比如從秒級(jí)變成分鐘級(jí)多半是SQL沒(méi)有命中預(yù)計(jì)算結(jié)果Kylin內(nèi)部在走更曲折的執(zhí)行路徑。這時(shí)打開(kāi)查詢?nèi)罩敬_認(rèn)匹配的Cuboid是什么就能定位到底哪個(gè)環(huán)節(jié)斷了。5.3 一份快速參考的排查速查表現(xiàn)象可能原因快速解決手段Web界面打不開(kāi)Kylin未啟動(dòng)、端口被占、主機(jī)名解析錯(cuò)誤檢查7070端口查看啟動(dòng)日志修正/etc/hostsData Source里加載不出Hive表Hive連接配置錯(cuò)誤、Metastore未啟動(dòng)確認(rèn)hive-site.xml已放入conf目錄驗(yàn)證hive CLI能正常show tablesBuild任務(wù)一直失敗Spark資源不足、HDFS空間不夠、表字段類型不匹配看Job日志關(guān)鍵詞調(diào)整Spark參數(shù)清理HDFS檢查字段類型查詢結(jié)果為空未構(gòu)建Segment、時(shí)間范圍超界、模型關(guān)聯(lián)錯(cuò)誤檢查Cube segment列表調(diào)整查詢時(shí)間檢查模型關(guān)聯(lián)字段查詢速度突然變慢SQL未命中Cuboid可能在走非預(yù)期路徑查看查詢?nèi)罩緦?duì)比SQL維度和Cube維度集合結(jié)果多或少了記錄內(nèi)連接過(guò)濾、度量類型設(shè)置錯(cuò)誤、維度表有NULL值檢查模型Join條件核對(duì)度量類型處理NULL值邏輯5.4 幾個(gè)我后來(lái)才養(yǎng)成的調(diào)優(yōu)習(xí)慣建模維度不要貪多。統(tǒng)計(jì)下來(lái)維度從8個(gè)加到12個(gè)Cuboid數(shù)量往往不是線性增長(zhǎng)而是成倍增長(zhǎng)。分析需求不是特別豐富時(shí)寧可用多個(gè)Cube服務(wù)不同查詢場(chǎng)景也不要試圖用一個(gè)Cube包打天下。某開(kāi)發(fā)者曾經(jīng)把20個(gè)維度塞進(jìn)同一個(gè)Cube構(gòu)建了整整一個(gè)晚上最后存儲(chǔ)膨脹到幾十GB查詢卻并沒(méi)有快多少——那是一次非常深刻的教訓(xùn)。盡量壓縮基數(shù)高的維度。像用戶ID、訂單ID這種基數(shù)極高的字段做成維度不僅讓Cuboid爆炸還讓RowKey變長(zhǎng)、存儲(chǔ)變大。能不下沉到Kylin的就不下沉明細(xì)查詢的活交給其他引擎。反過(guò)來(lái)日期、城市、渠道這類基數(shù)適中、查詢化的字段非常適合做維度。定期合并Segment。增量構(gòu)建的Segment如果太多查詢時(shí)要掃的Segment列表也會(huì)變長(zhǎng)。Kylin有自動(dòng)合并策略比如把相鄰的小Segment合并成大Segment既能減少元數(shù)據(jù)量也能提升查詢性能。我一般會(huì)在Cube配置里設(shè)置“超過(guò)一定閾值自動(dòng)合并”省去手動(dòng)運(yùn)維的麻煩。心得Kylin的水很深但入門(mén)真的不難。它不像想象中那樣需要先把OLAP理論全部吃透才能動(dòng)手。最快的學(xué)習(xí)路徑就是搭一個(gè)最小環(huán)境造一點(diǎn)數(shù)據(jù)把一個(gè)Cube從零跑到能查詢?nèi)缓笤偃プ聊uboid裁剪和參數(shù)調(diào)優(yōu)。跑通一次之后你對(duì)“預(yù)計(jì)算”這三個(gè)字的理解比看十篇文章都深刻。最后再分享一個(gè)小技巧每次改完Cube配置先只構(gòu)建最近一兩天的數(shù)據(jù)來(lái)驗(yàn)證不要一上來(lái)就全量構(gòu)建。構(gòu)建快到讓你能看到結(jié)果時(shí)再放開(kāi)時(shí)間范圍。這個(gè)習(xí)慣讓我省下了大量等待和排查的時(shí)間你可以直接照著用。