canal 一个MySQL增量日志解析神器

1,779 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

canal 是什么

canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费,目前主要支持了mysql。

工作原理

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)

使用场景

基于日志增量订阅&消费支持的业务:

  1. 数据库镜像
  2. 数据库实时备份
  3. 多级索引 (卖家和买家各自分库索引)
  4. search build
  5. 业务cache刷新
  6. 价格变化等重要业务消息

快速开始

准备

要使用canal必须要开启binlog,通过下面语句可以查看binlog是否开启,关于binlog的文章请移步到

show variables like 'log_bin%'

OFF表示没有开启,那么就需要开启binlog,找到mysql配置文件,windows系统配置文件名为my.ini,Linux系统配置文件名为my.conf。

如果不知道配置文件的位置在哪,可以通过SELECT @@datadir 语句查看文件位置

配置文件会在datadir的上一级目录中。

找到配置文件之后,添加如下配置表示开启binlog

log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式

修改完配置文件之后重启mysql,再使用show variables like 'log_bin%'语句查看binlog是否开启,ON表示开启。

下载

下载链接:github.com/alibaba/can… ,目前最新的版本是1.1.6

配置

下载之后解压,得到如下文件夹

进入到 conf\example文件加下,修改 instance.properties 配置

#position info,需要改成自己的数据库信息
canal.instance.master.address = 127.0.0.1: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=root
canal.instance.dbPassword=1234
canal.instance.connectionCharset = UTF-8
#table regex 数据库表的过滤
canal.instance.filter.regex=db_test.*

这里配置的是本地数据db_test,里面有张user

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_name` varchar(40) NOT NULL DEFAULT '' COMMENT '全名',
  `gender` tinyint(2) NOT NULL DEFAULT '0' COMMENT '性别',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

启动

进入到解压后的bin文件夹下,双击startup.bat或者通过命令行启动 然后进入logs\canal 文件夹下查看canal.log文件,出现以下界面表示成功

实操

canal 1.1.1版本之后, 默认支持将canal server接收到的binlog数据直接投递到MQ, 目前默认支持的MQ系统有:kafka、RocketMQ 、RabbitMQ 、pulsarmq。

本篇文章将使用springboot + canal + RocketMQ 进行一次简单的实操,如果项目中用不到消息队列也可参考官网示例使用github.com/alibaba/can…

准备

  1. 实操前要先把rocketmq安装好,关于rocketmq的安装这里也不过多赘述,可以看这篇文章RocketMq 系列】 rocketmq 安装教程

  2. 修改instance配置文件conf\example\instance.properties

# mq config
# 要发送到的topic
canal.mq.topic=consumer_topic
# 针对库名或者表名发送动态topic
#canal.mq.dynamicTopic=mytest1.user,topic2:mytest2\\..*,.*\\..*
canal.mq.partition=0
# hash partition config
#canal.mq.enableDynamicQueuePartition=false
#canal.mq.partitionsNum=3
#canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6
# 库名.表名: 唯一主键,多个表之间用逗号分隔
#canal.mq.partitionHash=test.table:id^name,.*\\..*
  1. 修改canal 配置文件/conf/canal.properties
# 默认为tcp, 可选项:tcp, kafka, rocketMQ, rabbitMQ, pulsarMQ
canal.serverMode = rocketMQ

##################################################
######### 		    RocketMQ	     #############
##################################################
# 生产者组
rocketmq.producer.group = canal_producer_group

rocketmq.enable.message.trace = false
rocketmq.customized.trace.topic =
rocketmq.namespace =
rocketmq.namesrv.addr = 127.0.0.1:9876
rocketmq.retry.times.when.send.failed = 0
rocketmq.vip.channel.enabled = false
# rocketmq tag标签
rocketmq.tag = 

修改完重启一下canal。

集成到springboot中

  1. 新建一个springboot项目,引入依赖

canal把数据都发送到了mq里面,所以这里可以不引入canal的依赖,直接从rockemq中获取数据即可。

<dependency>
   <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
  1. 修改配置
server.port=8090

rocketmq.name-server=localhost:9876
# 消费者组
rocketmq.producer.group=canal_producer_group

# 消息发送
rocketmq.producer.retry-times-when-send-async-failed=2
# 消息发送失败重试次数,默认为2
rocketmq.producer.retry-times-when-send-failed=2

rocketmq.consumer.topic=consumer_topic
rocketmq.consumer.group=consumer_group
  1. 新建一个 RocketMQListener 实现类
@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}",consumerGroup ="${rocketmq.consumer.group}")
@Component
public class CanalListener implements RocketMQListener<MessageExt> {

    @Override
    public void onMessage(MessageExt message) {
        final byte[] body = message.getBody();

        System.out.println(new String(body , StandardCharsets.UTF_8));
    }
}
  1. 测试

修改表中一条记录信息

UPDATE `db_test`.`user` SET `id`='13', `user_name`='username13', `gender`='1', `create_date`='2022-09-09 10:13:53' WHERE (`id`='13');

控制台会打印一条如下数据:

{
    "data": [
        {
            "create_date": "2022-09-09 10:13:53",
            "gender": "1",
            "id": "13",
            "user_name": "username13"
        }
    ],
    "database": "db_test",
    "es": 1665633148000,
    "id": 8,
    "isDdl": false,
    "mysqlType": {
        "create_date": "datetime",
        "gender": "tinyint(2)",
        "id": "bigint(20)",
        "user_name": "varchar(40)"
    },
    "old": [
        {
            "create_date": "2022-09-09 10:18:53",
            "user_name": "username133"
        }
    ],
    "pkNames": [
        "id"
    ],
    "sql": "",
    "sqlType": {
        "create_date": 93,
        "gender": -6,
        "id": -5,
        "user_name": 12
    },
    "table": "user",
    "ts": 1665633148529,
    "type": "UPDATE"
}

数据解释:

  • data:修改前的字段和数据
  • database:数据库名
  • old:修改字段和数据
  • table:表名
  • type:事件类型。INSERT、UPDATE、DELETE、CREATE等
  • mysqlType:字段名及数据结构
  • isDdl:是否是DDL操作
  • pkNames:主键名

至此,一个canal的简单入门就完成了,感兴趣的可以更深入的了解一下。

参考资料:

github.com/alibaba/can…