使用canal同步MySQL数据到ES

一、概述

简介

canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。

Git地址:https://github.com/alibaba/canal

docker : https://github.com/alibaba/canal/wiki/Docker-QuickStart

docker-hub: https://hub.docker.com/r/canal/canal-server/tags/

原理

canal模拟MySQL主库和从库的交互协议,向MySQL主库发送dump协议,MySQL主库收到dump请求会向canal推送binlog,canal通过解析binlog将数据同步到其他存储(MySQL、Elasticsearch、HBase)中去。

模块
  • canal-server:监听MySQL的binlog。

  • canal-adapter:相当于canal的客户端,会从canal-server中获取数据,然后对数据进行同步,可以同步到MySQL、Elasticsearch和HBase等存储中去。

  • canal-admin:支持面向WebUI的canal动态管理能力,支持配置、任务、日志等在线白屏运维能力,具体文档:https://github.com/alibaba/canal/wiki/Canal-Admin-Guide

二、配置Mysql

使用版本
软件版本
canal1.1.5
es6.8.0
环境要求

https://github.com/alibaba/canal/wiki/AdminGuide

1. 操作系统

a. 纯java开发,windows/linux均可支持

b. jdk建议使用1.6.25以上的版本,稳定可靠,目前阿里巴巴使用基本为此版本。

2. mysql要求

a. 当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48),ps. mysql4.x版本没有经过严格测试,理论上是可以兼容

b. canal的原理是基于mysql binlog技术,所以这里一定需要开启mysql的binlog写入功能,并且配置binlog模式为row.

[mysqld]  
log-bin=mysql-bin #添加这一行就ok  
binlog-format=ROW #选择row模式  
server_id=1 #配置mysql replaction需要定义,不能和canal的slaveId重复
# 指定同步数据库

数据库重启后, 简单测试 my.cnf 配置是否生效:

mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+

mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+

c. canal的原理是模拟自己为mysql slave,所以这里一定需要做为mysql slave的相关权限

mysql -uroot -p

mysql> CREATE USER canal IDENTIFIED BY 'canal';
mysql> GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
# -- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
mysql> FLUSH PRIVILEGES;

# 针对已有的账户可通过grants查询权限:
mysql> show grants for 'canal';

三、配置canal-server

server代表一个canal运行实例,instance对应于一个数据队列(1个server对应n个instance)。

离线下载

访问:https://github.com/alibaba/canal/tree/gh-pages/download ,会列出所有历史的发布版本包

在线下载
wget https://raw.github.com/alibaba/canal/gh-pages/download/canal.deployer-1.1.5.tar.gz
上传解压
# ---------------------------- canal deployer ----------------------------
# 上传
scp /Users/wangfugui/Downloads/dev/middleware/elasticsearch/tools/canal/canal.deployer-1.1.5.tar.gz root@172.16.227.129:/root/software
# canal.deployer-1.1.5.tar.gz 没有父文件夹
mkdir -p /root/software/canal/canal.deployer-1.1.5
# 解压
tar zxvf canal.deployer-1.1.5.tar.gz -C /root/software/canal/canal.deployer-1.1.5
# ---------------------------- canal adapter ----------------------------
# 上传
scp /Users/wangfugui/Downloads/dev/middleware/elasticsearch/tools/canal/canal.adapter-1.1.5.tar.gz root@172.16.227.129:/root/software
# canal.adapter-1.1.5.tar.gz 没有父文件夹
mkdir -p /root/software/canal/canal.adapter-1.1.5
# 解压
tar zxvf canal.adapter-1.1.5.tar.gz -C /root/software/canal/canal.adapter-1.1.5
# ---------------------------- canal admin ----------------------------
# 上传
scp /Users/wangfugui/Downloads/dev/middleware/elasticsearch/tools/canal/canal.admin-1.1.5.tar.gz root@172.16.227.129:/root/software
# canal.admin-1.1.5.tar.gz 没有父文件夹
mkdir -p /root/software/canal/canal.admin-1.1.5
# 解压
tar zxvf canal.admin-1.1.5.tar.gz -C /root/software/canal/canal.admin-1.1.5
修改配置

配置文件说明

[root@localhost conf]# tree
.
├── canal_local.properties
├── canal.properties # 全局配置,很多配置都可以被 instance.propeties 覆盖,配置通用信息
├── example
│   └── instance.properties # 实例配置,存放实例的非共享配置,比如数据库 IP,账号,密码等
├── logback.xml
├── metrics
│   └── Canal_instances_tmpl.json
└── spring
    ├── base-instance.xml
    ├── default-instance.xml
    ├── file-instance.xml
    ├── group-instance.xml #  多库合并时,可以将多个物理instance合并为一个逻辑instance。
    ├── memory-instance.xml
    └── tsdb
        ├── h2-tsdb.xml
        ├── mysql-tsdb.xml
        ├── sql
        │   └── create_table.sql
        └── sql-map
            ├── sqlmap-config.xml
            ├── sqlmap_history.xml
            └── sqlmap_snapshot.xml
单机配置

https://github.com/alibaba/canal/wiki/QuickStart

集群配置

https://github.com/alibaba/canal/wiki/AdminGuide#ha%E6%A8%A1%E5%BC%8F%E9%85%8D%E7%BD%AE

分库分表配置

处理分表分库的场景,主要是要使用配置group-instance.xml。group-instance主要针对需要进行多库合并时,可以将多个物理instance合并为一个逻辑instance,提供客户端访问。

1.修改全局配置

https://github.com/alibaba/canal/wiki/AdminGuide

vim /root/software/canal/canal.deployer-1.1.5/conf/canal.properties
#############################################################
#########               common argument         #############
#############################################################
canal.ip = 172.16.227.129
canal.port = 11111
# canal.instance.parser.parallel = false # 如果系统是1个 cpu,设置为 false
# canal.instance.parser.parallelThreadSize = 2 # cpu个数
#############################################################
#########               destinations            #############
#############################################################
# 当前server上部署的instance列表:可设置多个,需要在canal.conf.dir对应的目录下建立同名的文件
canal.destinations = mysql_server_0,mysql_server_1,mysql_server_2,mysql_server_3

# 启动 group-instance.xml
# 解析数据库信息 : <property name="address" value="${canal.instance.master1.address}" />
canal.instance.global.spring.xml = classpath:spring/group-instance.xml
2.实例配置
垂直分库

有几个数据库,复制几份canal instance的文件夹:

cd /root/software/canal/canal.deployer-1.1.5/conf
cp -R example mysql_server_0;

修改每个实例配置

vim /root/software/canal/canal.deployer-1.1.5/conf/mysql_server_0/instance.properties

# position info (group-instance.xml中解析默认为canal.instance.master1.address)
canal.instance.master1.address=172.25.1.246:3307
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
# mq config # topic用同一个,客户端只监听这一个,也可不同示例配成不同,客户端分别监听
canal.mq.topic=enterprise_topic
# 从库配置
#canal.instance.standby.address =

# -------------------------- 监听表 --------------------------
# table regex : 监听表规则(Perl正则表达式,多规则用逗号分隔)
canal.instance.filter.regex=.*\\..*
# table black regex : 过滤表规则
canal.instance.filter.black.regex=mysql\\.slave_.*
# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
canal.instance.filter.field=db_toker_*.tbl_enterprise_mtype_*:id/eid/credit_no/full_name,db_toker_*.tbl_enterprise_stime_*:id/eid/credit_no/full_name
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch

配置好第一个,其他实例只改数据库连接。

cp -R mysql_server_0 mysql_server_1;
cp -R mysql_server_0 mysql_server_2;
cp -R mysql_server_0 mysql_server_3;
rm -rf example;
水平分库

修改全局配置

vim /root/software/canal/canal.deployer-1.1.5/conf/canal.properties
canal.destinations = mysql_server

修改实例配置

cd /root/software/canal/canal.deployer-1.1.5/conf
mv example mysql_server;

vim /root/software/canal/canal.deployer-1.1.5/conf/mysql_server/instance.properties

# position info
canal.instance.master1.address=172.25.1.246:3307
canal.instance.master1.journal.name=
canal.instance.master1.position=
canal.instance.master1.timestamp=
canal.instance.master1.gtid=

# position info
canal.instance.master2.address=172.25.1.246:3308
canal.instance.master2.journal.name=
canal.instance.master2.position=
canal.instance.master2.timestamp=
canal.instance.master2.gtid=

# position info
canal.instance.master3.address=172.25.1.246:3309
canal.instance.master3.journal.name=
canal.instance.master3.position=
canal.instance.master3.timestamp=
canal.instance.master3.gtid=

# position info
canal.instance.master4.address=172.25.1.246:3310
canal.instance.master4.journal.name=
canal.instance.master4.position=
canal.instance.master4.timestamp=
canal.instance.master4.gtid=

# mq config
canal.mq.topic=enterprise_topic
3.修改group-instance.xml

主要针对需要进行多库合并时,可以将多个物理instance合并为一个逻辑instance,提供客户端访问。

场景:分库业务。 比如产品数据拆分了4个库,每个库会有一个instance,如果不用group,业务上要消费数据时,需要启动4个客户端,分别链接4个instance实例。使用group后,可以在canal server上合并为一个逻辑instance,只需要启动1个客户端,链接这个逻辑instance即可。

vim /root/software/canal/canal.deployer-1.1.5/conf/spring/
# -------------------- 修改 eventParser --------------------
        <bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.group.GroupEventParser">
                <property name="eventParsers">
                        <list>
                                <ref bean="eventParser1" />
                                <ref bean="eventParser2" />
                                <ref bean="eventParser3" />
                                <ref bean="eventParser4" />
                        </list>
                </property>
        </bean>
# -------------------- 增加 eventParser3,4 --------------------
<bean id="eventParser3" parent="baseEventParser">
		<property name="destination" value="${canal.instance.destination}" />
		<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
		<!-- 心跳配置 -->
		<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
		<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
		<property name="detectingIntervalInSeconds" value="${canal.instance.detecting.interval.time:5}" />
		<property name="haController">
			<bean class="com.alibaba.otter.canal.parse.ha.HeartBeatHAController">
				<property name="detectingRetryTimes" value="${canal.instance.detecting.retry.threshold:3}" />
				<property name="switchEnable" value="${canal.instance.detecting.heartbeatHaEnable:false}" />
			</bean>
		</property>

		<property name="alarmHandler" ref="alarmHandler" />

		<!-- 解析过滤处理 -->
		<property name="eventFilter">
			<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
				<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
			</bean>
		</property>

		<property name="eventBlackFilter">
			<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
				<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
				<constructor-arg index="1" value="false" />
			</bean>
		</property>
		<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
		<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />

		<!-- 网络链接参数 -->
		<property name="receiveBufferSize" value="${canal.instance.network.receiveBufferSize:16384}" />
		<property name="sendBufferSize" value="${canal.instance.network.sendBufferSize:16384}" />
		<property name="defaultConnectionTimeoutInSeconds" value="${canal.instance.network.soTimeout:30}" />

		<!-- 解析编码 -->
		<!-- property name="connectionCharsetNumber" value="${canal.instance.connectionCharsetNumber:33}" /-->
		<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />

		<!-- 解析位点记录 -->
		<property name="logPositionManager">
			<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
		</property>

		<!-- failover切换时回退的时间 -->
		<property name="fallbackIntervalInSeconds" value="${canal.instance.fallbackIntervalInSeconds:60}" />

		<!-- 解析数据库信息 -->
		<property name="masterInfo">
			<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo" init-method="initPwd">
				<property name="address" value="${canal.instance.master3.address}" />
				<property name="username" value="${canal.instance.dbUsername:retl}" />
				<property name="password" value="${canal.instance.dbPassword:retl}" />
				<property name="pwdPublicKey" value="${canal.instance.pwdPublicKey:retl}" />
				<property name="enableDruid" value="${canal.instance.enableDruid:false}" />
				<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:}" />
			</bean>
		</property>
		<property name="standbyInfo">
			<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo" init-method="initPwd">
				<property name="address" value="${canal.instance.standby3.address}" />
				<property name="username" value="${canal.instance.dbUsername:retl}" />
				<property name="password" value="${canal.instance.dbPassword:retl}" />
				<property name="pwdPublicKey" value="${canal.instance.pwdPublicKey:retl}" />
				<property name="enableDruid" value="${canal.instance.enableDruid:false}" />
				<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:}" />
			</bean>
		</property>

		<!-- 解析起始位点 -->
		<property name="masterPosition">
			<bean class="com.alibaba.otter.canal.protocol.position.EntryPosition">
				<property name="journalName" value="${canal.instance.master3.journal.name}" />
				<property name="position" value="${canal.instance.master3.position}" />
				<property name="timestamp" value="${canal.instance.master3.timestamp}" />
				<property name="gtid" value="${canal.instance.master3.gtid}" />
			</bean>
		</property>
		<property name="standbyPosition">
			<bean class="com.alibaba.otter.canal.protocol.position.EntryPosition">
				<property name="journalName" value="${canal.instance.standby3.journal.name}" />
				<property name="position" value="${canal.instance.standby3.position}" />
				<property name="timestamp" value="${canal.instance.standby3.timestamp}" />
				<property name="gtid" value="${canal.instance.standby3.gtid}" />
			</bean>
		</property>
		<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
		<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
		<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
		<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
		<property name="filterDmlInsert" value="${canal.instance.filter.dml.insert:false}" />
		<property name="filterDmlUpdate" value="${canal.instance.filter.dml.update:false}" />
		<property name="filterDmlDelete" value="${canal.instance.filter.dml.delete:false}" />
		<property name="filterRows" value="${canal.instance.filter.rows:false}" />
		<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
		<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
		<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />

		<!-- parallel parser -->
		<property name="parallel" value="${canal.instance.parser.parallel:true}" />
		<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
		<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />

		<property name="autoResetLatestPosMode" value="${canal.auto.reset.latest.pos.mode:false}" />
	</bean>
4.启动监听
cd /root/software/canal/canal.deployer-1.1.5/bin
# 虚机1核1G启动失败,改成1核2G启动成功
./startup.sh
# 查看canal进程
ps aux | grep canal
# 日志路径
cd /root/software/canal/canal.deployer-1.1.5/logs
#./stop.sh

https://github.com/alibaba/canal/blob/master/example/src/main/java/com/alibaba/otter/canal/example

客户端过滤数据会覆盖服务端过滤规则,按需配置。

四、配置canal-adapter

同步ES配置 : Sync-ES

  • Adapter本质上是为了将canal-server订阅到的实时增量数据进行消费,相当于client。

  • client-adapter分为适配器和启动器两部分, 适配器为多个fat jar, 每个适配器会将自己所需的依赖打成一个包, 以SPI的方式让启动器动态加载, 目前所有支持的适配器都放置在plugin目录下。

  • logback.xml中默认日志等级为debug,线上使用时,记得改info。

1 修改启动配置
vim /root/software/canal/canal.adapter-1.1.5/conf/application.yml

canal.conf:
  consumerProperties:
    # canal tcp consumer
    canal.tcp.server.host: 127.0.0.1:11111
    canal.tcp.zookeeper.hosts:
    canal.tcp.batch.size: 500
    canal.tcp.username:
    canal.tcp.password:
  canalAdapters:
  - instance: mysql_server # canal instance Name or mq topic name
    groups:
    - groupId: g1
      outerAdapters:
      - name: logger
      - name: es
        hosts: 172.16.227.129:9300,172.16.227.130:9300,172.16.227.131:9300 # 127.0.0.1:9200 for rest mod(es 集群地址, 逗号分隔)
        properties:
          mode: rest # or rest
          # security.auth: test:123456 #  only used for rest mode
          cluster.name: my-es-cluster

adapter将会自动加载 conf/es 下的所有.yml结尾的配置文件

说明:

  1. 一份数据可以被多个group同时消费, 多个group之间会是一个并行执行, 一个group内部是一个串行执行多个outerAdapters
  2. 目前client adapter数据订阅的方式支持两种,直连canal server 或者 订阅kafka/RocketMQ的消息
2 配置映射文件

使用adapter同步mysql数据至es,需要预先在es中建立mapping关系,每个索引都需要定义一个配置。

修改 conf/es/mytest_user.yml文件:

dataSourceKey: defaultDS        # 源数据源的key, 对应上面配置的srcDataSources中的值
outerAdapterKey: exampleKey     # 对应application.yml中es配置的key 
destination: mysql_server_0     # cannal的instance或者MQ的topic
groupId: g1                     # 对应MQ模式下的groupId, 只会同步对应groupId的数据
esMapping:
  _index: enterprise        # es 的索引名称
  _type: _doc                   # es 的type名称, es7下无需配置此项
  _id: _id                      # es 的_id, 如果不配置该项必须配置下面的pk项_id则会由es自动分配
#  pk: id                       # 如果不需要_id, 则需要指定一个属性为主键属性
  # sql映射
  sql: "select a.id as _id, a.name, a.role_id, a.c_time from user a"
#  objFields:
#    _labels: array:;           # 数组或者对象属性, array:; 代表以;字段里面是以;分隔的
#    _obj: object               # json对象
  etlCondition: ""              # etl 的条件参数
  commitBatch: 3000             # 提交批大小

该sql对应的es mapping示例:

{
    "mytest_user": {
        "mappings": {
            "_doc": {
                "properties": {
                    "name": {
                        "type": "text"
                    },
                    "role_id": {
                        "type": "long"
                    },
                    "c_time": {
                        "type": "date"
                    }
                }
            }
        }
    }
}

ClientAdapter目前只支持垂直分库,不支持水平分库,待完善。

  • (后续支持) ElasticSearch多表数据同步,ETL功能
3 启动ES数据同步
查询所有订阅同步
curl http://127.0.0.1:8081/destinations
数据同步开关
  • 增量同步

    默认值开启DML 同步,如果需要使用DDL同步能力,需要在rdb中将配置mirrorDb为true注释打开。

  • 全量同步

    参考官网 https://github.com/alibaba/canal/wiki/ClientAdapter#322-%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E5%BC%80%E5%85%B3

# 开关
curl http://127.0.0.1:8081/syncSwitch/example/off -X PUT
# 开关状态
curl http://127.0.0.1:8081/syncSwitch/example
启动
# 启动canal-adapter启动器
./startup.sh

4.验证
  1. 新增mysql mytest.user表的数据, 将会自动同步到es的mytest_user索引下面, 并会打出DML的log
  2. 修改mysql mytest.role表的role_name, 将会自动同步es的mytest_user索引中的role_name数据
  3. 新增或者修改mysql mytest.label表的label, 将会自动同步es的mytest_user索引中的labels数据

五、配置canal-admin

# 在线下载
wget https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.admin-1.1.5.tar.gz

# 解压
tar zxvf canal.admin-1.1.5.tar.gz

# 创建canal-admin对应数据库
  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码上富贵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值