自开源以来,Tapdata 吸引了越来越多开发者的关注。在和社区成员讨论共创的过程中,我们也意识到在基础教程之外,补充更多原理解析的重要性和必要性。为了辅助开发者更好地理解 Tapdata Community 的技术逻辑,真正实现快速理解、深度参与,我们特别增加了 Tapdata 功能特性及原理解读教程。 本期主题为「异构数据库的模型推演」,核心内容包括::
- 异构数据库模型推演关键名词解释
- 异构数据库模型推演核心原理解析
- 模型推演的可维护性保障
01
什么是异构数据库模型推演
关键名词解释
- 异构数据库模型推演:用以解决异构数据库间数据同步时目标库数据类型的“最佳选择”问题。
- TapType:Tapdata 提供的中间数据类型,可在数据同步之前,将所有数据库类型转换成中间数据库类型,是介于源库和目标库之间的“中转站”。
- 模型推演算法:采用算分机制进行类型排序, 并返回最匹配数据类型,这个算法可以做到相对稳定。
- 模块单元测试:模型推演可维护性的解决方法,用以保障模型推演的可持续发展。
为什么需要异构数据库模型推演?
以 MongoDB 到 MySQL 的数据同步为例:
MongoDB 的数据类型
MySQL 的数据类型
如上图所示,两个数据库之间的数据类型明显不同。假设现要将 MongoDB 中存在的 _id 数据 ObjectId、企业名称、企业创建日期、员工人数同步到 MySQL,就需要选择 MySQL 所适配的数据类型:
MongoDB 示例库表推演出 MySQL 的建表语句
其中,都是24位的 ObjectId 可以适配字符串 varchar(24);Tapdata 这个名称对应 varchar(100),MongoDB 的 string 实际上很长,这里出于经验考量会为其选择一个相对小一点的方式来存储这个字符串,如果在使用过程中发现不够长,也支持修改;企业创建日期属于日期类型,对应到 MySQL 就是 datetime(3),毫秒位的精度类型;员工人数则是从 MongoDB 的 NumberInt 到 MySQL 的 int。
以上就是我们在异构数据库数据同步过程中,所面临的工序:在目标库中,为源库数据选择对应的匹配类型→建表→插入数据。而这里还涉及到一个数据库类型的“最佳选择”问题,也就是异构数据库模型推演所要解决的问题。
02
如何完成异构数据库模型推演?
核心思想:引入中间数据类型 TapType
在将数据导入异构数据库之前,可以先将其转换为中间数据类型,也就是我们所说的 TapType。
那么我们为什么需要“创造”这样一种中间数据类型呢?
TapType:解决异构数据同步复杂度不断升级的问题
假设不存在 TapType,在进行数据同步时,所有异构数据库之间都会是直接连接的一一对照关系。随着支持的数据源和目标的数量不断增加,其间对照关系网的复杂度就会呈指数级增长。但如果我们尝试引入一个中间数据类型,先将这些数据库都用中间类型来描述,再由此实现向下游数据库类型的转换,就能很好地解决这一问题。
以下是中间类型 TapType 的设计思路:
1. 通过 json key 类型表达式匹配数据库类型,json value 描述类型边界
2. 每种 TapType 类型自己专有的属性参数
3. 源库数据流入 Tapdata 引擎时,会对其做一层转换,然后再根据目标库所对应的 TapType 配置对数值进行再生成,这里涉及一个值转换过程。值转换采用 MongoDB 类似的 Codec 设计, 提供默认 Codec 和自定义 Codec 实现
4. 采用算分机制作为核心算法,为原表类型匹配目标表的最佳类型
如何使用中间类型 TapType?
以“为 PDK 数据源提供 API 支持”为例:
1. 定义数据源的类型表达式以及边界描述
*表达式说明:
通过这样的方式,描述了源库所有字段的边界情况,以及如何用表达式来进行类型识别。与此同时,目标库也提供了相应的类型表达式,由此,我们就可以通过两者的关系为模型推演提供实现的基础。
在 PDK 开发中,text
相当于精准匹配,这里数据库类型就叫 text,最大边界是 4GB,优先级为 2,为中间类型TapString;bit varying[($byte)]
里的 byte
是一个变量,是二进制的长度, 可以在创建表时指定,这里的[]
代表可有可无,在完全没有变量的情况下,就会采用 default 值,所以这部分的最大边界是64,没有[]
的情况就是 64,queryOnly=true 的含义是这个类型只会用于源表读取,目标建表就不会使用它来选择建表字段;"value": [-2147483648, 2147483647]
配置了最小、最大值。
2. 自定义 Codec,指定特定 TapType 类型采用什么数据库类型来接收并如何接收
*表达式说明:
我们是通过这种方式,来支持开发者自定义所需要的 Codec 转换的。
从创建目标库的角度来看,流入的数据是 TapMapValue,也就是 Map 值,但目标库中没有相匹配的类型可以接收,这可能就需要将其转换为一个 text,转成 toJson String 之后再 return 回去。简言之, TapMap 这样的数据过来,我们会用 text 建表,同时将其转成 Json String 的方式入库。也就是说,在我们不支持某一些类型的时候,可以通过这样自定义的方式来完成值转换过程,并干预建表的字段选择。
另附类型表达式介绍文档(详见本篇附录),我们在这里详细描述了 TapType 的相关定义: https://tapdata.github.io/docs/Connectors/docs/data-type-expressions.html
模型推演的实现原理是什么?
推演模型模块划分
模型推演可以大致分为以下三个模块:
1. 类型映射模块
a. Package io.tapdata.entity.mapping
b. 一个特殊的 Map(Tapdata 定制 Map,输入数据库的真实类型,就能自动匹配到表达式的边界信息),通过类型表达式匹配数据库类型作为 key 存储,并对应类型边界描述信息作为 value 存储
c. 通过 TapType 对象和类型表达式生成数据库类型
d. 每个 TapMapping 对象提供了和 TapField 的亲和度分数计算
2. 值转换模块
a. Package io.tapdata.entity.codec
b. 提供 Java 类型的默认值转换Codec
c. 运行通过 PDK API 扩展自定义的值转换
i. 从源库读出的特殊数据类型, 通过 ToTapValue 的 Codec 转换成为某一种 TapValue 对象
ii. 从引擎过来的 TapValue 对象可以通过 FromTapValue 的 Codec 进行转换并指定建表的数据库类型
3. 模型推演模块
a. Package io.tapdata.entity.conversion
b. 通过类型映射模块和值转换模块完成该模块功能
c. 提供类型映射 API(autoFill),输入数据库类型以及类型表达式 json, 就能自动生成 TapType
d. 提供类型转换 API(convert),输入原表字段列表, 目标表类型表达式 json 和目标表值转换, 就能输出目标表最佳匹配的字段列表
Tapdata 类图:共五个模块,中间构成了模型推演的核心对外模块
整体层次结构上,如上图所示,四大模块(Schema、Event、Mapping、值转换 Codec)加持下,我们的模型推演模块(Conversion)就可以“坐享其成”,轻松使用各大模块的功能,快速实现 AutoFill,帮助各数据库类型自动匹配 TapType,并根据输入的源库的字段类型、目标库的类型表达式,以及值转换相关的 CodecFieldManager,推演出最适合的目标库类型,从而达到模型推演的目的。
其他处理逻辑问题补充
关于模型推演,还有一些开发者们在使用 Tapdata 或进行 PDK 开发过程中,可能会关心的非主线功能的处理逻辑问题,这里也依据大家的常见反馈,作补充说明如下:
1. 如果出现没有映射到的数据库类型, 统一采用 TapRaw 去处理;
2. TapRaw 在目标端如果没有特殊定义, 选择目标库最大的字符串类型接收并且按对象 toString 做值转换(*注意:这一条特指在开发者不知道具体该如何做的情况下,我们通过找到最大字符串的办法来尽可能满足需求,但在实际操作过程中,最终结果往往不会特别好看,因此我们还是希望是大家在做类型描述时能够做到更加精准。当然,如果不可避免地出现这种情况,我们也会有日志打印出来);
3. 如果源库字段边界大于目标库所有字段时, 会选择不匹配里距离源库字段最接近的字段, 并会有警告记录;
4. 类型表达式大小写不敏感,但是对空格敏感。
模型推演算法简介
① 模型推演的算分机制
模型推演的算法采用算分机制,对各个类型的亲和度进行算分并排序,并返回最匹配类型。这个算法可以做到相对稳定,因为它将感官上的“感觉应该更好”,抽象化为数字化的结论,通过量化的方式,更方便地得到相对稳定的排序。在可维护性上,复杂度也会比写 if else 更简单。任何复杂的东西我们都应该将其抽象为简单算法,这才是真正的最佳解。
在算分机制上,我们的大致思路是依靠“权重”来处理稳定性的问题,通过在各项基准的分数上加加减减,来稳定模型推演算法。举个简单的例子:对于 TapString, 就可以基于源库和目标库的 byte 差来衡量,差越大,亲和度越低,这是一种基准。再例如 fixed=true/false,当两个字段都是 fixed=true 时,亲和度显然就更高,两者相同就是加分项,不同就是减分项,以此为权重计量标准。除此之外,TapNumber 中 scale 描述有无小数点、是否都是 unsigned 等等,都会影响权重分值的计算。
当发现模型推演效果不佳时, 可以通过提高或者修改 PDK 数据源的 Json 类型描述的准确度,快速高效地解决问题。参数配置填得越精细,匹配精度也会越高。这也是我们后续的一个发展方向——通过这细化参数配置来去提高我们模型推演的精度。我们将更详细地提供更多参数,让 PDK 开发者能够更细粒度地描述这些类行差异性。从而通过更细节的权重算分,让算法得以越来越精准、越来越精准。
验证模型推演准确性
为了验证模型推演的准确性,我们提供模型推演对照表如下:
1. 通过数据库模型对照表能更容易的发现模型推演的问题, 有助于尽早解决
2. 通过类型表达式能支持数据库类型的各种灵活写法
以 Oracle 到 MySQL 为例
以上表为例(Oracle → MySQL),我们会自动读取 PDK 的 Oracle.jar 和 MySQL.jar,并读出其中的类型表达式以及自定义值转换等相关干预,然后会将所有类型依次推演一遍。在这个过程中,我们会自动寻找这些变量边界的最小值和最大值以及中间值,然后自动生成一个类型,并推到目标数据库类型。这个表在这里更多扮演预览的角色,用于验证 Oracle 到 MySQL 的这些类型是否能推演,我们可以通过自身经验,来判断是否有出错的地方,再对应地去调整。
http://mpvideo.qpic.cn/0bc3baaaaaaa3iaigtzjujrvacgdaaeaaaaa.f10002.mp4?
详解如何生成模型推演对照表,