日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

大圣魔方——美团点评酒旅BI报表工具平台开发实践

發布時間:2024/7/5 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 大圣魔方——美团点评酒旅BI报表工具平台开发实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

當前的互聯網數據倉庫系統里,數據中心往往存放了大量Cube化或者半Cube化的數據。如果需要將這些數據的內在關系體現出來,需要寫大量的程序和SQL來發現數據之間的內在規律,往往會造成用戶做非常多的重復性工作;而且由于沒有數據校驗的機制,還容易出錯,無法直觀查看各種數據(沒有可視化的UI圖表)。這時就急需一款基于Cube的報表工具快速為用戶提供報表服務,可以完成多維查詢、上卷、下鉆等各種功能。為此,美團點評酒旅技術團隊開發了大圣魔方。

一款好的BI報表工具,需要考慮并能夠解決如下問題:

  • 統一數據源
  • SQL生成
  • 跨數據源數據聚合
  • 自定義計算指標
  • 數據權限
  • 標準化UI組件,自助生成可視化報表

體系架構

圖1 大圣魔方體系架構

具體方案

1. 統一數據源

提供多數據源查詢服務,需要解決的問題主要是兩個:

  • 以什么樣的統一方式從數據源獲取數據。
  • 不是所有的數據源引擎都能提供OLAP服務和數據聚合的能力,我們需要從上層考慮,去實現數據的聚合、上卷、下鉆、切割、自定義計算等功能。
  • 圖2 大圣魔方多數據源

    大圣魔方上對能夠通過SQL查詢的數據源,例如MySQL和Kylin都通過統一SQL查詢來獲取數據;對于ES(Elasticsearch)采用ES提供的API來查詢;對于普通文本格式的數據采用自定義API從數據源獲取數據。

    如圖2所示,大圣魔方只是從數據源里面獲取基礎的數據,之后通過實現自己的計算引擎對數據進行聚合、切割等操作,對此,魔方中設置了四個引擎,用于實現不同的功能。

    2. SQL生成

    對于SQL的生成也存在兩個問題:

  • 不是所有的支持SQL的數據源,都支持標準的SQL,同時,支持標準SQL的數據源也會支持帶有自身特征的SQL。
  • 根據用戶選擇的條件、維度和指標,動態生成SQL的核心內容。
  • 針對第一個問題,我們對SQL模板進行了定義,當選擇不同的數據源時,根據數據源的Dialect選擇不同的SQL模板,而這就決定了SQL的組成部分(骨架)。

    為了解決第二個問題,我們在SQL模板的基礎之上做了內容填充和替換操作,選擇具體的維度、指標和篩選項的值,再填充到SQL模板的不同地方,最終就會生成能夠被數據源執行的SQL。

    在SQL生成的時候也考慮過其它的框架,如Apache Calcite Avatica、Alibaba的Druid,但是最終都放棄了,原因也是基于兩個方面:

  • 這些框架龐大且功能多,適用于我們場景的SQL生成的部分API使用起來過于復雜。
  • 大都是基于標準的ANSI SQL-92,很難個性化地生成我們所需要的SQL。
  • 最終,我們采用了SQL模板和字符串填充替換操作來完成。為此我們在Java的正則表達式基礎之上做了一個功能很多的字符串操作類庫。

    3. 跨數據源數據聚合

    一般情況下,同一個數據源的大部分數據源引擎都能夠支持多表的join操作,但是也存在不支持的,例如老版本的Kylin就不支持多Cube的join操作,還有一個更重要的問題是數據源引擎無法解決跨數據源的數據聚合問題,必須要自己實現數據的聚合操作,一般的情況下需要自己去實現inner join、left outer join和full outer join的邏輯。

    大圣魔方實現了inner join和left outer join兩個邏輯,因為full outer join的需求場景不是很多,所以沒有實現。下面是大圣魔方的實現代碼:

    inner join核心代碼

    private void join(List<Map<String,String>>[] contents,List<Project> sharedList,final int n,int[] rowsStatus,LinkedList<MatchRow> result){if(this.cubeJoin==1){throw new java.lang.IllegalArgumentException("left join call leftJoin method,not call join method");}if(n<contents.length){List<Map<String,String>> list = contents[n];for(int k=0;k<list.size();k++) {boolean equal = true;if(n!=0) {Map<String, String> prev = contents[n - 1].get(rowsStatus[n - 1]);Map<String, String> cur = list.get(k);for (Project proj : sharedList) {String key = proj.fieldName.toUpperCase();if (key.matches("^\\d+$") || key.equals("*")) {key = "_";}key = proj.isCompanion() ? key + proj.getFactId() : key;String prevValue = prev.get(key);String curValue = cur.get(key);if (prevValue == curValue) {continue;}if (prevValue == null || curValue == null || !prevValue.equals(curValue)) {equal = false;break;}}}if (equal) {rowsStatus[n] = k;if(n==contents.length-1){//last dataset matchMatchRow mr = new MatchRow();List<MatchRow.DatasetRow> tmp = new ArrayList<>();for(int i=0;i<rowsStatus.length;i++){MatchRow.DatasetRow dr = new MatchRow.DatasetRow();dr.setDatasetIndex(i);dr.setRowIndex(rowsStatus[i]);tmp.add(dr);}mr.addMatchRow(tmp);result.add(mr);}else{join(contents,sharedList,n+1,rowsStatus,result);}}}}}

    上述代碼就是通過回溯算法實現inner join的核心邏輯,具體解析如下:

    • contents參數表示每個數據源里面的結果集。
    • sharedList表示關聯的字段。
    • n和rowsStatus是回溯算法記錄狀態用的。
    • result里面包含的是符合join條件的記錄。
    • MatchRow里面記錄的是一個數據源里面的某一行與其余的數據源里面的那一行是相等的,記錄的是下標號。

    只有當sharedList里面的每個字段都相等的時候,兩條記錄才滿足inner join的條件。這個算法是一個通用算法,因為是通過回溯算法實現的,所以要join的數據源理論上可以有無限個。

    left outer join核心代碼

    private boolean leftJoin(List<Map<String,String>>[] contents,List<Project> sharedList,final int n,int[] rowsStatus,LinkedList<MatchRow> result){boolean leftJoinMatch = false;if(n<contents.length){List<Map<String,String>> list = contents[n];for(int k=0;k<list.size();k++) {boolean equal = true;if(n!=0) {//in left join,compare with the first dataset.Map<String, String> prev = contents[0].get(rowsStatus[0]);Map<String, String> cur = list.get(k);for (Project proj : sharedList) {String key = proj.fieldName.toUpperCase();if (key.matches("^\\d+$") || key.equals("*")) {key = "_";}key = proj.isCompanion() ? key + proj.getFactId() : key;String prevValue = prev.get(key);String curValue = cur.get(key);if (prevValue == curValue) {continue;}if (prevValue == null || curValue == null || !prevValue.equals(curValue)) {equal = false;break;}}}if (equal) {leftJoinMatch = true;rowsStatus[n] = k;if(n==contents.length-1){//last dataset matchMatchRow mr = new MatchRow();List<MatchRow.DatasetRow> tmp = new ArrayList<>();for(int i=0;i<rowsStatus.length;i++){MatchRow.DatasetRow dr = new MatchRow.DatasetRow();dr.setDatasetIndex(i);dr.setRowIndex(rowsStatus[i]);tmp.add(dr);}mr.addMatchRow(tmp);result.add(mr);}else{//if next dataset is not match,use the next's next...for(int loopFlag=n+1;loopFlag<rowsStatus.length;loopFlag++){boolean match = leftJoin(contents,sharedList,loopFlag,rowsStatus,result);if(match){break;}rowsStatus[loopFlag]=-1;if(loopFlag==contents.length-1){MatchRow mr = new MatchRow();List<MatchRow.DatasetRow> tmp = new ArrayList<>();for(int i=0;i<rowsStatus.length;i++){MatchRow.DatasetRow dr = new MatchRow.DatasetRow();dr.setDatasetIndex(i);dr.setRowIndex(rowsStatus[i]);tmp.add(dr);}mr.addMatchRow(tmp);result.add(mr);}}}}}}return leftJoinMatch;}

    上面的代碼是left outer join的實現邏輯,同樣也是用的回溯算法,它與inner join有2個不同之處:

  • left outer join的數據源匹配邏輯是當第一個數據源與第二個數據源沒有匹配的時候,會繼續與第三個數據源進行匹配操作,原因是數據源的順序導致了不匹配,繼續往下匹配就可以避免這個問題。
  • 行與行做相等操作的時候,右邊沒有匹配行的時候,左邊的行繼續保留,這個是left outer join的邏輯決定的。
  • 4. 自定義計算指標

    使用自定義計算的原因,主要是基于下面的兩個方面:

  • 數據源引擎不支持數據的混合運算或有特殊邏輯的數據處理。
  • 結果數據跨數據源。
  • 對此,我們對大圣魔方做了如下操作:

  • 通過Java里面的ScriptEngine進行封裝來實現數據列的混合運算,不需要自己再去寫編譯程序解析。對于特殊的數據處理,例如同環比這樣的特殊指標,需要單獨定義接口,讓實現類繼承改特定接口,實現類是一個特殊的指標,它需要進行多次數據查詢,將最終的結果通過ScriptEngine進行運算。
  • 第二個問題是在上文中“跨數據源數據聚合”的基礎上實現的,數據聚合后通過ScriptEngine做最后的處理。
  • 5. 數據權限的問題

    只要是有數據展示,數據權限問題就無法避免,權限主要是分為報表的可查看權限和維度、指標權限。權限遇到的最主要的問題是構成權限矩陣的數據量太大,參與者有用戶和組織,權限的實體有維度和指標,這樣大的數據維護起來的成本很高;其次是權限數據配置會占用很多的人力。

    對此,我們做了如下操作:

  • 使用UPM控制報表的可查看權限。公司推薦使用UPM來控制權限,不過UPM也具有一定的局限性,即只能夠判斷用戶或者組織是否滿足某個權限,而不能滿足獲取部分權限數據的需求,例如某個用戶對某個權限只擁有一部分權限,那他就無法提供具體數據。但是UPM可以提供是否的權限,所以報表的可查看權限可以使用UPM來控制,這樣可以節約大量的工作。
  • 使用默認任何人都有權限的機制。通過使用默認有權限的這個機制可以大大減少權限數據。需要鑒權的那些維度和指標采用默認無權限的機制,這樣兩種方案結合,可以最大限度地減少權限數據。
  • 通過走審批流機制自助申請。通過審批流機制可以讓用戶走自助申請,大大節約權限數據的維護人力成本。
  • 6. 標準化UI組件,自助生成可視化報表

    報表上展示數據需要有各種各樣的圖表,沒法為用戶只做一個統一的報表,這個時候需要用戶能夠創建自己想要的報表,這時需要提供一個標準的組件庫、布局庫和一些常用的模板。用戶選擇好想要的模板,然后選擇布局對報表頁面進行布局,接著在每個布局里面填充不同的組件,這樣就可以構建一張報表了,也就是我們常說的所見即所得的方式。

    大圣魔方就是采用上述的機制提供了一套可視化報表編輯工具。使用它可以快速地創建一個報表,管理人員只需要維護對應的組件、布局和模板就行了。

    上述幾點就是大圣魔方的一個總綱,其中大部分功能已經實現了,還有一小部分處于開發之中(標準化UI組件、自助生成可視化報表)。目前大圣魔方已經上線將近一年了,支持了內部眾多業務,后續我們還會在UI易用性、星型模型、配置簡化、元數據同步等方面做一些提高。

    最后插播一個招聘廣告,有對BI工具開發感興趣的可以發郵件給 fuyishan@meituan.com

    總結

    以上是生活随笔為你收集整理的大圣魔方——美团点评酒旅BI报表工具平台开发实践的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。