1. Canal 简介
Canal 是阿里巴巴开源的一款基于数据库增量日志解析,提供增量数据订阅和消费,支持 MySQL、Oracle、SqlServer 等多种数据库的数据订阅和消费的中间件,可以将数据库变更信息异步地推送到MQ中间件,如 Kafka、RocketMQ 等。
Canal 首先基于Google的开源协议protobuf定义一套数据格式,然后基于此协议解析各种数据库的增量日志,提供类似于MySQL replication的增量订阅&消费机制,支持丰富的sql语句过滤功能,同时提供多种数据输出格式,如json、protobuf等。Canal可以广泛应用于数据实时同步、数据分析、数据监控等场景。
2. Canal 安装
2.1 mysql开启binlog模式
1)查看当前 mysql 是否开启 binlog 模式。
SHOW VARIABLES LIKE '%log_bin%'
如果log_bin的值为OFF是未开启,为ON是已开启。
2)修改/etc/my.cnf 需要开启binlog模式。
[mysqld]
log-bin=mysql-bin
binlog-format=ROW
server_id=1
修改完成之后,重启mysqld的服务。
3)进入mysql
mysql -h localhost -u root -p
4)创建账号 用于测试使用
使用root账号创建用户并授予权限
create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;
2.2 Canal 下载安装
(这里我是在window下安装的)
1)下载地址:Releases · alibaba/canal · GitHub
2)解压压缩包。
3)配置文件:..\canal.deployer-1.1.5\conf\example.instance.properties
# position info
canal.instance.master.address=localhost:3306 #需要连接的数据库地址及端口
...
# username/password
canal.instance.dbUsername=canal # 数据库账号
canal.instance.dbPassword=canal # 数据库密码
...
canal.instance.defaultDatabaseName = 数据库名 #默认一个数据库
4)开启服务
3. 项目监控数据变化
3.1 导入 maven
<dependency>
<groupId>com.xpand</groupId>
<artifactId>starter-canal</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
导入 starter-canal 依赖导不上:畅购商城canal依赖,搭建微服务的问题_xiaopeng_thriller的博客-CSDN博客
3.2 配置文件
canal:
client:
instances:
example:
host: localhost
port: 11111
batchSize: 9099
3.3 启动类
@SpringBootApplication
@EnableCanalClient
public class CanalApplication {
public static void main(String[] args) {
SpringApplication.run(CanalApplication.class, args);
}
}
3.4 监听类
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.xpand.starter.canal.annotation.CanalEventListener;
import com.xpand.starter.canal.annotation.ListenPoint;
@CanalEventListener
public class BusinessListener {
@ListenPoint(schema = "数据库名", table = {"表名"})
public void adUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
System.err.println("数据发生变化");
rowData.getBeforeColumnsList().forEach((c) -> System.err.println("更改前数据: " + c.getName() + " :: " + c.getValue()));
rowData.getAfterColumnsList().forEach((c) -> System.err.println("更改后数据: " + c.getName() + " :: " + c.getValue()));
}
}
修改表的数据之后:
Canal 数据监控的使用_@enablecanalclient_YKenan的博客-CSDN博客
第二种方式
package com.cigna.hmc.activity.canal;
import java.net.InetSocketAddress;
import java.util.List;
import com.cigna.hmc.activity.common.util.DateUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
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;
import com.alibaba.otter.canal.protocol.Message;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class CannalClient implements InitializingBean {
@Value("${canal.monitor.table}")
private String monitorTable;
private final static int BATCH_SIZE = 1000;
@Override
public void afterPropertiesSet() throws Exception {
// 创建链接
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("10.141.162.89", 11111), "example", "canal", "canal");
try {
// 打开连接
connector.connect();
// 订阅数据库表,全部表
// connector.subscribe(".*..*");
connector.subscribe(monitorTable);
// 回滚到未进行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 {
// 线程休眠1秒
Thread.sleep(1000);
}
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<Entry> entrys) {
for (Entry entry : entrys) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
// 开启/关闭事务的实体类型,跳过
continue;
}
// RowChange对象,包含了一行数据变化的所有特征
// 比如isDdl 是否是ddl变更操作 sql 具体的ddl sql beforeColumns afterColumns 变更前后的数据字段等等
RowChange rowChage;
try {
rowChage = 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类型
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 (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());
}
System.out.println("time:" + DateUtil.getNow());
}
}
}
private static void printColumn(List<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
}
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>
参考文档:
数据同步解决方案-canal与rabbitmq_canal集成rabbitmq没生效_ICoder_Next的博客-CSDN博客