ClientAdapter – canal 1.1.1版本之后, 增加客户端数据落地的适配及启动功能, 目前支持功能:
客户端启动器
同步管理REST接口
日志适配器, 作为DEMO
关系型数据库的数据同步(表对表同步), ETL功能
Hbase的数据同步(表对表同步), ETL功能
(后续支持) ElasticSearch多表数据同步,ETL功能
准备Canal Admin – canal-admin设计上是为canal提供整体配置管理、节点运维等面向运维的功能,提供相对友好的WebUI操作界面,方便更多用户快速和安全的操作
对于自建 MySQL , 需要先开启 Binlog 写入功能,配置 binlog-format 为 ROW 模式,my.cnf 中配置如下
[mysqld] log-bin=mysql-bin # 开启 binlog binlog-format=ROW # 选择 ROW 模式 server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant
CREATE USER canal IDENTIFIED BY 'canal'; GRANT SELECt, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; -- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ; FLUSH PRIVILEGES;
mysql8.0 使用
ALTER USER 'canal'@'%' IDENTIFIED WITH mysql_native_password BY 'canal';启动
下载 canal, 访问 release 页面 , 选择需要的包下载, 如以 1.0.17 版本为例
https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.deployer-1.1.5.tar.gz
解压缩
mkdir /canal tar zxvf canal.deployer-$version.tar.gz -C /canal
解压完成后,进入 /canal 目录,可以看到如下结构
修改配置
vi conf/example/instance.properties
## mysql serverId canal.instance.mysql.slaveId = 1234 #position info,需要改成自己的数据库信息 canal.instance.master.address = 127.0.0.1:3306 canal.instance.master.journal.name = canal.instance.master.position = 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 #table regex canal.instance.filter.regex = .*\\..*
- canal.instance.connectionCharset 代表数据库的编码方式对应到 java 中的编码类型,比如 UTF-8,GBK , ISO-8859-1如果系统是1个 cpu,需要将 canal.instance.parser.parallel 设置为 falsemysql链接时的起始位置
canal.instance.master.journal.name + canal.instance.master.position : 精确指定一个binlog位点,进行启动
canal.instance.master.timestamp : 指定一个时间戳,canal会自动遍历mysql binlog,找到对应时间戳的binlog位点后,进行启动
不指定任何信息:默认从当前数据库的位点,进行启动。(show master status)
sh bin/startup.sh
sh bin/startup.sh
查看 server 日志
vi logs/canal/canal.log
查看 instance 的日志
vi logs/example/example.log
关闭
sh bin/stop.shspringboot整合
com.alibaba.otter canal.client 1.1.4
package com.example.couponintegratedmission.util;
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.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.protobuf.InvalidProtocolBufferException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentlinkedQueue;
@Component
public class AliCanalClient implements ApplicationRunner {
//读取的binlog sql 队列缓存 一边Push 一边poll
private Queue canalQueue = new ConcurrentlinkedQueue<>();
@Value("${canal.host}")
private String host;
@Value("${canal.port}")
private int port;
@Value("${canal.username}")
private String username;
@Value("${canal.password}")
private String password;
@Value("${canal.instance}")
private String instance;
@Override
public void run(ApplicationArguments args) throws Exception {
CanalConnector conn = getConn();
while (true) {
try {
conn.connect();
//订阅实例中所有的数据库和表
conn.subscribe(".*\..*");
// 回滚到未进行ack的地方
conn.rollback();
// 获取数据 每次获取一百条改变数据
Message message = conn.getWithoutAck(100);
long id = message.getId();
int size = message.getEntries().size();
if (id != -1 && size > 0) {
// 数据解析
analysis(message.getEntries());
} else {
Thread.sleep(1000);
}
// 确认消息
conn.ack(message.getId());
} catch (CanalClientException | InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭连接
conn.disconnect();
}
}
}
private void analysis(List entries) {
for (CanalEntry.Entry entry : entries) {
// 只解析mysql事务的操作,其他的不解析
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN) {
continue;
}
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
continue;
}
// 解析binlog
CanalEntry.RowChange rowChange = null;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStorevalue());
} catch (Exception e) {
throw new RuntimeException("解析出现异常 data:" + entry.toString(), e);
}
if (rowChange != null) {
// 获取操作类型
CanalEntry.EventType eventType = rowChange.getEventType();
// 获取当前操作所属的数据库
String dbName = entry.getHeader().getSchemaName();
// 获取当前操作所属的表
String tableName = entry.getHeader().getTableName();
// 事务提交时间
long timestamp = entry.getHeader().getExecuteTime();
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
dataDetails(rowData.getBeforeColumnsList(), rowData.getAfterColumnsList(), dbName, tableName, eventType, timestamp);
System.out.println("-------------------------------------------------------------");
}
}
}
}
private static void dataDetails(List beforeColumns,
List afterColumns,
String dbName,
String tableName,
CanalEntry.EventType eventType,
long timestamp) {
System.out.println("数据库:" + dbName);
System.out.println("表名:" + tableName);
System.out.println("操作类型:" + eventType);
if (CanalEntry.EventType.INSERT.equals(eventType)) {
System.out.println("新增数据:");
printColumn(afterColumns);
} else if (CanalEntry.EventType.DELETE.equals(eventType)) {
System.out.println("删除数据:");
printColumn(beforeColumns);
} else {
System.out.println("更新数据:更新前数据--");
printColumn(beforeColumns);
System.out.println("更新数据:更新后数据--");
printColumn(afterColumns);
}
System.out.println("操作时间:" + timestamp);
}
private static void printColumn(List columns) {
for (CanalEntry.Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
public CanalConnector getConn() {
return CanalConnectors.newSingleConnector(new InetSocketAddress(host, port), instance, username, password);
}
}



