Kylin, Mondrian, Saiku系統的整合

Kylin, Mondrian, Saiku系統的整合

19 APRIL 2016 on OLAPKylinSaikuMondrian

本文主要介紹有贊數據團隊爲了滿足在不同維度查看、分析重點指標的需求而搭建的OLAP分析工具。這個工具對Kylin、Mondrian以及Saiku做了一個整合,主要工作包括一些定製化的修改以及環境的配置。 目前這個系統還處於一個需要優化、完善的過程,這篇博文也會相應地更新。

背景

有贊發展的初期,數據團隊主要的工作之一就是根據運營人員的報表需求,編寫sql,從hive中獲得數據並寫入mysql中存儲。最後,前端人員寫相應的代碼展現mysql中存儲的報表數據。 隨着公司業務的快速發展,如此長週期的報表開發流程已經很難跟上運營人員的分析需求了。爲了避免深陷報表開發、維護的泥潭,數據組決定調研大數據場景下的OLAP分析工具。參考了明略數據的解決方案之後,我們選擇整合KylinMondrianSaiku來實現這樣一個OLAP系統。

三巨頭

Kylin

kylin是apache軟件基金會的頂級項目,一個開源的分佈式多維分析工具。下面是摘自Kylin官網的介紹:

Apache Kylin™ is an open source Distributed Analytics Engine designed to provide SQL interface and multi-dimensional analysis (OLAP) on Hadoop supporting extremely large datasets, original contributed from eBay Inc.

個人的理解是:Kylin通過預計算所有合理的維度組合下各個指標的值並把計算結果存儲到HBASE中的方式,大大提高分佈式多維分析的查詢效率。Kylin接收sql查詢語句作爲輸入,以查詢結果作爲輸出。通過預計算的方式,將在hive中可能需要幾分鐘的查詢響應時間下降到毫秒級。更細緻的關於Kylin的介紹,可以參考我的另一片博客Kylin初體驗

Mondrian

Mondrian is an Open Source Business Analytics engine that enables organizations of any size to give business users access to their data for interactive analysis. You can build powerful Business Intelligence solutions with Mondrian as your Online Analytical Processing (OLAP) engine, enabling multidimensional queries against your business data, using the powerful MDX query language.

Mondrian是一個OLAP分析的引擎,主要工作是根據事先配置好的schema,將輸入的多維分析語句MDX(Multidimensional Expressions )翻譯成目標數據庫/數據引擎的執行語言(比如SQL)。

Saiku

Saiku allows business users to explore complex data sources, using a familiar drag and drop interface and easy to understand business terminology, all within a browser. Select the data you are interested in, look at it from different perspectives, drill into the detail. Once you have your answer, save your results, share them, export them to Excel or PDF, all straight from the browser.

Saiku提供了一個多維分析的用戶操作界面,可以通過簡單拖拉拽的方式迅速生成報表。Saiku的主要工作是根據事先配置好的schema,將用戶的操作轉化成MDX語句提供給Mondrian引擎執行。

技術架構

架構圖

Kylin + Mondrian + Saiku是一個簡單的三層架構。git上開源的Saiku的項目已經整合了mondrian的jar包。所以構建這樣一個三層架構主要的工作是將Mondrian的schema和Kylin的schema對應起來,同時需要針對Kylin的語法對Mondrian做一些Kylin dialect的定製開發。 
Git上已經有一個整合Kylin,Mondrian以及Saiku的項目。照着這個項目的指引,可以很輕鬆的搭建這麼一個三層的系統。在此,致謝開源項目作者mustangore

一些細節

介紹完整體的結構,下面講一些構建過程中遇到的坑。有些可能是我們的理解還不夠深入,有些可能隨着開源軟件版本的升級已經不再是一個坑了。希望能給大家帶來一些幫助,如果是由於我們理解的偏差導致踩到的坑,也希望大家留言給出指正:) 本套系統構建基於kylin1.5, Mondrian4.4以及Saiku3.7.4。底層是Hive0.14以及Hbase0.98。

關於schema

前面提到,要讓系統運轉,Kylin的schema必須和mondrian的schema能夠對接上。Kylin是根據自身cube配置的schema來進行預計算的,schema決定Kylin能夠接收的sql查詢的範圍。Mondrian又根據自身的shema翻譯MDX到sql, Mondrian的schema決定它生成的sql的範圍。如果兩者有不一致的情況,就可能導致Mondrian生成的sql無法被Kylin執行。 kylin的schema配置比較簡單,管理頁面上有一套圖形界面指引你一步步地構建一個星型模型,配置di mension、measure。不過要把cube設計得高效,Kylin還是有不少高級地設置的,比如選擇 attribute group, derived dimension等。官網上有詳細的介紹。 
Mondrian的schema沒有比較好的圖形配置工具,需要手寫Mondrian schema的XML文檔,文檔格式參考官方文檔,通過Saiku上傳。 
需要注意的坑:

  • 不要用view作爲lookup table 在設計Kylin cube時,用hive view作爲fact table是一個比較好的實踐方式,可以屏蔽一些底層數據結構變化對Kylin cube的影響。但是不要用view作look up table,在build cube計算維度表容量時會出問題。
  • Kylin無法在預計算指標時制定條件 比如有兩個字段:orderpay, ispayed。我們可以配置sum(orderpay)作爲訂單金額, sum(ispayed)作爲付款訂單數。但是沒法配置sum(orderpay) where ispayed = 1來表示付款訂單金額。我們需要在fact view中添加字段payedorderpay表示付款的訂單金額。
  • 儘量在Kylin中用int類型 比如is_payed字段,就0/1兩個值,通常我們在hive裏可以設置爲tiny int類型的字段。但是在Kylin中,針對tiny int 和 int類型的字段配置出來的measure類型是不一樣的,tiny int 類型的字段得到的measure在和Saiku結合時可能會出現問題。
  • 把hive表放在default庫中 Kylin添加hive table的時候是可以指定hive table所在庫的,但是建議將fact table、lookup table都放在default庫中。因爲在Mondrian的schema中,physical table是默認去default庫查找的,目前還沒有發現很好的在Mondrian schema中指定數據庫的方式。

關於count distinct

Kylin配置cube的時候可以指定某個measure的聚合方式爲count distinct,有精準計算的方式也有基於hyperloglog算法的近似計算方式。同樣,在Mondrian的schema裏也可以配置count distinct的指標聚合方式。 
看上去一切都OK,然而問題來了: Kylin的count distinct語法只針用count distinct聚合的指標字段,在計算維度表大小的時候,kylin無法計算類似 select count(distinct date) from lu_date這樣的sql語句。在mustangore的項目中,對Mondrian打了Kylin-dialect的補丁。其中添加了一個JdbcDialect的實現:


public class KylinDialect extends JdbcDialectImpl {

    public static final JdbcDialectFactory FACTORY =
            new JdbcDialectFactory(KylinDialect.class, DatabaseProduct.KYLIN) {
                protected boolean acceptsConnection(Connection connection) {
                    return super.acceptsConnection(connection);
                }
            };

    /**
     * Creates a KylinDialect.
     *
     * @param connection Connection
     * @throws SQLException on error
     */
    public KylinDialect(Connection connection) throws SQLException {
        super(connection);
    }

    @Override
    public boolean allowsCountDistinct() {
        return false;
    }

    @Override
    public boolean allowsJoinOn() {
        return true;
    }
}

注意到:allowsCountDistinct()函數被設置成了return false; mustangore 通過這種方式避免了Mondrian計算維度大小的時候count disctinct,然而這種一杆子打死的方式也使得Mondrian計算count distinct的指標的時候出現問題:select count(distinct XXX) from tableA這樣的語句會被翻譯成select count YYY from (select distinct XXX as YYY from tableA),而Kylin又不能很好的執行後者。爲了解決這個兩難的問題,我們深入到Mondrian的源碼中去,找到了計算維度表大小的代碼:


 private static String generateColumnCardinalitySql(
        Dialect dialect,
        String schema,
        String table,
        String column)
    {
        final StringBuilder buf = new StringBuilder();
        String exprString = dialect.quoteIdentifier(column);
        if (dialect.allowsCountDistinct()) {
            // e.g. "select count(distinct product_id) from product"
            buf.append("select count(distinct ")
                .append(exprString)
                .append(") from ");
            dialect.quoteIdentifier(buf, schema, table);
            return buf.toString();
        } 
        else if (dialect.allowsFromQuery()) {
            // Some databases (e.g. Access) don't like 'count(distinct)',
            // so use, e.g., "select count(*) from (select distinct
            // product_id from product)"
            buf.append("select count(*) from (select distinct ")
                .append(exprString)
                .append(" from ");
            dialect.quoteIdentifier(buf, schema, table);
            buf.append(")");
            ...

注意到只有dialect.allowsCountDistinct()爲true時纔會用count distinct來計算維度表大小。 我們只要將Kylin dialect的allowsCountDistinct()設置爲true,同時在generateColumnCardinalitySql添加一個判斷條件:


  if (dialect.allowsCountDistinct()
            && !dialect.getDatabaseProduct().name().equalsIgnoreCase("KYLIN")) {
            ...

就可以實現和kylin的count distcint measure的正常對接了。

關於Kylin sql

有了處理count distinct的問題的經驗,我們發現,只要瞭解Kylin sql的特點,針對Kylin sql定製Mondrian 的Kylin—diect就能將Mondrian和kylin較好的對接。經過在Kylin1.5的交互界面中的測試,我們列出如下的區別:

  • 不能limit beg, end 只能limit length
  • 不支持 union, union all
  • 不支持 where exists 子句

結束語

以上是有贊數據團隊實現多維分析工具的探索過程。總的來說,Kylin + Saiku + Mondrian的一套流程是能走通的,中途遇到一些零碎的問題沒有完全列出來。通常是因爲Kylin只支持cube範圍內的查詢,如果Mondrian翻譯出的sql超出這個範圍就會引起系統的錯誤。通常有三種解決方案: - 重新構建Kylin cube,讓它能覆蓋更廣範圍的查詢 - 修改Mondrian schema,讓它的cube描述和Kylin cube吻合 - 定製化開發Mondrian的Kylin dialect,讓Mondrian生成符合Kylin特點的sql

目前我們還在對這套三層框架做一些定製化的功能開發以及性能的調優工作。希望這篇文章能給大家帶來些幫助,也希望有獨特見解或者發現我們的理解不對的朋友們留言交流。

知識共享許可協議 
如無特殊說明,本文版權歸 本文作者及有贊技術團隊 所有,採用 署名-非商業性使用 4.0 國際許可協議 進行許可。
轉載請註明:來自有贊技術團隊博客 http://tech.youzan.com/kylin-mondrian-saiku/

發佈了1 篇原創文章 · 獲贊 29 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章