cnanal
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 设置为 false
- mysql链接时的起始位置
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
vi logs/canal/canal.log</pre>
vi logs/example/example.log
sh bin/stop.sh
springboot整合
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.4</version>
</dependency>
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 {
private Queue<String> 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(".*\\..*");
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<CanalEntry.Entry> entries) {
for (CanalEntry.Entry entry : entries) {
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN) {
continue;
}
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
continue;
}
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<CanalEntry.Column> beforeColumns,
List<CanalEntry.Column> 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<CanalEntry.Column> 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);
}
}
|