阿里巴巴开源超27k关注的Canal项目,我整理了这份万字总结

- 服务器应用

2024-02-29T14:51:14.png
本文将从 Canal 的使用场景、工作原理和架构开始介绍,然后总结了计划使用canal前必须要知道的 15 个关键问题。


Canal 介绍

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

我这里主要基于 canal 实现以下三个场景:




工作原理

MySQL 主备复制原理

2024-02-29T14:54:09.png


canal 工作原理

2024-02-29T14:54:40.png

当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x 。



Canal 架构

2024-02-29T14:55:24.png
1、server代表一个canal运行实例,对应于一个jvm。
2、instance对应于一个数据队列(用来操作不同的 MySQL 实例)。

instance 模块




canal 使用总结

配置管理

开源版本的 canal 基于本地spring xml的配置管理方式,其原理是将整个配置抽象为两部分:
xxxx-instance.xml:canal组件的配置定义,可以在多个instance配置中共享(默认在 ${CANAL_HOME}/conf/spring/ 目录下)。

xxxx.properties:每个instance都有一个独立的配置,连接的 mysql 地址、账号、密码可能会不同。


[canal@imzcy canal]$ ll conf/spring/
total 60
-rwxrwxrwx 1 canal canal  2192 Nov 16  2022 base-instance.xml
-rwxrwxrwx 1 canal canal 11154 Sep 11 10:52 default-instance.xml
-rwxrwxrwx 1 canal canal 10682 Sep 11 10:52 file-instance.xml
-rwxrwxrwx 1 canal canal 15722 Apr 21  2023 group-instance.xml
-rwxrwxrwx 1 canal canal 10192 Sep 11 10:51 memory-instance.xml
drwxr-xr-x 4 canal canal  4096 Feb 16 18:27 tsdb
[canal@imzcy canal]$

properties配置分为两部分:


位点组件

先了解一下canal如何维护一份增量订阅&消费的关系信息:

对应的两个位点组件,目前都有几种实现:


默认位点信息存储

canal 默认情况下使用 file-instance.xml

[canal@imzcy conf]$ cat canal.properties |grep global.spring.xml |grep -v ^#
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
[canal@imzcy conf]$

[canal@imzcy conf]$ cat spring/file-instance.xml |grep metaManager
                <property name="metaManager">
                        <ref bean="metaManager" />
        <bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
                                                <constructor-arg ref="metaManager"/>
[canal@imzcy conf]$

将位点信息保存到每个 instance 目录下的 meta.dat 文件中


[canal@imzcy canal]$ cat conf/zcy_test/meta.dat |jq .
{
  "clientDatas": [
    {
      "clientIdentity": {
        "clientId": 1001,
        "destination": "zcy_test",
        "filter": ""
      },
      "cursor": {
        "identity": {
          "slaveId": -1,
          "sourceAddress": {
            "address": "localhost",
            "port": 3306
          }
        },
        "postion": {
          "gtid": "",
          "included": false,
          "journalName": "mysql-bin.000012",
          "position": 5332,
          "serverId": 1,
          "timestamp": 1708169799000
        }
      }
    }
  ],
  "destination": "zcy_test"
}
[canal@imzcy canal]$


工作模式和端口

canal 默认 serverMode 为 tcp 模式,可选为 tcp, kafka, rocketMQ, rabbitMQ, pulsarMQ 。

[canal@imzcy conf]$ cat canal.properties |grep serverMode
canal.serverMode = tcp
[canal@imzcy conf]$

默认会监听如下三个端口,其中 11111 为工作于 tcp 模式时才会监听的端口,11112 为 prometheus 拉取监控数据时请求的端口,11110 为 canal admin 监听的端口。

[canal@imzcy conf]$ cat canal.properties |grep 'port ='
canal.port = 11111
canal.metrics.pull.port = 11112
canal.admin.port = 11110
[canal@imzcy conf]$


多实例

根据上面 canal 的架构我们知道,一个 canal server 中,可以运行多个 instance 用于连接处理多个 mysql 实例的数据。方法是直接在 ${CANAL_HOME}/conf/ 目录下基于名为 example 的示例 instance 复制出多个即可(目录名将作为 instance 的名称)。

[canal@imzcy conf]$ cp -rp example/ test
[canal@imzcy conf]$ ll test/
total 4
-rwxrwxrwx 1 canal canal 2224 Apr 21  2023 instance.properties
[canal@imzcy conf]$


动态加载 instance 配置

canal 默认情况下启用了自动扫描功能,自动将 ${CANAL_HOME}/conf/ 下所有包含 instance.properties 文件的子目录都作为一个 instance 来启动(创建空目录不或不包含 instance.properties 文件的目录不会被匹配上)。

[canal@imzcy conf]$ cat canal.properties |grep scan
# auto scan instance dir add/remove and start/stop instance
canal.auto.scan = true
canal.auto.scan.interval = 5
[canal@imzcy conf]$

canal server 运行过程中,也会本剧上面 interval 定义的间隔时间(秒)来进行扫描,如果发现有包含 instance.properties 文件的目录新增则启动新的 instance,如果发现目录被删除则停止对应的 instance,发现对应目录下 instance.properties 文件被更改则重启 instance。

修改 example目录下 instance.properties 文件配置

2024-02-17 16:29:33.825 [canal-instance-scan-0] INFO  com.alibaba.otter.canal.deployer.CanalController - auto notify stop example successful.
2024-02-17 16:29:34.513 [canal-instance-scan-0] INFO  com.alibaba.otter.canal.deployer.CanalController - auto notify start example successful.
2024-02-17 16:29:34.513 [canal-instance-scan-0] INFO  com.alibaba.otter.canal.deployer.CanalController - auto notify reload example successful

添加名为 test 的 instance(添加test目录)

2024-02-17 16:57:25.869 [canal-instance-scan-0] INFO  com.alibaba.otter.canal.deployer.CanalController - auto notify start test successful

删除名为 test 的 instance(删除 test 目录)

2024-02-17 16:59:25.914 [canal-instance-scan-0] INFO  com.alibaba.otter.canal.deployer.CanalController - auto notify stop test successful.

正在运行的 canal server 如果要添加新的 instance ,最好是在 conf 目录外先创建好对应的目录,调整好 instance.properties 文件配置后,直接将目录移动到 canal conf 目录下(避免自动扫描将未调整配置的instance启动引起报错等)。

手动管理 instance 配置

如果不希望动态管理 instance 配置,也可以禁用掉自动扫描,采用手动管理的方式。

[canal@imzcy conf]$ cat canal.properties |grep 'scan\|destinations'
#########               destinations            #############
canal.destinations = example,test
# auto scan instance dir add/remove and start/stop instance
canal.auto.scan = false
[canal@imzcy conf]$

如上所示,将 canal.auto.scan 参数值设置为 false 来禁用自动扫描;通过 canal.destinations 参数手动指定(英文逗号分隔多个 instance 名称,即文件夹名称)要启用的 instance,conf目录下即使有多个instance配置,只要没有在这里显示指定,对应 instance 就不会被启动。

确认 slaveId 是否会重复

如果配置多个 instance 连接同一个 mysql 时,不确定指定的 slaveId 是否被使用,可以到 mysql 上查询一下。


mysql> show slave hosts;
+-----------+-----------+-------+-----------+--------------------------------------+
| Server_id | Host      | Port  | Master_id | Slave_UUID                           |
+-----------+-----------+-------+-----------+--------------------------------------+
|        12 | 127.0.0.1 | 40780 |         1 | 0223f366-cd88-11ee-8e07-000c29a86b73 |
|        22 | 127.0.0.1 | 40972 |         1 | 215bb2fa-cda5-11ee-8e07-000c29a86b73 |
+-----------+-----------+-------+-----------+--------------------------------------+
2 rows in set (0.00 sec)

mysql>

如上所示,当前 12、22 这两个已经被使用了。


instance 首次启动时使用的 binlog 位置

在实例的 instance.properties 配置文件中可以指定首次启动时 binlog 开始的位置,有两种方式。


[canal@imzcy conf]$ vi zcy_test/instance.properties
# position info
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=mysql-bin.000012
canal.instance.master.position=5952
[canal@imzcy conf]$

[canal@imzcy conf]$ vi zcy_test/instance.properties
# position info
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.timestamp=1708165800000
[canal@imzcy conf]$

如果不指定任何位点信息,只提供 mysql 实例的连接地址,那么则会从当前位点开始:


mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000012 |     5952 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.02 sec)

mysql>


mq的顺序性

将 binlog 数据发送到 mq(例如 kafka)时,必须要考虑的一个问题就是数据的顺序性。假设 topic 有三个分区,那么 canal 在写入数据的时候会分散写入到三个分区中,但是 kafka 只能保证数据在一个分区内的顺序性,当客户端连接到 kafka 多个分区消费数据的时候,就会导致数据不会像在mysql执行时的顺序一样被处理。在 mysql 里某条记录先更新了某个 number 字段值为 50 然后又更新为 20 ,从 kafka 消费后顺序变了就有可能先执行更新为 20 的条目 ,再更新为 50 ,这样最终数据就不对了。

上面的问题 canal 官方也进行了解答,canal的消费顺序性主要取决于用户使用的MQ数据的路由方式:单topic单分区,单topic多分区、多topic单分区、多topic多分区。


正则匹配

在 canal 实例的配置文件 instance.properties 中,canal.instance.filter.regex 指定要处理连接到 mysql 实例中哪些表的数据。多个正则表达式使用英文逗号(,)分隔,转义字符使用双反斜杠(\)。

所有表: .* or .*\\..*
imzcy 库中的所有表:imzcy\\..*
imzcy 库中的账户表和订单表:imzcy\\.kernel_account, imzcy\\.kernel_order
imzcy 库中以 test 开头的表: imzcy\\.test.*
imzcy 库中名为 test1 的这一个表:imzcy.test1
多种规则的组合:canal\\..*, mysql.test1, mysql.test2 (以英文逗号作为分隔符)


动态 topic

在 canal 实例的配置文件 instance.properties 中,canal.mq.dynamicTopic 指定 canal 工作在 kafka 模式时对应表的数据应该写入到哪个 topic 中(多个配置之间以英文逗号隔开)。

test\\.test 指定匹配的单表,发送到以test_test为名字的topic上。

test\\..* 匹配test库下所有表,针对匹配的表会发送到各自表名的topic上。

.*\\..* 匹配所有表,每个表都会发送到各自表名的topic上(库名和表名以下划线连接,例如 testdb 下的 kernel_order 表匹配后写入的 topic 名称为 testdb_kernel_order)。

同时为满足更大的灵活性,canal 允许对匹配条件的规则指定发送的topic名字,配置格式:topicName:schema 或 topicName:schema.table 。

test:test\\.test 指定匹配的单表,发送到以test为名字的topic上。

test:test 匹配对应的库,test 库的所有表都会发送到名为test的topic下。

test0:test,test1:test1\\.test1 指定多个表达式,会将test库的表都发送到test0的topic下,test1\\.test1的表发送到对应的test1的topic下,其余的表发送到默认的canal.mq.topic值。


启动和停止 canal

目录 ${CANAL_HOME}/bin/ 下存放了操作 canal 的相关脚本


[canal@imzcy canal]$ ll bin/
total 16
-rwxr-xr-x 1 canal canal  226 Nov 16  2022 restart.sh
-rwxr-xr-x 1 canal canal 1244 Nov 16  2022 startup.bat
-rwxr-xr-x 1 canal canal 3805 Oct  8 14:38 startup.sh
-rwxr-xr-x 1 canal canal 1356 Nov 16  2022 stop.sh
[canal@imzcy canal]$

执行 startup.sh 脚本即可启动 canal server

[canal@imzcy canal]$ sh bin/startup.sh
cd to /usr/local/canal/bin for workaround relative path
LOG CONFIGURATION : /usr/local/canal/bin/../conf/logback.xml
canal conf : /usr/local/canal/bin/../conf/canal.properties
CLASSPATH :/usr/local/canal/bin/../conf:/usr/local/canal/bin/../lib/zstd-jni-1.5.2-5.jar:/usr/local/canal/bin/../lib/zookeeper-jute-3.5.6.jar:/usr/local/canal/bin/../lib/zookeeper-3.5.6.jar:/usr/local/canal/bin/../lib/zkclient-0.10.jar:/usr/local/canal/bin/../lib/spring-tx-5.3.9.jar:/usr/local/canal/bin/../lib/spring-orm-5.3.9.jar:/usr/local/canal/bin/../lib/spring-jdbc-5.3.9.jar:/usr/local/canal/bin/../lib/spring-jcl-5.3.9.jar:/usr/local/canal/bin/../lib/spring-expression-5.3.9.jar:/usr/local/canal/bin/../lib/spring-core-5.3.9.jar:/usr/local/canal/bin/../lib/spring-context-5.3.9.jar:/usr/local/canal/bin/../lib/spring-beans-5.3.9.jar:/usr/local/canal/bin/../lib/spring-aop-5.3.9.jar:/usr/local/canal/bin/../lib/slf4j-api-1.7.12.jar:/usr/local/canal/bin/../lib/simpleclient_pushgateway-0.4.0.jar:/usr/local/canal/bin/../lib/simpleclient_httpserver-0.4.0.jar:/usr/local/canal/bin/../lib/simpleclient_hotspot-0.4.0.jar:/usr/local/canal/bin/../lib/simpleclient_common-0.4.0.jar:/usr/local/canal/bin/../lib/simpleclient-0.4.0.jar:/usr/local/canal/bin/../lib/protobuf-java-3.6.1.jar:/usr/local/canal/bin/../lib/oro-2.0.8.jar:/usr/local/canal/bin/../lib/netty-all-4.1.68.Final.jar:/usr/local/canal/bin/../lib/netty-3.2.10.Final.jar:/usr/local/canal/bin/../lib/mysql-connector-java-5.1.48.jar:/usr/local/canal/bin/../lib/mybatis-spring-2.0.4.jar:/usr/local/canal/bin/../lib/mybatis-3.5.6.jar:/usr/local/canal/bin/../lib/logback-core-1.2.8.jar:/usr/local/canal/bin/../lib/logback-classic-1.2.8.jar:/usr/local/canal/bin/../lib/jsr305-3.0.2.jar:/usr/local/canal/bin/../lib/joda-time-2.9.4.jar:/usr/local/canal/bin/../lib/jctools-core-2.1.2.jar:/usr/local/canal/bin/../lib/jcl-over-slf4j-1.7.12.jar:/usr/local/canal/bin/../lib/javax.annotation-api-1.3.2.jar:/usr/local/canal/bin/../lib/j2objc-annotations-1.1.jar:/usr/local/canal/bin/../lib/httpcore-4.4.13.jar:/usr/local/canal/bin/../lib/httpclient-4.5.13.jar:/usr/local/canal/bin/../lib/h2-2.1.210.jar:/usr/local/canal/bin/../lib/guava-22.0.jar:/usr/local/canal/bin/../lib/fastjson2-2.0.31.jar:/usr/local/canal/bin/../lib/error_prone_annotations-2.0.18.jar:/usr/local/canal/bin/../lib/druid-1.2.17.jar:/usr/local/canal/bin/../lib/disruptor-3.4.2.jar:/usr/local/canal/bin/../lib/connector.core-1.1.7.jar:/usr/local/canal/bin/../lib/commons-logging-1.2.jar:/usr/local/canal/bin/../lib/commons-lang3-3.7.jar:/usr/local/canal/bin/../lib/commons-lang-2.6.jar:/usr/local/canal/bin/../lib/commons-io-2.4.jar:/usr/local/canal/bin/../lib/commons-compress-1.22.jar:/usr/local/canal/bin/../lib/commons-collections-3.2.2.jar:/usr/local/canal/bin/../lib/commons-codec-1.9.jar:/usr/local/canal/bin/../lib/commons-beanutils-1.9.4.jar:/usr/local/canal/bin/../lib/canal.store-1.1.7.jar:/usr/local/canal/bin/../lib/canal.sink-1.1.7.jar:/usr/local/canal/bin/../lib/canal.server-1.1.7.jar:/usr/local/canal/bin/../lib/canal.protocol-1.1.7.jar:/usr/local/canal/bin/../lib/canal.prometheus-1.1.7.jar:/usr/local/canal/bin/../lib/canal.parse.driver-1.1.7.jar:/usr/local/canal/bin/../lib/canal.parse.dbsync-1.1.7.jar:/usr/local/canal/bin/../lib/canal.parse-1.1.7.jar:/usr/local/canal/bin/../lib/canal.meta-1.1.7.jar:/usr/local/canal/bin/../lib/canal.instance.spring-1.1.7.jar:/usr/local/canal/bin/../lib/canal.instance.manager-1.1.7.jar:/usr/local/canal/bin/../lib/canal.instance.core-1.1.7.jar:/usr/local/canal/bin/../lib/canal.filter-1.1.7.jar:/usr/local/canal/bin/../lib/canal.deployer-1.1.7.jar:/usr/local/canal/bin/../lib/canal.common-1.1.7.jar:/usr/local/canal/bin/../lib/aviator-2.2.1.jar:/usr/local/canal/bin/../lib/audience-annotations-0.5.0.jar:/usr/local/canal/bin/../lib/animal-sniffer-annotations-1.14.jar:/usr/local/java/lib/dt.jar:/usr/local/java/lib/tools.jar
cd to /usr/local/canal for continue
[canal@imzcy canal]$

启动脚本会在 bin 目录下创建一个名为 canal.pid 的文件,后续 stop.sh 脚本会根据此文件中 pid 来停止 canal server 。

[canal@imzcy canal]$ cat bin/canal.pid
8959
[canal@imzcy canal]$

停止后 canal.pid 文件就会被自动删除

[canal@imzcy canal]$ sh bin/stop.sh
imzcy: stopping canal 8959 ...
Oook! cost:1
[canal@imzcy canal]$
[canal@imzcy canal]$ ll bin/
total 16
-rwxr-xr-x 1 canal canal  226 Nov 16  2022 restart.sh
-rwxr-xr-x 1 canal canal 1244 Nov 16  2022 startup.bat
-rwxr-xr-x 1 canal canal 3805 Oct  8 14:38 startup.sh
-rwxr-xr-x 1 canal canal 1356 Nov 16  2022 stop.sh
[canal@imzcy canal]$

如果canal server 启动后被异常结束,那么当再次执行 startup.sh 脚本启动时会因为已存在 canal.pid 文件而报错需要先执行 stop.sh 再启动。

[canal@imzcy canal]$ sh bin/startup.sh
found canal.pid , Please run stop.sh first ,then startup.sh
[canal@imzcy canal]$


HA

为了保证 canal 服务的高可用,可以以 HA 模式部署 canal (需要依赖外部 zookeeper 实现注册发现以及存储位点信息等)。
相比之前单节点部署 canal,只需要调整如下配置:

[canal@imzcy conf]$ vi canal.properties
canal.zkServers = 192.168.13.212:2181,192.168.13.212:2182,192.168.13.212:2183
canal.instance.global.spring.xml = classpath:spring/default-instance.xml
[canal@imzcy conf]$

第2行指定 zookeeper 集群地址,第3行指定使用 default-instance.xml 配置(这样才会将位点信息存储到 zookeeper ,否则默认使用 file-instance.xml 配置会存储在本地)。

另外需要注意的是,部署到多台服务器上的 canal 做 HA 的时候,除了连接同一个 zookeeper 地址,做 HA 的 instance 名称(目录名)也必须一样,并且需要注意 slaveId 不要重复。

HA 模式部署 canal 后,多个服务器上部署的 instance 同时只会有一个在运行,正在运行的节点挂掉后,其他节点会接替继续工作。


监控

canal 原生支持prometheus监控,只需要新增一个 job 指定 canal.properties 配置文件中 canal.metrics.pull.port 的端口即可(默认为11112)。

- job_name: 'canal'
  static_configs:
  - targets: ['192.168.2.103:11112']

需要注意的是,如果 HA 模式部署的 canal,存在多个节点时,每个节点都要加到 targets 里。

相关参考:
本文中关于 canal 介绍、工作原理、架构描述及相关配图来自 canal 官方:https://github.com/alibaba/canal/wiki/Home