一.canal服务端
canal 是阿里巴巴 MySQL 数据库 Binlog 的增量订阅 & 消费组件。基于数据库增量日志解析,提供增量数据订阅 & 消费。
1.下载canal服务端(下载不下来可联系我)
https://github.com/alibaba/canal/releases
2.安装
以liunx为例,将下载好的canal.deployer-1.1.6.tar.gz,上传到服务
3.解压
#创建canal文件传
mkdir canal
#解压到指定目录
tar -zxvf canal.deployer-1.1.6.tar.gz -C canal
4.修改canal.properties配置
重要配置:
# canal端口 canal.port = 11111# 可以支持tcp, kafka, rocketMQ, rabbitMQ, pulsarMQ canal.serverMode = tcp#实例名称,可以配置多个,配置多个会生产多个文件夹 canal.destinations = example
4.修改对应实力instance.properties配置
重要配置:
# 配置你连接的数据库地址 canal.instance.master.address=127.0.0.1:3306# 配置数据库的username/password canal.instance.dbUsername=root canal.instance.dbPassword=123456# table regex 监听的数据库规则 #.your_database 表示你要同步的数据库名,\\.表示匹配一个点号(.)的转义字符,.在正则表达式中表示匹配任意字符,因此需要转义表示匹配点号。.*表示匹配所有以 your_database.开头的字符串。 canal.instance.filter.regex=your_database\\..*
5.启动canal
1.cd 到对应安装包目录bin下
2.启动canal服务:./startup.sh
3.停止canal服务:./stop.sh
4.重启canal服务:./restart.sh
二.canal客户端
这里将canal集成到springboot中。
1.添加pom依赖。
com.alibaba.otter
canal.client
1.1.6
2.yml配置
canal:
host: 192.168.1.111 #自己的服务器ip
port: 11111
destination: example #配置文件配置的名称
username:
password:
batch:
size: 100
3.代码(根据业务自行优化)
package com.pw.test;
import com.alibaba.fastjson2.JSONObject;
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 lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class CanalClient implements InitializingBean, DisposableBean {
@Value("${canal.host}")
private String canalHost;
@Value("${canal.port}")
private int canalPort;
@Value("${canal.destination}")
private String canalDestination;
@Value("${canal.username}")
private String canalUsername;
@Value("${canal.password}")
private String canalPassword;
@Value("${canal.batch.size}")
private int batchSize;
private static final Logger logger = LoggerFactory.getLogger(CanalClient.class);
private CanalConnector canalConnector;
private ExecutorService executorService;
@Bean
public void canalConnector() {
this.canalConnector = CanalConnectors.newSingleConnector(
new InetSocketAddress(canalHost, canalPort),
canalDestination,
canalUsername,
canalPassword
);
}
@Override
public void afterPropertiesSet() throws Exception {
this.executorService = Executors.newSingleThreadExecutor();
this.executorService.execute(new Task());
}
@Override
public void destroy() throws Exception {
if (executorService != null) {
executorService.shutdown();
}
}
private class Task implements Runnable {
@Override
public void run() {
while (true) {
try {
//连接
canalConnector.connect();
//订阅
canalConnector.subscribe();
while (true) {
Message message = canalConnector.getWithoutAck(batchSize); // batchSize为每次获取的batchSize大小
long batchId = message.getId();
//获取批量的数量
int size = message.getEntries().size();
//如果没有数据
if (batchId == -1 || size == 0) {
// log.info("无数据");
try {
// 线程休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果有数据,处理数据
printEntry(message.getEntries());
}
canalConnector.ack(batchId);
}
} catch (Exception e) {
logger.error("Error occurred when running Canal Client", e);
} finally {
canalConnector.disconnect();
}
}
}
}
private void printEntry(List entrys) {
for (CanalEntry.Entry entry : entrys) {
if (isTransactionEntry(entry)){
//开启/关闭事务的实体类型,跳过
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信息
log.info("================》; binlog[{} : {}] , name[{}, {}] , eventType : {}",
entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
eventType);
//判断是否是DDL语句
if (rowChage.getIsDdl()) {
log.info("================》;isDdl: true,sql:{}", rowChage.getSql());
}
System.out.println(rowChage.getSql());
//获取RowChange对象里的每一行数据,打印出来
for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
//如果是删除语句
if (eventType == CanalEntry.EventType.DELETE) {
log.info(">>>>>>>>>> 删除 >>>>>>>>>>");
printColumnAndExecute(rowData.getBeforeColumnsList(), "DELETE");
//如果是新增语句
} else if (eventType == CanalEntry.EventType.INSERT) {
log.info(">>>>>>>>>> 新增 >>>>>>>>>>");
printColumnAndExecute(rowData.getAfterColumnsList(), "INSERT");
//如果是更新的语句
} else {
log.info(">>>>>>>>>> 更新 >>>>>>>>>>");
//变更前的数据
log.info("------->; before");
printColumnAndExecute(rowData.getBeforeColumnsList(), null);
//变更后的数据
log.info("------->; after");
printColumnAndExecute(rowData.getAfterColumnsList(), "UPDATE");
}
}
}
}
/**
* 执行数据同步
* @param columns
* @param type
*/
private void printColumnAndExecute(List columns, String type) {
if(type == null){
return;
}
JSONObject jsonObject = new JSONObject();
for (CanalEntry.Column column : columns) {
jsonObject.put(column.getName(), column.getValue());
log.info("{}: {}", column.getName(), column.getValue());
}
// 此处使用json转对象的方式进行转换
// JSONObject.parseObject(jsonObject.toString(), xxx.class)
if(type.equals("INSERT")){
// 执行新增
log.info("新增成功->{}", jsonObject.toJSONString());
}else if (type.equals("UPDATE")){
// 执行编辑
log.info("编辑成功->{}", jsonObject.toJSONString());
}else if (type.equals("DELETE")){
// 执行删除
log.info("删除成功->{}", jsonObject.toJSONString());
}
}
/**
* 判断当前entry是否为事务日志
*/
private boolean isTransactionEntry(CanalEntry.Entry entry){
if(entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN){
log.info("********* 日志文件为:{}, 事务开始偏移量为:{}, 事件类型为type={}",
entry.getHeader().getLogfileName(),
entry.getHeader().getLogfileOffset(),
entry.getEntryType()
);
return true;
}else if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND){
log.info("********* 日志文件为:{}, 事务结束偏移量为:{}, 事件类型为type={}",
entry.getHeader().getLogfileName(),
entry.getHeader().getLogfileOffset(),
entry.getEntryType()
);
return true;
}else {
return false;
}
}
}
三.Mysql配置
1.使用canal需要mysql开启binlog日志
[mysqld]
#设置服务器id(确保唯一)
server-id=666
#设置日志个数默认ROW 根据需求选择
binlog_format=STATEMENT
#开启binlog日志(mysql-bin 日志名称)
log-bin=mysql-bin
#设置需要复制的数据库,默认复制全部
#binlog-do-db=mydb
2.重启mysql服务
systemctl restart mysqld
3.查看状态(如果不为空则配置成功)
SHOW MASTER STATUS;