yugong之單張表oracle到mysql遷移

jx_yu發表於2016-03-31

阿里在3月初開源了yugong,該專案使用純Java開發,主要作用是進行資料庫遷移,目前該專案主要支援從oracle資料庫向Mysql和DRDS資料庫進行遷移。剛好最近有相關需求,所以嘗試了一把yugong,以下是嘗試的筆記,供大家參考。


此文:單張表從oralemysql的遷移,源端oracle和目標端mysql的表結構不完全相同

測試環境

源庫

目標庫

資料庫型別

ORACLE

MYSQL

使用者

test

test

密碼

test

test

URL

jdbc:oracle:thin:@127.0.0.1:1521:test

jdbc:mysql://127.0.0.1:3306/test

表名稱

yugong_example_a

yugong_example_mysql_a

源端oracle

#建立test使用者,並且授權(此處由於測試環境直接給個DBA角色)

SQL> create user test identified by test;

SQL> grant dba to test;

注:oracle全量基於JDBC拉取資料,增量基於物化檢視來實現,所以這裡需要給oracle資料庫賬號開啟特殊許可權:

GRANT SELECT,INSERT,UPDATE,DELETE ON XXX TO XXX; #常見CRUD許可權

GRANT CREATE ANY MATERIALIZED VIEW TO XXX;

GRANT DROP ANY MATERIALIZED VIEW TO XXX;

 

#建立測試表

create table yugong_example_a(

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)

);

 

#插入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);

commit;

目標端MySQL

#建立資料庫、使用者、授權

mysql> create database test;

mysql> grant all on test.* to 'test'@'%' identified by 'test';

mysql> flush privileges;

 

#建立測試表

create table yugong_example_mysql_a

 (   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)

 );

配置yugong

下載

#目測需要翻牆,下載yugong

Wget

解壓安裝

#解壓到安裝目錄

]# mkdir /data/yugong

]# tar -zxvf yugong-1.0.0.tar.gz -C /data/yugong

]# cd /data/yugong

配置屬性檔案

]# 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=MYSQL

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_join,yugong_example_oracle,yugong_example_two

yugong.table.white= yugong_example_a

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

注:上面紅色部分按照實際環境修改,這裡是測試而已,其他選項暫時都是預設的,具體的選項說明參考:

使用過程中,可以根據實際需要配置

配置資料轉換邏輯

如果要遷移的oraclemysql的表結構不同,比如表名,欄位名有差異,欄位型別不相容,需要使用自定義資料轉換。如果完全相同那就可以跳過此章節

整個資料流為:DB -> Extractor -> DataTranslator -> Applier -> DB,本程式預留DataTranslator介面,允許外部使用者自定義資料處理邏輯,比如:

1.   表名不同

2.   欄位名不同

3.   欄位型別不同

4.   欄位個數不同

5.   執行過程join其他表的資料做計算等

說明

1.DataTranslator目前僅支援java擴充套件,允許使用者完成類實現後,將類原始檔放置到conf/translator/目錄下,yugong啟動後會進行動態編譯.

2.DataTranslator目前查詢規則會根據表名自動查詢,比如需要處理的表為dbname.test_all_one_pk,查詢的時候會test_all_one_pk轉化為TestAllOnePk + 固定DataTranslator字尾. (如果當前classpath中存在,優先使用classpath,如果不存在,則到conf/translator中查詢該名字的java檔案進行動態編譯)

3.目前提供了幾個樣例,可參見解壓後的conf/translator/目錄

a. YugongExampleOracleDataTranslator  (當前例子,介紹oracle一張表和mysql一張表之間的轉換處理)

b. YugongExampleJoinDataTranslator  (介紹oracle多張表和mysql一張表之間的轉換處理,oracle中會透過一張表為主表,執行時join查詢出其他表資料,合併同步到mysql)

c. YugongExampleTwoDataTranslator (介紹oracle一張表和mysql多張表之間的轉換處理,oracle的一張大表資料,可執行時拆分後輸出到多張mysql表上)

 

綜上,如果源端和目標端的表存在表名稱、欄位名、資料型別、欄位個數等不同,則遷移需要配置表名稱對應的DataTranslator,也就是有多少張存在不同的表相應就需要配置多少個DataTranslator。如果懂java的同學可以透過類的繼承(實現同一型別的不同選項)的配置。

自定義資料轉換

上文準備的測試環境的源端oracle的表yugong_example_a和目標端mysql的表yugong_example_mysql_a有如下不同的地方:

1. table名不同. oracle中為yugong_example_amysql中為yugong_example_mysql_a

2. 欄位名字不同. oracle中的name欄位,對映到mysqldisplay_name

3. 欄位邏輯處理. mysqldisplay_name欄位資料來源為oracle庫的:name+'('alias_name+')'

4. 欄位型別不同. oracle中的amountnumber型別,對映到mysqlamountvarchar文字型

5. 源庫多一個欄位. oracle中多了一個alias_name欄位

6. 目標庫多了一個欄位.mysql中多了一個gmt_move欄位,(簡單的用遷移時的當前時間進行填充)

故,需要根據不同的地方定義DataTranslator.java,下文的YugongExampleADataTranslator.java中配置這裡的6項不同之處

 

根據DataTranslator目前查詢規則會根據表名自動查詢

#源庫oracle的表為yugong_example_mysql_a,故對應conf/translator/YugongExampleADataTranslator.java

]# vi conf/translator/YugongExampleADataTranslator.java 

//由檔名可定位源表為:yugong_example_mysql_a

package com.taobao.yugong.translator;

 

import java.sql.Types;

import java.util.Date;

 

import org.apache.commons.lang.ObjectUtils;

 

import com.taobao.yugong.common.db.meta.ColumnMeta;

import com.taobao.yugong.common.db.meta.ColumnValue;

import com.taobao.yugong.common.model.record.Record;

 

public class YugongExampleADataTranslator extends AbstractDataTranslator implements DataTranslator {

 

    public boolean translator(Record record) {

        // 1. schema/table名不同

        // record.setSchemaName("test");

        record.setTableName("yugong_example_mysql_a");

            //至此可知道源表為yugong_example_a,目標表為yugong_example_mysql

      

 // 2. 欄位名字不同,

//源表的name欄位,目標表中叫display_name

        ColumnValue nameColumn = record.getColumnByName("name");

        if (nameColumn != null) {

            nameColumn.getColumn().setName("display_name");

        }

       

 // 3. 欄位邏輯處理

        ColumnValue aliasNameColumn = record.getColumnByName("alias_name");

        if (aliasNameColumn != null) {

            StringBuilder displayNameValue = new StringBuilder(64);

            displayNameValue.append(ObjectUtils.toString(nameColumn.getValue()))

                .append('(')

                .append(ObjectUtils.toString(aliasNameColumn.getValue()))

                .append(')');

            nameColumn.setValue(displayNameValue.toString());

        }

 

        // 4. 欄位型別不同

        ColumnValue amountColumn = record.getColumnByName("amount");

        if (amountColumn != null) {

            amountColumn.getColumn().setType(Types.VARCHAR);

            amountColumn.setValue(ObjectUtils.toString(amountColumn.getValue()));

        }

 

        // 5. 源庫多一個欄位

        record.removeColumnByName("alias_name");

 

        // 6. 目標庫多了一個欄位

        ColumnMeta gmtMoveMeta = new ColumnMeta("gmt_move", Types.TIMESTAMP);

        ColumnValue gmtMoveColumn = new ColumnValue(gmtMoveMeta, new Date());

        gmtMoveColumn.setCheck(false);// 該欄位不做對比

        record.addColumn(gmtMoveColumn);

 

        // ColumnValue text_c = record.getColumnByName("text_c");

        // try {

        // text_c.setValue(new String((byte[]) text_c.getValue(), "GBK"));

        // } catch (UnsupportedEncodingException e) {

        // e.printStackTrace();

        // }

        return super.translator(record);

    }

}

啟動yugong

]# cd /data/yugong

]# bin/startup.sh

bin/startup.sh: line 54: get_pid: command not found

cd to /data/yugong/bin for workaround relative path

LOG CONFIGURATION : /data/yugong/bin/../conf/logback.xml

yugong conf : /data/yugong/bin/../conf/yugong.properties

CLASSPATH :/data/yugong/bin/../conf:/data/yugong/bin/../lib/yugong-1.0.0.jar:/data/yugong/bin/../lib/spring-2.5.6.jar:/data/yugong/bin/../lib/slf4j-api-1.6.0.jar:/data/yugong/bin/../lib/oro-2.0.8.jar:/data/yugong/bin/../lib/ojdbc14-10.2.0.3.0.jar:/data/yugong/bin/../lib/mysql-connector-java-5.1.35.jar:/data/yugong/bin/../lib/mail-1.4.7.jar:/data/yugong/bin/../lib/logback-core-1.0.6.jar:/data/yugong/bin/../lib/logback-classic-1.0.6.jar:/data/yugong/bin/../lib/jcl-over-slf4j-1.6.0.jar:/data/yugong/bin/../lib/guava-r09.jar:/data/yugong/bin/../lib/fastjson-1.2.8.jar:/data/yugong/bin/../lib/druid-1.0.17.jar:/data/yugong/bin/../lib/commons-logging-1.1.1.jar:/data/yugong/bin/../lib/commons-lang-2.6.jar:/data/yugong/bin/../lib/commons-io-2.4.jar:/data/yugong/bin/../lib/commons-configuration-1.9.jar:/data/yugong/bin/../lib/activation-1.1.jar:.:/usr/java/jdk1.6.0_31/lib:/usr/java/jdk1.6.0_31/jre/lib:

cd to /data/yugong for continue

檢視log

#log的位置,

]# ls /data/yugong/logs/ -l

drwxr-xr-x 2 root root 4096  3 28 11:11 TEST.YUGONG_EXAMPLE_A

drwxr-xr-x 2 root root 4096  3 28 11:11 yugong

注:你會發現logs目錄下有同步的源庫.表命名的資料夾

其中:yugong目錄下的table.log是主日誌

      源庫.表命名的目錄下的table.log是此表同步的日誌

 

#檢視yugong的主log

]# tail -f /data/yugong/logs/yugong/table.log

2016-03-28 11:11:21.516 [main] INFO  com.taobao.yugong.YuGongLauncher - ## start the YuGong.

2016-03-28 11:11:21.598 [main] INFO  com.taobao.yugong.controller.YuGongController - check source database connection ...

2016-03-28 11:11:21.625 [main] INFO  com.taobao.yugong.controller.YuGongController - check source database is ok

2016-03-28 11:11:21.625 [main] INFO  com.taobao.yugong.controller.YuGongController - check target database connection ...

2016-03-28 11:11:21.645 [main] INFO  com.taobao.yugong.controller.YuGongController - check target database is ok

2016-03-28 11:11:21.646 [main] INFO  com.taobao.yugong.controller.YuGongController - check source tables read privileges ...

2016-03-28 11:11:21.771 [main] INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited

2016-03-28 11:11:22.933 [main] INFO  com.taobao.yugong.controller.YuGongController - check source tables is ok.

2016-03-28 11:11:23.572 [main] INFO  com.taobao.yugong.controller.YuGongController - ## prepare start tables[1] with concurrent[5]

2016-03-28 11:11:23.975 [main] INFO  com.taobao.yugong.YuGongLauncher - ## the YuGong is running now ......

2016-03-28 11:11:23.976 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  com.taobao.yugong.controller.YuGongInstance - table[TEST.YUGONG_EXAMPLE_A] is start

2016-03-28 11:11:23.980 [main] INFO  com.taobao.yugong.YuGongLauncher -

[YuGong Version Info]

[version ]

[hexVeision]

[date    ]2016-03-05 02:02:14

[branch  ]master

[url     ]git@github.com:alibaba/yugong.git

 

2016-03-28 11:12:23.578 [pool-2-thread-2] INFO  com.taobao.yugong.common.stats.ProgressTracer - {未啟動:0,全量中:0,增量中:0,已追上:1,異常數:0}

2016-03-28 11:12:23.579 [pool-2-thread-2] INFO  com.taobao.yugong.common.stats.ProgressTracer - 已完成:[TEST.YUGONG_EXAMPLE_A]

2016-03-28 11:13:23.579 [pool-2-thread-2] INFO  com.taobao.yugong.common.stats.ProgressTracer - {未啟動:0,全量中:0,增量中:0,已追上:1,異常數:0}

2016-03-28 11:13:23.579 [pool-2-thread-2] INFO  com.taobao.yugong.common.stats.ProgressTracer - 已完成:[TEST.YUGONG_EXAMPLE_A]

 

#檢視錶同步的log

]# tail -f /data/yugong /logs/TEST.YUGONG_EXAMPLE_A/table.log

2016-03-28 11:11:23.730 [main] INFO  c.t.yugong.extractor.oracle.OracleRecRecordExtractor - create mlog successed. sql : CREATE MATERIALIZED VIEW LOG ON TEST.YUGONG_EXAMPLE_A with  primary key

2016-03-28 11:11:23.749 [main] INFO  c.t.y.e.o.OracleFullRecordExtractor$ContinueExtractor - TEST.YUGONG_EXAMPLE_A start postion:0

2016-03-28 11:11:23.770 [main] INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-2} inited

2016-03-28 11:11:23.975 [main] INFO  com.taobao.yugong.controller.YuGongInstance - table[TEST.YUGONG_EXAMPLE_A] start successful. extractor:com.taobao.yugong.extractor.oracle.OracleAllRecordEx

tractor , applier:com.taobao.yugong.applier.AllRecordApplier

2016-03-28 11:11:24.039 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  c.t.yugong.extractor.oracle.OracleAllRecordExtractor - table [TEST.YUGONG_EXAMPLE_A] full extractor is end , next auto star

t inc extractor

2016-03-28 11:11:24.786 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[TEST.YUGONG_EXAMPLE_A] now is NO_UPDATE ...

2016-03-28 11:11:25.790 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[TEST.YUGONG_EXAMPLE_A] now is NO_UPDATE ...

日誌中:

1.full extractor is end , next auto start inc extractor #代表全量遷移已完成,自動進入增量模式

2.now is NO_UPDATE #代表增量表暫時無日誌

源端oracle執行增量變更

#在源庫oracle上對源表進行增量變更

insert into yugong_example_a values(3,'test','test',88,188, NULL , NULL ,sysdate,sysdate) ;

update yugong_example_a set alias_name = 'superman' where id = 1;

#檢視錶同步日誌

]# tail -f /data/yugong /logs/TEST.YUGONG_EXAMPLE_A/table.log

2016-03-28 13:08:35.132 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[TEST.YUGONG_EXAMPLE_A] now is NO_UPDATE ...

2016-03-28 13:08:36.136 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[TEST.YUGONG_EXAMPLE_A] now is CATCH_UP ...

2016-03-28 13:08:36.237 [YuGongInstance-TEST.YUGONG_EXAMPLE_A] INFO  c.t.y.e.oracle.OracleMaterializedIncRecordExtractor - table[TEST.YUGONG_EXAMPLE_A] now is NO_UPDATE ..

# 會瞬間出現now is CATCH_UP,代表剛完成處理了增量資料

目標端mysql檢視結果

select * from yugong_example_mysql_a;

image

#可以看到全量和後面增量的操作都已經同步

附:yugong對應日誌結構為:

logs/

  - yugong/  #系統根日誌

     - table.log

  - ${table}/  #每張同步表的日誌資訊

     - table.log

     - extractor.log

     - applier.log

     - check.log

1.全量完成的日誌

(會在yugong/table.log ${table}/table.log中出現記錄)

table[OTTER2.TEST_ALL_ONE_PK] is end!

2.增量日誌

(會在${table}/table.log中出現記錄)

table[OTTER2.TEST_ALL_ONE_PK] now is CATCH_UP ... #代表已經追上,最後一次增量資料小於onceCrawNum數量

table[OTTER2.TEST_ALL_ONE_PK] now is NO_UPDATE ... #代表最近一次無增量資料

3.ALL(全量+增量)模式日誌

 (會在${table}/table.log中出現記錄)

table [OTTER2.TEST_ALL_ONE_PK] full extractor is end , next auto start inc extractor #出現這條代表全量已經完成,進入增量模式

4.CHECK日誌

(會在${table}/check.log中出現diff記錄)

-----------------

- Schema: yugong , Table: test_all_one_pk

-----------------

---Pks

        ColumnValue[column=ColumnMeta[index=0,name=ID,type=3],value=2576]

---diff

        ColumnMeta[index=3,name=AMOUNT,type=3] , values : [0] vs [0.0]

5.同步過程資料日誌

會透過extractor.log/applier.log分別記錄extractorapplier的資料記錄,因為有DataTranslator的存在,兩者記錄可能不一致,所以分開兩份記錄.

統計資訊

progress統計,會在主日誌下,輸出當前全量/增量/異常表的資料,可透過該日誌,全域性把握整個遷移任務的進度,輸出類似:

{未啟動:0,全量中:2,增量中:3,已追上:3,異常數:0}

stat統計,會在每個表遷移日誌下,輸出當前遷移的tps資訊

{總記錄數:180000,取樣記錄數:5000,同步TPS:4681,最長時間:215,最小時間:212,平均時間:213}

異常處理

表同步的log error

ERROR com.taobao.yugong.controller.YuGongInstance - retry 1 ,something error happened. caused by com.taobao.yugong.exception.YuGongException: com.taobao.yugong.exception.YuGongException: table[TEST.null] is not found

最終發現,在最初版本的yugong對錶使用yugong進行過同步,則會在conf/positioner目錄下產生一個SHCENMA_TABLE.dat文字,如對錶test. yugong_example_a進行過同步,對應的會有:

/data/yugong/conf/positioner/TEST_YUGONG_EXAMPLE_A.dat 檔案。

如果下次(次檔案存在的情況下)再次對相同的表進行yugong同步,則會報錯,表同步的log中會出現:

table[TEST.null] is not found的異常

解決辦法:刪除此檔案(/data/yugong/conf/positioner/TEST_YUGONG_EXAMPLE_A.dat),再啟動yugong

 

新版本中yugong開發人員 好像已經修復此問題

 

參考連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/27000195/viewspace-2072904/,如需轉載,請註明出處,否則將追究法律責任。

相關文章