根本原因:DNS 反向解析导致超时
javascript
Caused by: java.net.SocketTimeoutException: Timeout occurred, failed to read total 4 bytes in 5000 milliseconds, actual read only 0 bytes
at com.alibaba.otter.canal.parse.driver.mysql.socket.BioSocketChannel.read(BioSocketChannel.java:91)
at com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager.readHeader(PacketManager.java:19)
at com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector.negotiate(MysqlConnector.java:162)
at com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector.connect(MysqlConnector.java:82)
解决方法:跳过域名解析
我是使用docker安装mysql的,找到配置文件对应的挂载目录 修改里面的my.cnf文件,在[mysqld]下面加配置 skip-name-resolve,然后重启主节点


实测:
javascript
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.3</version>
</dependency>
java
package com.ldj.springboot.importbean.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 com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* User: ldj
* Date: 2026/4/2
* Time: 9:06
* Description: 测试Canal 同步监听Mysql
*/
@Slf4j
public class CanalSyncData {
private static final String CANAL_IP = "192.168.208.131";
private static final int CANAL_PORT = 11111;
private static final String CANAL_NAME = "root";
private static final String CANAL_PWD = "root";
private static final String CANAL_FILTER = "demo\\..*";
private static final String DESTINATION = "example";
public static void main(String args[]) {
// 1. 初始连接配置
InetSocketAddress inetSocketAddress = new InetSocketAddress(CANAL_IP, CANAL_PORT);
CanalConnector connector = CanalConnectors.newSingleConnector(inetSocketAddress, DESTINATION, CANAL_NAME, CANAL_PWD);
// 2. 启动阶段:死循环直到连上
while (true) {
try {
getConnection(connector);
break;
} catch (Exception e) {
log.error("启动连接失败,30秒后重试", e);
try { TimeUnit.SECONDS.sleep(30); } catch (InterruptedException ignored) {}
}
}
// 3.监听阶段
int emptyCount = 0;
while (true) {
try {
// 获取数据 (带超时)
Message message = connector.getWithoutAck(100, 1L, TimeUnit.SECONDS);
if (!CollectionUtils.isEmpty(message.getEntries())) {
emptyCount = 0;
for (CanalEntry.Entry entry : message.getEntries()) {
if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
Map<String, Object> jsonRoot = buildResponse(entry, rowChange);
log.info(getInstance().writeValueAsString(jsonRoot));
}
}
// 确认
connector.ack(message.getId());
} else {
emptyCount++;
if (emptyCount % 60 == 0) {
log.info("监听中... (无数据发生改变)");
}
}
} catch (Exception e) {
log.error("Canal服务中断,准备重连", e);
try {
// 4. 重连逻辑:先休眠,再重建对象,再连接
TimeUnit.SECONDS.sleep(30);
// 关键点:必须 new 一个新的,否则旧连接状态会导致重连失败
connector = CanalConnectors.newSingleConnector(inetSocketAddress, "example", "root", "root");
try {
getConnection(connector);
} catch (Exception reConnectEx) {
log.error("重连失败,将在下一次循环继续重试", reConnectEx);
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
private static void getConnection(CanalConnector connector) {
try {
log.info("正在尝试连接Canal...");
// 1. 尝试断开旧连接 (防止状态不一致)
try {
connector.disconnect();
} catch (Exception ignored) {}
// 2. 建立新连接
connector.connect();
// 3. 订阅
connector.subscribe(CANAL_FILTER);
// 4. 打印成功日志
log.info("=== 连接Canal成功 ===");
} catch (Exception e) {
// 抛出异常,让外层循环去处理重试
throw e;
}
}
private static Map<String, Object> buildResponse(CanalEntry.Entry entry, CanalEntry.RowChange rowChange) {
// 1. 操作基本信息
Map<String, Object> jsonRoot = new HashMap<>();
jsonRoot.put("database", entry.getHeader().getSchemaName());
jsonRoot.put("table", entry.getHeader().getTableName());
jsonRoot.put("type", rowChange.getEventType().toString());
jsonRoot.put("time", entry.getHeader().getExecuteTime());
// 2. 监听到变化的数据
List<Object> rowsJson = new ArrayList<>();
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
Map<String, Object> rowMap = new HashMap<>();
// INSERT: 只有新数据
if (rowChange.getEventType() == CanalEntry.EventType.INSERT) {
rowMap.put("insert_data", columnToMap(rowData.getAfterColumnsList()));
}
// DELETE: 只有旧数据
else if (rowChange.getEventType() == CanalEntry.EventType.DELETE) {
rowMap.put("deleted_data", columnToMap(rowData.getBeforeColumnsList()));
}
// UPDATE: 包含修改前和修改后
else if (rowChange.getEventType() == CanalEntry.EventType.UPDATE) {
rowMap.put("before_update", columnToMap(rowData.getBeforeColumnsList()));
rowMap.put("after_update", columnToMap(rowData.getAfterColumnsList()));
}
rowsJson.add(rowMap);
}
jsonRoot.put("data", rowsJson);
return jsonRoot;
}
private static Map<String, Object> columnToMap(List<CanalEntry.Column> columns) {
Map<String, Object> dataMap = new HashMap<>();
for (CanalEntry.Column column : columns) {
dataMap.put(column.getName(), column.getValue());
}
return dataMap;
}
private static ObjectMapper getInstance() {
return JacksonHolder.INSTANCE;
}
private static class JacksonHolder {
static final ObjectMapper INSTANCE = new ObjectMapper();
static {
// 开启格式化
INSTANCE.enable(SerializationFeature.INDENT_OUTPUT);
}
}
}
在DBeaver修改一行数据

Canal会监听到数据的更新
