mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
2 Commits
revert-252
...
startup_ti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3249d3c50d | ||
|
|
70d8a379f2 |
@@ -84,6 +84,7 @@ canal 特别设计了 client-server 模式,交互协议使用 protobuf 3.0 , c
|
|||||||
- canal go客户端: [https://github.com/CanalClient/canal-go](https://github.com/CanalClient/canal-go)
|
- canal go客户端: [https://github.com/CanalClient/canal-go](https://github.com/CanalClient/canal-go)
|
||||||
- canal php客户端: [https://github.com/xingwenge/canal-php](https://github.com/xingwenge/canal-php)
|
- canal php客户端: [https://github.com/xingwenge/canal-php](https://github.com/xingwenge/canal-php)
|
||||||
- canal Python客户端:[https://github.com/haozi3156666/canal-python](https://github.com/haozi3156666/canal-python)
|
- canal Python客户端:[https://github.com/haozi3156666/canal-python](https://github.com/haozi3156666/canal-python)
|
||||||
|
- canal Rust客户端:[https://github.com/laohanlinux/canal-rs](https://github.com/laohanlinux/canal-rs)
|
||||||
|
|
||||||
canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递到 MQ 系统中,比如 Kafka/RocketMQ,可以借助于 MQ 的多语言能力
|
canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递到 MQ 系统中,比如 Kafka/RocketMQ,可以借助于 MQ 的多语言能力
|
||||||
|
|
||||||
|
|||||||
@@ -174,11 +174,6 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
if (i == retry - 1) {
|
if (i == retry - 1) {
|
||||||
connector.ack();
|
connector.ack();
|
||||||
logger.error(e.getMessage() + " Error sync but ACK!");
|
logger.error(e.getMessage() + " Error sync but ACK!");
|
||||||
try {
|
|
||||||
Thread.sleep(500);
|
|
||||||
} catch (InterruptedException e1) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
connector.rollback();
|
connector.rollback();
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
batchSize = 100;
|
batchSize = 100;
|
||||||
}
|
}
|
||||||
properties.put("max.poll.records", batchSize.toString());
|
properties.put("max.poll.records", batchSize.toString());
|
||||||
properties.put("key.deserializer", StringDeserializer.class);
|
properties.put("key.deserializer", StringDeserializer.class.getName());
|
||||||
if (!flatMessage) {
|
if (!flatMessage) {
|
||||||
properties.put("value.deserializer", MessageDeserializer.class);
|
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
||||||
} else {
|
} else {
|
||||||
properties.put("value.deserializer", StringDeserializer.class);
|
properties.put("value.deserializer", StringDeserializer.class.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,13 +183,14 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
|
|
||||||
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
|
||||||
currentOffsets.clear();
|
currentOffsets.clear();
|
||||||
|
for (TopicPartition topicPartition : records.partitions()) {
|
||||||
|
currentOffsets.put(topicPartition.partition(), kafkaConsumer.position(topicPartition));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!records.isEmpty()) {
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
for (ConsumerRecord<String, Message> record : records) {
|
for (ConsumerRecord<String, Message> record : records) {
|
||||||
if (currentOffsets.get(record.partition()) == null) {
|
|
||||||
currentOffsets.put(record.partition(), record.offset());
|
|
||||||
}
|
|
||||||
messages.add(record.value());
|
messages.add(record.value());
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
@@ -220,13 +221,14 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
|
|
||||||
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
|
||||||
currentOffsets.clear();
|
currentOffsets.clear();
|
||||||
|
for (TopicPartition topicPartition : records.partitions()) {
|
||||||
|
currentOffsets.put(topicPartition.partition(), kafkaConsumer2.position(topicPartition));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!records.isEmpty()) {
|
||||||
List<FlatMessage> flatMessages = new ArrayList<>();
|
List<FlatMessage> flatMessages = new ArrayList<>();
|
||||||
for (ConsumerRecord<String, String> record : records) {
|
for (ConsumerRecord<String, String> record : records) {
|
||||||
if (currentOffsets.get(record.partition()) == null) {
|
|
||||||
currentOffsets.put(record.partition(), record.offset());
|
|
||||||
}
|
|
||||||
String flatMessageJson = record.value();
|
String flatMessageJson = record.value();
|
||||||
FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
|
FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
|
||||||
flatMessages.add(flatMessage);
|
flatMessages.add(flatMessage);
|
||||||
@@ -246,14 +248,12 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
// 回滚所有分区
|
// 回滚所有分区
|
||||||
if (kafkaConsumer != null) {
|
if (kafkaConsumer != null) {
|
||||||
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
||||||
kafkaConsumer.seek(new TopicPartition(topic, entry.getKey()), currentOffsets.get(entry.getKey()));
|
kafkaConsumer.seek(new TopicPartition(topic, entry.getKey()), entry.getValue() - 1);
|
||||||
kafkaConsumer.commitSync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (kafkaConsumer2 != null) {
|
if (kafkaConsumer2 != null) {
|
||||||
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
||||||
kafkaConsumer2.seek(new TopicPartition(topic, entry.getKey()), currentOffsets.get(entry.getKey()));
|
kafkaConsumer2.seek(new TopicPartition(topic, entry.getKey()), entry.getValue() - 1);
|
||||||
kafkaConsumer.commitSync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,12 +493,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
&& logPosition.getPostion().getServerId() != null
|
&& logPosition.getPostion().getServerId() != null
|
||||||
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
|
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
|
||||||
if (case2) {
|
if (case2) {
|
||||||
long timestamp = logPosition.getPostion().getTimestamp();
|
EntryPosition findPosition = fallbackFindByStartTimestamp(logPosition, mysqlConnection);
|
||||||
long newStartTimestamp = timestamp - fallbackIntervalInSeconds * 1000;
|
|
||||||
logger.warn("prepare to find start position by last position {}:{}:{}", new Object[] { "", "",
|
|
||||||
logPosition.getPostion().getTimestamp() });
|
|
||||||
EntryPosition findPosition = findByStartTimeStamp(mysqlConnection, newStartTimestamp);
|
|
||||||
// 重新置为一下
|
|
||||||
dumpErrorCount = 0;
|
dumpErrorCount = 0;
|
||||||
return findPosition;
|
return findPosition;
|
||||||
}
|
}
|
||||||
@@ -508,6 +503,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// 如果binlog位点不存在,并且属于timestamp不为空,可以返回null走到oss binlog处理
|
// 如果binlog位点不存在,并且属于timestamp不为空,可以返回null走到oss binlog处理
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} else if (StringUtils.isBlank(logPosition.getPostion().getJournalName())
|
||||||
|
&& logPosition.getPostion().getPosition() <= 0
|
||||||
|
&& logPosition.getPostion().getTimestamp() > 0) {
|
||||||
|
return fallbackFindByStartTimestamp(logPosition,mysqlConnection);
|
||||||
}
|
}
|
||||||
// 其余情况
|
// 其余情况
|
||||||
logger.warn("prepare to find start position just last position\n {}",
|
logger.warn("prepare to find start position just last position\n {}",
|
||||||
@@ -523,6 +522,21 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find position by timestamp with a fallback interval seconds.
|
||||||
|
*
|
||||||
|
* @param logPosition
|
||||||
|
* @param mysqlConnection
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected EntryPosition fallbackFindByStartTimestamp(LogPosition logPosition,MysqlConnection mysqlConnection){
|
||||||
|
long timestamp = logPosition.getPostion().getTimestamp();
|
||||||
|
long newStartTimestamp = timestamp - fallbackIntervalInSeconds * 1000;
|
||||||
|
logger.warn("prepare to find start position by last position {}:{}:{}", new Object[] { "", "",
|
||||||
|
logPosition.getPostion().getTimestamp() });
|
||||||
|
return findByStartTimeStamp(mysqlConnection, newStartTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
// 根据想要的position,可能这个position对应的记录为rowdata,需要找到事务头,避免丢数据
|
// 根据想要的position,可能这个position对应的记录为rowdata,需要找到事务头,避免丢数据
|
||||||
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
||||||
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
||||||
|
|||||||
Reference in New Issue
Block a user