Cube.js —— 数据可视化中的黏合剂

2,472 阅读4分钟

介绍

Cube.js用于构建BI看板或数据分析应用,为这些应用提供了从Database到页面展现的中间层,支持以API形式的SQL执行操作,从而实现看板看板的实时性。

架构

Cube.js包含了客户端与服务端两部分,客户端为前端开发者提供了数据调用API,该API将SQL操作转换为了schema,前端通过cube.js将schema传给服务端,再由服务端对该schema进行解析并转换为对应Database的SQL语句。

Cube.js的服务端也提供了基本的查询队列,缓存,数据聚合等功能。

Data Schema

Data Schema是查询语句到SQL语句的一种映射,是Cube.js前后端通信的主要数据结构。

它提供的动态查询能力可以在不改变Schema的情况下实现多样化的数据查询,同时实现了查询语句的复用,避免传统查询语句中需要书写重复语句问题。

Data Schema由Measures和Dimensions两个基本元素组成:

  • Measures指需要计算的数据,通常由其他数据计算得来,例如数量、占比等;
  • Dimensions指不需要计算的明确的数据,通常为直接从表中可以查询到的数据,例如ID、名字等。

示例

通过以下Data Schema我们可以获取到:

  • 用户的数量
  • 用户的所有城市
  • 用户的所有公司
cube(`Users`, {
  sql: `SELECT * FROM users`,

  measures: {
    count: {
      sql: `id`,
      type: `count`,
    },
  },

  dimensions: {
    city: {
      sql: `city`,
      type: `string`,
    },

    companyName: {
      sql: `company_name`,
      type: `string`,
    },
  },
});

当我们想获取正在支付的用户数量的占比时,使用SQL我们可以写出下面这样:

SELECT
  count(
    CASE WHEN (users.paying = 'true') THEN users.id END
  ) "users.paying_count"
FROM users

然而使用Data Schema我们就实现了之前数据的复用,可以直接通过之前已经获取的数据进行计算获得,如下面所示:

cube(`Users`, {
  measures: {
    count: {
      sql: `id`,
      type: `count`,
    },

    payingCount: {
      sql: `id`,
      type: `count`,
      filters: [{ sql: `${CUBE}.paying = 'true'` }],
    },

    payingPercentage: {
      sql: `100.0 * ${payingCount} / ${count}`,
      type: `number`,
      format: `percent`,
    },
  },
});

缓存

Cube.js提供二级缓存系统,第一级是内存数据库缓存,例如使用Redis进行缓存,第一级缓存是默认开启的。二级缓存使用的Pre-Aggregations功能,需要在Schema中指定配置才会开启。

Pre-Aggregations

从名称的含义中我们可以知道该功能提供了一种提前聚合数据的能力,该能力使我们在Schema中配置不同的过滤条件及时间频率,当满足条件时Cube.js便会自动对指定的数据进行聚合操作,从而在下次访问命中缓存时直接从内存中获取数据。

例如我们通过以下Schema来获取根据id和createdAt筛选出的订单数据:

cube(`Orders`, {
  sql: `SELECT * FROM public.orders`,

  measures: {
    count: {
      type: `count`,
      drillMembers: [id, createdAt],
    },
  },

  dimensions: {
    status: {
      sql: `status`,
      type: `string`,
    },

    id: {
      sql: `id`,
      type: `number`,
      primaryKey: true,
    },

    completedAt: {
      sql: `completed_at`,
      type: `time`,
    },
  },
});

由于筛选查询是一个耗时的操作,我们可以通过preAggregations来实现数据的的预聚合,下面的配置表示每个月会执行一次completedAt的聚合操作并进行缓存。

cube(`Orders`, {
  // Same content as before, but including the following:
  preAggregations: {
    ordersByCompletedAt: {
      measures: [count],
      timeDimension: completedAt,
      granularity: `month`,
    },
  },
});

鉴权

在Cube.js中,访问权限的处理与大部分服务端系统一致,由开发者的鉴权服务生成用于权限认证的JWT,然后使用该JWT形成的安全上下文来进行权限控制。

在一些底层存在统一的数据平台的场景中,虽然数据平台有自己的权限系统,但由于Cube.js中存在缓存,因此也需要实现Cube.js的权限控制!

API指南

Cube.js框架中封装了基于RESTful的API通信方式,该方式是前后端通信的主要方式。在使用过程中通常不需要手动去书写请求,而是通过Cube.js提供的核心库来发起,开发者只需要按照提供的数据结构配置请求参数即可。

Cube.js也提供了另外两种直接书写SQL的API方式,支持手动书写MySQL及GraphQL的方式来获取数据。

前端能力

Cube.js为前端开发者提供了查询数据的核心能力,支持HTTP和Socket两种数据获取方式。

Cube.js也针对于常用的前端框架封装了对应的数据获取组件,简化了开发者使用框架的门槛。

主要提供的npm库包含以下:

  • @cubejs-client/core
  • @cubejs-client/react
  • @cubejs-client/vue
  • @cubejs-client/ngx
  • @cubejs-client/ws-transport

部署

Cube.js支持Docker、Kubernetes、AWS Serverless、GCP Serverless、Cube Cloud几种部署方式,在每次部署线上环境时,需要依次检查以下几个方面:

  • 关闭开发模式
  • 设置缓存Worker
  • 配置缓存区
  • 配置Redis
  • 配置鉴权
  • 配置健康检查

在每一个部署的Cube.js集群中,主要包含API实例,缓存Worker、缓存区以及Redis四个模块。

开发者工具

Cube.js为开发者提供了两种开发方式,一种是通过cube-cli脚手架工具创建项目并手动编写代码,另一种方式是通过开发者后台可视化的进行数据查询的配置以及鉴权操作等。