diff --git a/common/pom.xml b/common/pom.xml
index 4789ac9e..eb702d71 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -35,6 +35,10 @@
commons-lang
commons-lang
+
+ commons-codec
+ commons-codec
+
com.alibaba
fastjson
diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/monitor/SpringInstanceConfigMonitor.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/monitor/SpringInstanceConfigMonitor.java
index 38d6d24c..3483324d 100644
--- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/monitor/SpringInstanceConfigMonitor.java
+++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/monitor/SpringInstanceConfigMonitor.java
@@ -208,8 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
action.reload(destination);
logger.info("auto notify reload {} successful.", destination);
} catch (Throwable e) {
- logger.error(String.format("scan reload found[%s] but reload failed",
- destination), e);
+ logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
}
}
}
@@ -312,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
}
}
-
}
diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties
index 033a3f89..f765e71b 100644
--- a/deployer/src/main/resources/canal.properties
+++ b/deployer/src/main/resources/canal.properties
@@ -66,6 +66,7 @@ canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
canal.instance.global.mode = spring
canal.instance.global.lazy = false
#canal.instance.global.manager.address = 127.0.0.1:1099
+#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties
index bef5cac3..31b625de 100644
--- a/deployer/src/main/resources/example/instance.properties
+++ b/deployer/src/main/resources/example/instance.properties
@@ -1,10 +1,10 @@
#################################################
## mysql serverId
-canal.instance.mysql.slaveId=1234
+canal.instance.mysql.slaveId=0
# position info
canal.instance.master.address=127.0.0.1:3306
-canal.instance.master.journal.name=mysql-bin.000001
-canal.instance.master.position=104606
+canal.instance.master.journal.name=
+canal.instance.master.position=
canal.instance.master.timestamp=
@@ -27,7 +27,7 @@ canal.instance.dbPassword=canal
canal.instance.defaultDatabaseName=test
canal.instance.connectionCharset=UTF-8
# table regex
-canal.instance.filter.regex=test\\..*
+canal.instance.filter.regex=*\\..*
# table black regex
-canal.instance.filter.black.regex=canal_tsdb\\..*
+canal.instance.filter.black.regex=
#################################################
\ No newline at end of file
diff --git a/deployer/src/main/resources/example/rds_instance.properties b/deployer/src/main/resources/example/rds_instance.properties
new file mode 100644
index 00000000..9712cf93
--- /dev/null
+++ b/deployer/src/main/resources/example/rds_instance.properties
@@ -0,0 +1,31 @@
+#################################################
+canal.instance.rds.open.url=https://rds.aliyuncs.com/
+canal.instance.rds.open.accesskey=
+canal.instance.rds.open.secretkey=
+canal.instance.rds.instanceId=
+canal.instance.rds.startTime=
+canal.instance.rds.endTime=
+
+# local binlog dir
+canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
+# position info
+canal.instance.master.address=127.0.0.1:3306
+
+# table meta tsdb info
+canal.instance.tsdb.enable=true
+canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
+canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
+#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
+#canal.instance.tsdb.dbUsername=canal
+#canal.instance.tsdb.dbPassword=canal
+
+# username/password
+canal.instance.dbUsername=canal
+canal.instance.dbPassword=canal
+canal.instance.defaultDatabaseName=test
+canal.instance.connectionCharset=UTF-8
+# table regex
+canal.instance.filter.regex=test\\..*
+# table black regex
+canal.instance.filter.black.regex=
+#################################################
\ No newline at end of file
diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml
index 9c7ef5aa..8a26c34a 100644
--- a/deployer/src/main/resources/spring/default-instance.xml
+++ b/deployer/src/main/resources/spring/default-instance.xml
@@ -85,7 +85,7 @@
-
+
@@ -185,7 +185,7 @@
-
+
diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml
index 4c6f750d..3ee38c98 100644
--- a/deployer/src/main/resources/spring/file-instance.xml
+++ b/deployer/src/main/resources/spring/file-instance.xml
@@ -71,7 +71,7 @@
-
+
@@ -170,7 +170,7 @@
-
+
diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml
index f5e872f3..5ac91794 100644
--- a/deployer/src/main/resources/spring/group-instance.xml
+++ b/deployer/src/main/resources/spring/group-instance.xml
@@ -77,7 +77,7 @@
-
+
@@ -168,7 +168,7 @@
-
+
diff --git a/deployer/src/main/resources/spring/local-instance.xml b/deployer/src/main/resources/spring/local-instance.xml
index e2fb44b3..5ac62df1 100644
--- a/deployer/src/main/resources/spring/local-instance.xml
+++ b/deployer/src/main/resources/spring/local-instance.xml
@@ -48,41 +48,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
- ${canal.zkServers:127.0.0.1:2181}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -96,10 +69,10 @@
-
+
-
+
@@ -144,25 +117,25 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
-
+
\ 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 129ce2b0..7f1eff3f 100644
--- a/deployer/src/main/resources/spring/memory-instance.xml
+++ b/deployer/src/main/resources/spring/memory-instance.xml
@@ -68,7 +68,7 @@
-
+
diff --git a/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml b/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
index 5c3b869d..6f8ba05d 100644
--- a/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
+++ b/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
@@ -23,7 +23,7 @@
-
+
diff --git a/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml b/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
index 5f02431d..555eec44 100644
--- a/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
+++ b/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
@@ -23,7 +23,7 @@
-
+
diff --git a/parse/pom.xml b/parse/pom.xml
index 0dd84d6f..f495b52c 100644
--- a/parse/pom.xml
+++ b/parse/pom.xml
@@ -62,6 +62,14 @@
com.h2database
h2
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.commons
+ commons-compress
+
junit
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java
index 3c91350a..73b4f3d1 100644
--- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java
@@ -8,6 +8,7 @@ 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;
@@ -84,26 +85,19 @@ public class LocalBinLogConnection implements ErosaConnection {
while (running) {
boolean needContinue = true;
LogEvent event = null;
- L: while (fetcher.fetch()) {
- /*
- * event = decoder.decode(fetcher, context); if (event ==
- * null) { throw new CanalParseException("parse failed"); }
- * if (!func.sink(event)) { needContinue = false; break; }
- */
+ // 处理一下binlog文件名
+ event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
+ func.sink(event);
+ while (fetcher.fetch()) {
+ event = decoder.decode(fetcher, context);
+ if (event == null) {
+ throw new CanalParseException("parse failed");
+ }
- 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 (!func.sink(event)) {
+ needContinue = false;
+ break;
+ }
}
if (needContinue) {// 读取下一个
@@ -121,7 +115,6 @@ public class LocalBinLogConnection implements ErosaConnection {
}
current = nextFile;
-
fetcher.open(current);
context.setLogPosition(new LogPosition(nextFile.getName()));
} else {
@@ -161,30 +154,27 @@ public class LocalBinLogConnection implements ErosaConnection {
binlogFilename = lastXidLogFilename;
binlogFileOffset = lastXidLogFileOffset;
- L: while (fetcher.fetch()) {
- LogEvent event;
- do {
- event = decoder.decode(fetcher, context);
- if (event != null) {
- if (event.getWhen() > timestampSeconds) {
- break L;
- }
+ 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()) {
+ 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();
}
- } while (event != null);
+ }
}
if (needContinue) {// 读取下一个
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java
index d2c2fd2e..ea6b0365 100644
--- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java
@@ -9,6 +9,7 @@ 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.inbound.mysql.tsdb.DatabaseTableMeta;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
@@ -23,14 +24,14 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
// 数据库信息
- private AuthenticationInfo masterInfo;
- private EntryPosition masterPosition; // binlog信息
- private MysqlConnection metaConnection; // 查询meta信息的链接
- private TableMetaCache tableMetaCache; // 对应meta
+ protected AuthenticationInfo masterInfo;
+ protected EntryPosition masterPosition; // binlog信息
+ protected MysqlConnection metaConnection; // 查询meta信息的链接
+ protected TableMetaCache tableMetaCache; // 对应meta
- private String directory;
- private boolean needWait = false;
- private int bufferSize = 16 * 1024;
+ protected String directory;
+ protected boolean needWait = false;
+ protected int bufferSize = 16 * 1024;
public LocalBinlogEventParser(){
// this.runningInfo = new AuthenticationInfo();
@@ -50,6 +51,12 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
throw new CanalParseException(e);
}
+ if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
+ ((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
+ ((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
+ ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
+ }
+
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
}
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 94625b10..7b141010 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
@@ -156,6 +156,7 @@ public class MysqlConnection implements ErosaConnection {
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
binlogDumpCmd.binlogFileName = binlogfilename;
binlogDumpCmd.binlogPosition = binlogPosition;
+ // binlogDumpCmd.slaveServerId = this.slaveId;
binlogDumpCmd.slaveServerId = this.slaveId;
byte[] cmdBody = binlogDumpCmd.toBytes();
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 912787b4..efee2b71 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
@@ -1,7 +1,9 @@
package com.alibaba.otter.canal.parse.inbound.mysql;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -26,7 +28,7 @@ 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.inbound.mysql.tsdb.TableMetaManager;
+import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
@@ -118,10 +120,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
}
}
- if (tableMetaTSDB != null && tableMetaTSDB instanceof TableMetaManager) {
- ((TableMetaManager) tableMetaTSDB).setConnection(metaConnection);
- ((TableMetaManager) tableMetaTSDB).setFilter(eventFilter);
- ((TableMetaManager) tableMetaTSDB).setBlackFilter(eventBlackFilter);
+ if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
+ ((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
+ ((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
+ ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
}
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
@@ -313,10 +315,35 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
connection.getConnector().setSendBufferSize(sendBufferSize);
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
connection.setCharset(connectionCharset);
+ // 随机生成slaveId
+ if (this.slaveId <= 0) {
+ this.slaveId = generateUniqueServerId();
+ }
connection.setSlaveId(this.slaveId);
return connection;
}
+ private final long generateUniqueServerId() {
+ try {
+ // a=`echo $masterip|cut -d\. -f1`
+ // b=`echo $masterip|cut -d\. -f2`
+ // c=`echo $masterip|cut -d\. -f3`
+ // d=`echo $masterip|cut -d\. -f4`
+ // #server_id=`expr $a \* 256 \* 256 \* 256 + $b \* 256 \* 256 + $c
+ // \* 256 + $d `
+ // #server_id=$b$c$d
+ // server_id=`expr $b \* 256 \* 256 + $c \* 256 + $d `
+ InetAddress localHost = InetAddress.getLocalHost();
+ byte[] addr = localHost.getAddress();
+ int salt = (destination != null) ? destination.hashCode() : 0;
+ return ((0x7f & salt) << 24) + ((0xff & (int) addr[1]) << 16) // NL
+ + ((0xff & (int) addr[2]) << 8) // NL
+ + (0xff & (int) addr[3]);
+ } catch (UnknownHostException e) {
+ throw new CanalParseException("Unknown host", e);
+ }
+ }
+
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
EntryPosition startPosition = findStartPositionInternal(connection);
if (needTransactionPosition.get()) {
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 538f6b95..34474efd 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
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.sql.Types;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
@@ -294,7 +295,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
String queryString = null;
try {
queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name());
- return buildQueryEntry(queryString, event.getHeader());
+ String tableName = null;
+ if (useDruidDdlFilter) {
+ List results = DruidDdlParser.parse(queryString, null);
+ if (results.size() > 0) {
+ tableName = results.get(0).getTableName();
+ }
+ }
+
+ return buildQueryEntry(queryString, event.getHeader(), tableName);
} catch (UnsupportedEncodingException e) {
throw new CanalParseException(e);
}
@@ -355,6 +364,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
}
+ boolean isRDSHeartBeat = tableMetaCache.isOnRDS()
+ && isRDSHeartBeat(table.getDbName(), table.getTableName());
+
String fullname = table.getDbName() + "." + table.getTableName();
// check name filter
if (nameFilter != null && !nameFilter.filter(fullname)) {
@@ -364,9 +376,17 @@ 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;
+ // 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));
}
EventType eventType = null;
@@ -396,8 +416,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
BitSet columns = event.getColumns();
BitSet changeColumns = event.getChangeColumns();
boolean tableError = false;
- TableMeta tableMeta = null;
- if (tableMetaCache != null) {// 入错存在table meta cache
+ if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
+ // cache
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
if (tableMeta == null) {
tableError = true;
@@ -661,6 +681,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
}
+ private Entry buildQueryEntry(String queryString, LogHeader logHeader, String tableName) {
+ Header header = createHeader(binlogFileName, logHeader, "", tableName, EventType.QUERY);
+ RowChange.Builder rowChangeBuider = RowChange.newBuilder();
+ rowChangeBuider.setSql(queryString);
+ rowChangeBuider.setEventType(EventType.QUERY);
+ return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
+ }
+
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
@@ -739,6 +767,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType);
}
+ private boolean isRDSHeartBeat(String schema, String table) {
+ return "mysql".equalsIgnoreCase(schema) && "ha_health_check".equalsIgnoreCase(table);
+ }
+
public static TransactionBegin createTransactionBegin(long threadId) {
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
beginBuilder.setThreadId(threadId);
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java
index 15577298..b2f4fa01 100644
--- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java
@@ -140,21 +140,21 @@ public class DruidDdlParser {
} else if (statement instanceof SQLInsertStatement) {
DdlResult ddlResult = new DdlResult();
SQLInsertStatement insert = (SQLInsertStatement) statement;
- processName(ddlResult, schmeaName, insert.getTableName(), true);
+ processName(ddlResult, schmeaName, insert.getTableName(), false);
ddlResult.setType(EventType.INSERT);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLUpdateStatement) {
DdlResult ddlResult = new DdlResult();
SQLUpdateStatement update = (SQLUpdateStatement) statement;
// 拿到的表名可能为null,比如update a,b set a.id=x
- processName(ddlResult, schmeaName, update.getTableName(), true);
+ processName(ddlResult, schmeaName, update.getTableName(), false);
ddlResult.setType(EventType.UPDATE);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLDeleteStatement) {
DdlResult ddlResult = new DdlResult();
SQLDeleteStatement delete = (SQLDeleteStatement) statement;
// 拿到的表名可能为null,比如delete a,b from a where a.id = b.id
- processName(ddlResult, schmeaName, delete.getTableName(), true);
+ processName(ddlResult, schmeaName, delete.getTableName(), false);
ddlResult.setType(EventType.DELETE);
ddlResults.add(ddlResult);
}
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java
new file mode 100644
index 00000000..7b641ade
--- /dev/null
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java
@@ -0,0 +1,368 @@
+package com.alibaba.otter.canal.parse.inbound.mysql.rds;
+
+import static org.apache.http.client.config.RequestConfig.custom;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.ssl.TrustStrategy;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+public class HttpHelper {
+
+ protected static final Logger logger = LoggerFactory.getLogger(HttpHelper.class);
+
+ public static byte[] getBytes(String url, int timeout) throws Exception {
+ long start = System.currentTimeMillis();
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setMaxConnPerRoute(50);
+ builder.setMaxConnTotal(100);
+ CloseableHttpClient httpclient = builder.build();
+ URI uri = new URIBuilder(url).build();
+ RequestConfig config = custom().setConnectTimeout(timeout)
+ .setConnectionRequestTimeout(timeout)
+ .setSocketTimeout(timeout)
+ .build();
+ HttpGet httpGet = new HttpGet(uri);
+ HttpClientContext context = HttpClientContext.create();
+ context.setRequestConfig(config);
+ CloseableHttpResponse response = httpclient.execute(httpGet, context);
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ if (logger.isWarnEnabled()) {
+ logger.warn("post " + url + ", cost : " + cost);
+ }
+ if (statusCode == HttpStatus.SC_OK) {
+ return EntityUtils.toByteArray(response.getEntity());
+ } else {
+ String errorMsg = EntityUtils.toString(response.getEntity());
+ throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
+ + ", error msg=" + errorMsg);
+ }
+ } finally {
+ response.close();
+ httpGet.releaseConnection();
+ }
+ }
+
+ public static String get(String url, int timeout) {
+ // logger.info("get url is :" + url);
+ // 支持采用https协议,忽略证书
+ url = url.trim();
+ if (url.startsWith("https")) {
+ return getIgnoreCerf(url, null, null, timeout);
+ }
+ long start = System.currentTimeMillis();
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setMaxConnPerRoute(50);
+ builder.setMaxConnTotal(100);
+ CloseableHttpClient httpclient = builder.build();
+ CloseableHttpResponse response = null;
+ HttpGet httpGet = null;
+ try {
+ URI uri = new URIBuilder(url).build();
+ RequestConfig config = custom().setConnectTimeout(timeout)
+ .setConnectionRequestTimeout(timeout)
+ .setSocketTimeout(timeout)
+ .build();
+ httpGet = new HttpGet(uri);
+ HttpClientContext context = HttpClientContext.create();
+ context.setRequestConfig(config);
+ response = httpclient.execute(httpGet, context);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_OK) {
+ return EntityUtils.toString(response.getEntity());
+ } else {
+ String errorMsg = EntityUtils.toString(response.getEntity());
+ throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
+ + ", error msg=" + errorMsg);
+ }
+ } catch (Throwable t) {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ String curlRequest = getCurlRequest(url, null, null, cost);
+ throw new RuntimeException("requestGet remote error, request : " + curlRequest, t);
+ } finally {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ printCurlRequest(url, null, null, cost);
+ if (response != null) {
+ try {
+ response.close();
+ } catch (IOException e) {
+ }
+ }
+ httpGet.releaseConnection();
+ }
+ }
+
+ private static String getIgnoreCerf(String url, CookieStore cookieStore, Map params, int timeout) {
+ long start = System.currentTimeMillis();
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setMaxConnPerRoute(50);
+ builder.setMaxConnTotal(100);
+ HttpGet httpGet = null;
+ CloseableHttpResponse response = null;
+ try {
+ // 创建支持忽略证书的https
+ final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
+
+ @Override
+ public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ return true;
+ }
+ }).build();
+
+ CloseableHttpClient httpClient = HttpClientBuilder.create()
+ .setSSLContext(sslContext)
+ .setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder. create()
+ .register("http", PlainConnectionSocketFactory.INSTANCE)
+ .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
+ .build()))
+ .build();
+ // ---------------- 创建支持https 的client成功---------
+
+ URI uri = new URIBuilder(url).build();
+ RequestConfig config = custom().setConnectTimeout(timeout)
+ .setConnectionRequestTimeout(timeout)
+ .setSocketTimeout(timeout)
+ .build();
+ httpGet = new HttpGet(uri);
+ HttpClientContext context = HttpClientContext.create();
+ context.setRequestConfig(config);
+ response = httpClient.execute(httpGet, context);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_OK) {
+ return EntityUtils.toString(response.getEntity());
+ } else {
+ String errorMsg = EntityUtils.toString(response.getEntity());
+ throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
+ + ", error msg=" + errorMsg);
+ }
+ } catch (Throwable t) {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ String curlRequest = getCurlRequest(url, cookieStore, params, cost);
+ throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest, t);
+ } finally {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ printCurlRequest(url, null, null, cost);
+ if (response != null) {
+ try {
+ response.close();
+ } catch (IOException e) {
+ }
+ }
+ if (httpGet != null) {
+ httpGet.releaseConnection();
+ }
+ }
+ }
+
+ private static String postIgnoreCerf(String url, CookieStore cookieStore, Map params, int timeout) {
+ long start = System.currentTimeMillis();
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setMaxConnPerRoute(50);
+ builder.setMaxConnTotal(100);
+ HttpPost httpPost = null;
+ CloseableHttpResponse response = null;
+ try {
+ // 创建支持忽略证书的https
+ final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
+
+ @Override
+ public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ return true;
+ }
+ }).build();
+
+ CloseableHttpClient httpClient = HttpClientBuilder.create()
+ .setSSLContext(sslContext)
+ .setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder. create()
+ .register("http", PlainConnectionSocketFactory.INSTANCE)
+ .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
+ .build()))
+ .build();
+ // ---------------- 创建支持https 的client成功---------
+
+ URI uri = new URIBuilder(url).build();
+ RequestConfig config = custom().setConnectTimeout(timeout)
+ .setConnectionRequestTimeout(timeout)
+ .setSocketTimeout(timeout)
+ .build();
+ httpPost = new HttpPost(uri);
+ List parameters = Lists.newArrayList();
+ for (String key : params.keySet()) {
+ NameValuePair nameValuePair = new BasicNameValuePair(key, params.get(key));
+ parameters.add(nameValuePair);
+ }
+ httpPost.setEntity(new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8")));
+ HttpClientContext context = HttpClientContext.create();
+ context.setRequestConfig(config);
+ context.setCookieStore(cookieStore);
+
+ response = httpClient.execute(httpPost, context);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_OK) {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ printCurlRequest(url, cookieStore, params, cost);
+ return EntityUtils.toString(response.getEntity());
+ } else {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ String curlRequest = getCurlRequest(url, cookieStore, params, cost);
+ throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest
+ + ", statusCode=" + statusCode + "");
+ }
+ } catch (Throwable t) {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ String curlRequest = getCurlRequest(url, cookieStore, params, cost);
+ throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest, t);
+ } finally {
+ if (response != null) {
+ try {
+ response.close();
+ } catch (IOException e) {
+ }
+ }
+ if (httpPost != null) {
+ httpPost.releaseConnection();
+ }
+ }
+ }
+
+ public static String post(String url, CookieStore cookieStore, Map params, int timeout) {
+ url = url.trim();
+ // 支持采用https协议,忽略证书
+ if (url.startsWith("https")) {
+ return postIgnoreCerf(url, cookieStore, params, timeout);
+ }
+ long start = System.currentTimeMillis();
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setMaxConnPerRoute(50);
+ builder.setMaxConnTotal(100);
+ HttpPost httpPost = null;
+ CloseableHttpResponse response = null;
+ try {
+ CloseableHttpClient httpclient = builder.build();
+ URI uri = new URIBuilder(url).build();
+ RequestConfig config = custom().setConnectTimeout(timeout)
+ .setConnectionRequestTimeout(timeout)
+ .setSocketTimeout(timeout)
+ .build();
+ httpPost = new HttpPost(uri);
+ List parameters = Lists.newArrayList();
+ for (String key : params.keySet()) {
+ NameValuePair nameValuePair = new BasicNameValuePair(key, params.get(key));
+ parameters.add(nameValuePair);
+ }
+ httpPost.setEntity(new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8")));
+ HttpClientContext context = HttpClientContext.create();
+ context.setRequestConfig(config);
+ context.setCookieStore(cookieStore);
+
+ response = httpclient.execute(httpPost, context);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_OK) {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ printCurlRequest(url, cookieStore, params, cost);
+ return EntityUtils.toString(response.getEntity());
+ } else {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ String curlRequest = getCurlRequest(url, cookieStore, params, cost);
+ throw new RuntimeException("requestPost remote error, request : " + curlRequest + ", statusCode="
+ + statusCode + ";" + EntityUtils.toString(response.getEntity()));
+ }
+ } catch (Throwable t) {
+ long end = System.currentTimeMillis();
+ long cost = end - start;
+ String curlRequest = getCurlRequest(url, cookieStore, params, cost);
+ throw new RuntimeException("requestPost remote error, request : " + curlRequest, t);
+ } finally {
+ if (response != null) {
+ try {
+ response.close();
+ } catch (IOException e) {
+ }
+ }
+ if (httpPost != null) {
+ httpPost.releaseConnection();
+ }
+ }
+ }
+
+ public static void printCurlRequest(String url, CookieStore cookieStore, Map params, long cost) {
+ logger.warn(getCurlRequest(url, cookieStore, params, cost));
+ }
+
+ private static String getCurlRequest(String url, CookieStore cookieStore, Map params, long cost) {
+ if (params == null) {
+ return "curl '" + url + "'\ncost : " + cost;
+ } else {
+ StringBuilder paramsStr = new StringBuilder();
+ Iterator> iterator = params.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ paramsStr.append(entry.getKey() + "=" + entry.getValue());
+ if (iterator.hasNext()) {
+ paramsStr.append("&");
+ }
+ }
+ if (cookieStore == null) {
+ return "curl '" + url + "' -d '" + paramsStr.toString() + "'\ncost : " + cost;
+ } else {
+ StringBuilder cookieStr = new StringBuilder();
+ List cookies = cookieStore.getCookies();
+ Iterator iteratorCookie = cookies.iterator();
+ while (iteratorCookie.hasNext()) {
+ Cookie cookie = iteratorCookie.next();
+ cookieStr.append(cookie.getName() + "=" + cookie.getValue());
+ if (iteratorCookie.hasNext()) {
+ cookieStr.append(";");
+ }
+ }
+ return "curl '" + url + "' -b '" + cookieStr + "' -d '" + paramsStr.toString() + "'\ncost : " + cost;
+ }
+ }
+ }
+}
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java
new file mode 100644
index 00000000..eb638739
--- /dev/null
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java
@@ -0,0 +1,334 @@
+package com.alibaba.otter.canal.parse.inbound.mysql.rds;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * @author agapple 2017年10月14日 下午1:53:52
+ * @since 1.0.25
+ */
+public class RdsBinlogOpenApi {
+
+ protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class);
+ private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+ private static final int TIMEOUT = 10000;
+ private static final String ENCODING = "UTF-8";
+ private static final String MAC_NAME = "HmacSHA1";
+ private static final String API_VERSION = "2014-08-15";
+ private static final String SIGNATURE_VERSION = "1.0";
+
+ public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
+ Date endTime, File destDir) throws Throwable {
+ int pageSize = 100;
+ int pageNumber = 0;
+ int pageRecordCount = 1;
+ String hostInstanceID = null;
+ while (pageRecordCount > 0 && pageRecordCount <= pageSize) {
+ pageNumber += 1;
+ String result = describeBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime, pageSize, pageNumber);
+ JSONObject jsobObj = JSON.parseObject(result);
+ pageRecordCount = jsobObj.getInteger("PageRecordCount");
+
+ if (pageRecordCount > 0) {
+ FileUtils.forceMkdir(destDir);
+ File hostIdFile = new File(destDir, "hostId");
+ if (hostIdFile.exists()) {
+ List lines = IOUtils.readLines(new FileInputStream(hostIdFile));
+ hostInstanceID = StringUtils.join(lines, "\n");
+ }
+
+ String itemStr = jsobObj.getString("Items");
+ JSONObject binLogFileObj = JSONObject.parseObject(itemStr);
+ JSONArray items = binLogFileObj.getJSONArray("BinLogFile");
+ if (items == null || items.isEmpty()) {
+ continue;
+ }
+ for (int i = 0; i < items.size(); i++) {
+ JSONObject item = (JSONObject) items.get(i);
+ String oneHostInstanceID = item.getString("HostInstanceID");
+ if (hostInstanceID == null) {
+ hostInstanceID = oneHostInstanceID;
+ FileOutputStream hostIdFileOut = null;
+ try {
+ hostIdFileOut = new FileOutputStream(hostIdFile);
+ hostIdFileOut.write(oneHostInstanceID.getBytes());
+ hostIdFileOut.flush();
+ } finally {
+ IOUtils.closeQuietly(hostIdFileOut);
+ }
+ }
+
+ if (hostInstanceID.equals(oneHostInstanceID)) { // 只选择一个host下载
+ String downloadLink = item.getString("DownloadLink");
+ String fileName = StringUtils.substringBetween(downloadLink, "mysql-bin.", ".tar");
+ if (StringUtils.isNotEmpty(fileName)) {
+ File currentFile = new File(destDir, "mysql-bin." + fileName);
+ if (currentFile.isFile() && currentFile.exists()) {
+ // 检查一下文件是否存在,存在就就没必要下载了
+ continue;
+ }
+ }
+
+ HttpGet httpGet = new HttpGet(downloadLink);
+ CloseableHttpClient httpClient = HttpClientBuilder.create()
+ .setMaxConnPerRoute(50)
+ .setMaxConnTotal(100)
+ .build();
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(TIMEOUT)
+ .setConnectionRequestTimeout(TIMEOUT)
+ .setSocketTimeout(TIMEOUT)
+ .build();
+ httpGet.setConfig(requestConfig);
+ HttpResponse response = httpClient.execute(httpGet);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpResponseStatus.OK.code()) {
+ throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:"
+ + statusCode);
+ }
+ saveFile(destDir, response);
+ }
+ }
+ }
+ }
+ }
+
+ private static void saveFile(File parentFile, HttpResponse response) throws IOException {
+ InputStream is = response.getEntity().getContent();
+ long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue());
+ String fileName = response.getFirstHeader("Content-Disposition").getValue();
+ fileName = StringUtils.substringAfter(fileName, "filename=");
+ boolean isTar = StringUtils.endsWith(fileName, ".tar");
+ FileUtils.forceMkdir(parentFile);
+ FileOutputStream fos = null;
+ try {
+ if (isTar) {
+ TarArchiveInputStream tais = new TarArchiveInputStream(is);
+ TarArchiveEntry tarArchiveEntry = null;
+ while ((tarArchiveEntry = tais.getNextTarEntry()) != null) {
+ String name = tarArchiveEntry.getName();
+ File tarFile = new File(parentFile, name);
+ logger.info("start to download file " + tarFile.getName());
+ BufferedOutputStream bos = null;
+ try {
+ bos = new BufferedOutputStream(new FileOutputStream(tarFile));
+ int read = -1;
+ byte[] buffer = new byte[1024];
+ while ((read = tais.read(buffer)) != -1) {
+ bos.write(buffer, 0, read);
+ }
+ logger.info("download file " + tarFile.getName() + " end!");
+ } finally {
+ IOUtils.closeQuietly(bos);
+ }
+ }
+ tais.close();
+ } else {
+ File file = new File(parentFile, fileName);
+ if (!file.isFile()) {
+ file.createNewFile();
+ }
+ try {
+ fos = new FileOutputStream(file);
+ byte[] buffer = new byte[1024];
+ int len;
+ long copySize = 0;
+ long nextPrintProgress = 0;
+ logger.info("start to download file " + file.getName());
+ while ((len = is.read(buffer)) != -1) {
+ fos.write(buffer, 0, len);
+ copySize += len;
+ long progress = copySize * 100 / totalSize;
+ if (progress >= nextPrintProgress) {
+ logger.info("download " + file.getName() + " progress : " + progress
+ + "% , download size : " + copySize + ", total size : " + totalSize);
+ nextPrintProgress += 10;
+ }
+ }
+ logger.info("download file " + file.getName() + " end!");
+ fos.flush();
+ } finally {
+ IOUtils.closeQuietly(fos);
+ }
+ }
+ } finally {
+ IOUtils.closeQuietly(fos);
+ }
+ }
+
+ public static String describeBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
+ Date endTime, int pageSize, int pageNumber) throws Exception {
+ Map paramMap = new HashMap();
+ paramMap.put("Action", "DescribeBinlogFiles");
+ paramMap.put("DBInstanceId", dbInstanceId); // rds实例id
+ paramMap.put("StartTime", formatIso8601Date(startTime));
+ paramMap.put("EndTime", formatIso8601Date(endTime));
+ paramMap.put("PageSize", String.valueOf(pageSize));
+ paramMap.put("PageNumber", String.valueOf(pageNumber));
+ return doRequest(url, paramMap, ak, sk);
+ }
+
+ private static String doRequest(String domin, Map param, String ak, String sk) throws Exception {
+ param.put("AccessKeyId", ak);
+ param.put("SignatureMethod", "HMAC-SHA1");
+ param.put("SignatureVersion", SIGNATURE_VERSION);
+ param.put("Version", API_VERSION);
+ param.put("SignatureNonce", UUID.randomUUID().toString());
+ param.put("Format", "JSON");
+ param.put("Timestamp", formatIso8601Date(new Date()));
+ String signStr = generate("POST", param, sk);
+ param.put("Signature", signStr);
+ String request = concatQueryString(param);
+ String url = domin + "?" + request;
+ String result = HttpHelper.post(url, null, Collections.EMPTY_MAP, TIMEOUT);
+ return result;
+ }
+
+ public static String concatQueryString(Map parameters) throws UnsupportedEncodingException {
+ if (null == parameters) {
+ return null;
+ }
+ StringBuilder urlBuilder = new StringBuilder("");
+ for (Map.Entry entry : parameters.entrySet()) {
+ String key = entry.getKey();
+ String val = entry.getValue();
+ urlBuilder.append(encode(key));
+ if (val != null) {
+ urlBuilder.append("=").append(encode(val));
+ }
+ urlBuilder.append("&");
+ }
+ int strIndex = urlBuilder.length();
+ if (parameters.size() > 0) {
+ urlBuilder.deleteCharAt(strIndex - 1);
+ }
+ return urlBuilder.toString();
+ }
+
+ public static String encode(String value) throws UnsupportedEncodingException {
+ return URLEncoder.encode(value, "UTF-8");
+ }
+
+ private static String formatIso8601Date(Date date) {
+ SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT);
+ df.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return df.format(date);
+ }
+
+ /**
+ * 使用 HMAC-SHA1 签名方法对对encryptText进行签名
+ *
+ * @param encryptText 被签名的字符串
+ * @param encryptKey 密钥
+ * @return
+ * @throws Exception
+ */
+ public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
+ byte[] data = encryptKey.getBytes(ENCODING);
+ // 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
+ SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
+ // 生成一个指定 Mac 算法 的 Mac 对象
+ Mac mac = Mac.getInstance(MAC_NAME);
+ // 用给定密钥初始化 Mac 对象
+ mac.init(secretKey);
+
+ byte[] text = encryptText.getBytes(ENCODING);
+ // 完成 Mac 操作
+ return mac.doFinal(text);
+ }
+
+ private static String base64(byte input[]) throws UnsupportedEncodingException {
+ return new String(Base64.encodeBase64(input), ENCODING);
+ }
+
+ /** 对参数名称和参数值进行URL编码 **/
+ public static String generate(String method, Map parameter, String accessKeySecret)
+ throws Exception {
+ String signString = generateSignString(method, parameter);
+ byte[] signBytes = HmacSHA1Encrypt(signString, accessKeySecret + "&");
+ String signature = base64(signBytes);
+ if ("POST".equals(method)) {
+ return signature;
+ }
+ return URLEncoder.encode(signature, "UTF-8");
+ }
+
+ private static String generateQueryString(TreeMap treeMap) {
+ StringBuilder canonicalizedQueryString = new StringBuilder();
+ boolean first = true;
+ for (String key : treeMap.navigableKeySet()) {
+ String value = treeMap.get(key);
+ if (!first) {
+ canonicalizedQueryString.append("&");
+ }
+ first = false;
+ canonicalizedQueryString.append(percentEncode(key)).append("=").append(percentEncode(value));
+ }
+ return canonicalizedQueryString.toString();
+ }
+
+ public static String generateSignString(String httpMethod, Map parameter) throws IOException {
+ TreeMap sortParameter = new TreeMap();
+ sortParameter.putAll(parameter);
+ String canonicalizedQueryString = generateQueryString(sortParameter);
+ if (null == httpMethod) {
+ throw new RuntimeException("httpMethod can not be empty");
+ }
+ /** 构造待签名的字符串* */
+ StringBuilder stringToSign = new StringBuilder();
+ stringToSign.append(httpMethod).append("&");
+ stringToSign.append(percentEncode("/")).append("&");
+ stringToSign.append(percentEncode(canonicalizedQueryString));
+ return stringToSign.toString();
+ }
+
+ public static String percentEncode(String value) {
+ try {
+ return value == null ? null : URLEncoder.encode(value, ENCODING)
+ .replaceAll("\\+", "%20")
+ .replaceAll("\\*", "%2A")
+ .replaceAll("%7E", "~");
+ } catch (Exception e) {
+ }
+ return "";
+ }
+}
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java
new file mode 100644
index 00000000..68be38e5
--- /dev/null
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java
@@ -0,0 +1,111 @@
+package com.alibaba.otter.canal.parse.inbound.mysql.rds;
+
+import java.io.File;
+import java.util.Date;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.util.Assert;
+
+import com.alibaba.otter.canal.parse.CanalEventParser;
+import com.alibaba.otter.canal.parse.exception.CanalParseException;
+import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser;
+import com.alibaba.otter.canal.protocol.position.EntryPosition;
+
+/**
+ * 基于rds binlog备份文件的复制
+ *
+ * @author agapple 2017年10月15日 下午1:27:36
+ * @since 1.0.25
+ */
+public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser {
+
+ private String url = "https://rds.aliyuncs.com/"; // openapi地址
+ private String accesskey; // 云账号的ak
+ private String secretkey; // 云账号sk
+ private String instanceId; // rds实例id
+ private Long startTime;
+ private Long endTime;
+
+ public RdsLocalBinlogEventParser(){
+ }
+
+ public void start() throws CanalParseException {
+ try {
+ Assert.notNull(startTime);
+ Assert.notNull(accesskey);
+ Assert.notNull(secretkey);
+ Assert.notNull(instanceId);
+ Assert.notNull(url);
+ if (endTime == null) {
+ endTime = System.currentTimeMillis();
+ }
+
+ RdsBinlogOpenApi.downloadBinlogFiles(url,
+ accesskey,
+ secretkey,
+ instanceId,
+ new Date(startTime),
+ new Date(endTime),
+ new File(directory));
+
+ // 更新一下时间戳
+ masterPosition = new EntryPosition(startTime);
+ } catch (Throwable e) {
+ logger.error("download binlog failed", e);
+ throw new CanalParseException(e);
+ }
+
+ super.start();
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ if (StringUtils.isNotEmpty(url)) {
+ this.url = url;
+ }
+ }
+
+ public String getAccesskey() {
+ return accesskey;
+ }
+
+ public void setAccesskey(String accesskey) {
+ this.accesskey = accesskey;
+ }
+
+ public String getSecretkey() {
+ return secretkey;
+ }
+
+ public void setSecretkey(String secretkey) {
+ this.secretkey = secretkey;
+ }
+
+ public String getInstanceId() {
+ return instanceId;
+ }
+
+ public void setInstanceId(String instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ public Long getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(Long startTime) {
+ this.startTime = startTime;
+ }
+
+ public Long getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(Long endTime) {
+ this.endTime = endTime;
+ }
+
+}
diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManager.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
similarity index 98%
rename from parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManager.java
rename to parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
index 4e6a392d..3c43e94a 100644
--- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManager.java
+++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
@@ -37,14 +37,14 @@ import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
/**
- * 基于console远程管理 see internal class: CanalTableMeta , ConsoleTableMetaTSDB
+ * 基于db远程管理 see internal class: CanalTableMeta , ConsoleTableMetaTSDB
*
* @author agapple 2017年7月27日 下午10:47:55
* @since 3.2.5
*/
-public class TableMetaManager implements TableMetaTSDB {
+public class DatabaseTableMeta implements TableMetaTSDB {
- private static Logger logger = LoggerFactory.getLogger(TableMetaManager.class);
+ private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class);
private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'");
private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation");
private static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L);
@@ -58,7 +58,7 @@ public class TableMetaManager implements TableMetaTSDB {
private MetaHistoryDAO metaHistoryDAO;
private MetaSnapshotDAO metaSnapshotDAO;
- public TableMetaManager(){
+ public DatabaseTableMeta(){
}
diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java
new file mode 100644
index 00000000..596847ae
--- /dev/null
+++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java
@@ -0,0 +1,27 @@
+package com.alibaba.otter.canal.parse.inbound.mysql;
+
+import java.io.File;
+import java.util.Date;
+
+import org.apache.commons.lang.time.DateUtils;
+
+import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi;
+
+/**
+ * @author agapple 2017年10月15日 下午2:14:34
+ * @since 1.0.25
+ */
+public class RdsBinlogOpenApiTest {
+
+ public void testSimple() throws Throwable {
+ Date startTime = DateUtils.parseDate("2017-10-13 20:56:58", new String[] { "yyyy-MM-dd HH:mm:ss" });
+ Date endTime = DateUtils.parseDate("2017-10-14 02:57:59", new String[] { "yyyy-MM-dd HH:mm:ss" });
+ RdsBinlogOpenApi.downloadBinlogFiles("https://rds.aliyuncs.com/",
+ "",
+ "",
+ "rm-bp180v4mfjnm157es",
+ startTime,
+ endTime,
+ new File("/tmp/binlog/"));
+ }
+}
diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsLocalBinlogDumpTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsLocalBinlogDumpTest.java
new file mode 100644
index 00000000..82fe8803
--- /dev/null
+++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsLocalBinlogDumpTest.java
@@ -0,0 +1,117 @@
+package com.alibaba.otter.canal.parse.inbound.mysql;
+
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.alibaba.otter.canal.parse.exception.CanalParseException;
+import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser;
+import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager;
+import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
+import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
+import com.alibaba.otter.canal.protocol.CanalEntry.Column;
+import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
+import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
+import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
+import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
+import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
+import com.alibaba.otter.canal.protocol.position.LogPosition;
+import com.alibaba.otter.canal.sink.exception.CanalSinkException;
+
+/**
+ * @author agapple 2017年10月15日 下午2:16:58
+ * @since 1.0.25
+ */
+public class RdsLocalBinlogDumpTest {
+
+ @Test
+ public void testSimple() {
+ String directory = "/tmp/rds";
+ final RdsLocalBinlogEventParser controller = new RdsLocalBinlogEventParser();
+ controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"));
+ controller.setConnectionCharset(Charset.forName("UTF-8"));
+ controller.setDirectory(directory);
+ controller.setAccesskey("");
+ controller.setSecretkey("");
+ controller.setInstanceId("");
+ controller.setStartTime(1507860498350L);
+ controller.setEventSink(new AbstractCanalEventSinkTest>() {
+
+ public boolean sink(List entrys, InetSocketAddress remoteAddress, String destination)
+ throws CanalSinkException,
+ InterruptedException {
+
+ for (Entry entry : entrys) {
+ if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN
+ || entry.getEntryType() == EntryType.TRANSACTIONEND) {
+ continue;
+ }
+
+ if (entry.getEntryType() == EntryType.ROWDATA) {
+ RowChange rowChage = null;
+ try {
+ rowChage = RowChange.parseFrom(entry.getStoreValue());
+ } catch (Exception e) {
+ throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:"
+ + entry.toString(), e);
+ }
+
+ EventType eventType = rowChage.getEventType();
+ System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s",
+ entry.getHeader().getLogfileName(),
+ entry.getHeader().getLogfileOffset(),
+ entry.getHeader().getSchemaName(),
+ entry.getHeader().getTableName(),
+ eventType));
+
+ for (RowData rowData : rowChage.getRowDatasList()) {
+ if (eventType == EventType.DELETE) {
+ print(rowData.getBeforeColumnsList());
+ } else if (eventType == EventType.INSERT) {
+ print(rowData.getAfterColumnsList());
+ } else {
+ System.out.println("-------> before");
+ print(rowData.getBeforeColumnsList());
+ System.out.println("-------> after");
+ print(rowData.getAfterColumnsList());
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ });
+ controller.setLogPositionManager(new AbstractLogPositionManager() {
+
+ @Override
+ public LogPosition getLatestIndexBy(String destination) {
+ return null;
+ }
+
+ @Override
+ public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
+ System.out.println(logPosition);
+ }
+ });
+
+ controller.start();
+
+ try {
+ Thread.sleep(100 * 1000 * 1000L);
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
+ controller.stop();
+ }
+
+ private void print(List columns) {
+ for (Column column : columns) {
+ System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
+ }
+ }
+}
diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManagerTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManagerTest.java
index af8c9dc5..cd5bfe6e 100644
--- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManagerTest.java
+++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaManagerTest.java
@@ -26,7 +26,7 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition;
public class TableMetaManagerTest {
@Resource
- TableMetaManager tableMetaManager;
+ DatabaseTableMeta tableMetaManager;
@Test
public void testSimple() throws FileNotFoundException, IOException {
diff --git a/parse/src/test/resources/tsdb/derby-tsdb.xml b/parse/src/test/resources/tsdb/derby-tsdb.xml
index a6c19486..4ce4dcbf 100644
--- a/parse/src/test/resources/tsdb/derby-tsdb.xml
+++ b/parse/src/test/resources/tsdb/derby-tsdb.xml
@@ -7,7 +7,7 @@
default-autowire="byName">
-
+
diff --git a/parse/src/test/resources/tsdb/h2-tsdb.xml b/parse/src/test/resources/tsdb/h2-tsdb.xml
index 452e7ee5..c0a3d385 100644
--- a/parse/src/test/resources/tsdb/h2-tsdb.xml
+++ b/parse/src/test/resources/tsdb/h2-tsdb.xml
@@ -7,7 +7,7 @@
default-autowire="byName">
-
+
diff --git a/parse/src/test/resources/tsdb/mysql-tsdb.xml b/parse/src/test/resources/tsdb/mysql-tsdb.xml
index e3743109..099c034d 100644
--- a/parse/src/test/resources/tsdb/mysql-tsdb.xml
+++ b/parse/src/test/resources/tsdb/mysql-tsdb.xml
@@ -7,7 +7,7 @@
default-autowire="byName">
-
+
diff --git a/pom.xml b/pom.xml
index 649abb36..f0e4b5a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -173,6 +173,21 @@
commons-io
2.4
+
+ commons-codec
+ commons-codec
+ 1.9
+
+
+ org.apache.commons
+ commons-compress
+ 1.9
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.1
+
org.apache.zookeeper
zookeeper