我是使用docker来搭建,在服务器上建这么两个目录,用来挂载docker里面的mysql的配置文件,和mysql的数据存储, 后面docker run要用
/etc/mysql ##挂载mysql的配置文件
/opt/mysql ##挂载mysql的数据存储
docker pull mysql:5.7
docker run -itd --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=www123456 -v /etc/mysql:/etc/mysql -v /opt/mysql:/var/lib/mysql -v /etc/localtime:/etc/localtime mysql:5.7
查看挂载情况,里面可以看到详细的挂载情况,可以看到我们的两个目录是被正确挂载了
docker inspect 容器名或者id
直接在服务器上编辑mysql的配置文件(和docker容器里面的文件相互挂载了),里面有可能是空的,直接往里面加就行了
vim /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
log-bin=mysql-bin
binlog-format=ROW
server_id=1
docker restart 容器名或者id
用mysql工具查看数据库的binlog是否开启
show variables like '%log_bin%';
执行一条建表语句看看有没有binlog存在服务器上面
确实有,在mysql里面看看日志,到这里mysql就配置好了!
mysql> CREATE USER canal IDENTIFIED BY 'canal';
mysql> GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
mysql> FLUSH PRIVILEGES;
mysql> show grants for 'canal'@'%';
+----------------------------------------------------------------------------+
| Grants for canal@%% |
+----------------------------------------------------------------------------+
| GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `canal`@`%` |
+----------------------------------------------------------------------------+
1 row in set (0.00 sec)
create database woods_test;
从这开始,就开始经历很多的坑,,,可能是对docker网络这部分不太熟的原因。
我最开始的想法是在一台服务器上的docker里面,运行一个mysql容器,运行一个canal-server容器,然后本地起一个java的canal客户端连接canal-server,监听数据库的增量binlog日志。思路看起来没啥问题。
# bash run.sh -e canal.auto.scan=false -e canal.destinations=woods_test -e canal.instance.master.address=172.17.0.3:3306 -e canal.instance.dbUsername=canal -e canal.instance.dbPassword=canal -e canal.instance.connectionCharset=UTF-8 -e canal.instance.tsdb.enable=true -e canal.instance.gtidon=false
Java客户端代码, 其实就是官网的demo
public static void main(String args[]) {
// 创建链接
String hostIp = AddressUtils.getHostIp();
System.out.println(hostIp);
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(
"42.193.12.204", //canal-server的ip地址
11111),
"woods_test",
"canal",
"canal");
int batchSize = 1000;
int emptyCount = 0;
try {
connector.connect();
connector.subscribe(".*\\..*");
connector.rollback();
int totalEmptyCount = 120;
while (emptyCount < totalEmptyCount) {
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) {
}
} else {
emptyCount = 0;
// System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
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<Entry> entrys) {
for (Entry entry : entrys) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
continue;
}
RowChange rowChage = null;
try {
rowChage = RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
EventType eventType = rowChage.getEventType();
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));
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());
}
}
}
}
private static void printColumn(List<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
上面的Java客户端是怎么起都启动不起来,一直报这个错
Exception in thread "main" com.alibaba.otter.canal.protocol.exception.CanalClientException: failed to subscribe with reason: something goes wrong with channel:[id: 0x70fe412e, /118.112.75.194:7136 => /172.17.0.4:11111], exception=com.alibaba.otter.canal.server.exception.CanalServerException: destination:example should start first
tail -fn 100 /home/admin/canal-server/logs/example/example.log //查日志
去docker容器里面查看日志找原因,发现是canal1.16版本的bug,使得canal-server会去查一张不存在的表,所以这里我修改了一下run.sh里面最后的cmd命令,指定了canal-server的版本
cmd="docker run -d -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server:v1.1.4"
再次启动java的canal-client,可以看到连接成功,正在定时获取binlog,如果监听的库有表的插入或更新,客户端就会打印出来
version: '2'
services:
canal-server:
network_mode: "bridge" #因为我mysql是在这个默认的网络下面,所以我的canal-server也加入这个网络
image: canal/canal-server:v1.1.4 #最新的可能有bug,就用这个1.14吧
container_name: canal-server
ports:
- 11111:11111
environment:
- canal.auto.scan=false
- canal.destinations=woods_test
- canal.instance.master.address=172.17.0.3:3306 #要监听的mysql地址
- canal.instance.dbUsername=canal
- canal.instance.dbPassword=canal
- canal.instance.connectionCharset=UTF-8
- canal.instance.tsdb.enable=true
- canal.instance.gtidon=false
- canal.instance.filter.regex=.*\\..* #监听规则
volumes:
- ./canal-server/conf/:/admin/canal-server/conf/ #挂载文件至宿主机
- ./canal-server/logs/:/admin/canal-server/logs/
启动java客户端后同样可以正确监听到woods_test数据库的变化。success!
version: '2'
services:
canal-admin:
network_mode: "bridge" #因为我mysql和canal-server是在这个默认的网络下面,所以我的canal-admin也加入这个网络
image: canal/canal-admin:v1.1.4
container_name: canal-admin
ports:
- 8089:8089
environment:
- server.port=8089
- canal.adminUser=admin
- canal.adminPasswd=admin
ip就填canal-server的ip,端口就默认的那几个
改一下canal.instance.master.address就行,你的mysql IP地址
上面两步完成后,使用java的canal客户端进行连接,可以看到定时获取binlog的日志打印,如果监控的库有更新插入,则会有相应的日志打印出来,要注意的问题是,使用了admin客户端和server连接后,我们的java客户端在连接的时候不能使用之前用的明文的密码canal进行连接,需要使用密文 E3619321C1A937C46A0D8BD1DAC39F93B27D4458,或者你去mysql里查询
select password('canal'); ## *E3619321C1A937C46A0D8BD1DAC39F93B27D4458 用不要星号的后面就行
##java客户端连接 就变成这样了
// 创建链接
String hostIp = AddressUtils.getHostIp();
System.out.println(hostIp);
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(
"42.193.12.204",
11111),
"woods_test",
"canal",
"E3619321C1A937C46A0D8BD1DAC39F93B27D4458");
int batchSize = 1000;
int emptyCount = 0;