Canal入门
Canal是阿里开源的一个增量数据变更收集的工具,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费的一种中间件。
Canal主要包含以下几个组件:
canal-server:运行在后台的服务,用于监听MySQL的binlog日志,解析binlog日志的变更内容,然后将变更信息推送到canal-client。
canal-adapter:一个适配器,用于将canal-server获取的变更信息适配到其他数据源,如Elasticsearch、HBase等。
canal-admin:一个管理工具,用于管理和监控canal-server和canal-adapter的运行状态。
首先,你需要部署Canal Server和Canal Admin。
配置Canal Server,指定数据库和binlog的位置。 一个 server 里可以包含多个 instance
- canal-instance: canal-server中的一个处理实例,可以处理不同的业务逻辑, 一个最小的订阅 MySQL 的队列.
启动Canal Admin,它会自动连接到Canal Server。
通过Canal Admin可视化界面查看和管理Canal Server的状态。
接下来,配置Canal Adapter,指定要同步的数据库和对应的消息中间件。
启动Canal Adapter,它会消费Canal Server的数据,并将数据同步到指定的消息中间件。
下载地址: https://github.com/alibaba/canal/releases

下载 canal.addpter, canal.admin和canal.deployer(canal-server) ,安装之前,先安装mysql , 这个步骤略。
mysql需要 开启 Binlog 写入功能,配置 binlog-format 为 ROW 模式。
# 设置服务器ID(如果是复制集群,确保每个节点的ID唯一)
server-id=1
# 启用二进制日志
log-bin=mysql-bin
# 选择 ROW 模式
binlog-format=ROW
#指定对哪个database记录binary log events,这里记录test这个库
#binlog-do-db=test
创建用于主从复制的用户canal,并授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限,如果已有账户可直接 grant。
CREATE USER 'canal'@'%' identified by 'canal';
GRANT SELECT, SUPER, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
安装canal-admin
解压canal.admin文件到目录 /usr/local/canal/admin 中,
mysql中创建数据库 canal_manager ,具体脚本在 admin/conf/canal_manager.sql 中
修改配置文件 vim conf/application.yml
server:
port: 8089
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
spring.datasource:
address: 192.168.1.100:3306
database: canal_manager
username: canal
password: canal
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
hikari:
maximum-pool-size: 30
minimum-idle: 1
canal:
adminUser: admin
adminPasswd: admin
启动admin服务
[root@master admin]# bin/startup.sh
在浏览器打开: http://192.168.1.181:8089/
默认账号密码为:admin:123456, 登录后可以在账号管理中修改密码。
安装canal-deployer(server)
解压canal.depllyer 文件到目录 /usr/local/canal/server中,
配置: conf目录中有2个重要的配置文件: 具体可参加: https://github.com/alibaba/canal/wiki/Canal-Admin-ServerGuide
- canal_local.properties 为注册admin配置
- canal.properties 为本地配置
canal_local.properties配置比较少: 引入canal_local.properties,针对canal-server启动所需要的配置,做了最小化配置,只需要关注和admin请求的必要参数即可
# register ip
canal.register.ip =
# canal-admin的访问地址
canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110
# canal-admin的访问账号和密码,密码与adminPasswd对应,这里是密文
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
# 启动后自动注册到admin中
canal.admin.register.auto = true
# 集群名称 ,这里是单点可空着即可
canal.admin.register.cluster =
# 节点名称名称,里是单点可空着即可
canal.admin.register.name =
canal.propertie文件可以先不用修改,先进行实例配置
canal配置分为两部分:
一个是server/conf/canal.propertie配置文件;
一个是server/conf/example/instance.properties配置文件。
前者定义instance的公共参数,后者定义instance级别的配置,每个instance一份。
先看一个示例配置
[root@master server]# vim conf/example/instance.properties
## mysql serverId , v1.0.26+ will autoGen
## mysql serverId,这里的slaveId不能和myql集群中已有的server_id一样
canal.instance.mysql.slaveId=10
# enable gtid use true/false
canal.instance.gtidon=false
ZZ
# position info
canal.instance.master.address=192.168.1.100:3306
canal.instance.master.journal.name=mysql-bin.000007
canal.instance.master.position=68666
canal.instance.master.timestamp=
canal.instance.master.gtid=
# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=
# table meta tsdb info
canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==
# # .*\..*表示监听所有表 也可以写具体的表名,用,隔开
canal.instance.filter.regex=.*\\..*
# 数据解析表的黑名单,多个表用,隔开
canal.instance.filter.black.regex=mysql\\.slave_.*
如上配置信息所示,我们需要的配置是:
1:(mysql serverId)配置slaveId,之一在集群中,各个服务器的id是唯一的。
2:(position info)配置master 服务器的相关信息:服务器地址、binlog日志名称、mysql主库链接时起始的binlog偏移量。
这个数数据可以在mysql中获取:

3:(username/password)配置用户主从复制的账号和密码。
4:(table regex and table black regex)配置要监听的表和不需要解析的表。
注意:
1、修改成管理方式之后,之前的server 配置、instance配置全动态都保存到Canal admin的数据库里面了。所以之前在磁盘上的的配置文件就不生效了.
2、所以你可能会看到server的报错:Caused by: com.alibaba.otter.canal.common.CanalException: instance : example config is not found。明明已经有example这个instance了(磁盘上有文件夹)。
你只需在需要在 Canal Admin 手动添加实例example 的 instance 就行了。配置直接copy保存就行了
启动canan server. 在canal admin中可以到

添加一个instance, 名称为 example , 配置需要订阅的数据库信息地址,添加完成后如果:

测试新建一个SpringBoot项目
在pom中添加相关jar包
<canal.version>1.1.5</canal.version>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>${canal.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.protocol</artifactId>
<version>${canal.version}</version>
</dependency>
定义一个类,连接到canal服务。
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 org.springframework.stereotype.Component; import java.net.InetSocketAddress; import java.util.List; @Component public class CanalClient { private final static int BATCH_SIZE = 1000; public void run() { // 创建链接 CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.1.181", 11111), "example", "canal", "canal"); try { //打开连接 connector.connect(); //订阅数据库表,全部表 connector.subscribe(".*\\..*"); //回滚到未进行ack的地方,下次fetch的时候,可以从最后一个没有ack的地方开始拿 connector.rollback(); while (true) { // 获取指定数量的数据 Message message = connector.getWithoutAck(BATCH_SIZE); //获取批量ID long batchId = message.getId(); //获取批量的数量 int size = message.getEntries().size(); //如果没有数据 if (batchId == -1 || size == 0) { try { //线程休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } else { //如果有数据,处理数据 printEntry(message.getEntries()); } //进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。 connector.ack(batchId); } } catch (Exception e) { e.printStackTrace(); } finally { connector.disconnect(); } } /** * 打印canal server解析binlog获得的实体类信息 */ private static void printEntry(List<CanalEntry.Entry> entrys) { for (CanalEntry.Entry entry : entrys) { if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { //开启/关闭事务的实体类型,跳过 continue; } //RowChange对象,包含了一行数据变化的所有特征 //比如isDdl 是否是ddl变更操作 sql 具体的ddl sql beforeColumns afterColumns 变更前后的数据字段等等 CanalEntry.RowChange rowChage; try { rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); } catch (Exception e) { throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e); } //获取操作类型:insert/update/delete类型 CanalEntry.EventType eventType = rowChage.getEventType(); //打印Header信息 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)); //判断是否是DDL语句 if (rowChage.getIsDdl()) { System.out.println("================》;isDdl: true,sql:" + rowChage.getSql()); } //获取RowChange对象里的每一行数据,打印出来 for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) { //如果是删除语句 if (eventType == CanalEntry.EventType.DELETE) { printColumn(rowData.getBeforeColumnsList()); //如果是新增语句 } else if (eventType == CanalEntry.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<CanalEntry.Column> columns) { for (CanalEntry.Column column : columns) { System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated()); } } }
新建一个CommandLineRunner,项目启动完成执行
@Component public class MyCommandLineRunner implements CommandLineRunner { @Resource private CanalClient canalClient; @Override public void run(String... args) throws Exception { System.out.printf("MyCommandLineRunner..."); canalClient.run(); } }
启动springboot,在MySQL中执行一条update语句
UPDATE t_appuser SET realname='123567' where openid='o2Hsa0a_6hokVUBE6M9b4xmoWLHk' ;
可以看到项目的输入内容
================》; binlog[mysql-bin.000009:28656] , name[wuliu,t_appuser] , eventType : UPDATE ------->; before openid : o2Hsa0a_6hokVUBE6M9b4xmoWLHk update=false nickname : 林文 update=false realname : 123 update=false gender : 0 update=false type : 0 update=false province : update=false city : update=false avatarurl : https://thirdwx.qlogo.cn/mmhead/mibQlicQlBOl2ziaOAdLGiclUviaEdUF4hKMlOf3vlmWxZMo/132 update=false unionid : HO/TLqd+KhJykMRz+pt1fQ== update=false appid : wxbe4698a8050d0ebf update=false sessionkey : HO/TLqd+KhJykMRz+pt1fQ== update=false loginid : df2c22d706f842aaa1bb46a23730e6db update=false time : 1610789370111 update=false etime : 1610789370111 update=false mid : 0 update=false ------->; after openid : o2Hsa0a_6hokVUBE6M9b4xmoWLHk update=false nickname : 林文 update=false realname : 123567 update=true gender : 0 update=false type : 0 update=false province : update=false city : update=false avatarurl : https://thirdwx.qlogo.cn/mmhead/mibQlicQlBOl2ziaOAdLGiclUviaEdUF4hKMlOf3vlmWxZMo/132 update=false unionid : HO/TLqd+KhJykMRz+pt1fQ== update=false appid : wxbe4698a8050d0ebf update=false sessionkey : HO/TLqd+KhJykMRz+pt1fQ== update=false loginid : df2c22d706f842aaa1bb46a23730e6db update=false time : 1610789370111 update=false etime : 1610789370111 update=false mid : 0 update=false
可以看到数据变化前后的记录
参考:https://mp.weixin.qq.com/s/0nmaGqPJar5ssnYN223r1g

浙公网安备 33010602011771号