如果有一个这样的需求,数据修改后,需要及时将mysql中修改的记录同步到elasticsearch中,我们怎么来实现呢?
思考30s…,带着这个疑问我们来看一下canal如何为我们解决的
1.CANAL是什么
canal是阿里巴巴旗下的一款开源项目,纯Java开发。
https://github.com/alibaba/canal
1.1. CANAL的历史
- canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费
- 早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。
1.2. CANAL的功能
- 基于日志增量订阅和消费的业务包括
- 数据库镜像
- 数据库实时备份
- 索引构建和实时维护(拆分异构索引、倒排索引等
- 业务 cache 刷新
- 带业务逻辑的增量数据处理
- 当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
- canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递到 MQ 系统中,比如 Kafka/RocketMQ,可以借助于 MQ 的多语言能力
- canal 特别设计了 client-server 模式,交互协议使用 protobuf 3.0 , client 端可采用不同语言实现不同的消费逻辑
- canal java 客户端: https://github.com/alibaba/canal/wiki/ClientExample
- canal c# 客户端:
https://github.com/dotnetcore/CanalSharp - canal go客户端:
https://github.com/CanalClient/canal-go - canal php客户端:
https://github.com/xingwenge/canal-php - canal Python客户端:
https://github.com/haozi3156666/canal-python - canal Rust客户端:
https://github.com/laohanlinux/canal-rs
2.CANAL的工作原理
2.1. mysql主备工作原理
从上层来看,复制分成三步:
- MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
show binlog events
- MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
2.2 canal 工作原理
canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
canal 解析 binary log 对象(原始为 byte 流)
2.3. 基础架构
说明:
- server代表一个canal运行实例,对应于一个jvm
- instance对应于一个数据队列 (1个server对应1…n个instance)
instance模块:
- eventParser (数据源接入,模拟slave协议和master进行交互,协议解析)
- eventSink (Parser和Store链接器,进行数据过滤,加工,分发的工作)
- eventStore (数据存储)
metaManager (增量订阅&消费信息管理器)
2.3.1 科普一下:mysql的Binlay Log介绍
简单点说:
- mysql的binlog是多文件存储,定位一个LogEvent需要通过binlog filename + binlog position,进行定位
- mysql的binlog数据格式,按照生成的方式,主要分为:statement-based、row-based、mixed。
目前canal支持所有模式的增量订阅(但配合同步时,因为statement只有sql,没有数据,无法获取原始的变更日志,所以一般建议为ROW模式)
2.3.2 EventParser的设计与实现奥妙
首先我们先从官方文档来看 EventParser 的整体设计,其架构设计图如下所示:
上述图罗列出了 EventParser 的整体工作流程图,其关键步骤如下:
- 从 Log Position 管理器中获取上一次解析的日志位点。
- 向 Mysql Master 节点发送 BINLOG_DUMP 请求。
- Mysql Master 节点从 Slave 端传入的日志位点开始向从节点推送 binlog 日志。
- Slave 接收 binlog 日志,调用 BinlogParser 解析 binlog日志。
- 将解析后的结构化数据传入到 EventSink 组件。
- 定时记录解析 binlog 的日志,以便重启后继续进行增量订阅。
- 上图中还罗列一个HA 特性,即需要同步的 Master 如果宕机,可以从它的其他从节点继续同步 binlog 日志,避免单点故障。
2.3.3 EventSink的设计与实现奥妙
说明:
-
数据过滤:支持通配符的过滤模式,表名,字段内容等
-
数据路由/分发:解决1:n (1个parser对应多个store的模式)
-
数据归并:解决n:1 (多个parser对应1个store)
-
数据加工:在进入store之前进行额外的处理,比如join
2.3.3.1 数据1:n业务
-
为了合理的利用数据库资源, 一般常见的业务都是按照schema进行隔离,然后在mysql上层或者dao这一层面上,进行一个数据源路由,屏蔽数据库物理位置对开发的影响,阿里系主要是通过cobar/tddl来解决数据源路由问题。
-
所以,一般一个数据库实例上,会部署多个schema,每个schema会有由1个或者多个业务方关注
2.3.3.2 数据n:1业务
-
同样,当一个业务的数据规模达到一定的量级后,必然会涉及到水平拆分和垂直拆分的问题,针对这些拆分的数据需要处理时,就需要链接多个store进行处理,消费的位点就会变成多份,而且数据消费的进度无法得到尽可能有序的保证。
-
所以,在一定业务场景下,需要将拆分后的增量数据进行归并处理,比如按照时间戳/全局id进行排序归并.
2.3.4 EventStore的设计与实现奥妙
- 目前仅实现了Memory内存模式,后续计划增加本地file存储,mixed混合模式
- 借鉴了Disruptor的RingBuffer的实现思路
定义了3个cursor:
Put : Sink模块进行数据存储的最后一次写入位置
Get : 数据订阅获取的最后一次提取位置
Ack : 数据消费成功的最后一次消费位置
借鉴Disruptor的RingBuffer的实现,将RingBuffer拉直来看:
实现说明:
Put/Get/Ack cursor用于递增,采用long型存储
buffer的get操作,通过取余或者与操作。(与操作: cusor & (size - 1) , size需要为2的指数,效率比较高)
2.3.5 Instance设计
- instance代表了一个实际运行的数据队列,包括了EventPaser,EventSink,EventStore等组件。
抽象了CanalInstanceGenerator,主要是考虑配置的管理方式:
- manager方式: 和你自己的内部web console/manager系统进行对接
- spring方式:基于spring xml + properties进行定义,构建spring配置.
2.3.6 Server设计
server代表了一个canal的运行实例,为了方便组件化使用,特意抽象了Embeded(嵌入式) / Netty(网络访问)的两种实现
Embeded : 对latency和可用性都有比较高的要求,自己又能hold住分布式的相关技术(比如failover)
Netty : 基于netty封装了一层网络协议,由canal server保证其可用性,采用的pull模型,当然latency会稍微打点折扣,不过这个也视情况而定。(阿里系的notify和metaq,典型的push/pull模型,目前也逐步的在向pull模型靠拢,push在数据量大的时候会有一些问题)
2.3.7 HA机制设计
canal的ha分为两部分,canal server和canal client分别有对应的ha实现
- canal server:
为了减少对mysql dump的请求,不同server上的instance要求同一时间只能有一个处于running,其他的处于standby状态. - canal client:
为了保证有序性,一份instance同一时间只能由一个canal client进行get/ack/rollback操作,否则客户端接收无法保证有序。
整个HA机制的控制主要是依赖了zookeeper的几个特性,watcher和EPHEMERAL节点(和session生命周期绑定),可以看下我之前zookeeper的相关文章。
大致步骤:
1.canal server要启动某个canal instance时都先向zookeeper进行一次尝试启动判断 (实现:创建EPHEMERAL节点,谁创建成功就允许谁启动)
2.创建zookeeper节点成功后,对应的canal server就启动对应的canal instance,没有创建成功的canal instance就会处于standby状态
3.一旦zookeeper发现canal server A创建的节点消失后,立即通知其他的canal server再次进行步骤1的操作,重新选出一个canal server启动instance.
4.canal client每次进行connect时,会首先向zookeeper询问当前是谁启动了canal instance,然后和其建立链接,一旦链接不可用,会重新尝试connect.
Canal Client的方式和canal server方式类似,也是利用zookeeper的抢占EPHEMERAL节点的方式进行控制。
到此,canal历史与基础原理介绍完毕,下面看展实战演练,大家加油!
3. CANAL应用-QuickStart
3.1 准备工作:
3.1.1 启动mysql服务
3.1.2 开启binlog
对于自建 MySQL , 需要先开启 Binlog 写入功能,配置 binlog-format 为 ROW 模式,my.cnf 中配置如下
[mysql@test1 mysql]$ sudo vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
注意:针对阿里云 RDS for MySQL , 默认打开了 binlog , 并且账号默认具有 binlog dump 权限 , 不需要任何权限或者 binlog 设置,可以直接跳过这一步
[mysql@test1 mysql]$ sudo vim /etc/my.cnf
[mysqld]
log-bin=/usr/local/mysql/mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=7 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
[mysql@test1 mysql]$ service mysqld restart #重新启动
Shutting down MySQL....[ OK ]
Starting MySQL.[ OK ]
启动完成后,查看主库状态
mysql> show master status;
查看binlog_format模式
mysql> show variables like 'binlog_format';
3.1.3 授权
授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant
[mysql@test1 mysql]$ mysql -uroot -pmysql123 #登录数据库
CREATE USER canal IDENTIFIED BY 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
--GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;
3.1.4 下载-启动
下载 canal, 访问 release 页面 , 选择需要的包下载, 如以 1.1.4 版本为例
release 页面 :https://github.com/alibaba/canal/releases
wget https://github.com/alibaba/canal/releases/download/canal-1.1.4/canal.deployer-1.1.4.tar.gz
解压缩
mkdir /opt/canal
tar zxvf canal.deployer-$version.tar.gz -C /opt/canal
解压目录
解压完成后,进入 /opt/canal 目录,可以看到如下结构
[root@test1 software]# cd ../canal/
[root@test1 canal]# ll
total 4
drwxr-xr-x. 2 root root 76 Aug 12 16:39 bin
drwxr-xr-x. 5 root root 123 Aug 12 16:39 conf
drwxr-xr-x. 2 root root 4096 Aug 12 16:39 lib
drwxrwxrwx. 2 root root 6 Sep 2 2019 logs
修改配置
vim conf/example/instance.properties
## mysql serverId
canal.instance.mysql.slaveId = 1234
#position info,需要改成自己的数据库信息
canal.instance.master.address = 192.168.27:3306
canal.instance.master.journal.name =
canal.instance.master.position =
canal.instance.master.timestamp =
#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#username/password,需要改成自己的数据库信息
canal.instance.dbUsername = canal
canal.instance.dbPassword = canal
canal.instance.defaultDatabaseName =testdb
canal.instance.connectionCharset = UTF-8
#table regex
canal.instance.filter.regex = .\*\\\\..\*
canal.instance.connectionCharset 代表数据库的编码方式对应到 java 中的编码类型,比如 UTF-8,GBK , ISO-8859-1
如果系统是1个 cpu,需要将 canal.instance.parser.parallel 设置为 false
启动
sh bin/startup.sh
查看 server 日志
vi logs/canal/canal.log</pre>
查看 instance 的日志
vi logs/example/example.log
关闭
sh bin/stop.sh
必须安装JDK
which: no java in (/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/mysql/bin)
Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH.
3.2. 案例演示-ClientExample
- 使用IDEA创建maven工程canal-client-example
修改pom.xml,添加依赖:
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.0</version>
</dependency>
2.创建SimpleCanalClientExample.java文件
修改链接配置,canal.instance.defaultDatabaseName=testdb 数据库为testdb
确保配置一致
3.启动canal-client-example项目后,在testdb.tb_customer插入两条数据
INSERT INTO `tb_customer` VALUES ('3', '张三', '23');
INSERT INTO `tb_customer` VALUES ('4', '李四', '24');
启动工程
查看console控制台日志输出
4.canal-admin介绍与搭建
canal-admin设计上是为canal提供整体配置管理、节点运维等面向运维的功能,提供相对友好的WebUI操作界面,方便更多用户快速和安全的操作
canal-admin的限定依赖:
- MySQL,用于存储配置和节点等相关数据
- canal版本,要求>=1.1.4 (需要依赖canal-server提供面向admin的动态运维管理接口)
4.1 下载&解压
下载 canal-admin, 访问 release 页面 , 选择需要的包下载, 如以 1.1.4 版本为例
wget https://github.com/alibaba/canal/releases/download/canal-1.1.4/canal.admin-1.1.4.tar.gz
解压缩
mkdir /opt/canal-admin
tar zxvf canal.admin-$version.tar.gz -C /opt/canal-admin
解压完成后,进入 /opt/canal 目录,可以看到如下结构
配置修改
vi conf/application.yml
4.2 初始化
初始化元数据库:
初始化SQL脚本里会默认创建canal_manager的数据库,建议使用root等有超级权限的账号进行初始化 b. canal_manager.sql默认会在conf目录下,也可以通过链接下载 canal_manager.sql
4.3 启动&查看
启动
sh bin/startup.sh
查看 admin 日志
vi logs/admin.log
此时代表canal-admin已经启动成功,可以通过 http://127.0.0.1:8089/ 访问,默认密码:admin/123456
canal-server端配置
# register ip
canal.register.ip =
# canal admin config
canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
# admin auto register
canal.admin.register.auto = true
canal.admin.register.cluster =
启动admin-server即可。
或在启动命令中使用参数:sh bin/startup.sh local 指定配置
just have fun!