mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Merge branch 'master' into master
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.23-SNAPSHOT</version>
|
||||
<version>1.0.24-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.parse</artifactId>
|
||||
|
||||
@@ -22,7 +22,6 @@ import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
@@ -325,7 +324,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
startTs = System.currentTimeMillis();
|
||||
}
|
||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
||||
|
||||
if (enabled) {
|
||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
||||
}
|
||||
@@ -388,7 +386,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
String name = String.format("destination = %s , address = %s , HeartBeatTimeTask",
|
||||
destination,
|
||||
runningInfo == null ? null : runningInfo.getAddress().toString());
|
||||
synchronized (MysqlEventParser.class) {
|
||||
synchronized (AbstractEventParser.class) {
|
||||
// synchronized (MysqlEventParser.class) {
|
||||
// why use MysqlEventParser.class, u know, MysqlEventParser is
|
||||
// the child class 4 AbstractEventParser,
|
||||
// do this is ...
|
||||
if (timer == null) {
|
||||
timer = new Timer(name, true);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.alibaba.otter.canal.parse.inbound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@@ -44,6 +45,17 @@ public class TableMeta {
|
||||
this.fileds = fileds;
|
||||
}
|
||||
|
||||
public List<FieldMeta> getPrimaryFields() {
|
||||
List<FieldMeta> primarys = new ArrayList<TableMeta.FieldMeta>();
|
||||
for (FieldMeta meta : fileds) {
|
||||
if (meta.isKey()) {
|
||||
primarys.add(meta);
|
||||
}
|
||||
}
|
||||
|
||||
return primarys;
|
||||
}
|
||||
|
||||
public static class FieldMeta {
|
||||
|
||||
private String columnName;
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
||||
@@ -18,6 +17,7 @@ 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.QueryLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
||||
|
||||
/**
|
||||
* local bin log connection (not real connection)
|
||||
@@ -83,9 +83,9 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
|
||||
while (running) {
|
||||
boolean needContinue = true;
|
||||
LogEvent event;
|
||||
while (fetcher.fetch()) {
|
||||
event = decoder.decode(fetcher, context);
|
||||
LogEvent event = null;
|
||||
L:while (fetcher.fetch()) {
|
||||
/*event = decoder.decode(fetcher, context);
|
||||
if (event == null) {
|
||||
throw new CanalParseException("parse failed");
|
||||
}
|
||||
@@ -93,15 +93,20 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
if (!func.sink(event)) {
|
||||
needContinue = false;
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
// do {
|
||||
// event = decoder.decode(fetcher, context);
|
||||
// if (event != null && !func.sink(event)) {
|
||||
// needContinue = false;
|
||||
// break;
|
||||
// }
|
||||
// } while (event != null);
|
||||
do {
|
||||
if (event == null) {
|
||||
event = new RotateLogEvent(context.getLogPosition().getFileName(), context.getLogPosition().getPosition());
|
||||
} else {
|
||||
event = decoder.decode(fetcher, context);
|
||||
}
|
||||
|
||||
if (event != null && !func.sink(event)) {
|
||||
needContinue = false;
|
||||
break L;
|
||||
}
|
||||
} while (event != null);
|
||||
}
|
||||
|
||||
if (needContinue) {// 读取下一个
|
||||
@@ -145,6 +150,7 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
|
||||
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();
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
@@ -21,7 +25,9 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
// 数据库信息
|
||||
private AuthenticationInfo masterInfo;
|
||||
private EntryPosition masterPosition; // binlog信息
|
||||
|
||||
private MysqlConnection metaConnection; // 查询meta信息的链接
|
||||
private TableMetaCache tableMetaCache; // 对应meta
|
||||
|
||||
private String directory;
|
||||
private boolean needWait = false;
|
||||
private int bufferSize = 16 * 1024;
|
||||
@@ -33,17 +39,60 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
@Override
|
||||
protected ErosaConnection buildErosaConnection() {
|
||||
return buildLocalBinLogConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preDump(ErosaConnection connection) {
|
||||
metaConnection = buildMysqlConnection();
|
||||
try {
|
||||
metaConnection.connect();
|
||||
} catch (IOException e) {
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
|
||||
tableMetaCache = new TableMetaCache(metaConnection);
|
||||
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||
}
|
||||
|
||||
public void start() throws CanalParseException {
|
||||
@Override
|
||||
protected void afterDump(ErosaConnection connection) {
|
||||
if (metaConnection != null) {
|
||||
try {
|
||||
metaConnection.disconnect();
|
||||
} catch (IOException e) {
|
||||
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
|
||||
.getAddress(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() throws CanalParseException {
|
||||
if (runningInfo == null) { // 第一次链接主库
|
||||
runningInfo = masterInfo;
|
||||
}
|
||||
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (metaConnection != null) {
|
||||
try {
|
||||
metaConnection.disconnect();
|
||||
} catch (IOException e) {
|
||||
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
|
||||
.getAddress(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private ErosaConnection buildLocalBinLogConnection() {
|
||||
if (tableMetaCache != null) {
|
||||
tableMetaCache.clearTableMeta();
|
||||
}
|
||||
|
||||
super.stop();
|
||||
}
|
||||
|
||||
private ErosaConnection buildLocalBinLogConnection() {
|
||||
LocalBinLogConnection connection = new LocalBinLogConnection();
|
||||
|
||||
connection.setBufferSize(this.bufferSize);
|
||||
@@ -52,6 +101,19 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private MysqlConnection buildMysqlConnection() {
|
||||
MysqlConnection connection = new MysqlConnection(runningInfo.getAddress(),
|
||||
runningInfo.getUsername(),
|
||||
runningInfo.getPassword(),
|
||||
connectionCharsetNumber,
|
||||
runningInfo.getDefaultDatabaseName());
|
||||
connection.getConnector().setReceiveBufferSize(64 * 1024);
|
||||
connection.getConnector().setSendBufferSize(64 * 1024);
|
||||
connection.getConnector().setSoTimeout(30 * 1000);
|
||||
connection.setCharset(connectionCharset);
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntryPosition findStartPosition(ErosaConnection connection) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -24,6 +25,8 @@ import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
||||
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.FormatDescriptionLogEvent;
|
||||
|
||||
public class MysqlConnection implements ErosaConnection {
|
||||
|
||||
@@ -34,6 +37,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
private BinlogFormat binlogFormat;
|
||||
private BinlogImage binlogImage;
|
||||
private int binlogChecksum;
|
||||
|
||||
public MysqlConnection(){
|
||||
}
|
||||
@@ -79,7 +83,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
*/
|
||||
public void seek(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||
updateSettings();
|
||||
|
||||
loadBinlogChecksum();
|
||||
sendBinlogDump(binlogfilename, binlogPosition);
|
||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||
fetcher.start(connector.getChannel());
|
||||
@@ -89,6 +93,8 @@ public class MysqlConnection implements ErosaConnection {
|
||||
decoder.handle(LogEvent.QUERY_EVENT);
|
||||
decoder.handle(LogEvent.XID_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
context.setLogPosition(new LogPosition(binlogfilename));
|
||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
||||
while (fetcher.fetch()) {
|
||||
LogEvent event = null;
|
||||
event = decoder.decode(fetcher, context);
|
||||
@@ -105,11 +111,14 @@ public class MysqlConnection implements ErosaConnection {
|
||||
|
||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||
updateSettings();
|
||||
loadBinlogChecksum();
|
||||
sendBinlogDump(binlogfilename, binlogPosition);
|
||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||
fetcher.start(connector.getChannel());
|
||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
context.setLogPosition(new LogPosition(binlogfilename));
|
||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
||||
while (fetcher.fetch()) {
|
||||
LogEvent event = null;
|
||||
event = decoder.decode(fetcher, context);
|
||||
@@ -196,9 +205,12 @@ public class MysqlConnection implements ErosaConnection {
|
||||
// 如果不设置会出现错误: Slave can not handle replication events with the
|
||||
// checksum that master is configured to log
|
||||
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
||||
update("set @master_binlog_checksum= '@@global.binlog_checksum'");
|
||||
// '@@global.binlog_checksum'需要去掉单引号,在mysql 5.6.29下导致master退出
|
||||
update("set @master_binlog_checksum= @@global.binlog_checksum");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -256,6 +268,28 @@ public class MysqlConnection implements ErosaConnection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主库checksum信息
|
||||
* https://dev.mysql.com/doc/refman/5.6/en/replication-options
|
||||
* -binary-log.html#option_mysqld_binlog-checksum
|
||||
*/
|
||||
private void loadBinlogChecksum() {
|
||||
ResultSetPacket rs = null;
|
||||
try {
|
||||
rs = query("select @master_binlog_checksum");
|
||||
} catch (IOException e) {
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
|
||||
List<String> columnValues = rs.getFieldValues();
|
||||
if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0) != null
|
||||
&& columnValues.get(0).toUpperCase().equals("CRC32")) {
|
||||
binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32;
|
||||
} else {
|
||||
binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum BinlogFormat {
|
||||
|
||||
STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED");
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.CanalHASwitchable;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||
@@ -93,6 +94,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
for (BinlogFormat supportFormat : supportBinlogFormats) {
|
||||
if (supportFormat != null && format == supportFormat) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +109,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
for (BinlogImage supportImage : supportBinlogImages) {
|
||||
if (supportImage != null && image == supportImage) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,10 +180,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
protected void stopHeartBeat() {
|
||||
TimerTask heartBeatTimerTask = this.heartBeatTimerTask;
|
||||
super.stopHeartBeat();
|
||||
|
||||
if (heartBeatTimerTask != null) {
|
||||
|
||||
if (heartBeatTimerTask != null && heartBeatTimerTask instanceof MysqlDetectingTimeTask) {
|
||||
MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection();
|
||||
try {
|
||||
mysqlConnection.disconnect();
|
||||
@@ -395,7 +397,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
// binlog定位位点失败,可能有两个原因:
|
||||
// 1. binlog位点被删除
|
||||
// 2.vip模式的mysql,发生了主备切换,判断一下serverId是否变化,针对这种模式可以发起一次基于时间戳查找合适的binlog位点
|
||||
boolean case2 = (standbyInfo.getAddress() == null)
|
||||
boolean case2 = (standbyInfo == null || standbyInfo.getAddress() == null)
|
||||
&& logPosition.getPostion().getServerId() != null
|
||||
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
|
||||
if (case2) {
|
||||
@@ -410,7 +412,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
}
|
||||
// 其余情况
|
||||
logger.warn("prepare to find start position just last position");
|
||||
logger.warn("prepare to find start position just last position\n {}",
|
||||
JsonUtils.marshalToString(logPosition));
|
||||
return logPosition.getPostion();
|
||||
} else {
|
||||
// 针对切换的情况,考虑回退时间
|
||||
|
||||
@@ -85,7 +85,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
// 是否跳过table相关的解析异常,比如表不存在或者列数量不匹配,issue 92
|
||||
private boolean filterTableError = false;
|
||||
// 新增rows过滤,用于仅订阅除rows以外的数据
|
||||
private boolean filterRows = false;
|
||||
private boolean filterRows = false;
|
||||
|
||||
public Entry parse(LogEvent logEvent) throws CanalParseException {
|
||||
if (logEvent == null || logEvent instanceof UnknownLogEvent) {
|
||||
@@ -326,6 +326,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tableMetaCache.isOnRDS() && "mysql.ha_health_check".equals(fullname)) {
|
||||
// 忽略rds模式的mysql.ha_health_check心跳数据
|
||||
return null;
|
||||
}
|
||||
|
||||
EventType eventType = null;
|
||||
int type = event.getHeader().getType();
|
||||
if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
|
||||
@@ -402,34 +407,51 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
|
||||
private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols,
|
||||
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
|
||||
final int columnCnt = event.getTable().getColumnCnt();
|
||||
final ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
|
||||
int columnCnt = event.getTable().getColumnCnt();
|
||||
ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
|
||||
|
||||
boolean tableError = false;
|
||||
// check table fileds count,只能处理加字段
|
||||
boolean existRDSNoPrimaryKey = false;
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
||||
// online ddl增加字段操作步骤:
|
||||
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
||||
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
|
||||
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
||||
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
||||
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
||||
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false);// 强制重新获取一次
|
||||
if (tableMeta == null) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("not found [" + event.getTable().getDbName() + "."
|
||||
+ event.getTable().getTableName() + "] in db , pls check!");
|
||||
if (tableMetaCache.isOnRDS()) {
|
||||
// 特殊处理下RDS的场景
|
||||
List<FieldMeta> primaryKeys = tableMeta.getPrimaryFields();
|
||||
if (primaryKeys == null || primaryKeys.isEmpty()) {
|
||||
if (columnInfo.length == tableMeta.getFileds().size() + 1
|
||||
&& columnInfo[columnInfo.length - 1].type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||
existRDSNoPrimaryKey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在做一次判断
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName() + ","
|
||||
+ columnInfo.length + " vs " + tableMeta.getFileds().size());
|
||||
if (!existRDSNoPrimaryKey) {
|
||||
// online ddl增加字段操作步骤:
|
||||
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
||||
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
|
||||
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
||||
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
||||
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
||||
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false);// 强制重新获取一次
|
||||
if (tableMeta == null) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("not found [" + event.getTable().getDbName() + "."
|
||||
+ event.getTable().getTableName() + "] in db , pls check!");
|
||||
}
|
||||
}
|
||||
|
||||
// 在做一次判断
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName()
|
||||
+ "," + columnInfo.length + " vs " + tableMeta.getFileds().size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("[" + event.getTable().getDbName() + "." + event.getTable().getTableName()
|
||||
+ "] is no primary key , skip alibaba_rds_row_id column");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +462,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||
// 不解析最后一列
|
||||
buffer.nextValue(info.type, info.meta, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
Column.Builder columnBuilder = Column.newBuilder();
|
||||
|
||||
FieldMeta fieldMeta = null;
|
||||
@@ -465,6 +493,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
}
|
||||
}
|
||||
buffer.nextValue(info.type, info.meta, isBinary);
|
||||
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||
// 不解析最后一列
|
||||
continue;
|
||||
}
|
||||
|
||||
int javaType = buffer.getJavaType();
|
||||
if (buffer.isNull()) {
|
||||
@@ -718,7 +750,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
public void setFilterTableError(boolean filterTableError) {
|
||||
this.filterTableError = filterTableError;
|
||||
}
|
||||
|
||||
|
||||
public void setFilterRows(boolean filterRows) {
|
||||
this.filterRows = filterRows;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public class TableMetaCache {
|
||||
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
||||
public static final String EXTRA = "EXTRA";
|
||||
private MysqlConnection connection;
|
||||
private boolean isOnRDS = false;
|
||||
|
||||
// 第一层tableId,第二层schema.table,解决tableId重复,对应多张表
|
||||
private Map<String, TableMeta> tableMetaCache;
|
||||
@@ -56,6 +57,13 @@ public class TableMetaCache {
|
||||
|
||||
});
|
||||
|
||||
try {
|
||||
ResultSetPacket packet = connection.query("show global variables like 'rds\\_%'");
|
||||
if (packet.getFieldValues().size() > 0) {
|
||||
isOnRDS = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table) {
|
||||
@@ -136,4 +144,13 @@ public class TableMetaCache {
|
||||
.append('`')
|
||||
.toString();
|
||||
}
|
||||
|
||||
public boolean isOnRDS() {
|
||||
return isOnRDS;
|
||||
}
|
||||
|
||||
public void setOnRDS(boolean isOnRDS) {
|
||||
this.isOnRDS = isOnRDS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||
@@ -188,7 +190,9 @@ public class BinLogFileQueue {
|
||||
files.addAll(FileUtils.listFiles(directory, new IOFileFilter() {
|
||||
|
||||
public boolean accept(File file) {
|
||||
return file.getName().startsWith(baseName);
|
||||
Pattern pattern = Pattern.compile("\\d+$");
|
||||
Matcher matcher = pattern.matcher(file.getName());
|
||||
return file.getName().startsWith(baseName) && matcher.find();
|
||||
}
|
||||
|
||||
public boolean accept(File dir, String name) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.alibaba.otter.canal.protocol.position.LogIdentity;
|
||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||
@@ -22,8 +23,8 @@ public class MysqlEventParserTest {
|
||||
|
||||
private static final String DETECTING_SQL = "insert into retl.xdual values(1,now()) on duplicate key update x=now()";
|
||||
private static final String MYSQL_ADDRESS = "127.0.0.1";
|
||||
private static final String USERNAME = "xxxxx";
|
||||
private static final String PASSWORD = "xxxxx";
|
||||
private static final String USERNAME = "root";
|
||||
private static final String PASSWORD = "xxxxxx";
|
||||
|
||||
@Test
|
||||
public void test_position() throws InterruptedException {
|
||||
@@ -45,20 +46,23 @@ public class MysqlEventParserTest {
|
||||
public boolean sink(List<Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||
throws CanalSinkException {
|
||||
for (Entry entry : entrys) {
|
||||
entryCount.incrementAndGet();
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
entryPosition.setJournalName(logfilename);
|
||||
entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
|
||||
break;
|
||||
if (entry.getEntryType() != EntryType.HEARTBEAT) {
|
||||
entryCount.incrementAndGet();
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
entryPosition.setJournalName(logfilename);
|
||||
entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
if (entryCount.get() > 0) {
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -92,14 +96,14 @@ public class MysqlEventParserTest {
|
||||
|
||||
@Test
|
||||
public void test_timestamp() throws InterruptedException {
|
||||
final TimeoutChecker timeoutChecker = new TimeoutChecker(30 * 1000);
|
||||
final TimeoutChecker timeoutChecker = new TimeoutChecker(3000 * 1000);
|
||||
final AtomicLong entryCount = new AtomicLong(0);
|
||||
final EntryPosition entryPosition = new EntryPosition();
|
||||
|
||||
final MysqlEventParser controller = new MysqlEventParser();
|
||||
final EntryPosition defaultPosition = buildPosition(null, null, 1322803601000L);
|
||||
final EntryPosition defaultPosition = buildPosition(null, null, 1475116855000L);
|
||||
controller.setSlaveId(3344L);
|
||||
controller.setDetectingEnable(true);
|
||||
controller.setDetectingEnable(false);
|
||||
controller.setDetectingSQL(DETECTING_SQL);
|
||||
controller.setMasterInfo(buildAuthentication());
|
||||
controller.setMasterPosition(defaultPosition);
|
||||
@@ -109,22 +113,25 @@ public class MysqlEventParserTest {
|
||||
public boolean sink(List<Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||
throws CanalSinkException {
|
||||
for (Entry entry : entrys) {
|
||||
entryCount.incrementAndGet();
|
||||
if (entry.getEntryType() != EntryType.HEARTBEAT) {
|
||||
entryCount.incrementAndGet();
|
||||
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
|
||||
entryPosition.setJournalName(logfilename);
|
||||
entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
|
||||
break;
|
||||
entryPosition.setJournalName(logfilename);
|
||||
entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
if (entryCount.get() > 0) {
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -174,21 +181,26 @@ public class MysqlEventParserTest {
|
||||
public boolean sink(List<Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||
throws CanalSinkException {
|
||||
for (Entry entry : entrys) {
|
||||
entryCount.incrementAndGet();
|
||||
if (entry.getEntryType() != EntryType.HEARTBEAT) {
|
||||
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
entryCount.incrementAndGet();
|
||||
|
||||
entryPosition.setJournalName(logfilename);
|
||||
entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
break;
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
|
||||
entryPosition.setJournalName(logfilename);
|
||||
entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
if (entryCount.get() > 0) {
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -244,22 +256,27 @@ public class MysqlEventParserTest {
|
||||
public boolean sink(List<Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||
throws CanalSinkException {
|
||||
for (Entry entry : entrys) {
|
||||
entryCount.incrementAndGet();
|
||||
if (entry.getEntryType() != EntryType.HEARTBEAT) {
|
||||
entryCount.incrementAndGet();
|
||||
|
||||
// String logfilename = entry.getHeader().getLogfileName();
|
||||
// long logfileoffset =
|
||||
// entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
// String logfilename =
|
||||
// entry.getHeader().getLogfileName();
|
||||
// long logfileoffset =
|
||||
// entry.getHeader().getLogfileOffset();
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
|
||||
// entryPosition.setJournalName(logfilename);
|
||||
// entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
break;
|
||||
// entryPosition.setJournalName(logfilename);
|
||||
// entryPosition.setPosition(logfileoffset);
|
||||
entryPosition.setTimestamp(executeTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
if (entryCount.get() > 0) {
|
||||
controller.stop();
|
||||
timeoutChecker.stop();
|
||||
timeoutChecker.touch();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user