diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java index a50b2450..d0c6746d 100644 --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java @@ -92,6 +92,10 @@ public class BaseLogFetcherTest { final ColumnInfo[] columnInfo = map.getColumnInfo(); for (int i = 0; i < columnCnt; i++) { + if (!cols.get(i)) { + continue; + } + ColumnInfo info = columnInfo[i]; buffer.nextValue(info.type, info.meta); diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java index b8f4e47d..23a10f01 100644 --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java @@ -6,7 +6,6 @@ import java.sql.DriverManager; import java.sql.Statement; import org.junit.Assert; - import org.junit.Test; import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; @@ -30,7 +29,7 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest { statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'"); statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); - fecther.open(connection, "mysql-bin.000010", 4L, 2); + fecther.open(connection, "mysql-bin.000001", 4L, 2); LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/FileLogFetcherTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/FileLogFetcherTest.java index e7551932..7673f71b 100644 --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/FileLogFetcherTest.java +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/FileLogFetcherTest.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.net.URL; import org.junit.Assert; - import org.junit.Before; import org.junit.Test; @@ -27,7 +26,6 @@ public class FileLogFetcherTest extends BaseLogFetcherTest { URL url = Thread.currentThread().getContextClassLoader().getResource("dummy.txt"); File dummyFile = new File(url.getFile()); directory = new File(dummyFile.getParent() + "/binlog").getPath(); - // directory = "/home/jianghang/tmp/binlog"; } @Test @@ -37,8 +35,8 @@ public class FileLogFetcherTest extends BaseLogFetcherTest { LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); - File current = new File(directory, "mysql-bin.000006"); - fetcher.open(current); + File current = new File(directory, "mysql-bin.000001"); + fetcher.open(current, 2051L); context.setLogPosition(new LogPosition(current.getName())); while (fetcher.fetch()) { diff --git a/deployer/src/main/bin/stop.sh b/deployer/src/main/bin/stop.sh index 73eeb9ca..f398749c 100644 --- a/deployer/src/main/bin/stop.sh +++ b/deployer/src/main/bin/stop.sh @@ -1,10 +1,14 @@ #!/bin/bash cygwin=false; +linux=false; case "`uname`" in CYGWIN*) cygwin=true ;; + Linux*) + linux=true + ;; esac get_pid() { @@ -15,11 +19,19 @@ get_pid() { JAVA_CMD=`cygpath --path --unix $JAVA_CMD` JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'` else - if [ ! -z "$PID" ]; then - JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` - else - JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` - fi + if $linux; then + if [ ! -z "$PID" ]; then + JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` + else + JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` + fi + else + if [ ! -z "$PID" ]; then + JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` + else + JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'` + fi + fi fi echo $JAVA_PID; } diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 2831c4e1..cbcc19f8 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -41,6 +41,10 @@ canal.instance.filter.query.dml = false canal.instance.filter.query.ddl = false canal.instance.filter.table.error = false +# binlog format/image check +canal.instance.binlog.format = ROW,STATEMENT,MIXED +canal.instance.binlog.image = FULL,MINIMAL,NOBLOB + # binlog ddl isolation canal.instance.get.ddl.isolation = false diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index 1a1c3291..f12b72d3 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -179,5 +179,7 @@ + + \ No newline at end of file diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 74b48f98..618beed1 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -164,5 +164,7 @@ + + \ No newline at end of file diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 40c7787b..c135196b 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -161,6 +161,8 @@ + + @@ -249,5 +251,7 @@ + + \ No newline at end of file diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index 524ecb3c..b7afc130 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -152,5 +152,7 @@ + + \ No newline at end of file diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 08b330a6..0133f7e1 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -27,12 +27,13 @@ import com.taobao.tddl.dbsync.binlog.LogEvent; public class MysqlConnection implements ErosaConnection { - private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class); + private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class); private MysqlConnector connector; private long slaveId; - private Charset charset = Charset.forName("UTF-8"); - private BinlogFormat binlogFormat = BinlogFormat.ROW; + private Charset charset = Charset.forName("UTF-8"); + private BinlogFormat binlogFormat; + private BinlogImage binlogImage; public MysqlConnection(){ } @@ -104,11 +105,6 @@ public class MysqlConnection implements ErosaConnection { public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException { updateSettings(); - BinlogFormat format = getBinlogFormat(); - if (!format.isRow()) { - logger.warn("binlog_format : {} , it will not have rowData before/after columns", format.toString()); - } - sendBinlogDump(binlogfilename, binlogPosition); DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize()); fetcher.start(connector.getChannel()); @@ -212,7 +208,7 @@ public class MysqlConnection implements ErosaConnection { } /** - * 判断一下是否采用ROW模式 + * 获取一下binlog format格式 */ private void loadBinlogFormat() { ResultSetPacket rs = null; @@ -234,6 +230,30 @@ public class MysqlConnection implements ErosaConnection { } } + /** + * 获取一下binlog image格式 + */ + private void loadBinlogImage() { + ResultSetPacket rs = null; + try { + rs = query("show variables like 'binlog_row_image'"); + } catch (IOException e) { + throw new CanalParseException(e); + } + + List columnValues = rs.getFieldValues(); + if (columnValues == null || columnValues.size() != 2) { + // 可能历时版本没有image特性 + binlogImage = BinlogImage.FULL; + } else { + binlogImage = BinlogImage.valuesOf(columnValues.get(1)); + } + + if (binlogFormat == null) { + throw new IllegalStateException("unexpected binlog image query result:" + rs.getFieldValues()); + } + } + public static enum BinlogFormat { STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED"); @@ -267,6 +287,46 @@ public class MysqlConnection implements ErosaConnection { } } + /** + * http://dev.mysql.com/doc/refman/5.6/en/replication-options-binary-log. + * html#sysvar_binlog_row_image + * + * @author agapple 2015年6月29日 下午10:39:03 + * @since 1.0.20 + */ + public static enum BinlogImage { + + FULL("FULL"), MINIMAL("MINIMAL"), NOBLOB("NOBLOB"); + + public boolean isFull() { + return this == FULL; + } + + public boolean isMinimal() { + return this == MINIMAL; + } + + public boolean isNoBlob() { + return this == NOBLOB; + } + + private String value; + + private BinlogImage(String value){ + this.value = value; + } + + public static BinlogImage valuesOf(String value) { + BinlogImage[] formats = values(); + for (BinlogImage format : formats) { + if (format.value.equalsIgnoreCase(value)) { + return format; + } + } + return null; + } + } + // ================== setter / getter =================== public Charset getCharset() { @@ -303,4 +363,14 @@ public class MysqlConnection implements ErosaConnection { return binlogFormat; } + public BinlogImage getBinlogImage() { + if (binlogImage == null) { + synchronized (this) { + loadBinlogImage(); + } + } + + return binlogImage; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 787b72c9..b779e03c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -22,6 +22,8 @@ import com.alibaba.otter.canal.parse.ha.CanalHAController; import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.HeartBeatCallback; import com.alibaba.otter.canal.parse.inbound.SinkFunction; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage; 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.support.AuthenticationInfo; @@ -62,6 +64,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private TableMetaCache tableMetaCache; // 对应meta // cache private int fallbackIntervalInSeconds = 60; // 切换回退时间 + private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 + private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 // 心跳检查 @@ -82,6 +86,34 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE throw new CanalParseException(e); } + if (supportBinlogFormats != null && supportBinlogFormats.length > 0) { + BinlogFormat format = ((MysqlConnection) metaConnection).getBinlogFormat(); + boolean found = false; + for (BinlogFormat supportFormat : supportBinlogFormats) { + if (supportFormat != null && format == supportFormat) { + found = true; + } + } + + if (!found) { + throw new CanalParseException("Unsupported BinlogFormat " + format); + } + } + + if (supportBinlogImages != null && supportBinlogImages.length > 0) { + BinlogImage image = ((MysqlConnection) metaConnection).getBinlogImage(); + boolean found = false; + for (BinlogImage supportImage : supportBinlogImages) { + if (supportImage != null && image == supportImage) { + found = true; + } + } + + if (!found) { + throw new CanalParseException("Unsupported BinlogImage " + image); + } + } + tableMetaCache = new TableMetaCache(metaConnection); ((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache); } @@ -685,6 +717,32 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE return event; } + public void setSupportBinlogFormats(String formatStrs) { + String[] formats = StringUtils.split(formatStrs, ','); + if (formats != null) { + BinlogFormat[] supportBinlogFormats = new BinlogFormat[formats.length]; + int i = 0; + for (String format : formats) { + supportBinlogFormats[i++] = BinlogFormat.valuesOf(format); + } + + this.supportBinlogFormats = supportBinlogFormats; + } + } + + public void setSupportBinlogImages(String imageStrs) { + String[] images = StringUtils.split(imageStrs, ','); + if (images != null) { + BinlogImage[] supportBinlogImages = new BinlogImage[images.length]; + int i = 0; + for (String image : images) { + supportBinlogImages[i++] = BinlogImage.valuesOf(image); + } + + this.supportBinlogImages = supportBinlogImages; + } + } + // ===================== setter / getter ======================== public void setDefaultConnectionTimeoutInSeconds(int defaultConnectionTimeoutInSeconds) { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 14fd3498..c3465e8a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -422,6 +422,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar for (int i = 0; i < columnCnt; i++) { ColumnInfo info = columnInfo[i]; + // mysql 5.6开始支持nolob/mininal类型,并不一定记录所有的列,需要进行判断 + if (!cols.get(i)) { + continue; + } + Column.Builder columnBuilder = Column.newBuilder(); FieldMeta fieldMeta = null; @@ -604,25 +609,21 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar if (index < 0) { return false; } - if ((bfColumns.size() - 1) < index) { - return false; - } - Column column = bfColumns.get(index); - if (column.getIsNull()) { - if (newValue != null) { - return true; - } - } else { - if (newValue == null) { - return true; - } else { - if (!column.getValue().equals(newValue)) { - return true; + for (Column column : bfColumns) { + if (column.getIndex() == index) {// 比较before / after的column index + if (column.getIsNull() && newValue == null) { + // 如果全是null + return false; + } else if (newValue != null && column.getValue().equals(newValue)) { + // 如果不围null,并且相等 + return false; } } } - return false; + + // 比如nolob/minial模式下,可能找不到before记录,认为是有变化 + return true; } private TableMeta getTableMeta(String dbName, String tbName, boolean useCache) {