使用Canal进行MySql与 PostgresSQL的数据同步
测试环境搭建
1.安装MySql
使用yum安装Mysql8.0
下载mysql yum
地址:https://dev.mysql.com/downloads/repo/yum/
选择对应版本下载
[root@localhost ~]# wget https://dev.mysql.com/get/mysql80-community-release-el8-1.noarch.rpm
--2021-04-02 08:32:51-- https://dev.mysql.com/get/mysql80-community-release-el8-1.noarch.rpm
Resolving dev.mysql.com (dev.mysql.com)... 137.254.60.11
Connecting to dev.mysql.com (dev.mysql.com)|137.254.60.11|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://repo.mysql.com//mysql80-community-release-el8-1.noarch.rpm [following]
--2021-04-02 08:32:53-- https://repo.mysql.com//mysql80-community-release-el8-1.noarch.rpm
Resolving repo.mysql.com (repo.mysql.com)... 23.57.113.239
Connecting to repo.mysql.com (repo.mysql.com)|23.57.113.239|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 30388 (30K) [application/x-redhat-package-manager]
Saving to: ‘mysql80-community-release-el8-1.noarch.rpm’
100%[====================================================================================================================================================================================================================================>] 30,388 --.-K/s in 0s
2021-04-02 08:32:54 (136 MB/s) - ‘mysql80-community-release-el8-1.noarch.rpm’ saved [30388/30388]
[root@localhost ~]#
安装Mysql源
[root@localhost ~]# yum -y localinstall mysql80-community-release-el8-1.noarch.rpm
Loaded plugins: fastestmirror
Examining mysql80-community-release-el8-1.noarch.rpm: mysql80-community-release-el8-1.noarch
Marking mysql80-community-release-el8-1.noarch.rpm as an update to mysql80-community-release-el7-3.noarch
Resolving Dependencies
--> Running transaction check
---> Package mysql80-community-release.noarch 0:el7-3 will be updated
---> Package mysql80-community-release.noarch 0:el8-1 will be an update
--> Finished Dependency Resolution
Dependencies Resolved
==============================================================================================================================================================================================================================================================================
Package Arch Version Repository Size
==============================================================================================================================================================================================================================================================================
Updating:
mysql80-community-release noarch el8-1 /mysql80-community-release-el8-1.noarch 29 k
Transaction Summary
==============================================================================================================================================================================================================================================================================
Upgrade 1 Package
Total size: 29 k
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Updating : mysql80-community-release-el8-1.noarch 1/2
Cleanup : mysql80-community-release-el7-3.noarch 2/2
Verifying : mysql80-community-release-el8-1.noarch 1/2
Verifying : mysql80-community-release-el7-3.noarch 2/2
Updated:
mysql80-community-release.noarch 0:el8-1
Complete!
[root@localhost ~]#
在线安装mysql
yum -y install mysql-server
启动并查看Mysql
[root@localhost ~]# systemctl start mysqld
[root@localhost ~]# systemctl status mysqld
● mysqld.service - MySQL 8.0 database server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; disabled; vendor preset: disabled)
Active: active (running) since Thu 2021-04-01 20:53:35 EDT; 13s ago
Process: 2393 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS)
Process: 2267 ExecStartPre=/usr/libexec/mysql-prepare-db-dir mysqld.service (code=exited, status=0/SUCCESS)
Process: 2242 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS)
Main PID: 2349 (mysqld)
Status: "Server is operational"
Tasks: 39 (limit: 11216)
Memory: 430.2M
CGroup: /system.slice/mysqld.service
└─2349 /usr/libexec/mysqld --basedir=/usr
4月 01 20:53:31 localhost.localdomain systemd[1]: Starting MySQL 8.0 database server...
4月 01 20:53:31 localhost.localdomain mysql-prepare-db-dir[2267]: Initializing MySQL database
4月 01 20:53:35 localhost.localdomain systemd[1]: Started MySQL 8.0 database server.
[root@localhost ~]#
设置开机启动
[root@localhost ~]# systemctl enable mysqld
Created symlink /etc/systemd/system/multi-user.target.wants/mysqld.service → /usr/lib/systemd/system/mysqld.service.
[root@localhost ~]# systemctl daemon-reload
安装vim包,然后查看root账户的临时密码
yum install vim
[root@localhost ~]# grep “temporary password” /var/log/mysqld.log
grep: password”: 没有那个文件或目录
如果没有生产随机密码则需重置密码
步骤如下:
1.修改 /etc/my.cnf配置文件,在 [mysqld]下添加一行:skip-grant-tables=1
这一行配置让 mysqld 启动时不对密码进行验证
2.重启 mysqld 服务:systemctl restart mysqld
3.使用 root 用户登录到 mysql:mysql -u root
4.切换到mysql数据库(use mysql),设置root用户密码:
ALTER user ‘root’@‘localhost’ IDENTIFIED BY ‘root’;
5.修改允许用户远程连接
update user set host="%" where user=“root”
6.查看修改结果
select user,host from user
7.使本次修改立即生效,命令:flush privileges
8、quit命令退出 mysql,编辑 /etc/my.cnf 配置文件,删除 skip-grant-tables=1 这一行
9、systemctl restart mysqld 重启mysqld服务,再用新密码登录即可
附:修改root密码:ALTER USER ‘root’@’%’ IDENTIFIED WITH mysql_native_password BY’root’;
测试连接成功
2. 安装PostgreSQL
PostgreSQL安装见上一篇文章
Canal搭建
工作原理
canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump
协议 MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal ) canal 解析
binary log 对象(原始为 byte 流)
项目github地址:https://github.com/alibaba/canal
准备工作
首先我们需要对Mysql数据库进行设置,开启 Binlog 写入功能,配置 binlog-format 为 ROW 模式
在/etc/my.cnf 中增加如下配置:
log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
binlog_row_image=FULL #解决update更新会无效的问题
binlog-do-db=canal_test #需要复制的数据库名,如果复制多个数据库,重复设置这个选项即可。非必填
重启数据库使配置生效
[root@localhost ~]# systemctl restart mysqld
然后为mysql创建一个canal用户备用
CREATE USER canal IDENTIFIED BY 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;
同样的,对Postgresql也创建一个canal用户并授权
CREATE user canal CREATEDB PASSWORD 'canal' LOGIN;
ALTER user canal WITH superuser;
canal-deployer安装使用
下载 canal, 访问 项目github地址 页面 , 选择需要的包下载, 如以 1.1.5-alpha-2 版本为例
[root@localhost ~]# wget https://github.com/alibaba/canal/releases/download/canal-1.1.5-alpha-2/canal.deployer-1.1.5-SNAPSHOT.tar.gz
下载完成后,创建目录,然后解压缩
[root@localhost ~]# mkdir /tmp/canal
[root@localhost ~]# tar zxvf canal.deployer-1.1.5-SNAPSHOT.tar.gz -C /tmp/canal
解压缩完成后目录如下
修改instance配置文件(如果使用canal-admin进行管理,这一步可以跳过)
修改配置文件:conf/example/instance.properties
将数据库地址改为自己的地址
启动
sh /tmp/canal/bin/startup.sh
查看 server 日志,输入:wq退出
[root@localhost ~]# vi /tmp/canal/logs/canal/canal.log
2021-04-02 01:47:25.255 [main] INFO com.alibaba.otter.canal.deployer.CanalLauncher - ## set default uncaught exception handler
2021-04-02 01:47:25.373 [main] INFO com.alibaba.otter.canal.deployer.CanalLauncher - ## load canal configurations
2021-04-02 01:47:25.451 [main] INFO com.alibaba.otter.canal.deployer.CanalStarter - ## start the canal server.
2021-04-02 01:47:25.651 [main] INFO com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[192.168.60.3(192.168.60.3):11111]
2021-04-02 01:47:29.748 [main] INFO com.alibaba.otter.canal.deployer.CanalStarter - ## the canal server is running now ......
查看 instance 的日志,输入:wq退出
[root@localhost ~]# vi /tmp/canal/logs/example/example.log
2021-04-02 01:47:27.137 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
2021-04-02 01:47:27.140 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]
2021-04-02 01:47:27.912 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
2021-04-02 01:47:27.913 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]
2021-04-02 01:47:29.184 [main] INFO c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-example
2021-04-02 01:47:29.223 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table filter : ^.*\..*$
2021-04-02 01:47:29.223 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table black filter : ^mysql\.slave_.*$
2021-04-02 01:47:29.266 [main] INFO c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....
ps:推荐使用tail -f 命令查看日志
tail -f /tmp/canal/logs/example/example.log
canal启动完成
canal-admin安装使用
介绍:
canal-admin设计上是为canal提供整体配置管理、节点运维等面向运维的功能,提供相对友好的WebUI操作界面,方便更多用户快速和安全的操作,canal-admin可以在web页面中直接修改canal-server的配置信息等。
设计理念:
canal-admin的核心模型主要有:
1.instance,对应canal-server里的instance,一个最小的订阅mysql的队列
2.server,对应canal-server,一个server里可以包含多个instance
3.集群,对应一组canal-server,组合在一起面向高可用HA的运维
下载压缩包:
[root@localhost ~]# wget https://github.com/alibaba/canal/releases/download/canal-1.1.5-alpha-2/canal.admin-1.1.5-SNAPSHOT.tar.gz
解压、安装:
[root@localhost ~]# mkdir /tmp/canal-admin
[root@localhost ~]# tar zxvf canal.admin-1.1.5-SNAPSHOT.tar.gz -C /tmp/canal-admin
启动:
sh /tmp/canal-admin/bin/startup.sh
启动完成后访问地址:
http://192.168.60.3:8089
默认账号密码为:
admin 123456
点击新建server
填写上一步启动的canal-server地址,端口号使用默认值既可
修改配置
点击载入模板,使用默认值保存既可
这里需要将账号密码记下来,后面客户端建立连接时需要使用
如果需要修改密码,注意密码是16进制的,字母组合只能使用A-F(我这里第一次测试没用16进制,是可以的。但是后来报了一个密码转16进制无法转换的错误,尽量使用可以避免问题发生)
然后新建instance实例
填写instance名称和主机地址,然后点载入模板保存既可。
这里的instance名称example是canal-server安装时的默认值,对应的是在/tmp/canal/conf目录下的example文件夹。
新建的instance名称需要与/tmp/canal/conf目录下的文件夹名对应,如果不想使用example的话,直接复制粘贴example重命名既可
instance名称对应服务器文件为:
/tmp/canal/conf/example/instance.properties
其中example为instance名称
在web端修改完成就会自动将配置上传至canal服务器对应的目录下,配置是动态生效的
查看状态没问题,canal-admin配置完成
canal-adapter安装使用
官方介绍:
canal 1.1.1版本之后, 增加客户端数据落地的适配及启动功能, 目前支持功能:
- 客户端启动器
- 同步管理REST接口
- 日志适配器, 作为DEMO
- 关系型数据库的数据同步(表对表同步), ETL功能
- HBase的数据同步(表对表同步), ETL功能
- (后续支持) ElasticSearch多表数据同步,ETL功能
下载canal-adapter压缩包
[root@localhost ~]# wget https://github.com/alibaba/canal/releases/download/canal-1.1.5-alpha-2/canal.adapter-1.1.5-SNAPSHOT.tar.gz
安装adapter:
[root@localhost ~]# mkdir /tmp/canal-adapter
[root@localhost ~]# tar zxvf canal.adapter-1.1.5-SNAPSHOT.tar.gz -C /tmp/canal-adapter
我这里使用了远程配置中心(Mysql,可扩展)作为统一配置管理,
修改配置文件:
/tmp/canal-adapter/conf/bootstrap.yml
canal:
manager:
jdbc:
url: jdbc:mysql://192.168.60.3:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
username: canal
password: canal
canal_config表id=2的数据对应adapter下的application.yml文件
canal_adapter_config表对应每个adapter的子配置文件
content字段对应内容为yml文件内容
注意:必须是表中id=2的数据,这里应该是canal-ui部分没开发完,canal-adapter远程配置和canal-admin服务使用的是同一张表,而且没有web端页面,暂时只能从数据库中配置。
当然,你也可以再建一个数据库,可以避免与canal-admin业务冲突
canal_config.content:
server:
port: 8081
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
canal.conf:
mode: tcp
canalServerHost: 192.168.60.3:11111 # 对应单机模式下的canal server的ip:port
flatMessage: true # 扁平message开关, 是否以json字符串形式投递数据, 仅在kafka/rocketMQ模式下有效
zookeeperHosts:
syncBatchSize: 1000
retries: 0
timeout:
accessKey:
secretKey:
# canalInstances: # canal实例组, 如果是tcp模式可配置此项
consumerProperties:
# canal tcp consumer
canal.tcp.server.host: 192.168.60.3:11111
canal.tcp.zookeeper.hosts:
canal.tcp.batch.size: 500
canal.tcp.username: admin
canal.tcp.password: 4ACFE3202A5FF5CF467898FC58AAB1D615029441
# kafka consumer
kafka.bootstrap.servers: 127.0.0.1:9092
kafka.enable.auto.commit: false
kafka.auto.commit.interval.ms: 1000
kafka.auto.offset.reset: latest
kafka.request.timeout.ms: 40000
kafka.session.timeout.ms: 30000
kafka.isolation.level: read_committed
kafka.max.poll.records: 1000
# rocketMQ consumer
rocketmq.namespace:
rocketmq.namesrv.addr: 127.0.0.1:9876
rocketmq.batch.size: 1000
rocketmq.enable.message.trace: false
rocketmq.customized.trace.topic:
rocketmq.access.channel:
rocketmq.subscribe.filter:
# rabbitMQ consumer
rabbitmq.host:
rabbitmq.virtual.host:
rabbitmq.username:
rabbitmq.password:
rabbitmq.resource.ownerId:
srcDataSources:
centralDS:
url: jdbc:mysql://192.168.60.3:3306/canal_test?useUnicode=true
username: canal
password: canal
canalAdapters:
- instance: mpc
groups:
- groupId: g1
outerAdapters:
# - name: logger
# - name: rdb
# key: mpcDB
# properties:
# jdbc.driverClassName: com.mysql.jdbc.Driver
# jdbc.url: jdbc:mysql://192.168.60.4:3306/canal_test?useUnicode=true
# jdbc.username: canal
# jdbc.password: canal
# - name: rdb
# key: oracle1
# properties:
# jdbc.driverClassName: oracle.jdbc.OracleDriver
# jdbc.url: jdbc:oracle:thin:@localhost:49161:XE
# jdbc.username: mytest
# jdbc.password: m121212
- name: rdb
key: mpcDB
properties:
jdbc.driverClassName: org.postgresql.Driver
jdbc.url: jdbc:postgresql://192.168.60.4:3306/canal_test
jdbc.username: canal
jdbc.password: canal
threads: 1
commitSize: 3000
# - name: hbase
# properties:
# hbase.zookeeper.quorum: 127.0.0.1
# hbase.zookeeper.property.clientPort: 2181
# zookeeper.znode.parent: /hbase
# - name: es
# hosts: 127.0.0.1:9300 # 127.0.0.1:9200 for rest mode
# properties:
# mode: transport # or rest
# # security.auth: test:123456 # only used for rest mode
# cluster.name: elasticsearch
# - name: kudu
# key: kudu
# properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address
canal_adapter_config.content:
dataSourceKey: centralDS # 源数据源的key, 对应上面配置的srcDataSources中的值
destination: mpc # cannal的instance或者MQ的topic
groupId: g1 # 对应MQ模式下的groupId, 只会同步对应groupId的数据
outerAdapterKey: mpcDB # 对应application.yml配置outerAdapters中的key
concurrent: false # 是否按主键hase并行同步, 并行同步的表必须保证主键不会更改及主键不能为其他同步表的外键!!
dbMapping:
# mirrorDb: true # 是否为镜像数据库
database: canal_test # 源数据源的database/shcema
table: sys_user # 源数据源表名
targetTable: canal_test.sys_user # 目标数据源的库名.表名
targetPk: # 主键映射
user_id: user_id # 如果是复合主键可以换行映射多个
# mapAll: true # 是否整表映射, 要求源表和目标表字段名一模一样 (如果targetColumns也配置了映射,则以targetColumns配置为准)
targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填
user_id:
user_name: nick_name
nick_name: user_name
dept_id:
commitBatch: 3000 # 批量提交的大小
在服务启动时,会自动将canal_config表和canal_adapter_config表的content字段数据加载到服务器,生成对应的yml配置文件
启动,查看日志
[root@localhost ~]# sh /tmp/canal-adapter/bin/startup.sh
[root@localhost ~]# tail -f /tmp/canal-adapter/logs/adapter/adapter.log
查看服务开启状态:
http://127.0.0.1:8081/destinations
好了,服务都启动完成了,现在来对Mysql数据库进行增删改查,验证一下结果
结果居然失败了,我这里遇到了两个问题
第一个问题:我这里使用的postgres数据库版本是13,而我用的canal-adapterde 数据库驱动版本是postgresql-42.1.4.jar,是不支持postgres13的。需要手动下载高版本的数据库驱动,将原来的jar替换掉。
我的解决办法:手动将jar包升级到了42.2.18.jar,问题解决。
第二个问题:数据库SQL报错,错误如下
2021-04-07 02:07:41.503 [pool-3-thread-1] ERROR c.a.otter.canal.adapter.launcher.loader.AdapterProcessor - java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.RuntimeException: org.postgresql.util.PSQLException: 错误: 语法错误 在 "`" 或附近的
Position: 47
java.lang.RuntimeException: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.RuntimeException: org.postgresql.util.PSQLException: 错误: 语法错误 在 "`" 或附近的
Position: 47
at com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter.sync(RdbAdapter.java:171) ~[na:na]
at com.alibaba.otter.canal.adapter.launcher.loader.AdapterProcessor.batchSync(AdapterProcessor.java:139) ~[client-adapter.launcher-1.1.5-SNAPSHOT.jar:na]
at com.alibaba.otter.canal.adapter.launcher.loader.AdapterProcessor.lambda$null$1(AdapterProcessor.java:97) ~[client-adapter.launcher-1.1.5-SNAPSHOT.jar:na]
at java.util.concurrent.CopyOnWriteArrayList.forEach(CopyOnWriteArrayList.java:895) ~[na:1.8.0_275]
at com.alibaba.otter.canal.adapter.launcher.loader.AdapterProcessor.lambda$null$2(AdapterProcessor.java:94) ~[client-adapter.launcher-1.1.5-SNAPSHOT.jar:na]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_275]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_275]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_275]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_275]
Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.RuntimeException: org.postgresql.util.PSQLException: 错误: 语法错误 在 "`" 或附近的
经过一番排查发现这是Client-Adapter在1.14版本引入的BUG
修复方案:
github给出的修复方案:https://github.com/alibaba/canal/pull/3020
目前BUG在最新的发行版中并没有修复,那么就需要自己按照以上方案修改源码打包jar
我这人比较懒,修改源码自己打包不是我的风格
既然是1.1.4引入的BUG,而且我查看了1.1.4的更新内容对于我的需求几乎没有影响,所以我决定安装1.1.3版本
下载canal-adapter1.1.3压缩包
wget https://github.com/alibaba/canal/releases/download/canal-1.1.3/canal.adapter-1.1.3.tar.gz
解压,安装:
[root@localhost ~]# mkdir /tmp/canal-adapter-1.1.3
[root@localhost ~]# tar zxvf canal.adapter-1.1.3.tar.gz -C /tmp/canal-adapter-1.1.3
同样的,需要修改postgresql的驱动版本
配置过程省略…与上面的1.1.5版本一致
直接验证结果,发现SQL报错
排查原因结果:在执行插入语句时主键未同步,插入SQL中不包含主键信息。原因是1.1.3版本和1.1.5配置文件规则不同导致
在sys_user.yml文件中,1.1.5版本配置了主键映射targetPk后,字段映射targetColumns会默认把主键带上,而1.1.3版本未加入此功能。所以在targetColumns中将主键也加上就好了,完整配置如下:
dataSourceKey: centralDS # 源数据源的key, 对应上面配置的srcDataSources中的值
destination: mpc # cannal的instance或者MQ的topic
groupId: g1 # 对应MQ模式下的groupId, 只会同步对应groupId的数据
outerAdapterKey: mpcDB # 对应application.yml配置outerAdapters中的key
concurrent: false # 是否按主键hase并行同步, 并行同步的表必须保证主键不会更改及主键不能为其他同步表的外键!!
dbMapping:
database: canal_test # 源数据源的database/shcema
table: sys_user # 源数据源表名
targetDb: canal_test
targetTable: public.sys_user # 目标数据源的库名.模式名.表名
targetPk: # 主键映射
user_id: user_id # 如果是复合主键可以换行映射多个
targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填
user_id: user_id
user_name: nick_name
nick_name: user_name
dept_id:
commitBatch: 3000 # 批量提交的大小
好了,再次进行验证
至此,增删改操作已经完成了同步
查看Canal源码
从Git上下载Canal项目,可以查看源码
https://github.com/alibaba/canal.git
由于github访问速度太慢,我这里使用的是国内镜像地址
https://codechina.csdn.net/mirrors/alibaba/canal.git
参考网站:
https://developer.aliyun.com/article/706226
https://github.com/alibaba/canal/wiki
canal-v1.1.4中文文档地址:
https://www.bookstack.cn/read/canal-v1.1.4/a22d6e84c9c69d82.md