介绍
CubeJs是一个用于构建分析web应用程序的开源框架,主要用于构建内部的商业智能工具或将面向客户的分析添加到现有的应用程序当中。
Cubejs可分为前端和后端
通常,Cube.js的后端作为服务运行,管理与数据库的连接,包括查询队列,缓存,预聚合等。同时为前端应用程序公开一个API,用于构建仪表板和其他分析功能。
简单实现
单个数据库的查询
数据库连接
方式一:环境变量中配置(.env)
1 2 3 4 5 6
| CUBEJS_DB_HOST=<YOUR_DB_HOST_HERE> CUBEJS_DB_NAME=<YOUR_DB_NAME_HERE> CUBEJS_DB_USER=<YOUR_DB_USER_HERE> CUBEJS_DB_PASS=<YOUR_DB_PASS_HERE> CUBEJS_DB_TYPE=postgres CUBEJS_API_SECRET=secret
|
方式二:配置文件中配置(cube.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // Cube.js configuration options: https://cube.dev/docs/config const PostgresDriver = require('@cubejs-backend/postgres-driver'); module.exports = { // 数据库类型 dbType: 'postgres', driverFactory: () => { return new PostgresDriver({ database: 'you_database', host: 'you_host', user: 'you_user', password: 'your_password', port: 5432 }); } };
|
Cube.js文件优先级别高于.env
根据不同数据库所需要的配置项会有所不同,上面展示的是PostgresSQL
配置项路径:https://cube.dev/docs/connecting-to-the-database
后端配置
编写schema
表的基本信息、表与表之间的联系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| cube(`Comment`, { sql: `SELECT * FROM public."Comment"`, joins: { }, measures: { count: { type: `count` } }, dimensions: { id: { sql: `id`, type: `number`, primaryKey: true }, content: { sql: `content`, type: `string` } }, dataSource: `database1` });
|
- Dimensions(维度)
维度确定如何对可视化内容数据分组.
通常呈现在条形图的 X 轴上或饼图的切片上,例如时间、区域、产品类型等。
- Measures(度量)
度量是在可视化中使用的计算,结果为具体的参考数值.
通常呈现在条形图的 Y 轴上或表格的列中。
度量通过由聚合函数(例如 Sum 或 Max)组成的与一个或多个字段组合的表达式创建,例如蒸发量、降水量、销售额等。
- joins
编写表之间的关系(同数据库是用列连接而跨库则是dimension连接)
- dataSource
所属的数据库
- extends
1 2 3 4 5
| cube(`VipUser`, { extends: Login, sql: `select * from ${Login.sql()} where type = 1` });
|
前端查询
cubejs中有自己的API模块进行查询,下面这个是 core模块中的load方法。vue,react,ngx也有自己的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const cubejsApi = cubejs( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MjEzMzc5NjcsImV4cCI6MTYyMTQyNDM2N30.JBwrY8vaKPav4RKD5iFiSSNnV1PFpAcbSVO7ME7pP3I', { apiUrl: 'http://localhost:4000/cubejs-api/v1' } ); const resultSet = await cubejsApi.load({ measures: ['Login.count'], timeDimensions: [ { dimension: 'Login.date', granularity: 'day', dateRange: [ "2021-05-11", "2021-06-15" ] } ], order: { }, filters: [ { member: "Login.price", operator: "gt", values: [ "2" ] } ], dimensions: ['Login.type'], limit: 50 });
|
使用传统的方式也可以进行查询,只不过是将load传入的对象放在了一个固定的query字段里面(get方法的话是将数据放在params内,而post方法则是将数据放在body内。以及别忘记配置token了。)
复杂实现跨数据库查询
使用预聚合来进行多个数据库之间的查询。
多数据库连接配置
由于多个数据库需要获取schema中dataSource的值,所以只能用数据库连接方式二进行配置
数据库一: postgres数据库 database: database1
数据库二: postgres数据库 database: database2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const PostgresDriver = require('@cubejs-backend/postgres-driver'); const driverInfo = { database1: { database: 'database1', host: 'test.host.com', user: 'postgres', password: '1234567', port: 5432 }, database2: { database: 'database2', host: 'test.host.com', user: 'postgres', password: '1234567', port: 5432 } } module.exports = { dbType: 'postgres', driverFactory: ({ dataSource } = {}) => { const source = dataSource === 'default' ? 'database1' : dataSource const driver = driverInfo[souce] return new PostgresDriver(driver); } };
|
不同类型数据库也可以进行连接
预聚合
预聚合是由Cube.js构建和刷新的一层聚合数据,它可以显著提高查询性能并提供更高的并发性。
使用预聚合查询时,他会物化查询的结果,再以表的形式将这些信息进行持久保存。
Cube.js能够根据一组定义的预聚合规则分析查询,以便选择用于创建预聚合表的最佳规则。
如果Cube.js找到合适的预聚合规则,则数据库查询将成为一个多阶段过程:
- Cube.js检查是否存在预聚合的最新副本。
- Cube.js将针对预聚合的表而不是原始数据执行查询。
后端配置
后端需要配置外部数据库,以及在schema文件内编写预聚合信息
外部数据库配置
外部数据库 postgresSQL database: database3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| module.exports = { .... preAggregationsSchema: `dev_pre_aggregations`, externalDbType: 'postgres', externalDriverFactory: ({ dataSource } = {}) => { return new PostgresDriver({ database: 'database3', host: 'test.host.com', user: 'postgres', password: '1234567', port: 5432 }); }, }
|
编写预聚合
现在有两张表, database1数据库的Login表 和database2数据库中的users表
users表内的token 跟Login内的token进行对应(一对多)
我们现在根据users表内的 token 来获取Login所对应列的数量
users表
1 2 3 4 5 6 7 8 9 10 11 12
| cube(`Users`, { sql: `SELECT * FROM public.users`, ... preAggregations: { usersRollup: { type: `rollup`, dimensionReferences: [Users.token], external: true, }, }, dataSource: `database2` })
|
login表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| cube(`Login`,{ ... joins: { Users: { relationship: `belongsTo`, sql: `${CUBE.token} = ${Users.token}` }, }, preAggregations: { loginRollup: { type: `rollup`, measureReferences: [Login.count], dimensionReferences: [Login.token], refreshKey: { every: '5 minute' } external: true, }, joinedWithUsersRollup: { type: `rollupJoin`, measureReferences: [Login.count], dimensionReferences: [Users.token], rollupReferences: [Users.usersRollup, Login.loginRollup], external: true, }, }, dataSource: `database1`, })
|
前端传参 { measures: [‘Login.count’],dimensions: [‘Users.token’]}
拓展延伸
数据过多,导致总刷新时间过长可以使用partitionGranularity 属性来对时间属性来进行预聚合分区,这样可以大大减少汇总刷新时间。在此前提下,还可以使用增量刷新(只固定时间刷新所选的时间分区(过去一周,过去一月..),而其他时间分区数据则不会改变)。
更多资料可以查看
cubejs官网:https://cube.dev/docs/
总结
CubeJs的使用方式与GraphQL有些相似,后端构建表的基本信息,前端根据这些信息来获取所需的数据。不同的是CubeJs可操作多个数据库也可以跨数据库查询,且对于SQL语句查询更加完备。
CubeJs虽然具有动态生成Schema与仪表盘的功能,但过于简单,显得有点鸡肋。
CubeJs数据获取的底层是SQL语句,没有产生多余的字段,加之预聚合的使用使其查询速度快捷。