技術驅動:業務大盤數據可視化技術探索

著做權歸做者全部。商業轉載請聯繫 Scott 得到受權,非商業轉載請註明出處[務必保留全文,勿作刪減]。

數據的理解成本和決策價值會經歷結果可見、趨勢可見、過程可見,實際就是不斷把真相顯露水面的過程,越友好的數據展現方式,越低的決策成本越短的決策週期,這就是數據可視化的價值。javascript

在小菜,可視化的命題不只僅是把數據圖表化,而是實現不少數據的批量圖表化,或者說是業務數據可視化的量產,不是三五個而是幾十個,若是咱們用傳統的服務端 API 來對接幾十個大盤裏面共幾百個圖表的特定格式數據,那麼先後端基本上能夠鎖定好幾個工程師長期 base 在上面了,任何圖表展現維度或者格式發生改變,API 要隨之而變,這對於一個崇尚效率的技術團隊無異於一場災難。前端

量產數據可視化的背景

下圖是一個 Demo 階段的大盤需求彙總,這仍是很是粗顆粒度的目標,它們的每個底下都有更精細化的業務大盤,以及伴隨大盤的年月週日這種時間線上的過程疊加,這是一個既有跳轉深度也有豐富維度和數量的可視化產品。java

image.png

咱們再回到上一篇,溫習下數據的幾個階段(階段間並不是線性前後順序,每每是並行不斷迭代),當幾百張報表均可以批量生產後,從管理和決策的視角就要關注更多變化性的趨勢而不只僅是變更的數字,這個階段也就是數據聚合和數據大盤趨勢可視化的這兩個階段之和,前者關注結果後者關注過程。node

image.png

上文的報表量產咱們解決了,接下來就是可視化的量產,這兩個恰好是底層和上層堆疊的關係,本文咱們探討下如何實現基於量產化後報表的可視化的量產。python

再開始聊技術實現以前,先看下咱們的一些可視化的編輯界面和一些展現形式,編輯過程經過在線切換 Tab 的 Excel 快速輸出計算規則和公式,能夠定製展現形式和展現順序:linux

image.png

以及一些數據查詢條件,包括 Excel 模板的導入等等:git

image.png

在這些界面上,結合 Excel 模板,就能夠快速把一些數據格式化出來做展現,好比一些和完成指標掛鉤(能夠運營手動錄入)完成進度按照必定維度聚合配置的卡片圖:github

image.png

這些卡片就是結果可見,還有曲線圖餅狀圖之類的趨勢、過程和組成可見:面試

image.png

技術實現原理簡介

咱們已知,目前小菜的報表系統是用 Node + GraphQL + SQL 拼裝出來的一個工具界面輸出 SQL,SQL 到數據庫再到 Table 的配置與渲染工具,而報表系統之上的可視化,目前是藉助 Excel 完成數據統計與數據存儲的一個實驗性系統(將來不排除摒棄 Excel 文件),整個可視化依然是跑在整個報表項目中,除了 Node 處理業務邏輯外(提供給客戶端 GraphQL 接口),服務端還涉及 C#(Excel 數據的重算)的使用、Python(數據的獲取、過濾、分組等預處理工做),在客戶端則是使用 Antd Design 的 Charts 組件與 Bizchart(將來可能會換成 D3)。算法

整個技術方案中,可視化的製做流程能夠分爲下面幾步:

  • 管理員製做 Excel 模版和配置腳本
  • 定時任務執行腳本,更新緩存數據
  • 前端獲取數據用以圖表展現

流程很是簡單,從數據庫到數據再到展現,他們背後的技術棧以下圖,其中 grpc 則是在 Egg 與 Python 預處理任務之間的數據流通訊:
屏幕快照 2019-04-07 下午10.58.02.png
接下來咱們把技術棧上的單點概念和實現原理來捋一下。

數據可視化實現過程

Excel 模版

這裏主要說明一下爲何使用 Excel,以及遇到的一些問題,如今有一個場景,一個運營須要統計每日各個城市的 GMV,而目前有這麼一張全國 GMV 的明細表,以及一張城市分組的表,那麼這個運營只要作好一張 Excel 的統計模版(經過 Excel 公式統計各個城市的 GMV),天天這個運營都須要將新的 GMV 數據以及城市的數據複製在模版之中,那麼 Excel 會自動計算新的統計數據。可視化系統最初的構思就是將這個過程腳本化。因此先使用 Excel 做爲底層的數據處理的方式做爲實驗方式,先跑跑看。另外當初在設計這個系統的時候是但願這是一個通用的解決方案(這裏的通用是指能夠應用在以前作的一個量產表報表系統的任意一張或者多張報表上面,再解釋一下就是以前作了一個報表系統能夠生產不少報表,如今作一個系統可讓裏面全部的報表均可以變成可視化報表)。

業務數據的定製

因爲設計通用解決方案,讓人頭疼的天然就是不少報表的統計邏輯都有着本身的業務邏輯在裏面。舉例就是業務上有可能今天的目標值是1000,明天可能要改爲了800,這樣常常變更的數據,又或者一些來源與第三方的統計數據且暫時沒有進公司的系統之中可是統計時卻又要用獲得的。

像這樣須要變更而且數據源不明確的數據我稱爲業務定製數據,爲了減小開發量,這樣的數據源的被划進 Excel 模版數據源裏面,再經過提供一個 Web 版 Excel,讓管理員(通常都是BI 或者運營)來隨時更新數據到 Excel 模版之中。

數據量增大問題

顯而易見,隨着數據量的增大,Excel 文件也會愈來愈大,腳本的執行速度也會愈來愈慢,模版以 Excel 文件的形式物理保存在服務器上也不安全,容易會出現被刪除等狀況。

數據的預處理

主要使用 Python 中的 Pandas 以及 NumPy 來獲取、處理數據,最後寫入對應的 Excel 工做表之中。數據預處理目前只有數據分組以及數據過濾,以及一些特定圖表須要的數據的計算(如和絃圖),選用 Python 是考慮到更快實現,也就是前文技術棧落地的文章中咱們秉承的觀點 - 最小成本預研落地,這裏用 python 開發成本更低(實際是把 python 看成 matlab 來用)。

數據分組

在這個可視化系統中,咱們把報表的數據作了一個分類,分爲了

  • 索引(通常爲日期)
  • 分類數據(城市,品類等)
  • 其餘(把索引看作自變量的時候,這些通常看作因變量)

屏幕快照 2019-04-12 下午7.24.39.png

通常狀況下,圖上的銷售日期爲索引,銷售地區、銷售人員和品名爲分類數據數量、單價和銷售金額爲因變量,較多的時候須要統計的都是因變量,每日的數量單價銷售金額等。

因此當咱們接到需求要看銷售維度或銷售城市維度銷售金額的總額以及總體總額的時候,只須要在 Excel 中新建一張透視圖就能夠完成任務,而後再經過設置日期索引,拉取對應日期範圍的數據寫入 Excel,最後讓這個 Excel 重算一下讀取新的數據就能夠了。

屏幕快照 2019-04-12 下午7.43.35.png

屏幕快照 2019-04-12 下午7.49.08.png

爲了完成這個任務,咱們須要

  • Web 提供配置輸入(索引列選擇等)
  • Python 拉去數據寫入 Excel
  • C# 重算 Excel,返回新的數據

Web 配置

因爲咱們通常都是拉取某個日期範圍的數據,因此首要的須要配置的是數據源的索引列,再提供一個系統預設的日期選項能夠抽象爲這樣

sysDateRange<近|本, N, DateUnit>

DateRange<sysDateRange<近,1,月>>

表現出來這樣
屏幕快照 2019-04-13 下午2.22.47.png

根據這個預設每次拉取對應的數據再寫入對應的源表之中就能夠了,若是須要複製的需求的時候,如須要對數據去重等 Excel 公式很難完成的任務時,就要提到 Pandas 了。Pandas 是 Python 上的一個開源的數據分析包,專門作各類數據分析與預處理。日常很難的算法,用 Pandas 幾行就能夠實現。這裏列舉幾個經常使用的操做:

好比須要過濾出指定日期範圍的數據的話,那麼用 Pandas 一行就能夠了:

(df['銷售日期'] > start_date) & (df['銷售日期'] <= end_date)

或者對上述表格須要對品類去重,統計品類數,能夠這樣作:

new_source = source.drop_duplicates(subset='品名')

假如是再複雜點對多個條件進行去重,好比須要每日的品類數,也是一行代碼搞定:

new_source = source.drop_duplicates(subset=['品名', '銷售日期'])

再回到 Web 端,只要提供對應的配置表單便可。如前面提到的日期過濾條件表單,而且把這樣的配置應用到每一個工做表之中去,就能夠在一個工做簿裏面寫入各類不一樣維度的工做代表細了,這樣整個數據預處理過程就搞定了。

重算 Excel

所謂的重算 Excel,就是讀取預處理以後的 Excel,而且計算裏面 Excel 公式,獲得新的公式結果。這裏咱們主要是用 C# 裏面的 EEPlus 來完成,可能你們會疑問,爲何須要用一個新的語言來作這件事?

Python 或者 Node 不行嗎?這是由於咱們的服務器系統是 linux 系統,假如是 Window Server 的話,Python 或者 Node 也確實能夠找到可重算 Excel 的庫,咱們還嘗試過 Java 的 POI,雖然可用,可是跑不了太大的數據,而且還很慢,因此就 ban 了,剩下的就只有 C# 了,畢竟都是微軟自家的,支持程度比其餘的好不少。

Nuget 上能夠實現重算的庫又不少,NPOI 、Microsoft.Office.Interop.Excel 、和 EPPlus 等。試用下來都有一些 Bug,如 NPOI 對 SUMIFS 公式的支持就不是很好。這三者的速度比較的話,咱們小小測試了一下,結果是 NPOI > Microsoft.Office.Interop.Excel > EPPlus ,可是最後選的是 EEPlus,由於看中的就是他提供了自定義函數的這個 API,能夠定製本身的 Excel 公式函數,如在 Excel 2019 實驗的 Filter 函數,而且還能讓我在發現其餘函數的時候臨時複寫(不用 PR 到 git 上)。

再回到前面的重算,用 C# 也是隻要一行就好:

package.Workbook.Calculate();

是否是很是的簡單!完成接口的代碼差很少也就這麼幾十行:

String[] arguments = GetCommandLineArgs();
string filePath = args[0];
string outputPath = args[1];

FileInfo src = new FileInfo(filePath);
FileInfo dest = new FileInfo(outputPath);

if (dest.Exists)
{
  dest.Delete();
}
File.Copy(src.FullName, dest.FullName);
ExcelPackage package = new ExcelPackage(new FileInfo(dest.FullName));

if (package != null)
{
  package.Workbook.FormulaParserManager.AddOrReplaceFunction("sumifs", new MySumIfs());
  package.Workbook.Calculate();

  package.SaveAs(dest);
}

重算以後把工做簿裏面每張工做表裏面的數據緩存爲各個 LowDB 的 DB,Egg  通知每一個 Worker 去緩存新的數據。

前端組件

Antd Pro 組件

Ant Design Pro 給咱們提供很好的開箱即用的可視化模版組件,如圖:

image.png

像這樣的卡片組件,便可以一眼看到統計數據,又能夠大體的看出數據的增加趨勢,通用性很強。要作的只是提供一個對應的後臺配置,去對應的 LowDB 緩存裏面的對於的數據就好了。

複雜圖表

爲了很好的暫時頁面的數據埋點數據以及 PV 流量的流向,須要用到和絃圖,這種複雜的圖表須要把明細數據集轉化成一個 n * n 的二維數據表:

image.png
一開始咱們嘗試把這樣的算法放到前端,後來隨着數據量的增大,發現 JS 很難完成需求了(雖然能夠分批計算,增量統計,不過實現起來繁瑣一些),因此就把這樣的圖表定製算法切到 Python 服務那邊去。

訂閱服務

這個功能就是釘釘推送,主要是提供一個訂閱功能,讓用戶能夠訂閱對應的統計卡片數據(圖表在開發中),天天特定的時間經過釘釘,對該用戶推送卡片數據,屬因而愛心關懷的貼心功能,實現很簡單,服務端使用的是 Egg,安裝 egg-dingtalk 插件,按照文檔配置:

exports.dingtalk = {
  corpid: '',
  corpsecret: '',
  host: '',
  enableContextLogger: '',
};

再按照 node-dingtalk 調用就能夠了:

message.send({ touser, toparty, msgtype, ... })

關於圖表圖片,咱們目前的想法是借用 quickchart 來實現。

跨語言微服務

咱們使用了 Python 和 C# 來提供微服務,那麼 Egg 和這兩個微服務之間的通訊就是要解決的問題,這裏選用的是 gRPC

gRPC 一開始由 google 開發,是一款語言中立、平臺中立、開源的遠程過程調用(RPC)系統。

gRPC 對比其餘 RPC 系統最大的優點就是他是跨語言的,基本咱們須要的它都覆蓋了:

屏幕快照 2019-04-13 下午4.04.54.png

經過一個 .proto 文件,描述出每一個服務的下可調用的 API 以及該 API 的入參類型以及響應數據類型,再根據每一個語言不一樣的工具把這個 .proto,文件轉化成對應語言所需的文件,這裏用一個簡單的 .proto 爲例

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// SayHello 方法的入參類型, 這裏的意思就是入參數的字段名爲 name, 類型爲 string,這個字段的標識符爲 1
message HelloRequest {
  string name = 1;
}

// 和入參的邏輯同樣
message HelloReply {
  string message = 1;
}

// 服務描述,聲明又一個類名爲 greeter 的服務,該類提供 SayHello 與 SayHelloAgain 兩個方法
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  rpc SayHelloAgain(HelloRequest) returns (HelloReply) {}
}

Egg 以及提供可 egg-grpc 插件,咱們只要把對應的 proto 文件放如指定的文件夾之中就能夠了。比較須要注意的是該插件的默認配置會把類名以及方法換成首字母小寫:

const { ctx } = this;
const client = ctx.grpc.helloworld.greeter;

const result = await client.sayHello({ name: 'Gamelife' });

Python 須要使用命令行來生成對應的服務文件:

python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/helloworld.proto

C# 須要在工程描述文件 .csproj 裏面加入 .proto 文件的路徑:

<ItemGroup>
    <Protobuf Include="../../../app/proto/helloworld.proto" Link="helloworld.proto" />
</ItemGroup>

而後在代碼中就能夠直接做爲 nameplace 來使用了:

using System;
using System.Threading.Tasks;
using Grpc.Core;
using Helloworld;

namespace GreeterServer
{
    class GreeterImpl : Greeter.GreeterBase
    {
        public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
        }
    }

    class Program
    {
        const int Port = 50052;

        public static void Main(string[] args)
        {
            Server server = new Server
            {
                Services = { Greeter.BindService(new GreeterImpl()) },
                Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
            };
            server.Start();

            Console.WriteLine("Greeter server listening on port " + Port);
            Console.WriteLine("Press any key to stop the server...");
            Console.ReadKey();

            server.ShutdownAsync().Wait();
        }
    }
}

總結

結合上一姐報表量化的技術方案,咱們能夠把流程整理以下,首先是普通報表展現流程圖:

d1.jpg
其次是可視化報表展現流程:
d2.jpg

它的缺點很明顯,node,python,c# 這幾個服務都耦合在一塊,python 在處理數據時候容易暫用大量內存,使整個服務掛掉,目前進一步用 gRpc 協議將不一樣的服務拆分開來, python 也部署到另一臺機器上,但仍須要一個更大的優化重構才能 handle 更多的應用場景和需求。

咱們稍微總結下,目前實現的方案是在大表哥這個量產報表的系統上,再額外提供 Web 配置,利用 Pandas 來預處理數據, EEPlus 重算公式(解決定製需求),而後緩存數據展現到前端,整個底層實現後,只要會 Excel 計算公式的運營,就能夠根據一些寬表來定製本身業務線的數據看板了,尤爲是 BI 的同窗,在咱們的規劃裏,會讓這裏的製做成本愈來愈低,同時後期當預處理模塊的功能逐漸完善以後,咱們會但願替換掉 Excel 公式的做用,換一種更輕量級的方案來作。

Scott 近年面試或線下線上技術分享,遇到太多前端同窗,因爲團隊緣由/我的緣由/職業成長/技術與管理通道,甚至家庭城市等等緣由,在理想國與現實之間,在放棄與堅守之間,搖擺不停,心酸硬扛,你們能夠找我聊聊南聊聊北,對工程師的宿命和價值有更多的看見與瞭解,Scott 微信: codingdream,也能夠來 關注 Scott 跟進個人動態

2.png
1.png

相關文章
相關標籤/搜索