Oracle迁移到MySQL工具之愚公(yugong)
背景
2008年,阿里巴巴开始尝试使用 MySQL 支撑其业务,开发了围绕 MySQL 相关的中间件和工具,Cobar/TDDL(目前为阿里云DRDS产品),解决了单机 Oracle 无法满足的扩展性问题,当时也掀起一股去IOE项目的浪潮,愚公这项目因此而诞生,其要解决的目标就是帮助用户完成从 Oracle 数据迁移到 MySQL 上,完成去 IOE 的重要一步工作。
项目介绍
名称: yugong
译意: 愚公移山
语言: 纯java开发
定位: 数据库迁移 (目前主要支持oracle / mysql / DRDS)
整个数据迁移过程,分为两部分:
- 全量迁移
- 增量迁移
过程描述:
- 增量数据收集 (创建oracle表的增量物化视图)
- 进行全量复制
- 进行增量复制 (可并行进行数据校验)
- 原库停写,切到新库
架构
说明:
- 一个Jvm Container对应多个instance,每个instance对应于一张表的迁移任务
- instance分为三部分
a. extractor (从源数据库上提取数据,可分为全量/增量实现)
b. translator (将源库上的数据按照目标库的需求进行自定义转化)
c. applier (将数据更新到目标库,可分为全量/增量/对比的实现)
方案设计
DevDesign:https://github.com/alibaba/yugong/wiki/DevDesign
全量方案
业界常用的全量方案有:
- 数据文件导入/导出,比如EXPDP/IMPDP, mysqldump/source, xtrabackup等
- ETL数据导入/导出,主要原理为使用JDBC数据查询接口
yugong在项目设计之初考虑去IOE数据迁移的灵活性和自定义能力,最终选择的方案为基于JDBC接口遍历数据.
相比于数据文件导入/导出,其优点:
- 灵活数据同步
- 支持异构数据
- 实现相对简单
缺点:
- 全量拉取需要配合增量使用,会有部分数据重复同步
- 性能和影响,一次性全量拉取,如果持续时间过长,如果此时数据库变更过多,会导致segment过大
增量方案
业界常用的增量方案有:
- 基于时间戳定时dump
- oracle日志文件,比如LogMiner,OGG
- oracle CDC(Change Data Capture)
- oracle trigger机制,比如DataBus , SymmetricDS
- oracle 物化视图(materialized view)
- ...
yugong在项目设计之初考虑去IOE数据迁移的灵活性,支持多种oracle版本,同时为降低DBA的运维成本,最终选择oracle物化视图作为我们的增量方案.
相比于其他,物化视图方案其优点:
- 原理简单,方便理解和学习,用户可以理解为一种固化的简易trigger模式
- 运维简单,DBA一次账户授权后,程序可按需create一张物化视图表即可完成增量订阅
- 相对透明,不需要像时间戳sql扫描依赖数据库表设计,也不需要关注oracle版本和服务器存储等
缺点:
- 性能和影响,类似于trigger机制会对源库的数据写入造成一定的性能影响.
快速开始
QuickStart:https://github.com/alibaba/yugong/wiki/QuickStart
a. oracle全量基于JDBC拉取数据,增量基于物化视图来实现,所以这里需要给oracle数据库账号开启特殊权限
1 2 3 | GRANT SELECT,INSERT,UPDATE,DELETE ON XXX TO XXX; #常见CRUD权限 GRANT CREATE ANY MATERIALIZED VIEW TO XXX; GRANT DROP ANY MATERIALIZED VIEW TO XXX; |
1. 下载yugong
直接下载,可访问:https://github.com/alibaba/yugong/releases,会列出所有历史的发布版本包的下载,比如以1.0.0版本为例子:
1 | wget https://github.com/alibaba/yugong/releases/download/yugong-1.0.0/yugong-1.0.0.tar.gz |
or
自己编译
1 2 3 | git clone git@github.com:alibaba/yugong.git cd yugong; mvn clean install -Dmaven.test.skip -Denv=release |
编译完成后,会在根目录下产生target/yugong-$version.tar.gz
2. 解压缩
1 2 | mkdir /tmp/yugong tar zxvf yugong-$version.tar.gz -C /tmp/yugong |
3. 配置修改
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | vi conf/yugong.properties # 源数据库,需要修改成自己的数据库信息 yugong.database.source.username=test yugong.database.source.password=test yugong.database.source.type=ORACLE yugong.database.source.url=jdbc:oracle:thin:@127.0.0.1:1521:test yugong.database.source.encode=UTF-8 yugong.database.source.poolSize=30 # 目标数据库,需要修改成自己的数据库信息 yugong.database.target.url=jdbc:mysql://127.0.0.1:3306/test yugong.database.target.username=test yugong.database.target.password=test yugong.database.target.type=DRDS yugong.database.target.encode=UTF-8 yugong.database.target.poolSize=30 yugong.table.batchApply=true yugong.table.onceCrawNum=1000 yugong.table.tpsLimit=0 #use connection default schema yugong.table.ignoreSchema=false #skip Applier Load Db failed data yugong.table.skipApplierException=false #需要迁移的数据库表 yugong.table.white=yugong_example_oracle yugong.table.black= #tables use multi-thread enable or disable yugong.table.concurrent.enable=true #tables use multi-thread size yugong.table.concurrent.size=5 #retry times yugong.table.retry.times = 3 #retry interval or sleep time (ms) yugong.table.retry.interval = 1000 #MARK/FULL/INC/ALL(REC+FULL+INC)/CHECK/CLEAR yugong.table.mode=ALL #yugong extractor yugong.extractor.dump=false yugong.extractor.concurrent.enable=true yugong.extractor.concurrent.size=20 yugong.extractor.noupdate.sleep=1000 yugong.extractor.noupdate.thresold=0 yugong.extractor.once=false #{0} is all columns , {1}.{2} is schemaName.tableName , {3} is primaryKey #yugong.extractor.sql=select /+parallel(t)/ {0} from {1}.{2} t #yugong.extractor.sql=select * from (select {0} from {1}.{2} t where {3} > ? order by {3} asc) where rownum <= ? #yugong applier yugong.applier.concurrent.enable=true yugong.applier.concurrent.size=20 yugong.applier.dump=false #stats yugong.stat.print.interval=5 yugong.progress.print.interval=1 #alarm email yugong.alarm.email.host = smtp.163.com yugong.alarm.email.username = test@163.com yugong.alarm.email.password = yugong.alarm.email.stmp.port = 465 |
说明:
- 需要修改源和目标数据库的账号信息
- 需要修改yugong.table.white信息,登记需要同步的测试表
4. 准备测试表
a. 在源库oracle上创建一张待同步表
1 2 3 4 5 6 7 8 9 10 11 12 13 | create table yugong_example_oracle ( id NUMBER(11) , name varchar2(32) , alias_name char(32) default ' ' not null, amount number(11,2), score number(20), text_b blob, text_c clob, gmt_create date not null, gmt_modified date not null, CONSTRAINT yugong_example_oracle_pk_id PRIMARY KEY (id) ); |
b. 在目标库MySQL上创建一张目标表
1 2 3 4 5 6 7 8 9 10 11 12 13 | create table yugong_example_mysql ( id bigint(20) unsigned auto_increment, display_name varchar(128) , amount varchar(32), score bigint(20) unsigned , text_b blob, text_c text, gmt_create timestamp not null, gmt_modified timestamp not null, gmt_move timestamp not null, CONSTRAINT yugong_example_mysql_pk_id PRIMARY KEY (id) ); |
c. 在源库构造几条测试数据
1 2 | insert into yugong_example_oracle values(1,'ljh','agapple',10.2,100, NULL , NULL ,sysdate,sysdate) insert into yugong_example_oracle values(2,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate) |
5. 准备启动
1 | sh bin/startup.sh |
6. 查看日志
查看总日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | vi logs/yugong/table.log 2016-03-05 01:33:02.714 [main] INFO com.taobao.yugong.YuGongLauncher - ## start the YuGong. 2016-03-05 01:33:02.835 [main] INFO com.taobao.yugong.controller.YuGongController - check source database connection ... 2016-03-05 01:33:02.859 [main] INFO com.taobao.yugong.controller.YuGongController - check source database is ok 2016-03-05 01:33:02.859 [main] INFO com.taobao.yugong.controller.YuGongController - check target database connection ... 2016-03-05 01:33:02.880 [main] INFO com.taobao.yugong.controller.YuGongController - check target database is ok 2016-03-05 01:33:02.882 [main] INFO com.taobao.yugong.controller.YuGongController - check source tables read privileges ... 2016-03-05 01:33:03.027 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited 2016-03-05 01:33:03.550 [main] INFO com.taobao.yugong.controller.YuGongController - check source tables is ok. 2016-03-05 01:33:04.213 [main] INFO com.taobao.yugong.controller.YuGongController - ## prepare start tables[1] with concurrent[5] 2016-03-05 01:33:04.685 [YuGongInstance-ALIBABA.YUGONG_EXAMPLE_ORACLE] INFO com.taobao.yugong.controller.YuGongInstance - table[ALIBABA.YUGONG_EXAMPLE_ORACLE] is start2016-03-05 01:33:04.685 [main] INFO com.taobao.yugong.YuGongLauncher - ## the YuGong is running now ...... 2016-03-05 01:33:04.690 [main] INFO com.taobao.yugong.YuGongLauncher - [YuGong Version Info] [version ] [hexVeision] [date ]2016-03-05 01:09:25 [branch ]master [url ]git@github.com:alibaba/yugong.git 2016-03-05 01:34:04.219 [pool-2-thread-2] INFO com.taobao.yugong.common.stats.ProgressTracer - {未启动:0,全量中:0,增量中:0,已追上:1,异常数:0} 2016-03-05 01:34:04.227 [pool-2-thread-2] INFO com.taobao.yugong.common.stats.ProgressTracer - 已完成:[ALIBABA.YUGONG_EXAMPLE_ORACLE] |
查看表同步日志
1 2 3 4 5 | 2016-03-05 01:33:04.359 [main] INFO c.t.y.e.o.OracleFullRecordExtractor$ContinueExtractor - ALIBABA.YUGONG_EXAMPLE_ORACLE start postion:0 2016-03-05 01:33:04.365 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-2} inited 2016-03-05 01:33:04.685 [main] INFO com.taobao.yugong.controller.YuGongInstance - table[ALIBABA.YUGONG_EXAMPLE_ORACLE] start successful. extractor:com.taobao.yugong.extractor.oracle.OracleAllRecordExtractor , applier:com.taobao.yugong.applier.AllRecordApplier 2016-03-05 01:33:04.802 [YuGongInstance-ALIBABA.YUGONG_EXAMPLE_ORACLE] INFO c.t.yugong.extractor.oracle.OracleAllRecordExtractor - table [ALIBABA.YUGONG_EXAMPLE_ORACLE] full extractor is end , next auto start inc extractor 2016-03-05 01:33:05.010 [YuGongInstance-ALIBABA.YUGONG_EXAMPLE_ORACLE] INFO c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[ALIBABA.YUGONG_EXAMPLE_ORACLE] now is NO_UPDATE ... |
出现了:
- full extractor is end , next auto start inc extractor #代表全量迁移已完成,自动进入增量模式
- now is NO_UPDATE #代表增量表暂时无日志
7. oracle上执行增量变更
在源库oracle上对源表进行增量变更
1 2 | insert into yugong_example_oracle values(3,'test','test',88,188, NULL , NULL ,sysdate,sysdate) update yugong_example_oracle set alias_name = 'superman' where id = 1 |
查看表同步日志
1 | 2016-03-05 01:34:21.496 [YuGongInstance-ALIBABA.YUGONG_EXAMPLE_ORACLE] INFO c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[ALIBABA.YUGONG_EXAMPLE_ORACLE] now is CATCH_UP ... |
会瞬间出现now is CATCH_UP,代表刚完成处理了增量数据,并且当前没有新的增量.
8. 查看mysq目标库数据
9. 关闭
1 | sh bin/stop.sh |
10. 整理同步逻辑
整个测试例子包含特性:
- schema/table名不同. oracle中为yugong_example_oracle,mysql中为test.yugong_example_mysql
- 字段名字不同. oracle中的name字段,映射到mysql的display_name
- 字段逻辑处理. mysql的display_name字段数据来源为oracle库的:name+'('alias_name+')'
- 字段类型不同. oracle中的amount为number类型,映射到mysql的amount为varchar文本型
- 源库多一个字段. oracle中多了一个alias_name字段
目标库多了一个字段. mysql中多了一个gmt_move字段,(简单的用迁移时的当前时间进行填充)
具体的转换逻辑定义,可查看:
1 | vi conf/translator/YugongExampleOracleDataTranslator.java |
运维管理
AdminGuide:https://github.com/alibaba/yugong/wiki/AdminGuide
运行模式详细介绍
MARK模式(MARK)
开启增量日志的记录,如果是oracle就是创建物化视图
CLEAR模式(CLEAR)
清理增量日志的记录,如果是oracle就是删除物化视图
全量模式(FULL)
全量模式,顾名思议即为对源表进行一次全量操作,遍历源表所有的数据后,插入目标表.
全量有两种处理方式:
- 分页处理:如果源表存在主键,只有一个主键字段,并且主键字段类型为Number类型,默认会选择该分页处理模式. 优点:支持断点续做,对源库压力相对较小。 缺点:迁移速度慢
once处理:通过select * from访问整个源表的某一个mvcc版本的数据,通过cursor.next遍历整个结果集. 优点:迁移速度快,为分页处理的5倍左右。 缺点:源库压力大,如果源库并发修改量大,会导致数据库MVCC版本过多,出现栈错误. 还有就是不支持断点续做.
特别注意
如果全量模式运行过程中,源库有变化时,不能保证源库最近变化的数据能同步到目标表,这时需要配合增量模式. 具体操作就是:在运行全量模式之前,先开启增量模式的记录日志功能,然后开启全量模式,完成后,再将最近变化的数据通过增量模式同步到目标表
增量模式(INC)
全量模式,顾名思议即为对源表增量变化的数据插入目标表,增量模式依赖记录日志功能.
目前增量模式的记录日志功能,是通过oracle的物化视图功能。
创建物化视图
1 | CREATE MATERIALIZED VIEW LOG ON ${tableName} with primary key. |
- 运行增量模式之前,需要先开启记录日志的功能,即预先创建物化视图. 特别是配合全量模式时,创建物化视图的时间点要早于运行全量之前,这样才可以保证数据能全部同步到目标表
- 增量模式没有完成的概念,它只有追上的概念,具体的停止需有业务进行判断,可以看一下切换流程
自动模式(ALL)
自动模式,是对全量+增量模式的一种组合,自动化运行,减少操作成本.
自动模式的内部实现步骤:
- 开启记录日志功能. (创建物化视图)
- 运行全量同步模式. (全量完成后,自动进入下一步)
- 运行增量同步模式. (增量模式,没有完成的概念,所以也就不会自动退出,需要业务判断是否可以退出,可以看一下切换流程)
对比模式(CHECK)
对比模式,即为对源库和目标库的数据进行一次全量对比,验证一下迁移结果. 对比模式为一种可选运行,做完全量/增量/自动模式后,可选择性的运行对比模式,来确保本次迁移的正确性.
参数介绍
正常情况下,只需修改下yugong.database的源库和目标库的地址信息,通过yugong.table.white定义本次需要迁移的表,通过yugong.table.mode定义要执行的操作,是全量还是增量等,其他的可以使用默认值.
默认值
参数名字 | 参数说明 | 默认值 |
---|---|---|
数据库配置相关 | ||
yugong.database.source.username yugong.database.source.password yugong.database.source.type yugong.database.source.url yugong.database.source.encode | 源数据库的相关账户和链接信息 driver url 示例: 1. ORACLE : jdbc:oracle:thin:@10.20.144.29:1521:ointest 2. MYSQL : jdbc:mysql://10.20.144.34:3306/test | encode默认为UTF-8,其他无默认值 |
yugong.database.target.username yugong.database.target.password yugong.database.target.type yugong.database.target.url yugong.database.target.encode | 目标数据库的相关账户和链接信息 | encode默认为UTF-8,其他无默认值 |
yugong.table.white | 需要同步表,白名单,定义需要同步的表 几点说明: 1. 表名支持like匹配,比如'%'匹配一个或者多个字符,下划线'_'匹配单个字符,可以通过单斜杠\进行转义符定义. 2. 表明为schema+table name组成,多个表可加逗号分隔 3. 如果白名单为空,代表整个库所有表,否则按指定的表进行同步 例子: yugongexample% (可以匹配yugong_example打头的字符串) alibaba.yugong_exampletest (可以匹配alibaba.yugong_example_test1 / alibaba.yugong_example_test2) | 无 |
yugong.table.black | 需要同步表,黑名单,需要忽略同步的表 配置方式可参考yugong.table.white | 无 |
yugong.table.mode | 运行模式,目前支持的模式为: 1. MARK (开启增量记录,比如oracle就是创建物化视图) 2. FULL (全量模式) 3. INC (增量模式) 4. ALL (自动全量+增量模式) 5. CHECK (数据对比模式) 6. CLEAR (清理增量记录,比如oracle就是删除物化视图) | 无 |
yugong.table.concurrent.enable | 多张表之前是否开启并行处理,如果false代表需要串行处理 | true |
yugong.table.concurrent.size | 允许并行处理的表数 | 5 |
yugong.table.retry.times | 表同步出错后的重试次数 | 3 |
yugong.table.retry.interval | 表同步出错后的重试时的时间间隔,单位ms | 1000 |
yugong.table.batchApply | 是否开启jdbc batch处理 | true |
yugong.table.onceCrawNum | extractor/applier每个批次最多处理记录数 | 1000 |
yugong.table.tpslimit | tps限制,0代表不限制 | 0 |
yugong.table.ignoreSchema | 是否忽略schema同步 (如果mysql和oracle对应的schema不同,可设置为true) | false |
yugong.table.skipApplierException | true代表当applier出现数据库异常时,比如约束键冲突,可对单条出异常的数据进行忽略. 同时记录skiped record data信息,日志中包含record的所有列信息,包括主键. | false |
extractor配置相关 | ||
yugong.extractor.dump | 是否记录extractor提取到的所有数据 | false |
yugong.extractor.concurrent.enable | extractor是否开启并行处理,目前主要应用为增量模式反查源表 | true |
yugong.extractor.concurrent.global | extractor是启用全局线程池模式,如果true代表所有extractor任务都使用一组线程池,线程池大小由concurrent.size控制 | false |
yugong.extractor.concurrent.size | 允许并行处理的线程数,需要先开启concurrent.enable该参数才会生效 | 20 |
yugong.extractor.noupdate.sleep | 增量模式下,出现无变更数据时再次获取数据的sleep时间 | 1000 |
yugong.extractor.once | 是否强制使用一次性模式,不支持断点续作,可提升效率,约为5倍 | false |
yugong.extractor.noupdate.thresold | 处于增量数据追赶中,超过该值后认为增量任务已完成,会释放资源给下一个table 如果该值<=0,意味着永远不会退出增量任务,会一直跑. | 如果需迁移table数 > table.concurrent.size,则默认为3 如果需迁移table数 <= table.concurrent.size,则默认为-1 |
applier配置相关 | ||
yugong.applier.dump | 是否记录applier提取到的所有数据 | false |
yugong.applier.concurrent.enable | applier是否开启并行处理 | true |
yugong.applier.concurrent.global | applier是启用全局线程池模式,如果true代表所有applier任务都使用一组线程池,线程池大小由concurrent.size控制 | false |
yugong.applier.concurrent.size | 允许并行处理的线程数,需要先开启concurrent.enable该参数才会生效 | 20 |
统计和报警 | ||
yugong.stat.print.interval | 统计信息打印频率. 频率为5,代表,完成5轮extract/applier后,打印一次统计信息 | 5 |
yugong.progress.print.interval | 打印迁移进度状态,单位分钟 | 1 |
yugong.alarm.receiver | 报警接收人,支持邮件和手机,逗号分隔 | |
yugong.alarm.msgcenter.hosts | 报警中心地址 | |
性能报告
相关资料
- yugong简单介绍ppt: ppt
- 分布式关系型数据库服务DRDS
(前身为阿里巴巴公司的Cobar/TDDL的演进版本, 基本原理为MySQL分库分表)
总结
1、阿里巴巴去Oracle数据迁移同步工具(全量+增量,目标支持MySQL/DRDS),可以将Oracle迁移同步到MySQL
2、最新版本为2016年的v1.0.3版本,不再更新
3、oracle全量基于JDBC拉取数据,增量基于物化视图来实现
4、缺点:
- 全量拉取需要配合增量使用,会有部分数据重复同步
- 性能和影响,一次性全量拉取,如果持续时间过长,如果此时数据库变更过多,会导致segment过大
Oracle到MySQL迁移:用华为云的DRS、阿里云的DTS、工具Navicat、kettle、OGG、dataX都可以,可以在本博客搜索
停止更新的工具,都不建议使用了,若有Oracle到MySQL的项目,目前个人使用感受比较好的工具包括华为云的DRS和Navicat工具。