基于NIFI的通用数据库迁移流程

我们通过nifi将oralce数据库迁移到另外的一个oralce数据库中,进行备份。

为了演示方便,现在先初始化源表中的表信息和测试数据。

create table employee_info(  
       no number(8),  
       name varchar2(32),  
       age number(3),  
       birthday date,  
       phone number(11),  
       address varchar2(100)
       );  



学习喜欢创建学生信息表,工作人员,喜欢以员工信息表来演示某些问题。我们以员工信息表来演示下述的抽取过程。

并插入了2条数据:


insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('1', '张三', 25, to_date('23-02-2019 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '1890002145', '山东省济南市某某区');

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('2', '李四', 28, to_date('01-02-1994 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '18612352187', '山东省济南市某某区');

员工的生日信息还挺精确,到了分钟,当然,对于生日,我们一般用不到具体的时间。此处仅仅为了演示。

 

对于目标库,仅仅方便于演示,我们在同一个库中创建一个表结构一样的表

create table employee_info2(  
       no number(8),  
       name varchar2(32),  
       age number(3),  
       birthday date,  
       phone number(11),  
       address varchar2(100)
       );  

一、简单的流程

    稍微熟悉Nifi的用户,可能第一次接触的就是这样的示例,oracle中的数据迁移到oracle中。知道使用ExecuteSQL与PutDatabaseRecord组件可以对支持jdbc的关系型数据库数据进行抽取,并存入到对应的数据库中。我们似乎可以通过简单的配置,就可以完成这个抽取过程。

基于NIFI的通用数据库迁移流程_第1张图片

基于NIFI的通用数据库迁移流程_第2张图片

基于NIFI的通用数据库迁移流程_第3张图片

 

配置完成后,发现报错。

基于NIFI的通用数据库迁移流程_第4张图片

根据经验或者是代码调试,就会明白,这个原因是因为存入数据的时候,目标库中有日期类型的,而现有的配置,所取得的数据均是字符串。只需要将executeSQL中的Use Avro Logical Types修改为true,即可。

总体来说,第一次配置的话,也会很顺利。似乎~然而,当仔细去思考问题的时候,会发现,似乎没有那种简单。

如果数据量足够大,比如说某个表中有1亿条记录,50个字段(因为水平与实际设计中的某些考虑,不要将表的设计都想像的高度理想化,也不会所有的设计都符合数据库的范式,实际开发中,见到过200+字段的表比比皆是,似乎每个项目都能遇到)。这种方式是否还合理呢?将所有的数据一次性抽取,jvm需要多大的内存,似乎很大。总之普通的计算机难以一次性完成。

再仔细验证executeSQL的配置,会发现有如下配置。我们将他配置为某个数值,比如说10000。这样的话就可以完成分批次的抽取了。

 

 

    上面的配置,好像已经解决了我们的问题了。当我们实际使用的时候,1亿条的数据,逐条去遍历,可能需要几分钟时间,更何况在Nifi中还需要将数据组装成avro等格式的数据。如果是内网良好的情况下,几分钟似乎不是问题。如果网络是通过等网络连接的,或者是网络情况不好,网络就不那么稳定了,随时可能中断。如果中断了,我们似乎无能为力,只能从新开始遍历,下次依然无法保证顺利完成。

二、分页查询

在最初接触编程,尤其是数据库的时候,每次你去面试,经常会遇到的问题是:oracle与mysql的分页一样么?如何实现的?性能如何?

      在上面的讨论中,我们已经实现了简单的抽取流程,在比较理想的情况下,能够顺利的完成工作。即使一次不能,多试几次,应该也能够成功。

     如果数据量再大一些呢,服务器性能性能很差呢?需要几个小时,时间越长,似乎中间出现问题的可能性就越大,越可能需要重新抽取,时间成本成倍的增加。似乎上面的流程就很难顺利的完成;并且上面的流程,基于数据库的遍历的设计,总是单线程的、线性的,java编程语言是支持多线程的,服务器是多核的。这些早已经成熟的技术,用不上,是不是有些浪费?

   为了方便演示,再多插入几条数据。

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('1', '张三', '25', to_date('23-02-2019 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '1890002145', '山东省济南市某某区');

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('2', '李四', '28', to_date('01-02-1994 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '18612352187', '山东省济南市某某区');

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('3', '王五', '25', to_date('23-02-2019 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '1890002145', '山东省济南市某某区');

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('4', '孙六', '28', to_date('01-02-1994 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '18612352187', '山东省济南市某某区');

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('5', '钱八', '25', to_date('23-02-2019 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '1890002145', '山东省济南市某某区');

insert into employee_info (NO, NAME, AGE, BIRTHDAY, PHONE, ADDRESS)
values ('6', '赵九', '28', to_date('01-02-1994 00:06:00', 'dd-mm-yyyy hh24:mi:ss'), '18612352187', '山东省济南市某某区');

 

我们将GenerateTableFetch做为第一个节点,将ExecuteSQL做为第二个节点,并为ExecuteSQL添加了失败重试功能,这样的话,如果发生了比如数据库连接超时之类的错误,也可以保证数据不丢失。

基于NIFI的通用数据库迁移流程_第5张图片

GenerateTableFetch配置如下,演示的数据量比较小,我们每2条数据就分成1页。

基于NIFI的通用数据库迁移流程_第6张图片

 

ExecuteSQL的配置如下,SQL select query中的查询已经清空,查询语句将由第一个节点来生成,传过来使用。

基于NIFI的通用数据库迁移流程_第7张图片

挨个启动流程。

启动第一个流程后,分将6条数据,2个条一组,总共 生成3条语句。

基于NIFI的通用数据库迁移流程_第8张图片

打开其中的一条,查看:生成的就是oracle中的分页语句,总共生成了3条,分别取 1-2,3-4,5-6条数据。基于NIFI的通用数据库迁移流程_第9张图片

将上面的语句格式化一下:

SELECT *
  FROM (SELECT a.*, ROWNUM rnum
          FROM (SELECT * FROM employee_info WHERE 1 = 1) a
         WHERE ROWNUM <= 2)
 WHERE rnum > 0

如果是mysql的话,分页将会使用limit offset的方式进行。

这样的话,我们的基于分页的流程就配置完成了。

再仔细思考一下:

    第一个节点,只执行一次,里面会有一个统计总条数的的功能比较耗时,然后只生成对应的sql语句。统计功能虽然耗时一些,相对于遍历+组装数据还是要快得多,并且只执行一回。如果失败的了话,再重试一次,后面的结点也不需要任何修改。

    第二个节点,才真正执行查询。此时每次查询的数据量会很少,如果有查询失败,可以接着发给自己重试,保证了数据不丢失。更重要的,各段查询任务是独立的,相当之间没有影响,我们可以配置并发数,这样的话,就可以通过并发操作提高抽取的性能。

基于NIFI的通用数据库迁移流程_第10张图片

 

通过以上的流程,似乎所有的数据迁移都可以完成了。似乎,确实是仅仅是似乎。如果你对于oracle的分页熟悉的话,应该知道分页性能并不太好,其他其他的库的分页性能也不好,比如mysql。只是语法上有些区别。
explain plan for 
SELECT *
  FROM (SELECT a.*, ROWNUM rnum
          FROM (SELECT * FROM employee_info WHERE 1 = 1) a
         WHERE ROWNUM <= 4)
 WHERE rnum > 2

select * from table(dbms_xplan.display);

 

基于NIFI的通用数据库迁移流程_第11张图片

不同的表设计,包括有没有索引,执行计划可能会有不同,可能不会走全表扫描。但是,上图所示的15、16条的的两条过滤,一定会进行。上面的数据,表示的是查的比3、4条记录,至少要查询到第四条,才会中断查询,截取所需要的数据。如果查询的是

99990001到100000000条呢?至少要扫描到第100000000才会中止啊

如果1亿条,分1万段的话,每段数据的扫描情况:

第1段:前1万条每次都扫描,扫描1万次~

第2段:前10001-20000条每次都扫描,除了取第1段数据不扫描外,剩下的每次都扫描,扫描9999次

第3段:前20001-30000条每次都扫描,除了取第1、2段数据不扫描外,剩下的每次都扫描,扫描9998次

……………………

第10000段:前把前面的每条记录都先扫一遍,截取最后一万条~

这种情况的表现为:前边的一些数据在抽取的时候,速度还可以的。越往后越慢,越往后越慢。

分页性能的篇文章,可以了解下:https://blog.csdn.net/lansetiankong12/article/details/73741001

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(ETL,java,大数据)