目录
一、下载文件
开源插件地址canal.deployer-1.1.3.tar.gz
二、解压文件
对应的服务器上,目录如下
三、配置两个canal-server
需要先配置zookeeper,这个自行百度
canal-server所有的配置都在conf中;
第一台相关配置相关文件
1、canal.properties,如下标红项,需要正确配置,其余配置使用默认
canal.id = 1 #canal服务id,目前没有实际意义
canal.ip =
canal.port = 11111 #canal服务socket监听端口,代码中连接canal-server时,使用此段口连接
canal.metrics.pull.port = 11112
canal.zkServers =192.168.228.128:2181 #zookeeper服务地址端口
canal.destinations = example #表示实例的配置文件instance.properties地址
2、example/instance.properties配置
#################################################
## mysql serverId , v1.0.26+ will autoGen
canal.instance.mysql.slaveId=1234 #每个canal-server都是master数据库的slave,此处为slave的唯一标识
# position info
canal.instance.master.address=192.168.5.65:3306 #master的地址
canal.instance.master.journal.name= #mysql主库链接时起始的binlog文件,默认:无
canal.instance.master.position= #mysql主库链接时起始的binlog偏移量,默认:无
canal.instance.master.timestamp= #mysql主库链接时起始的binlog的时间戳,默认:无
canal.instance.master.gtid=
# table meta tsdb info
canal.instance.tsdb.enable=true
# username/password
canal.instance.dbUsername=canal #mysql数据库帐号
canal.instance.dbPassword=canal #mysql数据库密码
canal.instance.connectionCharset = UTF-8 #mysql数据库编码
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==
canal.instance.filter.regex=.*\\..*
canal.instance.filter.black.regex=
canal.mq.topic=example
canal.mq.partition=0
第二台机器配置需默认修改的地方
1、canal.properties,如下标红项,需要正确配置,其余配置使用默认
canal.id = 2
canal.port = 11112 #canal服务socket监听端口,代码中连接canal-server时,使用此段口连接
canal.metrics.pull.port = 12112
2、example/instance.properties配置
canal.instance.mysql.slaveId=1235
四、canal-client
package 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 java.net.InetSocketAddress;
import java.util.List;
public class ClientSample {
public static void main(String args[]) {
// 单连接
// CanalConnector connector = CanalConnectors.newSingleConnector(
// new InetSocketAddress("192.168.228.128", 11111), "example", "", "");
// 集群连接
//CanalConnector connector = CanalConnectors.newClusterConnector("192.168.113.101:2181,192.168.113.102:2181,192.168.113.103:2181/canal/cluster1", "canal_test", "", "");
CanalConnector connector = CanalConnectors.newClusterConnector("192.168.228.128:2181", "example", "", "");
//计数器
int emptyCount = 0;
//一次最多拉多少条Message
//注意:
int batchSize = 1000;
try {
//和server建立连接
connector.connect();
//订阅表
connector.subscribe("test.user");
//回滚到上次ack的位置
connector.rollback();
//最大空闲次数
int maxEmptyCount = 1000;
//死循环去拉取数据
while (emptyCount < maxEmptyCount) {
//尝试最多拿batchSize条记录
//注意:getWithoutAck一次,对应一个Message。
//一个Message有一个MessageID,还有一个List<CanalEntry.Entry>
Message message = connector.getWithoutAck(batchSize);
long batchId = message.getId();
int size = message.getEntries().size();
//没有数据---等待
if (batchId == -1 || size == 0) {
emptyCount++;
// System.out.println("empty count : " + emptyCount);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//拿到数据---开始解析--发送到目的地如kafka/elasticsearch/redis/hbase...等
emptyCount = 0;
//这里打印内容
printEntry(message.getEntries());
}
connector.ack(batchId); // 提交确认
//connector.rollback(batchId); // 处理失败, 回滚数据
}
System.out.println("empty too many times, exit");
} finally {
connector.disconnect();
}
}
private static void printEntry(List<CanalEntry.Entry> entrys) {
for (CanalEntry.Entry entry : entrys) {
//该条数据的数据类型是事务开始或事务结束,不是binlog 二进制数据本身,就跳过继续处理
//注意:这里是有bug的,当mysql开启binlog,且为Row行模式,且开启了在binlog中显示原始SQL。
//这时就会有一种新增的类型:CanalEntry.EventType.QUERY
// 原始
//if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
// continue;
//}
// 修改为 只保留binlog部分对应的Entry并解析
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN //跳过事务开始的Entry
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND //跳过事务结束的Entry
|| entry.getHeader().getEventType() == CanalEntry.EventType.QUERY //跳过事务为原始SQL的Entry
) {
continue;
}
//得到当前行变化的数据Before、After
CanalEntry.RowChange rowChage = null;
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);
}
//事件类型
CanalEntry.EventType eventType = rowChage.getEventType();
System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s",
entry.getHeader().getLogfileName(), //binlog文件名
entry.getHeader().getLogfileOffset(),//binlog offset
entry.getHeader().getSchemaName(),//库名
entry.getHeader().getTableName(),//表名
eventType //事件类型
));
for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
//INSERT
if (eventType == CanalEntry.EventType.INSERT) {
printColumn(rowData.getAfterColumnsList());
//DELETE
} else if (eventType == CanalEntry.EventType.DELETE) {
printColumn(rowData.getBeforeColumnsList());
//UPDATE
} else if (eventType == CanalEntry.EventType.UPDATE) {
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());
}
}
}
五、canal客户端集群
将canal-client 相同的消费代码部署多分,zk就能保证同一时刻,仅仅一份代码子运行,当其中一个down掉,另一个会在固定时间内被激活;