需求背景
最近要开发一个数据集成平台,希望用户能快速对接自己的数据,经过数据管道处理后产生新的数据,然后配置到不同的图表上面。
对上面需求进行细化之后,得出功能需求如下:
- 配置一个数据源,支持配置不同类型的数据源,包括 mysql,excel,api 等。
- 构建针对这个数据流的处理管道,例如根据条件筛选行,增减列,关联系统表(系统的基础数据)。
- 基于新创的表配置不同的图表。
一个典型用例: 用户配置了一个mysql 的数据源,可以通过堡垒机访问内部数据。 系统定时从堡垒机全量或增量同步数据,并经过数据管道处理之后产生新的表,用户再配置图表。
非功能性需求:
- 易用性高。面向普通用户,要有足够的易用性,屏蔽技术细节。
- 拓展性高。数据存储要具有拓展性,前期使用 mysql,后面数据量大可能会使用 polarDb,oceanbase 等。
看上去需求有点像 etl + bi 看板,bi 看板的逻辑是类似的,但处理逻辑和 etl 会有差异。
和常见的 etl 工具的不同点
和 hadoop,spark,flink 的不同点: 此类是偏底层的大数据框架,面向的是开发人员。大数据平台面向的是数据量大,多表关联的场景。但集成平台的数据量小,用户的表也不会很多。用户需要可以拖拽的界面,而非编写复杂代码。
和 kettle,nifi 的不同点: 虽然 kettle 和 nifi 功能强大,有可视化界面,但还是面向技术人员。而集成平台面向的是一般用户,需要屏蔽很多的技术细节。例如对接之后用户不关心数据存储在什么地方。而且从数据源配置,数据处理,到指标编写,操作都要足够简单,因此 kettle 和 nifi 并不能直接拿来用。虽然可以对 nifi 进行封装,但开发成本也会比较高。
和简道云数据工厂的不同点: 和数据工厂其实是比较类似,面向的也是普通用户。简道云数据工厂的数据源来自简道云的表单数据,因此屏蔽了数据源的底层实现。处理过程也是比较简单,因此非常有参考意义。数据工厂和集成平台的定位一样,是整个系统的一个部分,而非完全独立的系统,因此会和自己的系统高度结合。
其实可以看到上面的工具应用场景不同, 其实属于不同层次的产品。
计算引擎的设计
对于整个系统来说分成2部分,一部分是计算引擎,一部分是存储引擎。存储引擎包含 mysql,polarDb,oceanbase 等关系型数据库,后续可能也要支持 Es,spark 等。计算引擎则是要开发的内容。计算引擎会从存储引擎拿到数据,经过数据管道,进行行过滤,列计算,关联系统表,聚合运算等,再存入存储引擎。
计算引擎有 2 种设计方式
独立的计算引擎
常见的计算引擎,例如 nifi,计算引擎独立于存储引擎,是一个个和存储无关的处理组件。要经过处理组件,必须先从数据源提取数据,数据被序列化后再传给处理组件,处理组件处理完再分发给下一个节点。这样的好处是处理组件和数据源完全是独立的,处理组件不关心数据源。
例如下面的需要从用户配置的成绩表和系统的学生表进行关联和筛选,最终得到 获取一年级成绩大于 90 的学生成绩。用户可能会配置2个节点
- 过滤成绩 >90 的分数
- 每个成绩根据学生id 关联学生信息
- 过滤出一年级学生
数据库数据提取是数据源节点,会从数据库全量查数据,再把数据传给节点1,节点1 处理完再把结果传给下个节点。
依赖存储引擎的计算引擎
独立的计算引擎缺点是每次都要先提取数据,无法利用存储引擎的计算能力。例如用户配置了一个行过滤,添加了一个筛选条件。按这个方案需要把数据全量提取处理出来,才能给计算引擎计算。而一般存储引擎也会有计算的能力。
例如上面的例子,如果过滤成绩 >90 这个节点是依赖底层 mysql 来进行,那么这个节点并不会做什么操作,而是作为 sql 和数据提取节点一起执行,本质上过滤的逻辑是由 mysql 来执行的,配合 mysql 的索引性能将会更好。
如果计算逻辑能进行优化,并下推到对应的存储引擎去进行计算,那效率会更好。例如上面获取学生信息的例子,经过计算引擎的优化,可以变成2个sql,分别到2个库里面执行 (应用库和系统库是隔离的)。处理结果和上面的逻辑等价,但因为都下推给了mysql 去执行,所以性能更好,而且节点不用做任何处理就能得到数据。
但这方案缺点体现在 3 个地方:
- 需要针对多种存储引擎抽象出同一种计算方式。比较通用的是标准 sql,但不是所有的存储引擎都支持 sql,例如旧版本es,api。
- 可以对sql 执行计划进行优化,尽可能把计算下推到存储引擎中。例如把 A关联B表之后再分别对 A 和 B 的数据进行多个 filter 过滤,可以优化成先对 A 和B 表进行过滤,再进行关联,和上面获取一年级成绩大于 90 的学生信息一样,可以先分别对两个表进行过滤,再关联来提升性能。
- 如果用 sql 表达计算逻辑,则对于复杂的计算场景 sql 无法胜任。例如行转列,字段拆行。对于无法用 sql 解决的计算,只能在当前节点得到全量数据,再进行计算。
而刚好 apache calcite 可以解决上面2个问题。
- calcite 通过内置适配器或自定义适配器可以接入不同的数据源,并统一通过 sql 的方式查询。
- 支持不同数据源在同一个 sql 进行查询。例如有 2 个不同地址的数据库的表,可以加到同一个命名空间,直接关联查询。底层会自动对数据进行关联合并.。
- 可以对执行计划进行优化,通过对表达式的关系计,得到最优的执行计划。
使用 calcite 后,处理节点变成了一个个sql 的组合。每个处理节点都基于前一个处理节点的sql 进行包装,输出新的sql。处理节点变成了一种对数据流的声明,最终执行 sql 的时候 calcite 会生成一个个真正的处理节点去处理数据。
方案难点
- 需要自定义基于 API 的适配器。因为部分系统表是基于 API 实现的,无法执行 sql。
- 虽然 calcite 有很多规则,会根据 cost 来选择代价最小的执行计划。但不一定会选择到最合适的。例如需要遍历一个10w 的表,如果是基于 limit 遍历,calcite 会默认查出所有数据,然后内存分页,导致性能非常差。因此需要自己进行优化。
总结
如果 sql 基本上能满足对数据的处理,那依赖存储引擎的计算引擎是一个不错的方案。但也因为强依赖 calcite,需要对 calcite 比较熟悉才能用好它。下篇文章将介绍 calcite 的设计。