MySQL开源数据同步工具canal的使用

219 阅读3分钟

一、canal有什么用

官网对于canal的描述是这样的

canal 译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。

也就是说canal理解为一个用来同步增量数据的一个工具

官网上的这张图描述了canald的工作原理 68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139313130343130313733353934372e706e67.png

canal客户端伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议,之后MySQL会将binary log推送给canal客户端并由客户端进行解析。

二、canal客户端安装

1.配置MySQL开启binary log

修改MySQL的配置信息添加如下内容。

[mysqld]
log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复

修改完成后重启数据库验证配置是否生效。

show variables like 'log_bin';

PixPin_2024-05-08_21-08-35.png

二、安装canal

在canal官网进行下载 Releases · alibaba/canal (github.com)

下载完成后进行解压,修改canal配置文件。
conf下初始有一个example文件夹,修改其中的instance.properties文件。

# 配置 slaveId 自定义,不等于 mysql 的 server Id 即可
canal.instance.mysql.slaveId=10 
# 数据库地址:自己的数据库ip+端口
canal.instance.master.address=127.0.0.1:3306 
 
# 数据库用户名和密码 
canal.instance.dbUsername=
canal.instance.dbPassword=
canal.instance.connectionCharset = UTF-8
 
# 指定监听库和表,这里的 .* 表示 canal.instance.master.address 下面的所有数据库
canal.instance.filter.regex=
# 配置监听黑名单
canal.instance.filter.black.regex=

canal中regex配置有下面几种

表达式范围示例
.\..全库全表.\..
库名\..*指定库全表test\..*
库名.表名指定表test.users

如果多规则组合使用以,分隔。

配置完成后再calal的bin目录中运行startup.sh或者startup.bat即可开启canal客户端。

三、在Java程序中与canal客户端建立连接

1.引入相关依赖

<dependency>
  <groupId>com.alibaba.otter</groupId>
  <artifactId>canal.client</artifactId>
  <version>1.1.7</version>
</dependency>

<dependency>
  <groupId>com.alibaba.otter</groupId>
  <artifactId>canal.protocol</artifactId>
  <version>1.1.6</version>
</dependency>

示例Java代码

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;
import java.net.InetSocketAddress;

import java.util.List;


public class Main {
    private static final int BATCH_SIZE =100;
    public static void main(String[] args){

        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "", "");
        try {
            connector.connect();
            connector.rollback();
            while (true){
                Message message=connector.getWithoutAck(BATCH_SIZE);
                //获取批量ID
                long batchId = message.getId();
                //获取批量的数量
                int size = message.getEntries().size();
                //没有新数据休眠0.5ms
                if (batchId == -1 || size == 0) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    //获取所有消息
                    List<CanalEntry.Entry> entries = message.getEntries();
                    for (CanalEntry.Entry entry : entries) {
                        //判断消息类型是否为ROWDATA
                        if (entry.getEntryType().equals(CanalEntry.EntryType.ROWDATA)) {
                            //获取RowChange对象
                            CanalEntry.RowChange rowChange= CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                            System.out.println("事件类型为:"+rowChange.getEventType());
                            //获取该行所有数据
                            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
                            System.out.println("修改前数据:");
                            for(CanalEntry.RowData rowData :rowDatasList){
                                List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
                                for(CanalEntry.Column column:beforeColumnsList){
                                    System.out.println(column.getName()+":"+column.getValue());
                                }
                            }
                            System.out.println("修改后数据:");
                            for(CanalEntry.RowData rowData :rowDatasList){
                                List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
                                for(CanalEntry.Column column:afterColumnsList){
                                    System.out.println(column.getName()+":"+column.getValue());
                                }
                            }
                        }

                    }
                    connector.ack(batchId); // 提交确认
                }
            }

        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        } finally {
            connector.disconnect();
        }
      }
}

用以下SQL进行测试

insert into test.test values ('a',5,99);
update test.test set score=100 where id=5;
delete from test.test where id=5;

得到输出结果

PixPin_2024-05-08_21-50-25.png

以上就是canal基本的使用方法。

四、小结

canal的好处在于对业务代码没有侵入,因为是基于监听binlog日志去进行同步数据的
让基于基于日志增量订阅和消费的业务例如

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护(拆分异构索引、倒排索引等)
  • 业务 cache 刷新
  • 带业务逻辑的增量数据处理 更加便捷。