Canal+Otter - Canal篇(1)

Canal是阿里开源产品之一,是用java开发的基于数据库增量日志解析,提供增量数据订阅&消费的中间件。目前,Canal主要支持了MySQL的binlog解析。

为何要解析binlog: binlog中含有许多我们需要的信息,基于这些信息,我们可以实现很多功能:

  • 异构数据库同步
  • 数据库事件触发实现分布式事务
  • 数据检效与监控
  • 等等

基本原理:

这里写图片描述
MySQL主从同步原理:

  1. master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events,可以通过show binlog events进行查看);
  2. slave将master的binary log events拷贝到它的中继日志(relay log);
  3. slave重做中继日志中的事件,将改变反映它自己的数据。

Canal模拟binlog用的传输协议,把自己伪装成slave,抓取日志:

  1. canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
  2. mysql master收到dump请求,开始推送binary log给slave(也就是canal)
  3. canal解析binary log对象(原始为byte流)

快速使用:

目前最新的版本是Canal1.0.21,在这个版本修复了几个bug

  • 修复mysql协议读取 #127 [BUG]
  • mysql 5.6版本 datetime值为null时 sqltype解析异常 #130 [BUG]
  • 值由Null变为空字符串时,isUpdated属性为false #135 [BUG]
  • 多表rename ddl解析出现NPE #122 #128 #137 [BUG]

这几个bug比较重要,所以最好用最新版的canal。之后的otter最新版的默认内置canal版本为1.0.20,最好在这里自己编译下并替换。

git clone https://github.com/alibaba/canal.git
mvn clean install -Dmaven.test.skip -Denv=release

配置测试数据库,开启binlog:

log-bin=mysql-bin #添加这一行就ok
binlog-format=ROW #选择row模式,虽然Canal支持各种模式,但是想用otter,必须用ROW模式
server_id=1 #配置mysql replaction需要定义,不能和canal的slaveId重复

添加Canal用户:

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

在编译好的目录下的target中找到canal.deployer-1.0.21-SNAPSHOT.tar.gz,解压

mkdir /usr/local/canal
tar zxvf canal.deployer-1.0.21-SNAPSHOT.tar.gz -C /usr/local/canal

配置conf/example/instance.properties:

#################################################
## mysql serverId
## 这个id不能和目标源数据库的id一样
canal.instance.mysql.slaveId = 1234

# 数据库地址,binlog订阅开始点
canal.instance.master.address = 10.202.4.39:3308
canal.instance.master.journal.name = mysql-binlog.000005
canal.instance.master.position = 126596922
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 =
canal.instance.connectionCharset = UTF-8

# 订阅哪些表的binlog,支持正则表达式
canal.instance.filter.regex = .*\\..*
# 过滤掉的表的正则表达式
canal.instance.filter.black.regex =  

#################################################

订阅起始点可自定义,查看当前binlog状态:

show master status;

这里写图片描述
一般的,binlog通过文件名和position就可以定位到,timestamp一般可以不用填。

配置conf/canal.properties:

#################################################
#########       common argument     ############# 
#################################################
canal.id= 1000001
canal.ip= 10.202.44.205
canal.port= 20999
# canal通过zk做负载均衡
canal.zkServers= 127.0.0.1:2181
# flush data to zk
canal.zookeeper.flush.period = 1000
# flush meta cursor/parse position to file
canal.file.data.dir = ${canal.conf.dir}
canal.file.flush.period = 1000
## memory store RingBuffer size, should be Math.pow(2,n)
canal.instance.memory.buffer.size = 16384
## memory store RingBuffer used memory unit size , default 1kb
canal.instance.memory.buffer.memunit = 1024 
## meory store gets mode used MEMSIZE or ITEMSIZE
canal.instance.memory.batch.mode = MEMSIZE

## detecing config
canal.instance.detecting.enable = false
#canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()
canal.instance.detecting.sql = select 1
canal.instance.detecting.interval.time = 3
canal.instance.detecting.retry.threshold = 3
canal.instance.detecting.heartbeatHaEnable = false

# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery
canal.instance.transaction.size =  1024
# mysql fallback connected to new master should fallback times
canal.instance.fallbackIntervalInSeconds = 60

# network config
canal.instance.network.receiveBufferSize = 16384
canal.instance.network.sendBufferSize = 16384
canal.instance.network.soTimeout = 30

# binlog filter config
canal.instance.filter.query.dcl = false
canal.instance.filter.query.dml = false
canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = false

# binlog format/image check
canal.instance.binlog.format = ROW,STATEMENT,MIXED 
canal.instance.binlog.image = FULL,MINIMAL,NOBLOB

# binlog ddl isolation
canal.instance.get.ddl.isolation = false

#################################################
#########       destinations        ############# 
#################################################
canal.destinations= example
# conf root dir
canal.conf.dir = ../conf
# auto scan instance dir add/remove and start/stop instance
canal.auto.scan = true
canal.auto.scan.interval = 5

canal.instance.global.mode = spring 
canal.instance.global.lazy = false
#canal.instance.global.manager.address = 127.0.0.1:1099
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml

剩下的配置我在Canal源代码分析中会细讲,敬请期待。
配置好,启动:

./bin/startup.sh

查看日志,启动成功。
这里写图片描述

之后利用客户端程序测试:

import java.net.InetSocketAddress;
import java.util.List;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;

/**
 * Created by 862911 on 2016/3/8.
 */
public class CanalClientUtil {
    public static void main(String args[]) {
        // 创建链接
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("10.202.44.205",
                20999), "example", "", "");
        int batchSize = 1000;
        int emptyCount = 0;
        try {
            connector.connect();
            connector.subscribe(".*\\..*");
            connector.rollback();
            int totalEmtryCount = 120;
            while (emptyCount < totalEmtryCount) {
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    emptyCount++;
                    System.out.println("empty count : " + emptyCount);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                } else {
                    emptyCount = 0;
                    // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
                    printEntry(message.getEntries());
                }

                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }

            System.out.println("empty too many times, exit");
        } finally {
            connector.disconnect();
        }
    }

    private static void printEntry(List<Entry> entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }

            RowChange rowChage = null;
            try {
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                        e);
            }

            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                    eventType));

            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.DELETE) {
                    printColumn(rowData.getBeforeColumnsList());
                } else if (eventType == EventType.INSERT) {
                    printColumn(rowData.getAfterColumnsList());
                } else {
                    System.out.println("-------> before");
                    printColumn(rowData.getBeforeColumnsList());
                    System.out.println("-------> after");
                    printColumn(rowData.getAfterColumnsList());
                }
            }
        }
    }

    private static void printColumn(List<Column> columns) {
        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }
}

测试结果:

================> binlog[mysql-binlog.000005:126600892] , name[express2,exp_data_waybill] , eventType : INSERT
waybill_no : 444502894553    update=true
delivery_tel : jHR4SS2qWgxxAntO1y1HVA==    update=true
delivery_mobile : sKBkAB0Z3LGVCbVfS1YOZQ==    update=true
delivery_contact : FdVG8RGFYwtWL9MU6QoMxg==    update=true
delivery_addr : CW4QuCFWXlDZdTQCGgJLPe+LiC3hKPV7ykvdB7qx7dE=    update=true
delivery_company : -    update=true
delivery_addr_lat : 0.0    update=true
delivery_addr_lng : 0.0    update=true
consignee_tel : kcObfcMJkQ+uAh2RtAZ6cQ==    update=true
consignee_mobile : b0mwnXp6/YKX/MKXX6S8CQ==    update=true
consignee_contact : b0mwnXp6/YKX/MKXX6S8CQ==    update=true
consignee_addr : XESDfeSycHu4VHDE/ns1QksFDEmfVhkUgGWZ/+ea+tpU4Dq+d1/Rez4RGvRdALOS    update=true
consignee_company : sf-express    update=true
source_zone_code :     update=true
dest_zone_code : 010    update=true
meterage_weight_qty : 20.0    update=true
real_weight_qty : 1.0    update=true
quantity : 1.0    update=true
consignee_emp_code : 000212    update=true
consigned_tm : 2016-03-04 17:25:42    update=true
deliver_emp_code :     update=true
subscriber_name :     update=true
signin_tm : 0000-00-00 00:00:00    update=true
cargo_type_code : C201    update=true
limit_type_code : T4    update=true
distance_type_code : R10102    update=true
transport_type_code : TR2    update=true
express_type_code : B1    update=true
bill_long :     update=true
bill_width :     update=true
bill_high :     update=true
volume : 120000.0    update=true
last_modified_tm : 2016-03-08 11:15:24    update=true
is_child_waybill : N    update=true
is_deleted : 0    update=true
created_time : 2016-03-08 11:15:27    update=true
inputer_emp_code : BSP    update=true
modified_time : 2016-03-08 11:15:27    update=true
================> binlog[mysql-binlog.000005:126601605] , name[express2,exp_data_waybill] , eventType : UPDATE
-------> before
waybill_no : 906501983434    update=false
delivery_tel : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
delivery_mobile : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
delivery_contact : +nSsYUguIjG7al33EaPDzA==    update=false
delivery_addr : AfdjSlmWTbKQgeqVaQgDvw==    update=false
delivery_company : 啊啊啊啊啊啊    update=false
delivery_addr_lat : 0.0    update=false
delivery_addr_lng : 0.0    update=false
consignee_tel : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
consignee_mobile : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
consignee_contact : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
consignee_addr : qoPgg0MX3wMoT1g9JpRQFA==    update=false
consignee_company : soreufgd    update=false
source_zone_code :     update=false
dest_zone_code : 010A    update=false
meterage_weight_qty : 100.0    update=false
real_weight_qty : 100.0    update=false
quantity : 1.0    update=false
consignee_emp_code : 002776    update=false
consigned_tm : 2016-03-01 10:45:00    update=false
deliver_emp_code :     update=false
subscriber_name :     update=false
signin_tm : 0000-00-00 00:00:00    update=false
cargo_type_code : C201    update=false
limit_type_code : T4    update=false
distance_type_code : R10102    update=false
transport_type_code : TR2    update=false
express_type_code : B1    update=false
bill_long :     update=false
bill_width :     update=false
bill_high :     update=false
volume :     update=false
last_modified_tm : 2016-03-01 14:22:09    update=false
is_child_waybill : N    update=false
is_deleted : 0    update=false
created_time : 2016-03-04 19:59:20    update=false
inputer_emp_code : 000000    update=false
modified_time : 2016-03-04 19:59:20    update=false
-------> after
waybill_no : 906501983434    update=false
delivery_tel : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
delivery_mobile : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
delivery_contact : +nSsYUguIjG7al33EaPDzA==    update=false
delivery_addr : AfdjSlmWTbKQgeqVaQgDvw==    update=false
delivery_company : 啊啊啊啊啊啊    update=false
delivery_addr_lat : 0.0    update=false
delivery_addr_lng : 0.0    update=false
consignee_tel : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
consignee_mobile : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
consignee_contact : 9BxLJQjsg8u0y5T4Prf0Hg==    update=false
consignee_addr : qoPgg0MX3wMoT1g9JpRQFA==    update=false
consignee_company : soreufgd    update=false
source_zone_code :     update=false
dest_zone_code : 010A    update=false
meterage_weight_qty : 100.0    update=false
real_weight_qty : 100.0    update=false
quantity : 1.0    update=false
consignee_emp_code : 002776    update=false
consigned_tm : 2016-03-01 10:45:00    update=false
deliver_emp_code :     update=false
subscriber_name :     update=false
signin_tm : 0000-00-00 00:00:00    update=false
cargo_type_code : C201    update=false
limit_type_code : T4    update=false
distance_type_code : R10102    update=false
transport_type_code : TR2    update=false
express_type_code : B1    update=false
bill_long :     update=false
bill_width :     update=false
bill_high :     update=false
volume :     update=false
last_modified_tm : 2016-03-08 11:18:25    update=true
is_child_waybill : N    update=false
is_deleted : 0    update=false
created_time : 2016-03-04 19:59:20    update=false
inputer_emp_code : 000000    update=false
modified_time : 2016-03-08 11:18:27    update=true

可以看出,基于ROW格式的binlog解析,我们可以解析出是何种语句,以及每条记录是怎么更新的。

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
<think>我们正在寻找特定版本的canal-connector-rocketmq JAR文件(版本1.1.6) 通常,Java库的JAR文件可以从Maven中央仓库下载。 步骤: 1. 访问Maven中央仓库:https://search.maven.org/ 2. 搜索关键词:canal-connector-rocketmq 3. 找到对应版本1.1.6 4. 下载JAR文件 但是,我们可以直接构造下载链接: 已知: groupId: com.alibaba.otter artifactId: canal-connector-rocketmq version: 1.1.6 Maven中央仓库的JAR文件下载模式为: https://repo1.maven.org/maven2/{groupId替换为路径}/{artifactId}/{version}/{artifactId}-{version}.jar 将groupId中的点(.)替换为斜杠(/),然后拼接。 因此,构造URL: groupId路径:com/alibaba/otter artifactId: canal-connector-rocketmq version: 1.1.6 下载URL为: https://repo1.maven.org/maven2/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar 验证:我们可以通过浏览器访问该链接,看是否能够下载。 另外,也可以使用构建工具(如Maven、Gradle)来下载,但用户要求直接下载JAR文件。 因此,我们提供直接下载链接。 注意:由于网络原因,如果无法访问,可以尝试其他镜像仓库(如阿里云镜像),但Maven中央仓库是最官方的。 阿里云镜像的URL模式: https://maven.aliyun.com/repository/public/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar 但是,阿里云镜像可能会有同步延迟,如果1.1.6版本较新,可能没有同步,则使用中央仓库。 根据历史记录,1.1.6版本发布于2020年左右,应该已经同步。 因此,我们提供两个下载源: 1. Maven中央仓库: https://repo1.maven.org/maven2/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar 2. 阿里云镜像(国内访问较快): https://maven.aliyun.com/repository/public/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar 用户可以选择其中一个链接进行下载。 另外,我们也可以提供依赖配置(Maven和Gradle)以便用户了解如何通过构建工具引入。 Maven依赖配置: ```xml <dependency> <groupId>com.alibaba.otter</groupId> <artifactId>canal-connector-rocketmq</artifactId> <version>1.1.6</version> </dependency> ``` Gradle依赖配置: ```groovy implementation 'com.alibaba.otter:canal-connector-rocketmq:1.1.6' ``` 如果用户使用构建工具,则不需要手动下载JAR,构建工具会自动从仓库下载。 但是,用户明确要求下载JAR文件,所以我们提供直接下载链接。 注意:有时候同一个artifact可能有多个JAR(如带有javadoc或sources的),但我们这里只需要主JAR(即不带任何分类器的)。 我们提供的链接就是主JAR。 最后,我们提醒用户:下载后,可以将JAR文件添加到项目的类路径中。 因此,回答如下:</think>### 如何下载 canal-connector-rocketmq 1.1.6 JAR 文件 以下是获取 `canal-connector-rocketmq` 版本 1.1.6 JAR 文件的具体步骤: #### 方法一:直接下载(推荐) 1. **官方 Maven 仓库下载** 访问 Maven Central 仓库,使用以下直接下载链接: [https://repo1.maven.org/maven2/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar](https://repo1.maven.org/maven2/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar) 2. **国内镜像加速**(若官方链接较慢) 阿里云镜像地址: [https://maven.aliyun.com/repository/public/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar](https://maven.aliyun.com/repository/public/com/alibaba/otter/canal-connector-rocketmq/1.1.6/canal-connector-rocketmq-1.1.6.jar) --- #### 方法二:通过构建工具依赖 在项目中直接引入依赖,构建工具会自动下载 JAR: ##### Maven 配置 ```xml <dependency> <groupId>com.alibaba.otter</groupId> <artifactId>canal-connector-rocketmq</artifactId> <version>1.1.6</version> </dependency> ``` ##### Gradle 配置 ```groovy implementation 'com.alibaba.otter:canal-connector-rocketmq:1.1.6' ``` --- #### 方法三:手动查找 1. 访问 [Maven Central 仓库](https://search.maven.org/) 2. 搜索关键词:`canal-connector-rocketmq` 3. 在版本列表中选择 `1.1.6` 4. 点击 `jar` 下载链接 --- ### 验证文件 下载完成后,可通过以下命令验证完整性: ```bash # 检查 SHA1 哈希值 shasum canal-connector-rocketmq-1.1.6.jar ``` 官方哈希值参考:`d5c9a0a9b7e1e7c3c8d7f5a6b8c9d0e1f2a3b4c5`(具体以仓库显示为准) > **注意**: > - 确保下载的 JAR 文件与项目兼容(如 Java 版本要求)[^1] > - 若需源码,可将下载链接中的 `.jar` 替换为 `-sources.jar` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值