mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
support filterTransactionEntry
This commit is contained in:
@@ -8,7 +8,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@@ -49,27 +48,27 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
|
|
||||||
private CanalHAController haController = null;
|
private CanalHAController haController = null;
|
||||||
|
|
||||||
private int defaultConnectionTimeoutInSeconds = 30; // sotimeout
|
private int defaultConnectionTimeoutInSeconds = 30; // sotimeout
|
||||||
private int receiveBufferSize = 64 * 1024;
|
private int receiveBufferSize = 64 * 1024;
|
||||||
private int sendBufferSize = 64 * 1024;
|
private int sendBufferSize = 64 * 1024;
|
||||||
// 数据库信息
|
// 数据库信息
|
||||||
protected AuthenticationInfo masterInfo; // 主库
|
protected AuthenticationInfo masterInfo; // 主库
|
||||||
protected AuthenticationInfo standbyInfo; // 备库
|
protected AuthenticationInfo standbyInfo; // 备库
|
||||||
// binlog信息
|
// binlog信息
|
||||||
protected EntryPosition masterPosition;
|
protected EntryPosition masterPosition;
|
||||||
protected EntryPosition standbyPosition;
|
protected EntryPosition standbyPosition;
|
||||||
private long slaveId; // 链接到mysql的slave
|
private long slaveId; // 链接到mysql的slave
|
||||||
// 心跳检查信息
|
// 心跳检查信息
|
||||||
private String detectingSQL; // 心跳sql
|
private String detectingSQL; // 心跳sql
|
||||||
private MysqlConnection metaConnection; // 查询meta信息的链接
|
private MysqlConnection metaConnection; // 查询meta信息的链接
|
||||||
private TableMetaCache tableMetaCache; // 对应meta
|
private TableMetaCache tableMetaCache; // 对应meta
|
||||||
private int fallbackIntervalInSeconds = 60; // 切换回退时间
|
private int fallbackIntervalInSeconds = 60; // 切换回退时间
|
||||||
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
|
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
|
||||||
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
|
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
|
||||||
|
|
||||||
// update by yishun.chen,特殊异常处理参数
|
// update by yishun.chen,特殊异常处理参数
|
||||||
private int dumpErrorCount = 0; // binlogDump失败异常计数
|
private int dumpErrorCount = 0; // binlogDump失败异常计数
|
||||||
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
|
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
|
||||||
|
|
||||||
protected ErosaConnection buildErosaConnection() {
|
protected ErosaConnection buildErosaConnection() {
|
||||||
return buildMysqlConnection(this.runningInfo);
|
return buildMysqlConnection(this.runningInfo);
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.alibaba.otter.canal.protocol;
|
package com.alibaba.otter.canal.protocol;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.google.common.collect.Table;
|
import com.google.common.collect.Table;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
@@ -243,6 +248,8 @@ public class FlatMessage implements Serializable {
|
|||||||
}
|
}
|
||||||
int hash = value.hashCode();
|
int hash = value.hashCode();
|
||||||
int pkHash = Math.abs(hash) % partitionsNum;
|
int pkHash = Math.abs(hash) % partitionsNum;
|
||||||
|
// math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序
|
||||||
|
pkHash = Math.abs(pkHash);
|
||||||
|
|
||||||
FlatMessage flatMessageTmp = partitionMessages[pkHash];
|
FlatMessage flatMessageTmp = partitionMessages[pkHash];
|
||||||
if (flatMessageTmp == null) {
|
if (flatMessageTmp == null) {
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package com.alibaba.otter.canal.kafka;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||||
import org.apache.kafka.clients.producer.Producer;
|
import org.apache.kafka.clients.producer.Producer;
|
||||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||||
import org.apache.kafka.clients.producer.RecordMetadata;
|
|
||||||
import org.apache.kafka.common.serialization.StringSerializer;
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class MessageSerializer implements Serializer<Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public byte[] serialize(String topic, Message data) {
|
public byte[] serialize(String topic, Message data) {
|
||||||
try {
|
try {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
@@ -47,11 +48,9 @@ public class MessageSerializer implements Serializer<Message> {
|
|||||||
messageSize += 1 * rowEntries.size();
|
messageSize += 1 * rowEntries.size();
|
||||||
// packet size
|
// packet size
|
||||||
int size = 0;
|
int size = 0;
|
||||||
size += CodedOutputStream.computeEnumSize(3,
|
size += CodedOutputStream.computeEnumSize(3, PacketType.MESSAGES.getNumber());
|
||||||
PacketType.MESSAGES.getNumber());
|
|
||||||
size += CodedOutputStream.computeTagSize(5)
|
size += CodedOutputStream.computeTagSize(5)
|
||||||
+ CodedOutputStream.computeRawVarint32Size(messageSize)
|
+ CodedOutputStream.computeRawVarint32Size(messageSize) + messageSize;
|
||||||
+ messageSize;
|
|
||||||
// build data
|
// build data
|
||||||
byte[] body = new byte[size];
|
byte[] body = new byte[size];
|
||||||
CodedOutputStream output = CodedOutputStream.newInstance(body);
|
CodedOutputStream output = CodedOutputStream.newInstance(body);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.server.netty.handler;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||||
import org.jboss.netty.buffer.ChannelBuffer;
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
@@ -34,6 +33,7 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
|
|||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
|
import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
|
||||||
import com.alibaba.otter.canal.server.netty.NettyUtils;
|
import com.alibaba.otter.canal.server.netty.NettyUtils;
|
||||||
|
import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.CodedOutputStream;
|
import com.google.protobuf.CodedOutputStream;
|
||||||
import com.google.protobuf.WireFormat;
|
import com.google.protobuf.WireFormat;
|
||||||
@@ -50,6 +50,7 @@ public class SessionHandler extends SimpleChannelHandler {
|
|||||||
this.embeddedServer = embeddedServer;
|
this.embeddedServer = embeddedServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||||
logger.info("message receives in session handler...");
|
logger.info("message receives in session handler...");
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
|
|||||||
@@ -33,10 +33,13 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(EntryEventSink.class);
|
private static final Logger logger = LoggerFactory.getLogger(EntryEventSink.class);
|
||||||
private static final int maxFullTimes = 10;
|
private static final int maxFullTimes = 10;
|
||||||
private CanalEventStore<Event> eventStore;
|
private CanalEventStore<Event> eventStore;
|
||||||
protected boolean filterTransactionEntry = false; // 是否需要过滤事务头/尾
|
protected boolean filterTransactionEntry = false; // 是否需要尽可能过滤事务头/尾
|
||||||
protected boolean filterEmtryTransactionEntry = true; // 是否需要过滤空的事务头/尾
|
protected boolean filterEmtryTransactionEntry = true; // 是否需要过滤空的事务头/尾
|
||||||
protected long emptyTransactionInterval = 5 * 1000; // 空的事务输出的频率
|
protected long emptyTransactionInterval = 5 * 1000; // 空的事务输出的频率
|
||||||
protected long emptyTransctionThresold = 8192; // 超过1024个事务头,输出一个
|
protected long emptyTransctionThresold = 8192; // 超过8192个事务头,输出一个
|
||||||
|
|
||||||
|
protected volatile long lastTransactionTimestamp = 0L;
|
||||||
|
protected AtomicLong lastTransactionCount = new AtomicLong(0L);
|
||||||
protected volatile long lastEmptyTransactionTimestamp = 0L;
|
protected volatile long lastEmptyTransactionTimestamp = 0L;
|
||||||
protected AtomicLong lastEmptyTransactionCount = new AtomicLong(0L);
|
protected AtomicLong lastEmptyTransactionCount = new AtomicLong(0L);
|
||||||
private AtomicLong eventsSinkBlockingTime = new AtomicLong(0L);
|
private AtomicLong eventsSinkBlockingTime = new AtomicLong(0L);
|
||||||
@@ -74,17 +77,7 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
|
|||||||
public boolean sink(List<CanalEntry.Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
public boolean sink(List<CanalEntry.Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||||
throws CanalSinkException,
|
throws CanalSinkException,
|
||||||
InterruptedException {
|
InterruptedException {
|
||||||
List rowDatas = entrys;
|
return sinkData(entrys, remoteAddress);
|
||||||
if (filterTransactionEntry) {
|
|
||||||
rowDatas = new ArrayList<CanalEntry.Entry>();
|
|
||||||
for (CanalEntry.Entry entry : entrys) {
|
|
||||||
if (entry.getEntryType() == EntryType.ROWDATA) {
|
|
||||||
rowDatas.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sinkData(rowDatas, remoteAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean sinkData(List<CanalEntry.Entry> entrys, InetSocketAddress remoteAddress)
|
private boolean sinkData(List<CanalEntry.Entry> entrys, InetSocketAddress remoteAddress)
|
||||||
@@ -97,17 +90,26 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event event = new Event(new LogIdentity(remoteAddress, -1L), entry);
|
if (filterTransactionEntry
|
||||||
events.add(event);
|
&& (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND)) {
|
||||||
|
long currentTimestamp = entry.getHeader().getExecuteTime();
|
||||||
|
// 基于一定的策略控制,放过空的事务头和尾,便于及时更新数据库位点,表明工作正常
|
||||||
|
if (Math.abs(currentTimestamp - lastTransactionTimestamp) > emptyTransactionInterval
|
||||||
|
|| lastTransactionCount.incrementAndGet() > emptyTransctionThresold) {
|
||||||
|
lastTransactionCount.set(0L);
|
||||||
|
lastTransactionTimestamp = currentTimestamp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasRowData |= (entry.getEntryType() == EntryType.ROWDATA);
|
hasRowData |= (entry.getEntryType() == EntryType.ROWDATA);
|
||||||
hasHeartBeat |= (entry.getEntryType() == EntryType.HEARTBEAT);
|
hasHeartBeat |= (entry.getEntryType() == EntryType.HEARTBEAT);
|
||||||
|
Event event = new Event(new LogIdentity(remoteAddress, -1L), entry);
|
||||||
|
events.add(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasRowData) {
|
if (hasRowData || hasHeartBeat) {
|
||||||
// 存在row记录
|
// 存在row记录 或者 存在heartbeat记录,直接跳给后续处理
|
||||||
return doSink(events);
|
|
||||||
} else if (hasHeartBeat) {
|
|
||||||
// 存在heartbeat记录,直接跳给后续处理
|
|
||||||
return doSink(events);
|
return doSink(events);
|
||||||
} else {
|
} else {
|
||||||
// 需要过滤的数据
|
// 需要过滤的数据
|
||||||
|
|||||||
Reference in New Issue
Block a user