场景一:从Postgresql数据库,同步数据到Mysql数据库
- 鼠标右键页面,弹出配置框,进入【Controller Services】:
- 点击右侧➕,弹出Controller选择框,在输入框输入目标名称:
- 创建【PostgresqlDBCPController】,配置如图:
- 配置完成之后,点击右上角的√,测试配置是否链接正常:
- 结果如图所示为成功,点击下方【Apply】。
- 点击右侧的启用按钮:
- 至此,该Controller已经配置为可用状态;
- 配置完成之后,点击右上角的√,测试配置是否链接正常:
- 按照和上面的Postgresql一样的Controller一样的方式,配置一个Mysql Controller。区别在于配置的时候选择mysql驱动即可;需要注意:配置url的时候一定要注意合理的参数值,尤其是超时时间等,不然很容易导致超时,如图
- 添加【QueryDatabaseTable】Processor
- 双击组件,进入配置页面,选择之前配置好的Postgresql数据库链接Controller,配置数据库表格名称。然后同样的可以点击右侧的➕进行测试,通过之后点击应用。
- 按照同样的方式,添加【PutDatabaseRecord】Processor,配置页面如下,选择之前配置好的mysql数据库链接Controller。注意,表格一定要预先创建好。
- 之后拖追中间的箭头,链接两个Processor:
- 点击左侧的三角按钮,启动整个流程,右侧红色按钮会变为绿色三角:
- 可以右键点击组件,查看结果:
- 如果你需要修改从Postgresql获取的原始字段内容,比如截取某个字段字符串子串,配置如下
- 使用【UpdateRecord】processor:
- 注意,【Replacement Value Strategy】一定要选择【Rrcord Path Value】,另外一个【Literal Value】会直接把下面的配置当成字符串直接赋值,不会走公式计算。
- 结果对比,
- 原来字段内容为:
- 更新后字段内容为:
- 原来字段内容为:
- 其他参考转换方法:
- replaceRegex(toString(/field_name, ''), '^[\s\n\r]+|[\s\n\r]+$', '') # 删除前后空格及换行
- replaceRegex(toString(/field_name, ''), '[^\d]', '') # 抽取数字
- 使用【UpdateRecord】processor:
- 如果存在主键重复问题,可以在【PutDatabaseRecord】再加一个【PutDatabaseRecord】,设置为【UPDATE】类型,实现更新操作;相比【INSERT】需要多配置一个更新字段,一般是表格主键;
- 注意,预先配置的读写数据库记录相关的Controller,配置参考如图:
- 坑一:按照上面的流程实现了个数据迁移流程配置,本来按照预期应该是在【QueryDatabaseTable】设置好【Maximum-value Columns】【Initial Load Strategy】,同时在【PutDatabaseRecord】里面配置了【INSERT】和【UPDATE】策略,就可以一直实时保持增量,但是却发现一直存在数据对不上问题。
- 经过各种排查,发现【QueryDatabaseTable】数据拉取是没问题的;
- 问题就出现在【PutDatabaseRecord】往目标数据库表格插入的时候出现问题。问题就在于对【Batch Size】批量处理流程的理解。
- 一开始,我一直以为我设置的【Batch Size】为【500】,每次往Mysql插入的时候,会自动把可以直接【INSERT】的插入,存在逐渐重复的部分,才会去走【Update】;
- 经排查,才发现我理解错了,原来实际的逻辑是:如果一个批次里面,但凡存在一条【INSERT】不成功,整个批次,也就是整个500条,都会算作失败,按照流程,被传递给后面的【UPDATE】;但是按照同样的逻辑,部分之前没有的,直接【UPDATE】也会报错,所以导致最终整个批次都会被丢弃;
- 优化方案:
- 当【PutDatabaseRecord】因为主键重复失败后,后面接一个【SplitRecord】组件,把批量转化为单个;
- 再后面接【INSERT】->【UPDATE】;
- 经实践,可以实现实时同步;
- 坑二:因为我的源表格数据存在一定的存量插入,所以针对更新时间字段分布不均匀的情况,具体来说就是源表格【updated_time】字段存在十几万条历史数据的相同值。这种情况下,在首次全量时,仅仅配置【Maximum-value Columns】为【updated_time】一个字段,【Batch Size=10000】就会出现问题,所以我联合主键配置了多个【Maximum-value Columns】,即【updated_time,id】,这就给增量流程造成了一个很大的问题:
- 【QueryDatabaseTable】组件在已经存在状态后,下一次拉取数据的逻辑如下:
-
SELECT * FROM getvae1 WHERE updated_time > '2025-04-18 14:25:00' OR (updated_time = '2025-04-18 14:25:00' AND id > 123456789) ORDER BY updated_time ASC, vae01 ASC LIMIT ...
-
可以看到,当出现updated_time字段等于当前状态,但是id小于当前状态时,不会被拉取到,所以,就会出现增量数据被遗漏的情况。
-
所以,需要在完成全量后,针对增量进行配置调整,如果后面的新数据updated_time更新合理,可以把【Maximum-value Columns】直接设置为【updated_time】即可。
-
坑三【大坑】【增量遗漏数据问题】:按照上面的配置设置了增量,但是监控一段时间之后,发现还是存在目标表数据和源表数据一致性问题。
-
仔细梳理NIFI【QueryDatabaseTable】组件拉取数据流程,猜测大概率是【边界值问题导致】。因为我的源数据表格的时间字段updated_time字段是到【秒】,所以一旦发生当前批次拉取之后,state记录为当前时间t1,但是后面源表格出现更新记录的更新时间也是当前时间t1,那么下次就会从大于t1的时间开始拉取,这就出现数据丢失的情况。如果数据更新不频繁,这种情况出现概率较低,但是如果一旦数据更新频繁,这种情况出现的概率就会很大,从我的实际监控来看,隔段时间就会出现十几条的数据遗漏。
-
更新方案:
-
把数据库更新时间字段精确到微妙,我的不好更新,所以弃了;
-
【采用】使用【QueryDatabaseTableRecord】组件:
-
该组件和【QueryDatabaseTable】的区别是,如果不配置【Maximum-value Columns】参数,是不会主动记录state状态。可以通过【Custom Query】配置自定义的sql查询语句。
-
select * from *** where updated_time >= '${now():toNumber():minus(20000):format("yyyy-MM-dd HH:mm:ss")}'
-
同时在【Scheduling】配置1个线程,每5秒刷一次。
-
最终实现的效果时,每5秒刷一次前20秒的增量数据。会重复,但是能确保不会丢失数据。注意,具体退后多少时间,要根据具体情况来定,如果表格数据量比较大,建议增加时间范围,比如2分钟。
-
-
- 【候选方案】:正在尝试个人认为更好的方案,即每次拉取的时间从上一次拉取到的最新时间开始,或者为了保证不丢数据,从上次拉取到的最新updated_time-20s开始。问题就在于怎么获取上次拉取到的最新时间。正在尝试,成功后更新。
-
-