mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
fixed issue #726 , 多线程解析binlog DML事件,提升性能
This commit is contained in:
@@ -58,7 +58,6 @@ public final class LogContext {
|
|||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
|
|
||||||
mapOfTable.clear();
|
mapOfTable.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ public final class LogDecoder {
|
|||||||
/* Decoding binary-log to event */
|
/* Decoding binary-log to event */
|
||||||
event = decode(buffer, header, context);
|
event = decode(buffer, header, context);
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
|
// set logFileName
|
||||||
|
event.getHeader().setLogFileName(context.getLogPosition().getFileName());
|
||||||
event.setSemival(buffer.semival);
|
event.setSemival(buffer.semival);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ public final class LogHeader {
|
|||||||
*/
|
*/
|
||||||
protected long crc; // ha_checksum
|
protected long crc; // ha_checksum
|
||||||
|
|
||||||
|
/**
|
||||||
|
* binlog fileName
|
||||||
|
*/
|
||||||
|
protected String logFileName;
|
||||||
|
|
||||||
/* for Start_event_v3 */
|
/* for Start_event_v3 */
|
||||||
public LogHeader(final int type){
|
public LogHeader(final int type){
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -270,6 +275,14 @@ public final class LogHeader {
|
|||||||
return checksumAlg;
|
return checksumAlg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLogFileName() {
|
||||||
|
return logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogFileName(String logFileName) {
|
||||||
|
this.logFileName = logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
private void processCheckSum(LogBuffer buffer) {
|
private void processCheckSum(LogBuffer buffer) {
|
||||||
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
||||||
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public abstract class RowsLogEvent extends LogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final RowsLogBuffer getRowsBuf(String charsetName) {
|
public final RowsLogBuffer getRowsBuf(String charsetName) {
|
||||||
return new RowsLogBuffer(rowsBuf.duplicate(), columnLen, charsetName);
|
return new RowsLogBuffer(rowsBuf, columnLen, charsetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getFlags(final int flags) {
|
public final int getFlags(final int flags) {
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
|
|||||||
# binlog ddl isolation
|
# binlog ddl isolation
|
||||||
canal.instance.get.ddl.isolation = false
|
canal.instance.get.ddl.isolation = false
|
||||||
|
|
||||||
|
# parallel parser config
|
||||||
|
canal.instance.parser.parallel = true
|
||||||
|
canal.instance.parser.parallelThreadSize = 16
|
||||||
|
canal.instance.parser.parallelBufferSize = 256
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
######### destinations #############
|
######### destinations #############
|
||||||
#################################################
|
#################################################
|
||||||
|
|||||||
@@ -192,5 +192,10 @@
|
|||||||
|
|
||||||
<!--是否启用GTID模式-->
|
<!--是否启用GTID模式-->
|
||||||
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize:16}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -177,5 +177,10 @@
|
|||||||
|
|
||||||
<!--是否启用GTID模式-->
|
<!--是否启用GTID模式-->
|
||||||
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize:16}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -166,6 +166,11 @@
|
|||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize:16}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
@@ -260,5 +265,10 @@
|
|||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize:16}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -137,5 +137,10 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize:16}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
@@ -165,5 +165,10 @@
|
|||||||
|
|
||||||
<!--是否启用GTID模式-->
|
<!--是否启用GTID模式-->
|
||||||
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize:16}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -93,6 +93,37 @@ public class BioSocketChannel implements SocketChannel {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
int accTimeout = 0;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
while (n < len && accTimeout < timeout) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, off + n, len - n);
|
||||||
|
if (read > -1) {
|
||||||
|
n += read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
accTimeout += SO_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < len && accTimeout >= timeout) {
|
||||||
|
throw new SocketTimeoutException("Timeout occurred, failed to read " + len + " bytes in " + timeout
|
||||||
|
+ " milliseconds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
Socket socket = this.socket;
|
Socket socket = this.socket;
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
@@ -133,4 +164,5 @@ public class BioSocketChannel implements SocketChannel {
|
|||||||
this.socket = null;
|
this.socket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import io.netty.util.internal.SystemPropertyUtil;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -202,6 +203,11 @@ public class NettySocketChannel implements SocketChannel {
|
|||||||
} while (true);
|
} while (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return channel != null ? true : false;
|
return channel != null ? true : false;
|
||||||
}
|
}
|
||||||
@@ -224,4 +230,5 @@ public class NettySocketChannel implements SocketChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public interface SocketChannel {
|
|||||||
|
|
||||||
public byte[] read(int readSize, int timeout) throws IOException;
|
public byte[] read(int readSize, int timeout) throws IOException;
|
||||||
|
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException;
|
||||||
|
|
||||||
public boolean isConnected();
|
public boolean isConnected();
|
||||||
|
|
||||||
public SocketAddress getRemoteSocketAddress();
|
public SocketAddress getRemoteSocketAddress();
|
||||||
|
|||||||
@@ -73,6 +73,10 @@
|
|||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-compress</artifactId>
|
<artifactId>commons-compress</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lmax</groupId>
|
||||||
|
<artifactId>disruptor</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- test dependency -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|||||||
@@ -89,11 +89,18 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
protected Throwable exception = null;
|
protected Throwable exception = null;
|
||||||
|
|
||||||
protected boolean isGTIDMode = false; // 是否是GTID模式
|
protected boolean isGTIDMode = false; // 是否是GTID模式
|
||||||
|
protected boolean parallel = true; // 是否开启并行解析模式
|
||||||
|
protected int parallelThreadSize = Runtime.getRuntime()
|
||||||
|
.availableProcessors() * 60 / 100; // 60%的能力跑解析,剩余部分处理网络
|
||||||
|
protected int parallelBufferSize = 16 * parallelThreadSize;
|
||||||
|
protected MultiStageCoprocessor multiStageCoprocessor;
|
||||||
|
|
||||||
protected abstract BinlogParser buildParser();
|
protected abstract BinlogParser buildParser();
|
||||||
|
|
||||||
protected abstract ErosaConnection buildErosaConnection();
|
protected abstract ErosaConnection buildErosaConnection();
|
||||||
|
|
||||||
|
protected abstract MultiStageCoprocessor buildMultiStageCoprocessor();
|
||||||
|
|
||||||
protected abstract EntryPosition findStartPosition(ErosaConnection connection) throws IOException;
|
protected abstract EntryPosition findStartPosition(ErosaConnection connection) throws IOException;
|
||||||
|
|
||||||
protected void preDump(ErosaConnection connection) {
|
protected void preDump(ErosaConnection connection) {
|
||||||
@@ -217,20 +224,39 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 4. 开始dump数据
|
// 4. 开始dump数据
|
||||||
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
if (parallel) {
|
||||||
if (isGTIDMode()) {
|
// build stage processor
|
||||||
erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), sinkHandler);
|
multiStageCoprocessor = buildMultiStageCoprocessor();
|
||||||
} else {
|
multiStageCoprocessor.start();
|
||||||
if (StringUtils.isEmpty(startPosition.getJournalName())
|
|
||||||
&& startPosition.getTimestamp() != null) {
|
if (isGTIDMode()) {
|
||||||
erosaConnection.dump(startPosition.getTimestamp(), sinkHandler);
|
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
||||||
|
erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), multiStageCoprocessor);
|
||||||
} else {
|
} else {
|
||||||
erosaConnection.dump(startPosition.getJournalName(),
|
if (StringUtils.isEmpty(startPosition.getJournalName())
|
||||||
startPosition.getPosition(),
|
&& startPosition.getTimestamp() != null) {
|
||||||
sinkHandler);
|
erosaConnection.dump(startPosition.getTimestamp(), multiStageCoprocessor);
|
||||||
|
} else {
|
||||||
|
erosaConnection.dump(startPosition.getJournalName(),
|
||||||
|
startPosition.getPosition(),
|
||||||
|
multiStageCoprocessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isGTIDMode()) {
|
||||||
|
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
||||||
|
erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), sinkHandler);
|
||||||
|
} else {
|
||||||
|
if (StringUtils.isEmpty(startPosition.getJournalName())
|
||||||
|
&& startPosition.getTimestamp() != null) {
|
||||||
|
erosaConnection.dump(startPosition.getTimestamp(), sinkHandler);
|
||||||
|
} else {
|
||||||
|
erosaConnection.dump(startPosition.getJournalName(),
|
||||||
|
startPosition.getPosition(),
|
||||||
|
sinkHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (TableIdNotFoundException e) {
|
} catch (TableIdNotFoundException e) {
|
||||||
exception = e;
|
exception = e;
|
||||||
// 特殊处理TableIdNotFound异常,出现这样的异常,一种可能就是起始的position是一个事务当中,导致tablemap
|
// 特殊处理TableIdNotFound异常,出现这样的异常,一种可能就是起始的position是一个事务当中,导致tablemap
|
||||||
@@ -276,6 +302,9 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
eventSink.interrupt();
|
eventSink.interrupt();
|
||||||
transactionBuffer.reset();// 重置一下缓冲队列,重新记录数据
|
transactionBuffer.reset();// 重置一下缓冲队列,重新记录数据
|
||||||
binlogParser.reset();// 重新置位
|
binlogParser.reset();// 重新置位
|
||||||
|
if (multiStageCoprocessor != null) {
|
||||||
|
multiStageCoprocessor.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (running) {
|
if (running) {
|
||||||
// sleep一段时间再进行重试
|
// sleep一段时间再进行重试
|
||||||
@@ -314,6 +343,10 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
if (transactionBuffer.isStart()) {
|
if (transactionBuffer.isStart()) {
|
||||||
transactionBuffer.stop();
|
transactionBuffer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (multiStageCoprocessor != null && multiStageCoprocessor.isStart()) {
|
||||||
|
multiStageCoprocessor.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean consumeTheEventAndProfilingIfNecessary(List<CanalEntry.Entry> entrys) throws CanalSinkException,
|
protected boolean consumeTheEventAndProfilingIfNecessary(List<CanalEntry.Entry> entrys) throws CanalSinkException,
|
||||||
@@ -549,4 +582,29 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
public void setIsGTIDMode(boolean isGTIDMode) {
|
public void setIsGTIDMode(boolean isGTIDMode) {
|
||||||
this.isGTIDMode = isGTIDMode;
|
this.isGTIDMode = isGTIDMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isParallel() {
|
||||||
|
return parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallel(boolean parallel) {
|
||||||
|
this.parallel = parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParallelThreadSize() {
|
||||||
|
return parallelThreadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelThreadSize(int parallelThreadSize) {
|
||||||
|
this.parallelThreadSize = parallelThreadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParallelBufferSize() {
|
||||||
|
return parallelBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelBufferSize(int parallelBufferSize) {
|
||||||
|
this.parallelBufferSize = parallelBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,16 @@ public interface ErosaConnection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过GTID同步binlog
|
* 通过GTID同步binlog
|
||||||
*
|
|
||||||
* @param gtidSet
|
|
||||||
* @param func
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException;
|
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException;
|
||||||
|
|
||||||
|
// -------------
|
||||||
|
|
||||||
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException;
|
||||||
|
|
||||||
|
public void dump(long timestamp, MultiStageCoprocessor coprocessor) throws IOException;
|
||||||
|
|
||||||
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException;
|
||||||
|
|
||||||
ErosaConnection fork();
|
ErosaConnection fork();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.inbound;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.CanalLifeCycle;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对解析器提供一个多阶段协同的处理
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1. 网络接收 (单线程)
|
||||||
|
* 2. 事件基本解析 (单线程,事件类型、DDL解析构造TableMeta、维护位点信息)
|
||||||
|
* 3. 事件深度解析 (多线程, DML事件数据的完整解析)
|
||||||
|
* 4. 投递到store (单线程)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author agapple 2018年7月3日 下午4:54:17
|
||||||
|
* @since 1.0.26
|
||||||
|
*/
|
||||||
|
public interface MultiStageCoprocessor extends CanalLifeCycle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络数据投递
|
||||||
|
*/
|
||||||
|
public void publish(LogBuffer buffer);
|
||||||
|
|
||||||
|
public void publish(LogBuffer buffer, String binlogFileName);
|
||||||
|
|
||||||
|
public void reset();
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
|||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
|
||||||
@@ -117,6 +118,14 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected MultiStageCoprocessor buildMultiStageCoprocessor() {
|
||||||
|
return new MysqlMultiStageCoprocessor(parallelBufferSize,
|
||||||
|
parallelThreadSize,
|
||||||
|
(LogEventConvert) binlogParser,
|
||||||
|
transactionBuffer,
|
||||||
|
destination);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================ setter / getter =========================
|
// ============================ setter / getter =========================
|
||||||
|
|
||||||
public void setConnectionCharsetNumber(byte connectionCharsetNumber) {
|
public void setConnectionCharsetNumber(byte connectionCharsetNumber) {
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
||||||
import com.taobao.tddl.dbsync.binlog.FileLogFetcher;
|
import com.taobao.tddl.dbsync.binlog.FileLogFetcher;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogContext;
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
@@ -207,6 +209,128 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
File current = new File(directory, binlogfilename);
|
||||||
|
|
||||||
|
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
||||||
|
try {
|
||||||
|
fetcher.open(current, binlogPosition);
|
||||||
|
while (running) {
|
||||||
|
boolean needContinue = true;
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogBuffer buffer = fetcher.duplicate();
|
||||||
|
fetcher.consume(fetcher.limit());
|
||||||
|
coprocessor.publish(buffer, binlogfilename); // set filename
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needContinue) {// 读取下一个
|
||||||
|
fetcher.close(); // 关闭上一个文件
|
||||||
|
|
||||||
|
File nextFile;
|
||||||
|
if (needWait) {
|
||||||
|
nextFile = binlogs.waitForNextFile(current);
|
||||||
|
} else {
|
||||||
|
nextFile = binlogs.getNextFile(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextFile == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = nextFile;
|
||||||
|
fetcher.open(current);
|
||||||
|
binlogfilename = nextFile.getName();
|
||||||
|
} else {
|
||||||
|
break;// 跳出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.warn("LocalBinLogConnection dump interrupted");
|
||||||
|
} finally {
|
||||||
|
if (fetcher != null) {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(long timestampMills, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
List<File> currentBinlogs = binlogs.currentBinlogs();
|
||||||
|
File current = currentBinlogs.get(currentBinlogs.size() - 1);
|
||||||
|
long timestampSeconds = timestampMills / 1000;
|
||||||
|
|
||||||
|
String binlogFilename = null;
|
||||||
|
long binlogFileOffset = 0;
|
||||||
|
|
||||||
|
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
||||||
|
LogDecoder decoder = new LogDecoder();
|
||||||
|
decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT);
|
||||||
|
decoder.handle(LogEvent.QUERY_EVENT);
|
||||||
|
decoder.handle(LogEvent.XID_EVENT);
|
||||||
|
LogContext context = new LogContext();
|
||||||
|
try {
|
||||||
|
fetcher.open(current);
|
||||||
|
context.setLogPosition(new LogPosition(current.getName()));
|
||||||
|
while (running) {
|
||||||
|
boolean needContinue = true;
|
||||||
|
String lastXidLogFilename = current.getName();
|
||||||
|
long lastXidLogFileOffset = 0;
|
||||||
|
|
||||||
|
binlogFilename = lastXidLogFilename;
|
||||||
|
binlogFileOffset = lastXidLogFileOffset;
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogEvent event = decoder.decode(fetcher, context);
|
||||||
|
if (event != null) {
|
||||||
|
if (event.getWhen() > timestampSeconds) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
needContinue = false;
|
||||||
|
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
||||||
|
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
||||||
|
binlogFilename = lastXidLogFilename;
|
||||||
|
binlogFileOffset = lastXidLogFileOffset;
|
||||||
|
} else if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "COMMIT")) {
|
||||||
|
lastXidLogFilename = current.getName();
|
||||||
|
lastXidLogFileOffset = event.getLogPos();
|
||||||
|
}
|
||||||
|
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
|
||||||
|
lastXidLogFilename = current.getName();
|
||||||
|
lastXidLogFileOffset = event.getLogPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needContinue) {// 读取下一个
|
||||||
|
fetcher.close(); // 关闭上一个文件
|
||||||
|
|
||||||
|
File nextFile = binlogs.getBefore(current);
|
||||||
|
if (nextFile == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = nextFile;
|
||||||
|
fetcher.open(current);
|
||||||
|
context.setLogPosition(new LogPosition(current.getName()));
|
||||||
|
} else {
|
||||||
|
break;// 跳出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (fetcher != null) {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dump(binlogFilename, binlogFileOffset, coprocessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public ErosaConnection fork() {
|
public ErosaConnection fork() {
|
||||||
LocalBinLogConnection connection = new LocalBinLogConnection();
|
LocalBinLogConnection connection = new LocalBinLogConnection();
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket
|
|||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogContext;
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
@@ -156,7 +158,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.getSemival() == 1) {
|
if (event.getSemival() == 1) {
|
||||||
sendSemiAck(context.getLogPosition().getFileName(), binlogPosition);
|
sendSemiAck(context.getLogPosition().getFileName(), context.getLogPosition().getPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,20 +169,24 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
sendBinlogDumpGTID(gtidSet);
|
sendBinlogDumpGTID(gtidSet);
|
||||||
|
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
try {
|
||||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
fetcher.start(connector.getChannel());
|
||||||
LogContext context = new LogContext();
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
while (fetcher.fetch()) {
|
LogContext context = new LogContext();
|
||||||
LogEvent event = null;
|
while (fetcher.fetch()) {
|
||||||
event = decoder.decode(fetcher, context);
|
LogEvent event = null;
|
||||||
|
event = decoder.decode(fetcher, context);
|
||||||
|
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
throw new CanalParseException("parse failed");
|
throw new CanalParseException("parse failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!func.sink(event)) {
|
if (!func.sink(event)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
fetcher.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +194,49 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
throw new NullPointerException("Not implement yet");
|
throw new NullPointerException("Not implement yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
updateSettings();
|
||||||
|
sendRegisterSlave();
|
||||||
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
|
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
|
try {
|
||||||
|
fetcher.start(connector.getChannel());
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogBuffer buffer = fetcher.duplicate();
|
||||||
|
fetcher.consume(fetcher.limit());
|
||||||
|
coprocessor.publish(buffer);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(long timestamp, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
throw new NullPointerException("Not implement yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
updateSettings();
|
||||||
|
sendBinlogDumpGTID(gtidSet);
|
||||||
|
|
||||||
|
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
|
try {
|
||||||
|
fetcher.start(connector.getChannel());
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogBuffer buffer = fetcher.duplicate();
|
||||||
|
fetcher.consume(fetcher.limit());
|
||||||
|
coprocessor.publish(buffer);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendRegisterSlave() throws IOException {
|
private void sendRegisterSlave() throws IOException {
|
||||||
RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
|
RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
|
||||||
cmd.reportHost = authInfo.getAddress().getAddress().getHostAddress();
|
cmd.reportHost = authInfo.getAddress().getAddress().getHostAddress();
|
||||||
@@ -233,7 +282,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
connector.setDumping(true);
|
connector.setDumping(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendSemiAck(String binlogfilename, Long binlogPosition) throws IOException {
|
public void sendSemiAck(String binlogfilename, Long binlogPosition) throws IOException {
|
||||||
SemiAckCommandPacket semiAckCmd = new SemiAckCommandPacket();
|
SemiAckCommandPacket semiAckCmd = new SemiAckCommandPacket();
|
||||||
semiAckCmd.binlogFileName = binlogfilename;
|
semiAckCmd.binlogFileName = binlogfilename;
|
||||||
semiAckCmd.binlogPosition = binlogPosition;
|
semiAckCmd.binlogPosition = binlogPosition;
|
||||||
@@ -538,4 +587,5 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void setAuthInfo(AuthenticationInfo authInfo) {
|
public void setAuthInfo(AuthenticationInfo authInfo) {
|
||||||
this.authInfo = authInfo;
|
this.authInfo = authInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.lmax.disruptor.BatchEventProcessor;
|
||||||
|
import com.lmax.disruptor.BlockingWaitStrategy;
|
||||||
|
import com.lmax.disruptor.EventFactory;
|
||||||
|
import com.lmax.disruptor.EventHandler;
|
||||||
|
import com.lmax.disruptor.FatalExceptionHandler;
|
||||||
|
import com.lmax.disruptor.LifecycleAware;
|
||||||
|
import com.lmax.disruptor.RingBuffer;
|
||||||
|
import com.lmax.disruptor.Sequence;
|
||||||
|
import com.lmax.disruptor.SequenceBarrier;
|
||||||
|
import com.lmax.disruptor.WorkHandler;
|
||||||
|
import com.lmax.disruptor.WorkerPool;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogPosition;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对解析器提供一个多阶段协同的处理
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1. 网络接收 (单线程)
|
||||||
|
* 2. 事件基本解析 (单线程,事件类型、DDL解析构造TableMeta、维护位点信息)
|
||||||
|
* 3. 事件深度解析 (多线程, DML事件数据的完整解析)
|
||||||
|
* 4. 投递到store (单线程)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author agapple 2018年7月3日 下午4:54:17
|
||||||
|
* @since 1.0.26
|
||||||
|
*/
|
||||||
|
public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor {
|
||||||
|
|
||||||
|
private LogEventConvert logEventConvert;
|
||||||
|
private EventTransactionBuffer transactionBuffer;
|
||||||
|
private ErosaConnection connection;
|
||||||
|
|
||||||
|
private int parserThreadCount;
|
||||||
|
private int ringBufferSize;
|
||||||
|
private RingBuffer<MessageEvent> disruptorMsgBuffer;
|
||||||
|
private ExecutorService parserExecutor;
|
||||||
|
private ExecutorService stageExecutor;
|
||||||
|
private String destination;
|
||||||
|
private CanalParseException exception;
|
||||||
|
|
||||||
|
public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert,
|
||||||
|
EventTransactionBuffer transactionBuffer, String destination){
|
||||||
|
this.ringBufferSize = ringBufferSize;
|
||||||
|
this.parserThreadCount = parserThreadCount;
|
||||||
|
this.logEventConvert = logEventConvert;
|
||||||
|
this.transactionBuffer = transactionBuffer;
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
this.exception = null;
|
||||||
|
this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(),
|
||||||
|
ringBufferSize,
|
||||||
|
new BlockingWaitStrategy());
|
||||||
|
|
||||||
|
this.parserExecutor = Executors.newFixedThreadPool(parserThreadCount,
|
||||||
|
new NamedThreadFactory("MultiStageCoprocessor-Parser-" + destination));
|
||||||
|
|
||||||
|
this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-"
|
||||||
|
+ destination));
|
||||||
|
SequenceBarrier sequenceBarrier = disruptorMsgBuffer.newBarrier();
|
||||||
|
|
||||||
|
// stage 2
|
||||||
|
BatchEventProcessor<MessageEvent> simpleParserStage = new BatchEventProcessor<MessageEvent>(disruptorMsgBuffer,
|
||||||
|
sequenceBarrier,
|
||||||
|
new SimpleParserStage());
|
||||||
|
disruptorMsgBuffer.addGatingSequences(simpleParserStage.getSequence());
|
||||||
|
|
||||||
|
// stage 3
|
||||||
|
SequenceBarrier dmlParserSequenceBarrier = disruptorMsgBuffer.newBarrier(simpleParserStage.getSequence());
|
||||||
|
WorkHandler<MessageEvent>[] workHandlers = new DmlParserStage[parserThreadCount];
|
||||||
|
for (int i = 0; i < parserThreadCount; i++) {
|
||||||
|
workHandlers[i] = new DmlParserStage();
|
||||||
|
}
|
||||||
|
WorkerPool<MessageEvent> workerPool = new WorkerPool<MessageEvent>(disruptorMsgBuffer,
|
||||||
|
dmlParserSequenceBarrier,
|
||||||
|
new FatalExceptionHandler(),
|
||||||
|
workHandlers);
|
||||||
|
Sequence[] sequence = workerPool.getWorkerSequences();
|
||||||
|
disruptorMsgBuffer.addGatingSequences(sequence);
|
||||||
|
|
||||||
|
// stage 4
|
||||||
|
SequenceBarrier sinkSequenceBarrier = disruptorMsgBuffer.newBarrier(sequence);
|
||||||
|
BatchEventProcessor<MessageEvent> sinkStoreStage = new BatchEventProcessor<MessageEvent>(disruptorMsgBuffer,
|
||||||
|
sinkSequenceBarrier,
|
||||||
|
new SinkStoreStage());
|
||||||
|
disruptorMsgBuffer.addGatingSequences(sinkStoreStage.getSequence());
|
||||||
|
|
||||||
|
// start work
|
||||||
|
stageExecutor.submit(simpleParserStage);
|
||||||
|
stageExecutor.submit(sinkStoreStage);
|
||||||
|
workerPool.start(parserExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
parserExecutor.shutdownNow();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
stageExecutor.shutdownNow();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
disruptorMsgBuffer = null;
|
||||||
|
super.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络数据投递
|
||||||
|
*/
|
||||||
|
public void publish(LogBuffer buffer) {
|
||||||
|
if (!isStart() && exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
long next = disruptorMsgBuffer.next();
|
||||||
|
MessageEvent event = disruptorMsgBuffer.get(next);
|
||||||
|
event.setBuffer(buffer);
|
||||||
|
disruptorMsgBuffer.publish(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publish(LogBuffer buffer, String binlogFileName) {
|
||||||
|
if (!isStart() && exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
long next = disruptorMsgBuffer.next();
|
||||||
|
MessageEvent event = disruptorMsgBuffer.get(next);
|
||||||
|
event.setBuffer(buffer);
|
||||||
|
event.setBinlogFileName(binlogFileName);
|
||||||
|
disruptorMsgBuffer.publish(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
stop();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SimpleParserStage implements EventHandler<MessageEvent>, LifecycleAware {
|
||||||
|
|
||||||
|
private LogDecoder decoder;
|
||||||
|
private LogContext context;
|
||||||
|
|
||||||
|
public SimpleParserStage(){
|
||||||
|
decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
|
context = new LogContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
|
||||||
|
try {
|
||||||
|
LogBuffer buffer = event.getBuffer();
|
||||||
|
if (StringUtils.isNotEmpty(event.getBinlogFileName())
|
||||||
|
&& !context.getLogPosition().getFileName().equals(event.getBinlogFileName())) {
|
||||||
|
// set roate binlog file name
|
||||||
|
context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition()
|
||||||
|
.getPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEvent logEvent = decoder.decode(buffer, context);
|
||||||
|
event.setEvent(logEvent);
|
||||||
|
|
||||||
|
int eventType = logEvent.getHeader().getType();
|
||||||
|
TableMeta tableMeta = null;
|
||||||
|
boolean needDmlParse = false;
|
||||||
|
switch (eventType) {
|
||||||
|
case LogEvent.WRITE_ROWS_EVENT_V1:
|
||||||
|
case LogEvent.WRITE_ROWS_EVENT:
|
||||||
|
tableMeta = logEventConvert.parseRowsEventForTableMeta((WriteRowsLogEvent) logEvent);
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
case LogEvent.UPDATE_ROWS_EVENT_V1:
|
||||||
|
case LogEvent.UPDATE_ROWS_EVENT:
|
||||||
|
tableMeta = logEventConvert.parseRowsEventForTableMeta((UpdateRowsLogEvent) logEvent);
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
case LogEvent.DELETE_ROWS_EVENT_V1:
|
||||||
|
case LogEvent.DELETE_ROWS_EVENT:
|
||||||
|
tableMeta = logEventConvert.parseRowsEventForTableMeta((DeleteRowsLogEvent) logEvent);
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
CanalEntry.Entry entry = logEventConvert.parse(event.getEvent(), false);
|
||||||
|
event.setEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录一下DML的表结构
|
||||||
|
event.setNeedDmlParse(needDmlParse);
|
||||||
|
event.setTable(tableMeta);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
exception = new CanalParseException(e);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DmlParserStage implements WorkHandler<MessageEvent>, LifecycleAware {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(MessageEvent event) throws Exception {
|
||||||
|
try {
|
||||||
|
if (event.isNeedDmlParse()) {
|
||||||
|
int eventType = event.getEvent().getHeader().getType();
|
||||||
|
CanalEntry.Entry entry = null;
|
||||||
|
switch (eventType) {
|
||||||
|
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||||
|
entry = logEventConvert.parse(event.getEvent(), false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// 单独解析dml事件
|
||||||
|
entry = logEventConvert.parseRowsEvent((RowsLogEvent) event.getEvent(), event.getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setEntry(entry);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
exception = new CanalParseException(e);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SinkStoreStage implements EventHandler<MessageEvent>, LifecycleAware {
|
||||||
|
|
||||||
|
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
|
||||||
|
try {
|
||||||
|
if (event.getEntry() != null) {
|
||||||
|
transactionBuffer.add(event.getEntry());
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEvent logEvent = event.getEvent();
|
||||||
|
if (connection instanceof MysqlConnection && logEvent.getSemival() == 1) {
|
||||||
|
// semi ack回报
|
||||||
|
((MysqlConnection) connection).sendSemiAck(logEvent.getHeader().getLogFileName(),
|
||||||
|
logEvent.getHeader().getLogPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear for gc
|
||||||
|
event.setBuffer(null);
|
||||||
|
event.setBinlogFileName(null);
|
||||||
|
event.setEvent(null);
|
||||||
|
event.setTable(null);
|
||||||
|
event.setEntry(null);
|
||||||
|
event.setNeedDmlParse(false);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
exception = new CanalParseException(e);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageEvent {
|
||||||
|
|
||||||
|
private String binlogFileName; // for local binlog parse
|
||||||
|
private LogBuffer buffer;
|
||||||
|
private CanalEntry.Entry entry;
|
||||||
|
private boolean needDmlParse = false;
|
||||||
|
private TableMeta table;
|
||||||
|
private LogEvent event;
|
||||||
|
|
||||||
|
public String getBinlogFileName() {
|
||||||
|
return binlogFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBinlogFileName(String binlogFileName) {
|
||||||
|
this.binlogFileName = binlogFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogBuffer getBuffer() {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBuffer(LogBuffer buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEvent getEvent() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEvent(LogEvent event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CanalEntry.Entry getEntry() {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntry(CanalEntry.Entry entry) {
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedDmlParse() {
|
||||||
|
return needDmlParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedDmlParse(boolean needDmlParse) {
|
||||||
|
this.needDmlParse = needDmlParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableMeta getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTable(TableMeta table) {
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageEventFactory implements EventFactory<MessageEvent> {
|
||||||
|
|
||||||
|
public MessageEvent newInstance() {
|
||||||
|
return new MessageEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogEventConvert(LogEventConvert logEventConvert) {
|
||||||
|
this.logEventConvert = logEventConvert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransactionBuffer(EventTransactionBuffer transactionBuffer) {
|
||||||
|
this.transactionBuffer = transactionBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnection(ErosaConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -171,10 +171,13 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
private final boolean fetch0(final int off, final int len) throws IOException {
|
private final boolean fetch0(final int off, final int len) throws IOException {
|
||||||
ensureCapacity(off + len);
|
ensureCapacity(off + len);
|
||||||
|
|
||||||
byte[] read = channel.read(len, READ_TIMEOUT_MILLISECONDS);
|
// byte[] read = channel.read(len, READ_TIMEOUT_MILLISECONDS);
|
||||||
System.arraycopy(read, 0, this.buffer, off, len);
|
// System.arraycopy(read, 0, this.buffer, off, len);
|
||||||
|
|
||||||
if (limit < off + len) limit = off + len;
|
channel.read(buffer, off, len, READ_TIMEOUT_MILLISECONDS);
|
||||||
|
if (limit < off + len) {
|
||||||
|
limit = off + len;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent;
|
|||||||
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RandLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.RandLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer;
|
import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
||||||
@@ -89,7 +88,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
private volatile AviaterRegexFilter nameBlackFilter;
|
private volatile AviaterRegexFilter nameBlackFilter;
|
||||||
|
|
||||||
private TableMetaCache tableMetaCache;
|
private TableMetaCache tableMetaCache;
|
||||||
private String binlogFileName = "mysql-bin.000001";
|
|
||||||
private Charset charset = Charset.defaultCharset();
|
private Charset charset = Charset.defaultCharset();
|
||||||
private boolean filterQueryDcl = false;
|
private boolean filterQueryDcl = false;
|
||||||
private boolean filterQueryDml = false;
|
private boolean filterQueryDml = false;
|
||||||
@@ -119,9 +117,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|
|
||||||
int eventType = logEvent.getHeader().getType();
|
int eventType = logEvent.getHeader().getType();
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case LogEvent.ROTATE_EVENT:
|
|
||||||
binlogFileName = ((RotateLogEvent) logEvent).getFilename();
|
|
||||||
break;
|
|
||||||
case LogEvent.QUERY_EVENT:
|
case LogEvent.QUERY_EVENT:
|
||||||
return parseQueryEvent((QueryLogEvent) logEvent, isSeek);
|
return parseQueryEvent((QueryLogEvent) logEvent, isSeek);
|
||||||
case LogEvent.XID_EVENT:
|
case LogEvent.XID_EVENT:
|
||||||
@@ -158,7 +153,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
// do nothing
|
// do nothing
|
||||||
binlogFileName = "mysql-bin.000001";
|
|
||||||
if (tableMetaCache != null) {
|
if (tableMetaCache != null) {
|
||||||
tableMetaCache.clearTableMeta();
|
tableMetaCache.clearTableMeta();
|
||||||
}
|
}
|
||||||
@@ -174,7 +168,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
gtidSet.update(value);
|
gtidSet.update(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Header header = createHeader("", logHeader, "", "", EventType.GTID);
|
Header header = createHeader(logHeader, "", "", EventType.GTID);
|
||||||
return createEntry(header, EntryType.GTIDLOG, builder.build().toByteString());
|
return createEntry(header, EntryType.GTIDLOG, builder.build().toByteString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +181,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
beginBuilder.addProps(createSpecialPair(XA_TYPE, XA_START));
|
beginBuilder.addProps(createSpecialPair(XA_TYPE, XA_START));
|
||||||
beginBuilder.addProps(createSpecialPair(XA_XID, getXaXid(queryString, XA_START)));
|
beginBuilder.addProps(createSpecialPair(XA_XID, getXaXid(queryString, XA_START)));
|
||||||
TransactionBegin transactionBegin = beginBuilder.build();
|
TransactionBegin transactionBegin = beginBuilder.build();
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONBEGIN, transactionBegin.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONBEGIN, transactionBegin.toByteString());
|
||||||
} else if (StringUtils.startsWithIgnoreCase(queryString, XA_END)) {
|
} else if (StringUtils.startsWithIgnoreCase(queryString, XA_END)) {
|
||||||
// xa start use TransactionEnd
|
// xa start use TransactionEnd
|
||||||
@@ -196,11 +190,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
endBuilder.addProps(createSpecialPair(XA_TYPE, XA_END));
|
endBuilder.addProps(createSpecialPair(XA_TYPE, XA_END));
|
||||||
endBuilder.addProps(createSpecialPair(XA_XID, getXaXid(queryString, XA_END)));
|
endBuilder.addProps(createSpecialPair(XA_XID, getXaXid(queryString, XA_END)));
|
||||||
TransactionEnd transactionEnd = endBuilder.build();
|
TransactionEnd transactionEnd = endBuilder.build();
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
||||||
} else if (StringUtils.startsWithIgnoreCase(queryString, XA_COMMIT)) {
|
} else if (StringUtils.startsWithIgnoreCase(queryString, XA_COMMIT)) {
|
||||||
// xa commit
|
// xa commit
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", EventType.XACOMMIT);
|
Header header = createHeader(event.getHeader(), "", "", EventType.XACOMMIT);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setSql(queryString);
|
rowChangeBuider.setSql(queryString);
|
||||||
rowChangeBuider.addProps(createSpecialPair(XA_TYPE, XA_COMMIT));
|
rowChangeBuider.addProps(createSpecialPair(XA_TYPE, XA_COMMIT));
|
||||||
@@ -209,7 +203,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||||
} else if (StringUtils.startsWithIgnoreCase(queryString, XA_ROLLBACK)) {
|
} else if (StringUtils.startsWithIgnoreCase(queryString, XA_ROLLBACK)) {
|
||||||
// xa rollback
|
// xa rollback
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", EventType.XAROLLBACK);
|
Header header = createHeader(event.getHeader(), "", "", EventType.XAROLLBACK);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setSql(queryString);
|
rowChangeBuider.setSql(queryString);
|
||||||
rowChangeBuider.addProps(createSpecialPair(XA_TYPE, XA_ROLLBACK));
|
rowChangeBuider.addProps(createSpecialPair(XA_TYPE, XA_ROLLBACK));
|
||||||
@@ -218,11 +212,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||||
} else if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
} else if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
||||||
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONBEGIN, transactionBegin.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONBEGIN, transactionBegin.toByteString());
|
||||||
} else if (StringUtils.endsWithIgnoreCase(queryString, COMMIT)) {
|
} else if (StringUtils.endsWithIgnoreCase(queryString, COMMIT)) {
|
||||||
TransactionEnd transactionEnd = createTransactionEnd(0L); // MyISAM可能不会有xid事件
|
TransactionEnd transactionEnd = createTransactionEnd(0L); // MyISAM可能不会有xid事件
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
||||||
} else {
|
} else {
|
||||||
boolean notFilter = false;
|
boolean notFilter = false;
|
||||||
@@ -265,7 +259,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
tableMetaCache.apply(position, event.getDbName(), queryString, null);
|
tableMetaCache.apply(position, event.getDbName(), queryString, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
|
Header header = createHeader(event.getHeader(), schemaName, tableName, type);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
if (type != EventType.QUERY && type != EventType.INSERT && type != EventType.UPDATE
|
if (type != EventType.QUERY && type != EventType.INSERT && type != EventType.UPDATE
|
||||||
&& type != EventType.DELETE) {
|
&& type != EventType.DELETE) {
|
||||||
@@ -425,52 +419,78 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|
|
||||||
private Entry parseXidEvent(XidLogEvent event) {
|
private Entry parseXidEvent(XidLogEvent event) {
|
||||||
TransactionEnd transactionEnd = createTransactionEnd(event.getXid());
|
TransactionEnd transactionEnd = createTransactionEnd(event.getXid());
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Entry parseRowsEvent(RowsLogEvent event) {
|
public TableMeta parseRowsEventForTableMeta(RowsLogEvent event) {
|
||||||
|
TableMapLogEvent table = event.getTable();
|
||||||
|
if (table == null) {
|
||||||
|
// tableId对应的记录不存在
|
||||||
|
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isHeartBeat = isAliSQLHeartBeat(table.getDbName(), table.getTableName());
|
||||||
|
boolean isRDSHeartBeat = tableMetaCache.isOnRDS() && isRDSHeartBeat(table.getDbName(), table.getTableName());
|
||||||
|
|
||||||
|
String fullname = table.getDbName() + "." + table.getTableName();
|
||||||
|
// check name filter
|
||||||
|
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (nameBlackFilter != null && nameBlackFilter.filter(fullname)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (isHeartBeat || isRDSHeartBeat) {
|
||||||
|
// // 忽略rds模式的mysql.ha_health_check心跳数据
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
TableMeta tableMeta = null;
|
||||||
|
if (isRDSHeartBeat) {
|
||||||
|
// 处理rds模式的mysql.ha_health_check心跳数据
|
||||||
|
// 主要RDS的心跳表基本无权限,需要mock一个tableMeta
|
||||||
|
FieldMeta idMeta = new FieldMeta("id", "bigint(20)", true, false, "0");
|
||||||
|
FieldMeta typeMeta = new FieldMeta("type", "char(1)", false, true, "0");
|
||||||
|
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
||||||
|
} else if (isHeartBeat) {
|
||||||
|
// 处理alisql模式的test.heartbeat心跳数据
|
||||||
|
// 心跳表基本无权限,需要mock一个tableMeta
|
||||||
|
FieldMeta idMeta = new FieldMeta("id", "smallint(6)", false, true, null);
|
||||||
|
FieldMeta typeMeta = new FieldMeta("ts", "int(11)", true, false, null);
|
||||||
|
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
|
if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
|
||||||
|
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
|
||||||
|
if (tableMeta == null) {
|
||||||
|
if (!filterTableError) {
|
||||||
|
throw new CanalParseException("not found [" + fullname + "] in db , pls check!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry parseRowsEvent(RowsLogEvent event) {
|
||||||
|
return parseRowsEvent(event, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry parseRowsEvent(RowsLogEvent event, TableMeta tableMeta) {
|
||||||
if (filterRows) {
|
if (filterRows) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
TableMapLogEvent table = event.getTable();
|
if (tableMeta == null) { // 如果没有外部指定
|
||||||
if (table == null) {
|
tableMeta = parseRowsEventForTableMeta(event);
|
||||||
// tableId对应的记录不存在
|
|
||||||
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isHeartBeat = isAliSQLHeartBeat(table.getDbName(), table.getTableName());
|
if (tableMeta == null) {
|
||||||
boolean isRDSHeartBeat = tableMetaCache.isOnRDS()
|
// 拿不到表结构,执行忽略
|
||||||
&& isRDSHeartBeat(table.getDbName(), table.getTableName());
|
|
||||||
|
|
||||||
String fullname = table.getDbName() + "." + table.getTableName();
|
|
||||||
// check name filter
|
|
||||||
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (nameBlackFilter != null && nameBlackFilter.filter(fullname)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (isHeartBeat || isRDSHeartBeat) {
|
|
||||||
// // 忽略rds模式的mysql.ha_health_check心跳数据
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
TableMeta tableMeta = null;
|
|
||||||
if (isRDSHeartBeat) {
|
|
||||||
// 处理rds模式的mysql.ha_health_check心跳数据
|
|
||||||
// 主要RDS的心跳表基本无权限,需要mock一个tableMeta
|
|
||||||
FieldMeta idMeta = new FieldMeta("id", "bigint(20)", true, false, "0");
|
|
||||||
FieldMeta typeMeta = new FieldMeta("type", "char(1)", false, true, "0");
|
|
||||||
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
|
||||||
} else if (isHeartBeat) {
|
|
||||||
// 处理alisql模式的test.heartbeat心跳数据
|
|
||||||
// 心跳表基本无权限,需要mock一个tableMeta
|
|
||||||
FieldMeta idMeta = new FieldMeta("id", "smallint(6)", false, true, null);
|
|
||||||
FieldMeta typeMeta = new FieldMeta("ts", "int(11)", true, false, null);
|
|
||||||
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
|
||||||
}
|
|
||||||
|
|
||||||
EventType eventType = null;
|
EventType eventType = null;
|
||||||
int type = event.getHeader().getType();
|
int type = event.getHeader().getType();
|
||||||
@@ -484,12 +504,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
throw new CanalParseException("unsupport event type :" + event.getHeader().getType());
|
throw new CanalParseException("unsupport event type :" + event.getHeader().getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
Header header = createHeader(binlogFileName,
|
TableMapLogEvent table = event.getTable();
|
||||||
event.getHeader(),
|
Header header = createHeader(event.getHeader(), table.getDbName(), table.getTableName(), eventType);
|
||||||
table.getDbName(),
|
|
||||||
table.getTableName(),
|
|
||||||
eventType);
|
|
||||||
EntryPosition position = createPosition(event.getHeader());
|
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setTableId(event.getTableId());
|
rowChangeBuider.setTableId(event.getTableId());
|
||||||
rowChangeBuider.setIsDdl(false);
|
rowChangeBuider.setIsDdl(false);
|
||||||
@@ -498,18 +515,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
RowsLogBuffer buffer = event.getRowsBuf(charset.name());
|
RowsLogBuffer buffer = event.getRowsBuf(charset.name());
|
||||||
BitSet columns = event.getColumns();
|
BitSet columns = event.getColumns();
|
||||||
BitSet changeColumns = event.getChangeColumns();
|
BitSet changeColumns = event.getChangeColumns();
|
||||||
boolean tableError = false;
|
|
||||||
if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
|
|
||||||
// cache
|
|
||||||
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
|
|
||||||
if (tableMeta == null) {
|
|
||||||
tableError = true;
|
|
||||||
if (!filterTableError) {
|
|
||||||
throw new CanalParseException("not found [" + fullname + "] in db , pls check!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
boolean tableError = false;
|
||||||
while (buffer.nextOneRow(columns)) {
|
while (buffer.nextOneRow(columns)) {
|
||||||
// 处理row记录
|
// 处理row记录
|
||||||
RowData.Builder rowDataBuilder = RowData.newBuilder();
|
RowData.Builder rowDataBuilder = RowData.newBuilder();
|
||||||
@@ -539,7 +546,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
logger.warn("table parser error : {}storeValue: {}", entry.toString(), rowChange.toString());
|
logger.warn("table parser error : {}storeValue: {}", entry.toString(), rowChange.toString());
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
Entry entry = createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
Entry entry = createEntry(header, EntryType.ROWDATA, rowChange.toByteString());
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -548,7 +555,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EntryPosition createPosition(LogHeader logHeader) {
|
private EntryPosition createPosition(LogHeader logHeader) {
|
||||||
return new EntryPosition(binlogFileName,
|
return new EntryPosition(logHeader.getLogFileName(),
|
||||||
logHeader.getLogPos(),
|
logHeader.getLogPos(),
|
||||||
logHeader.getWhen() * 1000L,
|
logHeader.getWhen() * 1000L,
|
||||||
logHeader.getServerId()); // 记录到秒
|
logHeader.getServerId()); // 记录到秒
|
||||||
@@ -641,7 +648,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
isBinary = true;
|
isBinary = true;
|
||||||
} else if (StringUtils.containsIgnoreCase(fieldMeta.getColumnType(), "BINARY")) {
|
} else if (StringUtils.containsIgnoreCase(fieldMeta.getColumnType(), "BINARY")) {
|
||||||
isBinary = true;
|
isBinary = true;
|
||||||
} else if (StringUtils.containsIgnoreCase(fieldMeta.getColumnType(), "tinyint(1)")) {
|
} else if (StringUtils.containsIgnoreCase(fieldMeta.getColumnType(), "TINYINT(1)")) {
|
||||||
isSingleBit = true;
|
isSingleBit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -771,7 +778,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Entry buildQueryEntry(String queryString, LogHeader logHeader, String tableName) {
|
private Entry buildQueryEntry(String queryString, LogHeader logHeader, String tableName) {
|
||||||
Header header = createHeader(binlogFileName, logHeader, "", tableName, EventType.QUERY);
|
Header header = createHeader(logHeader, "", tableName, EventType.QUERY);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setSql(queryString);
|
rowChangeBuider.setSql(queryString);
|
||||||
rowChangeBuider.setEventType(EventType.QUERY);
|
rowChangeBuider.setEventType(EventType.QUERY);
|
||||||
@@ -779,19 +786,18 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
|
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
|
||||||
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
|
Header header = createHeader(logHeader, "", "", EventType.QUERY);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setSql(queryString);
|
rowChangeBuider.setSql(queryString);
|
||||||
rowChangeBuider.setEventType(EventType.QUERY);
|
rowChangeBuider.setEventType(EventType.QUERY);
|
||||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Header createHeader(String binlogFile, LogHeader logHeader, String schemaName, String tableName,
|
private Header createHeader(LogHeader logHeader, String schemaName, String tableName, EventType eventType) {
|
||||||
EventType eventType) {
|
|
||||||
// header会做信息冗余,方便以后做检索或者过滤
|
// header会做信息冗余,方便以后做检索或者过滤
|
||||||
Header.Builder headerBuilder = Header.newBuilder();
|
Header.Builder headerBuilder = Header.newBuilder();
|
||||||
headerBuilder.setVersion(version);
|
headerBuilder.setVersion(version);
|
||||||
headerBuilder.setLogfileName(binlogFile);
|
headerBuilder.setLogfileName(logHeader.getLogFileName());
|
||||||
headerBuilder.setLogfileOffset(logHeader.getLogPos() - logHeader.getEventLen());
|
headerBuilder.setLogfileOffset(logHeader.getLogPos() - logHeader.getEventLen());
|
||||||
headerBuilder.setServerId(logHeader.getServerId());
|
headerBuilder.setServerId(logHeader.getServerId());
|
||||||
headerBuilder.setServerenCode(UTF_8);// 经过java输出后所有的编码为unicode
|
headerBuilder.setServerenCode(UTF_8);// 经过java输出后所有的编码为unicode
|
||||||
|
|||||||
5
pom.xml
5
pom.xml
@@ -263,6 +263,11 @@
|
|||||||
<artifactId>druid</artifactId>
|
<artifactId>druid</artifactId>
|
||||||
<version>1.1.9</version>
|
<version>1.1.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lmax</groupId>
|
||||||
|
<artifactId>disruptor</artifactId>
|
||||||
|
<version>3.4.2</version>
|
||||||
|
</dependency>
|
||||||
<!-- log -->
|
<!-- log -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
|||||||
Reference in New Issue
Block a user