Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a3a1d955 | ||
|
|
a857b87388 | ||
|
|
b57ac0bcdf | ||
|
|
ea6391d1c3 | ||
|
|
89a32aaabd | ||
|
|
a2eb128698 | ||
|
|
760f2dda80 | ||
|
|
bf9dc0073d | ||
|
|
92f24bd02e | ||
|
|
d4797a8fb9 | ||
|
|
060bd8e31d | ||
|
|
88de747ed5 | ||
|
|
725d36aa98 | ||
|
|
d24e10fb3e | ||
|
|
fdae646e26 | ||
|
|
40c3e65341 | ||
|
|
b5a1898cec | ||
|
|
680faea2bb | ||
|
|
1cd77bf551 | ||
|
|
9786faba9a | ||
|
|
325fe89aed | ||
|
|
ffeec8fc86 | ||
|
|
7662c9241e | ||
|
|
d01195ac82 | ||
|
|
8b186d1330 | ||
|
|
b7431eb7d0 | ||
|
|
c7fc149e7e | ||
|
|
368554166c | ||
|
|
c4ee350717 | ||
|
|
ae0f43d1c7 |
+6
-6
@@ -18,12 +18,12 @@
|
||||
</dependency>
|
||||
|
||||
<!-- 这里指定runtime的metrics provider-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.alibaba.otter</groupId>-->
|
||||
<!--<artifactId>canal.prometheus</artifactId>-->
|
||||
<!--<version>${project.version}</version>-->
|
||||
<!--<scope>runtime</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal.prometheus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Additional line arg for current prometheus solution
|
||||
case "`uname`" in
|
||||
Linux)
|
||||
bin_abs_path=$(readlink -f $(dirname $0))
|
||||
;;
|
||||
*)
|
||||
bin_abs_path=`cd $(dirname $0); pwd`
|
||||
;;
|
||||
esac
|
||||
base=${bin_abs_path}/..
|
||||
if [ $(ls $base/lib/aspectjweaver*.jar | wc -l) -eq 1 ]; then
|
||||
WEAVER=$(ls $base/lib/aspectjweaver*.jar)
|
||||
METRICS_OPTS=" -javaagent:"${WEAVER}" "
|
||||
fi
|
||||
@@ -94,12 +94,7 @@ then
|
||||
echo LOG CONFIGURATION : $logback_configurationFile
|
||||
echo canal conf : $canal_conf
|
||||
echo CLASSPATH :$CLASSPATH
|
||||
# metrics support options
|
||||
# if [ -x $base/bin/metrics_env.sh ]; then
|
||||
# . $base/bin/metrics_env.sh
|
||||
# echo METRICS_OPTS $METRICS_OPTS
|
||||
# fi
|
||||
$JAVA $JAVA_OPTS $METRICS_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 &
|
||||
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 &
|
||||
echo $! > $base/bin/canal.pid
|
||||
|
||||
echo "cd to $current_path for continue"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.alibaba.otter.canal.example;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.SystemUtils;
|
||||
@@ -254,7 +254,18 @@ public class AbstractCanalClientTest {
|
||||
protected void printColumn(List<Column> columns) {
|
||||
for (Column column : columns) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(column.getName() + " : " + column.getValue());
|
||||
try {
|
||||
if (StringUtils.containsIgnoreCase(column.getMysqlType(), "BLOB")
|
||||
|| StringUtils.containsIgnoreCase(column.getMysqlType(), "BINARY")) {
|
||||
// get value bytes
|
||||
builder.append(column.getName() + " : "
|
||||
+ new String(column.getValue().getBytes("ISO-8859-1"), "UTF-8"));
|
||||
} else {
|
||||
builder.append(column.getName() + " : " + column.getValue());
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
|
||||
builder.append(" type=" + column.getMysqlType());
|
||||
if (column.getUpdated()) {
|
||||
builder.append(" update=" + column.getUpdated());
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.exception;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/7/20 下午2:54
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class PositionNotFoundException extends CanalParseException {
|
||||
|
||||
private static final long serialVersionUID = -7382448928116244017L;
|
||||
|
||||
public PositionNotFoundException(String errorCode) {
|
||||
super(errorCode);
|
||||
}
|
||||
|
||||
public PositionNotFoundException(String errorCode, Throwable cause) {
|
||||
super(errorCode, cause);
|
||||
}
|
||||
|
||||
public PositionNotFoundException(String errorCode, String errorDesc) {
|
||||
super(errorCode, errorDesc);
|
||||
}
|
||||
|
||||
public PositionNotFoundException(String errorCode, String errorDesc, Throwable cause) {
|
||||
super(errorCode, errorDesc, cause);
|
||||
}
|
||||
|
||||
public PositionNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.exception;
|
||||
|
||||
import com.alibaba.otter.canal.common.CanalException;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/8 下午1:07
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class ServerIdNotMatchException extends CanalException{
|
||||
|
||||
public ServerIdNotMatchException(String errorCode) {
|
||||
super(errorCode);
|
||||
}
|
||||
|
||||
public ServerIdNotMatchException(String errorCode, Throwable cause) {
|
||||
super(errorCode, cause);
|
||||
}
|
||||
|
||||
public ServerIdNotMatchException(String errorCode, String errorDesc) {
|
||||
super(errorCode, errorDesc);
|
||||
}
|
||||
|
||||
public ServerIdNotMatchException(String errorCode, String errorDesc, Throwable cause) {
|
||||
super(errorCode, errorDesc, cause);
|
||||
}
|
||||
|
||||
public ServerIdNotMatchException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import java.util.TimerTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.alibaba.otter.canal.parse.exception.PositionNotFoundException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.apache.commons.lang.math.RandomUtils;
|
||||
@@ -95,9 +94,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
.availableProcessors() * 60 / 100; // 60%的能力跑解析,剩余部分处理网络
|
||||
protected int parallelBufferSize = 256; // 必须为2的幂
|
||||
protected MultiStageCoprocessor multiStageCoprocessor;
|
||||
protected ParserExceptionHandler parserExceptionHandler;
|
||||
protected long serverId;
|
||||
|
||||
|
||||
|
||||
protected abstract BinlogParser buildParser();
|
||||
@@ -175,16 +171,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
preDump(erosaConnection);
|
||||
|
||||
erosaConnection.connect();// 链接
|
||||
|
||||
long queryServerId = erosaConnection.queryServerId();
|
||||
if (queryServerId != 0){
|
||||
serverId = queryServerId;
|
||||
}
|
||||
// 4. 获取最后的位置信息
|
||||
EntryPosition position = findStartPosition(erosaConnection);
|
||||
final EntryPosition startPosition = position;
|
||||
if (startPosition == null) {
|
||||
throw new PositionNotFoundException("can't find start position for " + destination);
|
||||
throw new CanalParseException("can't find start position for " + destination);
|
||||
}
|
||||
|
||||
if (!processTableMeta(startPosition)) {
|
||||
@@ -287,9 +278,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
runningInfo.getAddress().toString()), e);
|
||||
sendAlarm(destination, ExceptionUtils.getFullStackTrace(e));
|
||||
}
|
||||
if (parserExceptionHandler!=null){
|
||||
parserExceptionHandler.handle(e);
|
||||
}
|
||||
} finally {
|
||||
// 重新置为中断状态
|
||||
Thread.interrupted();
|
||||
@@ -628,19 +616,5 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
this.parallelBufferSize = parallelBufferSize;
|
||||
}
|
||||
|
||||
public ParserExceptionHandler getParserExceptionHandler() {
|
||||
return parserExceptionHandler;
|
||||
}
|
||||
|
||||
public void setParserExceptionHandler(ParserExceptionHandler parserExceptionHandler) {
|
||||
this.parserExceptionHandler = parserExceptionHandler;
|
||||
}
|
||||
|
||||
public long getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public void setServerId(long serverId) {
|
||||
this.serverId = serverId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,4 @@ public interface ErosaConnection {
|
||||
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException;
|
||||
|
||||
ErosaConnection fork();
|
||||
|
||||
public long queryServerId() throws IOException;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,11 @@ public class EventTransactionBuffer extends AbstractCanalLifeCycle {
|
||||
flush();
|
||||
}
|
||||
break;
|
||||
case HEARTBEAT:
|
||||
// master过来的heartbeat,说明binlog已经读完了,是idle状态
|
||||
put(entry);
|
||||
flush();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/7/20 下午3:55
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public interface ParserExceptionHandler {
|
||||
void handle(Throwable e);
|
||||
}
|
||||
+25
-17
@@ -1,6 +1,7 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,23 +22,24 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected static final long BINLOG_START_OFFEST = 4L;
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected static final long BINLOG_START_OFFEST = 4L;
|
||||
|
||||
protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory();
|
||||
protected boolean enableTsdb = false;
|
||||
protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory();
|
||||
protected boolean enableTsdb = false;
|
||||
protected String tsdbSpringXml;
|
||||
protected TableMetaTSDB tableMetaTSDB;
|
||||
|
||||
// 编码信息
|
||||
protected byte connectionCharsetNumber = (byte) 33;
|
||||
protected Charset connectionCharset = Charset.forName("UTF-8");
|
||||
protected boolean filterQueryDcl = false;
|
||||
protected boolean filterQueryDml = false;
|
||||
protected boolean filterQueryDdl = false;
|
||||
protected boolean filterRows = false;
|
||||
protected boolean filterTableError = false;
|
||||
protected boolean useDruidDdlFilter = true;
|
||||
protected byte connectionCharsetNumber = (byte) 33;
|
||||
protected Charset connectionCharset = Charset.forName("UTF-8");
|
||||
protected boolean filterQueryDcl = false;
|
||||
protected boolean filterQueryDml = false;
|
||||
protected boolean filterQueryDdl = false;
|
||||
protected boolean filterRows = false;
|
||||
protected boolean filterTableError = false;
|
||||
protected boolean useDruidDdlFilter = true;
|
||||
private final AtomicLong eventsPublishBlockingTime = new AtomicLong(0L);
|
||||
|
||||
protected BinlogParser buildParser() {
|
||||
LogEventConvert convert = new LogEventConvert();
|
||||
@@ -131,11 +133,13 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
}
|
||||
|
||||
protected MultiStageCoprocessor buildMultiStageCoprocessor() {
|
||||
return new MysqlMultiStageCoprocessor(parallelBufferSize,
|
||||
parallelThreadSize,
|
||||
(LogEventConvert) binlogParser,
|
||||
transactionBuffer,
|
||||
destination);
|
||||
MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize,
|
||||
parallelThreadSize,
|
||||
(LogEventConvert) binlogParser,
|
||||
transactionBuffer,
|
||||
destination);
|
||||
mysqlMultiStageCoprocessor.setEventsPublishBlockingTime(eventsPublishBlockingTime);
|
||||
return mysqlMultiStageCoprocessor;
|
||||
}
|
||||
|
||||
// ============================ setter / getter =========================
|
||||
@@ -200,6 +204,10 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
}
|
||||
}
|
||||
|
||||
public AtomicLong getEventsPublishBlockingTime() {
|
||||
return this.eventsPublishBlockingTime;
|
||||
}
|
||||
|
||||
public void setTableMetaTSDBFactory(TableMetaTSDBFactory tableMetaTSDBFactory) {
|
||||
this.tableMetaTSDBFactory = tableMetaTSDBFactory;
|
||||
}
|
||||
|
||||
+2
-48
@@ -4,7 +4,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -37,9 +36,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
private String directory;
|
||||
private int bufferSize = 16 * 1024;
|
||||
private boolean running = false;
|
||||
private long serverId;
|
||||
private FileParserListener parserListener;
|
||||
|
||||
|
||||
public LocalBinLogConnection(){
|
||||
}
|
||||
@@ -100,9 +96,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
if (event == null) {
|
||||
continue;
|
||||
}
|
||||
if (serverId != 0 && event.getServerId() != serverId){
|
||||
throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !");
|
||||
}
|
||||
|
||||
if (!func.sink(event)) {
|
||||
needContinue = false;
|
||||
@@ -110,9 +103,8 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
}
|
||||
}
|
||||
|
||||
fetcher.close(); // 关闭上一个文件
|
||||
parserFinish(current.getName());
|
||||
if (needContinue) {// 读取下一个
|
||||
fetcher.close(); // 关闭上一个文件
|
||||
|
||||
File nextFile;
|
||||
if (needWait) {
|
||||
@@ -168,11 +160,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
while (fetcher.fetch()) {
|
||||
LogEvent event = decoder.decode(fetcher, context);
|
||||
if (event != null) {
|
||||
|
||||
if (serverId != 0 && event.getServerId() != serverId){
|
||||
throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !");
|
||||
}
|
||||
|
||||
if (event.getWhen() > timestampSeconds) {
|
||||
break;
|
||||
}
|
||||
@@ -241,9 +228,8 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
}
|
||||
}
|
||||
|
||||
fetcher.close(); // 关闭上一个文件
|
||||
parserFinish(binlogfilename);
|
||||
if (needContinue) {// 读取下一个
|
||||
fetcher.close(); // 关闭上一个文件
|
||||
|
||||
File nextFile;
|
||||
if (needWait) {
|
||||
@@ -272,12 +258,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
}
|
||||
}
|
||||
|
||||
private void parserFinish(String fileName){
|
||||
if (parserListener != null){
|
||||
parserListener.onFinish(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(long timestampMills, MultiStageCoprocessor coprocessor) throws IOException {
|
||||
List<File> currentBinlogs = binlogs.currentBinlogs();
|
||||
@@ -306,11 +286,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
while (fetcher.fetch()) {
|
||||
LogEvent event = decoder.decode(fetcher, context);
|
||||
if (event != null) {
|
||||
|
||||
if (serverId != 0 && event.getServerId() != serverId){
|
||||
throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !");
|
||||
}
|
||||
|
||||
if (event.getWhen() > timestampSeconds) {
|
||||
break;
|
||||
}
|
||||
@@ -369,11 +344,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long queryServerId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isNeedWait() {
|
||||
return needWait;
|
||||
}
|
||||
@@ -398,20 +368,4 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public long getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public void setServerId(long serverId) {
|
||||
this.serverId = serverId;
|
||||
}
|
||||
|
||||
public void setParserListener(FileParserListener parserListener) {
|
||||
this.parserListener = parserListener;
|
||||
}
|
||||
|
||||
public interface FileParserListener{
|
||||
void onFinish(String fileName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-22
@@ -7,12 +7,11 @@ import java.net.InetSocketAddress;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor;
|
||||
@@ -39,18 +38,20 @@ 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;
|
||||
private BinlogImage binlogImage;
|
||||
private MysqlConnector connector;
|
||||
private long slaveId;
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
private BinlogFormat binlogFormat;
|
||||
private BinlogImage binlogImage;
|
||||
|
||||
// tsdb releated
|
||||
private AuthenticationInfo authInfo;
|
||||
protected int connTimeout = 5 * 1000; // 5秒
|
||||
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
||||
private AuthenticationInfo authInfo;
|
||||
protected int connTimeout = 5 * 1000; // 5秒
|
||||
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
||||
// dump binlog bytes, 暂不包括meta与TSDB
|
||||
private AtomicLong receivedBinlogBytes;
|
||||
|
||||
public MysqlConnection(){
|
||||
}
|
||||
@@ -126,6 +127,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
decoder.handle(LogEvent.XID_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
while (fetcher.fetch()) {
|
||||
accumulateReceivedBytes(fetcher.limit());
|
||||
LogEvent event = null;
|
||||
event = decoder.decode(fetcher, context);
|
||||
|
||||
@@ -148,6 +150,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
while (fetcher.fetch()) {
|
||||
accumulateReceivedBytes(fetcher.limit());
|
||||
LogEvent event = null;
|
||||
event = decoder.decode(fetcher, context);
|
||||
|
||||
@@ -176,6 +179,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
while (fetcher.fetch()) {
|
||||
accumulateReceivedBytes(fetcher.limit());
|
||||
LogEvent event = null;
|
||||
event = decoder.decode(fetcher, context);
|
||||
|
||||
@@ -206,6 +210,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
try {
|
||||
fetcher.start(connector.getChannel());
|
||||
while (fetcher.fetch()) {
|
||||
accumulateReceivedBytes(fetcher.limit());
|
||||
LogBuffer buffer = fetcher.duplicate();
|
||||
fetcher.consume(fetcher.limit());
|
||||
if (!coprocessor.publish(buffer)) {
|
||||
@@ -232,6 +237,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
try {
|
||||
fetcher.start(connector.getChannel());
|
||||
while (fetcher.fetch()) {
|
||||
accumulateReceivedBytes(fetcher.limit());
|
||||
LogBuffer buffer = fetcher.duplicate();
|
||||
fetcher.consume(fetcher.limit());
|
||||
if (!coprocessor.publish(buffer)) {
|
||||
@@ -326,16 +332,6 @@ public class MysqlConnection implements ErosaConnection {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long queryServerId() throws IOException {
|
||||
ResultSetPacket resultSetPacket = query("show variables like 'server_id'");
|
||||
List<String> fieldValues = resultSetPacket.getFieldValues();
|
||||
if (fieldValues == null || fieldValues.size() != 2){
|
||||
return 0;
|
||||
}
|
||||
return NumberUtils.toLong(fieldValues.get(1));
|
||||
}
|
||||
|
||||
// ====================== help method ====================
|
||||
|
||||
/**
|
||||
@@ -346,7 +342,6 @@ public class MysqlConnection implements ErosaConnection {
|
||||
* <li>net_read_timeout</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param channel
|
||||
* @throws IOException
|
||||
*/
|
||||
private void updateSettings() throws IOException {
|
||||
@@ -465,6 +460,14 @@ public class MysqlConnection implements ErosaConnection {
|
||||
}
|
||||
}
|
||||
|
||||
private void accumulateReceivedBytes(long x) {
|
||||
if (receivedBinlogBytes != null) {
|
||||
receivedBinlogBytes.addAndGet(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static enum BinlogFormat {
|
||||
|
||||
STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED");
|
||||
@@ -604,4 +607,8 @@ public class MysqlConnection implements ErosaConnection {
|
||||
this.authInfo = authInfo;
|
||||
}
|
||||
|
||||
public void setReceivedBinlogBytes(AtomicLong receivedBinlogBytes) {
|
||||
this.receivedBinlogBytes = receivedBinlogBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-7
@@ -53,11 +53,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
private int receiveBufferSize = 64 * 1024;
|
||||
private int sendBufferSize = 64 * 1024;
|
||||
// 数据库信息
|
||||
protected AuthenticationInfo masterInfo; // 主库
|
||||
protected AuthenticationInfo standbyInfo; // 备库
|
||||
private AuthenticationInfo masterInfo; // 主库
|
||||
private AuthenticationInfo standbyInfo; // 备库
|
||||
// binlog信息
|
||||
protected EntryPosition masterPosition;
|
||||
protected EntryPosition standbyPosition;
|
||||
private EntryPosition masterPosition;
|
||||
private EntryPosition standbyPosition;
|
||||
private long slaveId; // 链接到mysql的slave
|
||||
// 心跳检查信息
|
||||
private String detectingSQL; // 心跳sql
|
||||
@@ -68,8 +68,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
|
||||
|
||||
// update by yishun.chen,特殊异常处理参数
|
||||
private int dumpErrorCount = 0; // binlogDump失败异常计数
|
||||
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
|
||||
private int dumpErrorCount = 0; // binlogDump失败异常计数
|
||||
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
|
||||
|
||||
// instance received binlog bytes
|
||||
private final AtomicLong receivedBinlogBytes = new AtomicLong(0L);
|
||||
|
||||
protected ErosaConnection buildErosaConnection() {
|
||||
return buildMysqlConnection(this.runningInfo);
|
||||
@@ -313,6 +316,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
connection.getConnector().setSendBufferSize(sendBufferSize);
|
||||
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
||||
connection.setCharset(connectionCharset);
|
||||
connection.setReceivedBinlogBytes(receivedBinlogBytes);
|
||||
// 随机生成slaveId
|
||||
if (this.slaveId <= 0) {
|
||||
this.slaveId = generateUniqueServerId();
|
||||
@@ -511,7 +515,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
||||
throws IOException {
|
||||
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
||||
final AtomicLong preTransactionStartPosition = new AtomicLong(0L);
|
||||
final java.util.concurrent.atomic.AtomicLong preTransactionStartPosition = new java.util.concurrent.atomic.AtomicLong(0L);
|
||||
mysqlConnection.reconnect();
|
||||
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
||||
|
||||
@@ -910,4 +914,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
this.dumpErrorCountThreshold = dumpErrorCountThreshold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AtomicLong getReceivedBinlogBytes() {
|
||||
return this.receivedBinlogBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+37
-9
@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@@ -52,6 +53,7 @@ import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
||||
*/
|
||||
public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor {
|
||||
|
||||
private static final int maxFullTimes = 10;
|
||||
private LogEventConvert logEventConvert;
|
||||
private EventTransactionBuffer transactionBuffer;
|
||||
private ErosaConnection connection;
|
||||
@@ -63,6 +65,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
||||
private ExecutorService stageExecutor;
|
||||
private String destination;
|
||||
private volatile CanalParseException exception;
|
||||
private AtomicLong eventsPublishBlockingTime;
|
||||
|
||||
public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert,
|
||||
EventTransactionBuffer transactionBuffer, String destination){
|
||||
@@ -161,6 +164,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
||||
throw exception;
|
||||
}
|
||||
boolean interupted = false;
|
||||
long blockingStart = 0L;
|
||||
int fullTimes = 0;
|
||||
do {
|
||||
try {
|
||||
long next = disruptorMsgBuffer.tryNext();
|
||||
@@ -170,16 +175,39 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
||||
event.setBinlogFileName(binlogFileName);
|
||||
}
|
||||
disruptorMsgBuffer.publish(next);
|
||||
if (fullTimes > 0) {
|
||||
eventsPublishBlockingTime.addAndGet(System.nanoTime() - blockingStart);
|
||||
}
|
||||
break;
|
||||
} catch (InsufficientCapacityException e) {
|
||||
if (fullTimes == 0) {
|
||||
blockingStart = System.nanoTime();
|
||||
}
|
||||
// park
|
||||
LockSupport.parkNanos(1L);
|
||||
//LockSupport.parkNanos(1L);
|
||||
applyWait(++fullTimes);
|
||||
interupted = Thread.interrupted();
|
||||
if (fullTimes % 1000 == 0) {
|
||||
long nextStart = System.nanoTime();
|
||||
eventsPublishBlockingTime.addAndGet(nextStart - blockingStart);
|
||||
blockingStart = nextStart;
|
||||
}
|
||||
}
|
||||
} while (!interupted && isStart());
|
||||
return isStart();
|
||||
}
|
||||
|
||||
// 处理无数据的情况,避免空循环挂死
|
||||
private void applyWait(int fullTimes) {
|
||||
int newFullTimes = fullTimes > maxFullTimes ? maxFullTimes : fullTimes;
|
||||
if (fullTimes <= 3) { // 3次以内
|
||||
Thread.yield();
|
||||
} else { // 超过3次,最多只sleep 1ms
|
||||
LockSupport.parkNanos(100 * 1000L * newFullTimes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (isStart()) {
|
||||
@@ -189,6 +217,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
private class SimpleParserStage implements EventHandler<MessageEvent>, LifecycleAware {
|
||||
|
||||
private LogDecoder decoder;
|
||||
@@ -203,15 +232,10 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
||||
try {
|
||||
LogBuffer buffer = event.getBuffer();
|
||||
if (StringUtils.isNotEmpty(event.getBinlogFileName())
|
||||
&& (context.getLogPosition() == null
|
||||
|| !context.getLogPosition().getFileName().equals(event.getBinlogFileName()))) {
|
||||
&& !context.getLogPosition().getFileName().equals(event.getBinlogFileName())) {
|
||||
// set roate binlog file name
|
||||
if (context.getLogPosition() == null){
|
||||
context.setLogPosition(new LogPosition(event.getBinlogFileName(), 0));
|
||||
}else{
|
||||
context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition()
|
||||
.getPosition()));
|
||||
}
|
||||
context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition()
|
||||
.getPosition()));
|
||||
}
|
||||
|
||||
LogEvent logEvent = decoder.decode(buffer, context);
|
||||
@@ -432,4 +456,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public void setEventsPublishBlockingTime(AtomicLong eventsPublishBlockingTime) {
|
||||
this.eventsPublishBlockingTime = eventsPublishBlockingTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-25
@@ -10,9 +10,7 @@ import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException;
|
||||
import com.taobao.tddl.dbsync.binlog.event.*;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -43,22 +41,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Type;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RandLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo;
|
||||
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
||||
|
||||
/**
|
||||
@@ -90,10 +73,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
private volatile AviaterRegexFilter nameFilter; // 运行时引用可能会有变化,比如规则发生变化时
|
||||
private volatile AviaterRegexFilter nameBlackFilter;
|
||||
|
||||
|
||||
private TableMetaCacheInterface tableMetaCache;
|
||||
private String binlogFileName = "mysql-bin.000001";
|
||||
|
||||
private TableMetaCache tableMetaCache;
|
||||
private Charset charset = Charset.defaultCharset();
|
||||
private boolean filterQueryDcl = false;
|
||||
private boolean filterQueryDml = false;
|
||||
@@ -150,6 +130,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
return parseRandLogEvent((RandLogEvent) logEvent);
|
||||
case LogEvent.GTID_LOG_EVENT:
|
||||
return parseGTIDLogEvent((GtidLogEvent) logEvent);
|
||||
case LogEvent.HEARTBEAT_LOG_EVENT:
|
||||
return parseHeartbeatLogEvent((HeartbeatLogEvent) logEvent);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -164,6 +146,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
}
|
||||
}
|
||||
|
||||
private Entry parseHeartbeatLogEvent(HeartbeatLogEvent logEvent) {
|
||||
Header.Builder headerBuilder = Header.newBuilder();
|
||||
headerBuilder.setEventType(EventType.MHEARTBEAT);
|
||||
Entry.Builder entryBuilder = Entry.newBuilder();
|
||||
entryBuilder.setHeader(headerBuilder.build());
|
||||
entryBuilder.setEntryType(EntryType.HEARTBEAT);
|
||||
return entryBuilder.build();
|
||||
}
|
||||
|
||||
private Entry parseGTIDLogEvent(GtidLogEvent logEvent) {
|
||||
LogHeader logHeader = logEvent.getHeader();
|
||||
String value = logEvent.getSid().toString() + ":" + logEvent.getGno();
|
||||
@@ -268,8 +259,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
if (!isSeek) {
|
||||
// 使用新的表结构元数据管理方式
|
||||
EntryPosition position = createPosition(event.getHeader());
|
||||
String fulltbName = schemaName+"."+tableName;
|
||||
tableMetaCache.apply(position, fulltbName, queryString, null);
|
||||
tableMetaCache.apply(position, event.getDbName(), queryString, null);
|
||||
}
|
||||
|
||||
Header header = createHeader(event.getHeader(), schemaName, tableName, type);
|
||||
@@ -766,6 +756,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
} else {
|
||||
// byte数组,直接使用iso-8859-1保留对应编码,浪费内存
|
||||
columnBuilder.setValue(new String((byte[]) value, ISO_8859_1));
|
||||
// columnBuilder.setValueBytes(ByteString.copyFrom((byte[])
|
||||
// value));
|
||||
javaType = Types.BLOB;
|
||||
}
|
||||
break;
|
||||
@@ -944,7 +936,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
this.nameBlackFilter = nameBlackFilter;
|
||||
}
|
||||
|
||||
public void setTableMetaCache(TableMetaCacheInterface tableMetaCache) {
|
||||
public void setTableMetaCache(TableMetaCache tableMetaCache) {
|
||||
this.tableMetaCache = tableMetaCache;
|
||||
}
|
||||
|
||||
|
||||
+1
-6
@@ -6,7 +6,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||
@@ -30,7 +29,7 @@ import com.google.common.cache.LoadingCache;
|
||||
* @author jianghang 2013-1-17 下午10:15:16
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class TableMetaCache implements TableMetaCacheInterface {
|
||||
public class TableMetaCache {
|
||||
|
||||
public static final String COLUMN_NAME = "COLUMN_NAME";
|
||||
public static final String COLUMN_TYPE = "COLUMN_TYPE";
|
||||
@@ -100,10 +99,6 @@ public class TableMetaCache implements TableMetaCacheInterface {
|
||||
String createDDL = packet.getFieldValues().get(1);
|
||||
MemoryTableMeta memoryTableMeta = new MemoryTableMeta();
|
||||
memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null);
|
||||
String[] strings = table.split("\\.");
|
||||
if (strings.length > 1) {
|
||||
table = strings[1];
|
||||
}
|
||||
TableMeta tableMeta = memoryTableMeta.find(schema, table);
|
||||
return tableMeta.getFields();
|
||||
} else {
|
||||
|
||||
-260
@@ -1,260 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.otter.canal.parse.inbound.mysql.rds.data.BinlogFile;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午3:10
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class BinlogDownloadQueue {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BinlogDownloadQueue.class);
|
||||
private static final int TIMEOUT = 10000;
|
||||
|
||||
private LinkedBlockingQueue<BinlogFile> downloadQueue = new LinkedBlockingQueue<BinlogFile>();
|
||||
private LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
|
||||
private LinkedList<BinlogFile> binlogList;
|
||||
private final int batchSize;
|
||||
private Thread downloadThread;
|
||||
public boolean running = true;
|
||||
private final String destDir;
|
||||
private String hostId;
|
||||
private int currentSize;
|
||||
private String lastDownload;
|
||||
|
||||
public BinlogDownloadQueue(List<BinlogFile> downloadQueue, int batchSize, String destDir) throws IOException {
|
||||
this.binlogList = new LinkedList(downloadQueue);
|
||||
this.batchSize = batchSize;
|
||||
this.destDir = destDir;
|
||||
this.currentSize = 0;
|
||||
prepareBinlogList();
|
||||
cleanDir();
|
||||
}
|
||||
|
||||
private void prepareBinlogList(){
|
||||
for (BinlogFile binlog : this.binlogList) {
|
||||
String fileName = StringUtils.substringBetween(binlog.getDownloadLink(), "mysql-bin.", "?");
|
||||
binlog.setFileName(fileName);
|
||||
}
|
||||
Collections.sort(this.binlogList, new Comparator<BinlogFile>() {
|
||||
@Override
|
||||
public int compare(BinlogFile o1, BinlogFile o2) {
|
||||
return o1.getFileName().compareTo(o2.getFileName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void cleanDir() throws IOException {
|
||||
File destDirFile = new File(destDir);
|
||||
FileUtils.forceMkdir(destDirFile);
|
||||
FileUtils.cleanDirectory(destDirFile);
|
||||
}
|
||||
|
||||
public void silenceDownload() {
|
||||
if (downloadThread != null) {
|
||||
return;
|
||||
}
|
||||
downloadThread = new Thread(new DownloadThread());
|
||||
downloadThread.start();
|
||||
}
|
||||
|
||||
|
||||
public BinlogFile tryOne() throws IOException {
|
||||
BinlogFile binlogFile = binlogList.poll();
|
||||
download(binlogFile);
|
||||
hostId = binlogFile.getHostInstanceID();
|
||||
this.currentSize ++;
|
||||
return binlogFile;
|
||||
}
|
||||
|
||||
public void notifyNotMatch(){
|
||||
this.currentSize --;
|
||||
filter(hostId);
|
||||
}
|
||||
|
||||
private void filter(String hostInstanceId){
|
||||
Iterator<BinlogFile> it = binlogList.iterator();
|
||||
while (it.hasNext()){
|
||||
BinlogFile bf = it.next();
|
||||
if(bf.getHostInstanceID().equalsIgnoreCase(hostInstanceId)){
|
||||
it.remove();
|
||||
}else{
|
||||
hostId = bf.getHostInstanceID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLastFile(String fileName){
|
||||
String needCompareName = lastDownload;
|
||||
if (StringUtils.isNotEmpty(needCompareName) && StringUtils.endsWith(needCompareName, "tar")){
|
||||
needCompareName = needCompareName.substring(0, needCompareName.indexOf("."));
|
||||
}
|
||||
return fileName.equalsIgnoreCase(needCompareName) && binlogList.isEmpty();
|
||||
}
|
||||
|
||||
public void prepare() throws InterruptedException {
|
||||
for (int i = this.currentSize; i < batchSize && !binlogList.isEmpty(); i++) {
|
||||
BinlogFile binlogFile = null;
|
||||
while (!binlogList.isEmpty()){
|
||||
binlogFile = binlogList.poll();
|
||||
if (!binlogFile.getHostInstanceID().equalsIgnoreCase(hostId)){
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (binlogFile == null){
|
||||
break;
|
||||
}
|
||||
this.downloadQueue.put(binlogFile);
|
||||
this.lastDownload = "mysql-bin." + binlogFile.getFileName();
|
||||
this.currentSize ++;
|
||||
}
|
||||
}
|
||||
|
||||
public void downOne(){
|
||||
this.currentSize --;
|
||||
}
|
||||
|
||||
public void release(){
|
||||
running = false;
|
||||
this.currentSize = 0;
|
||||
binlogList.clear();
|
||||
downloadQueue.clear();
|
||||
}
|
||||
|
||||
private void download(BinlogFile binlogFile) throws IOException {
|
||||
String downloadLink = binlogFile.getDownloadLink();
|
||||
String fileName = binlogFile.getFileName();
|
||||
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(new File(destDir), "mysql-bin." + fileName, response);
|
||||
}
|
||||
|
||||
private static void saveFile(File parentFile, String fileName, HttpResponse response) throws IOException {
|
||||
InputStream is = response.getEntity().getContent();
|
||||
long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue());
|
||||
if(response.getFirstHeader("Content-Disposition")!=null){
|
||||
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 + ".tmp");
|
||||
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!");
|
||||
tarFile.renameTo(new File(parentFile, name));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(bos);
|
||||
}
|
||||
}
|
||||
tais.close();
|
||||
} else {
|
||||
File file = new File(parentFile, fileName + ".tmp");
|
||||
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);
|
||||
}
|
||||
file.renameTo(new File(parentFile, fileName));
|
||||
}
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fos);
|
||||
}
|
||||
}
|
||||
|
||||
public void execute(Runnable runnable) throws InterruptedException {
|
||||
taskQueue.put(runnable);
|
||||
}
|
||||
|
||||
private class DownloadThread implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
BinlogFile binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS);
|
||||
if (binlogFile != null){
|
||||
download(binlogFile);
|
||||
}
|
||||
Runnable runnable = taskQueue.poll(5000, TimeUnit.MILLISECONDS);
|
||||
if (runnable != null){
|
||||
runnable.run();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.exception.PositionNotFoundException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ParserExceptionHandler;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/7/20 上午10:52
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsBinlogEventParserProxy extends MysqlEventParser {
|
||||
|
||||
private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址
|
||||
private String accesskey; // 云账号的ak
|
||||
private String secretkey; // 云账号sk
|
||||
private String instanceId; // rds实例id
|
||||
private Long startTime;
|
||||
private Long endTime;
|
||||
private String directory; //binlog 目录
|
||||
private int batchSize = 4; //最多下载的binlog文件数量
|
||||
|
||||
private RdsLocalBinlogEventParser rdsBinlogEventParser = new RdsLocalBinlogEventParser();
|
||||
private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r, "rds-binlog-daemon-thread");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
final ParserExceptionHandler targetHandler = this.getParserExceptionHandler();
|
||||
rdsBinlogEventParser.setLogPositionManager(this.getLogPositionManager());
|
||||
rdsBinlogEventParser.setDestination(destination);
|
||||
rdsBinlogEventParser.setAlarmHandler(this.getAlarmHandler());
|
||||
rdsBinlogEventParser.setConnectionCharset(this.connectionCharset);
|
||||
rdsBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber);
|
||||
rdsBinlogEventParser.setEnableTsdb(this.enableTsdb);
|
||||
rdsBinlogEventParser.setEventBlackFilter(this.eventBlackFilter);
|
||||
rdsBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl);
|
||||
rdsBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl);
|
||||
rdsBinlogEventParser.setFilterQueryDml(this.filterQueryDml);
|
||||
rdsBinlogEventParser.setFilterRows(this.filterRows);
|
||||
rdsBinlogEventParser.setFilterTableError(this.filterTableError);
|
||||
rdsBinlogEventParser.setIsGTIDMode(this.isGTIDMode);
|
||||
rdsBinlogEventParser.setMasterInfo(this.masterInfo);
|
||||
rdsBinlogEventParser.setEventFilter(this.eventFilter);
|
||||
rdsBinlogEventParser.setMasterPosition(this.masterPosition);
|
||||
rdsBinlogEventParser.setTransactionSize(this.transactionSize);
|
||||
rdsBinlogEventParser.setUrl(this.rdsOpenApiUrl);
|
||||
rdsBinlogEventParser.setAccesskey(this.accesskey);
|
||||
rdsBinlogEventParser.setSecretkey(this.secretkey);
|
||||
rdsBinlogEventParser.setInstanceId(this.instanceId);
|
||||
rdsBinlogEventParser.setEventSink(eventSink);
|
||||
rdsBinlogEventParser.setDirectory(directory);
|
||||
rdsBinlogEventParser.setBatchSize(batchSize);
|
||||
rdsBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() {
|
||||
@Override
|
||||
public void onFinish() {
|
||||
executorService.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
rdsBinlogEventParser.stop();
|
||||
RdsBinlogEventParserProxy.this.start();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
this.setParserExceptionHandler(new ParserExceptionHandler() {
|
||||
|
||||
@Override
|
||||
public void handle(Throwable e) {
|
||||
handleMysqlParserException(e);
|
||||
if (targetHandler != null) {
|
||||
targetHandler.handle(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
super.start();
|
||||
}
|
||||
|
||||
public void handleMysqlParserException(Throwable throwable) {
|
||||
if (throwable instanceof PositionNotFoundException) {
|
||||
logger.info("remove rds not found position, try download rds binlog!");
|
||||
executorService.execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
logger.info("stop mysql parser!");
|
||||
RdsBinlogEventParserProxy rdsBinlogEventParserProxy = RdsBinlogEventParserProxy.this;
|
||||
long serverId = rdsBinlogEventParserProxy.getServerId();
|
||||
rdsBinlogEventParser.setServerId(serverId);
|
||||
rdsBinlogEventParserProxy.stop();
|
||||
logger.info("start rds mysql binlog parser!");
|
||||
rdsBinlogEventParser.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStart() {
|
||||
return super.isStart();
|
||||
}
|
||||
|
||||
public void setRdsOpenApiUrl(String rdsOpenApiUrl) {
|
||||
this.rdsOpenApiUrl = rdsOpenApiUrl;
|
||||
}
|
||||
|
||||
|
||||
public void setAccesskey(String accesskey) {
|
||||
this.accesskey = accesskey;
|
||||
}
|
||||
|
||||
|
||||
public void setSecretkey(String secretkey) {
|
||||
this.secretkey = secretkey;
|
||||
}
|
||||
|
||||
|
||||
public void setInstanceId(String instanceId) {
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
public void setDirectory(String directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
}
|
||||
+8
-53
@@ -1,9 +1,5 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
@@ -13,11 +9,16 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
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;
|
||||
@@ -55,52 +56,6 @@ public class RdsBinlogOpenApi {
|
||||
private static final String API_VERSION = "2014-08-15";
|
||||
private static final String SIGNATURE_VERSION = "1.0";
|
||||
|
||||
|
||||
public static List<BinlogFile> listBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
|
||||
Date endTime) {
|
||||
DescribeBinlogFilesRequest request = new DescribeBinlogFilesRequest();
|
||||
if (StringUtils.isNotEmpty(url)){
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
request.setEndPoint(uri.getHost());
|
||||
} catch (URISyntaxException e) {
|
||||
logger.error("resolve url host failed, will use default rds endpoint!");
|
||||
}
|
||||
}
|
||||
request.setStartDate(startTime);
|
||||
request.setEndDate(endTime);
|
||||
request.setPageNumber(1);
|
||||
request.setPageSize(100);
|
||||
request.setRdsInstanceId(dbInstanceId);
|
||||
request.setAccessKeyId(ak);
|
||||
request.setAccessKeySecret(sk);
|
||||
DescribeBinlogFileResult result = null;
|
||||
int retryTime = 3;
|
||||
while (true){
|
||||
try{
|
||||
result = request.doAction();
|
||||
break;
|
||||
}catch (Exception e){
|
||||
if(retryTime-- <= 0){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
} catch (InterruptedException e1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null){
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
RdsItem rdsItem = result.getItems();
|
||||
if (rdsItem != null){
|
||||
return rdsItem.getBinLogFile();
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
|
||||
public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
|
||||
Date endTime, File destDir) throws Throwable {
|
||||
int pageSize = 100;
|
||||
|
||||
+29
-130
@@ -2,23 +2,14 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
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.exception.PositionNotFoundException;
|
||||
import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.ParserExceptionHandler;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinLogConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||
|
||||
/**
|
||||
* 基于rds binlog备份文件的复制
|
||||
@@ -26,102 +17,47 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||
* @author agapple 2017年10月15日 下午1:27:36
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser, LocalBinLogConnection.FileParserListener {
|
||||
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;
|
||||
private BinlogDownloadQueue binlogDownloadQueue;
|
||||
private ParseFinishListener finishListener;
|
||||
private int batchSize;
|
||||
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);
|
||||
Assert.notNull(directory);
|
||||
|
||||
if (endTime == null) {
|
||||
endTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
EntryPosition entryPosition = findStartPosition(null);
|
||||
if (entryPosition == null) {
|
||||
throw new PositionNotFoundException("position not found!");
|
||||
}
|
||||
long startTimeInMill = entryPosition.getTimestamp();
|
||||
startTime = startTimeInMill;
|
||||
List<BinlogFile> binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, accesskey,
|
||||
RdsBinlogOpenApi.downloadBinlogFiles(url,
|
||||
accesskey,
|
||||
secretkey,
|
||||
instanceId,
|
||||
new Date(startTime),
|
||||
new Date(endTime));
|
||||
binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchSize, directory);
|
||||
binlogDownloadQueue.silenceDownload();
|
||||
needWait = true;
|
||||
parallel = false;
|
||||
// try to download one file,use to test server id
|
||||
binlogDownloadQueue.tryOne();
|
||||
new Date(endTime),
|
||||
new File(directory));
|
||||
|
||||
// 更新一下时间戳
|
||||
masterPosition = new EntryPosition(startTime);
|
||||
} catch (Throwable e) {
|
||||
logger.error("download binlog failed", e);
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
setParserExceptionHandler(new ParserExceptionHandler() {
|
||||
|
||||
@Override
|
||||
public void handle(Throwable e) {
|
||||
handleMysqlParserException(e);
|
||||
}
|
||||
});
|
||||
super.start();
|
||||
}
|
||||
|
||||
private void handleMysqlParserException(Throwable throwable) {
|
||||
if (throwable instanceof ServerIdNotMatchException) {
|
||||
logger.error("server id not match, try download another rds binlog!");
|
||||
binlogDownloadQueue.notifyNotMatch();
|
||||
try {
|
||||
binlogDownloadQueue.cleanDir();
|
||||
binlogDownloadQueue.prepare();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try {
|
||||
binlogDownloadQueue.execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
RdsLocalBinlogEventParser.super.stop();
|
||||
RdsLocalBinlogEventParser.super.start();
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ErosaConnection buildErosaConnection() {
|
||||
ErosaConnection connection = super.buildErosaConnection();
|
||||
if (connection instanceof LocalBinLogConnection) {
|
||||
LocalBinLogConnection localBinLogConnection = (LocalBinLogConnection) connection;
|
||||
localBinLogConnection.setNeedWait(true);
|
||||
localBinLogConnection.setServerId(serverId);
|
||||
localBinLogConnection.setParserListener(this);
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
@@ -132,81 +68,44 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(String fileName) {
|
||||
try {
|
||||
binlogDownloadQueue.downOne();
|
||||
File needDeleteFile = new File(directory + File.separator + fileName);
|
||||
if (needDeleteFile.exists()){
|
||||
needDeleteFile.delete();
|
||||
}
|
||||
// 处理下logManager位点问题
|
||||
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
||||
EntryPosition position = logPosition.getPostion();
|
||||
if (position != null){
|
||||
LogPosition newLogPosition = new LogPosition();
|
||||
String journalName = position.getJournalName();
|
||||
int sepIdx = journalName.indexOf(".");
|
||||
String fileIndex = journalName.substring(sepIdx+1);
|
||||
int index = NumberUtils.toInt(fileIndex) + 1;
|
||||
String newJournalName = journalName.substring(0, sepIdx) + "." + StringUtils.leftPad(String.valueOf(index), fileIndex.length(), "0");
|
||||
newLogPosition.setPostion(new EntryPosition(newJournalName, 4L, position.getTimestamp(), position.getServerId()));
|
||||
newLogPosition.setIdentity(logPosition.getIdentity());
|
||||
logPositionManager.persistLogPosition(destination, newLogPosition);
|
||||
}
|
||||
|
||||
if (binlogDownloadQueue.isLastFile(fileName)) {
|
||||
logger.info("all file parse complete, switch to mysql parser!");
|
||||
finishListener.onFinish();
|
||||
return;
|
||||
}
|
||||
binlogDownloadQueue.prepare();
|
||||
} catch (Exception e) {
|
||||
logger.error("prepare download binlog file failed!", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.binlogDownloadQueue.release();
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public void setFinishListener(ParseFinishListener finishListener) {
|
||||
this.finishListener = finishListener;
|
||||
}
|
||||
|
||||
public interface ParseFinishListener{
|
||||
void onFinish();
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
}
|
||||
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.data;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午2:26
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class BinlogFile {
|
||||
|
||||
private Long FileSize;
|
||||
private String LogBeginTime;
|
||||
private String LogEndTime;
|
||||
private String DownloadLink;
|
||||
private String HostInstanceID;
|
||||
private String LinkExpiredTime;
|
||||
private String fileName;
|
||||
|
||||
public Long getFileSize() {
|
||||
return FileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(Long fileSize) {
|
||||
FileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getLogBeginTime() {
|
||||
return LogBeginTime;
|
||||
}
|
||||
|
||||
public void setLogBeginTime(String logBeginTime) {
|
||||
LogBeginTime = logBeginTime;
|
||||
}
|
||||
|
||||
public String getLogEndTime() {
|
||||
return LogEndTime;
|
||||
}
|
||||
|
||||
public void setLogEndTime(String logEndTime) {
|
||||
LogEndTime = logEndTime;
|
||||
}
|
||||
|
||||
public String getDownloadLink() {
|
||||
return DownloadLink;
|
||||
}
|
||||
|
||||
public void setDownloadLink(String downloadLink) {
|
||||
DownloadLink = downloadLink;
|
||||
}
|
||||
|
||||
public String getHostInstanceID() {
|
||||
return HostInstanceID;
|
||||
}
|
||||
|
||||
public void setHostInstanceID(String hostInstanceID) {
|
||||
HostInstanceID = hostInstanceID;
|
||||
}
|
||||
|
||||
public String getLinkExpiredTime() {
|
||||
return LinkExpiredTime;
|
||||
}
|
||||
|
||||
public void setLinkExpiredTime(String linkExpiredTime) {
|
||||
LinkExpiredTime = linkExpiredTime;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.data;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午2:26
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class DescribeBinlogFileResult {
|
||||
private RdsItem Items;
|
||||
private long PageNumber;
|
||||
private long TotalRecordCount;
|
||||
private long TotalFileSize;
|
||||
private String RequestId;
|
||||
private long PageRecordCount;
|
||||
|
||||
public RdsItem getItems() {
|
||||
return Items;
|
||||
}
|
||||
|
||||
public void setItems(RdsItem items) {
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public long getPageNumber() {
|
||||
return PageNumber;
|
||||
}
|
||||
|
||||
public void setPageNumber(long pageNumber) {
|
||||
PageNumber = pageNumber;
|
||||
}
|
||||
|
||||
public long getTotalRecordCount() {
|
||||
return TotalRecordCount;
|
||||
}
|
||||
|
||||
public void setTotalRecordCount(long totalRecordCount) {
|
||||
TotalRecordCount = totalRecordCount;
|
||||
}
|
||||
|
||||
public long getTotalFileSize() {
|
||||
return TotalFileSize;
|
||||
}
|
||||
|
||||
public void setTotalFileSize(long totalFileSize) {
|
||||
TotalFileSize = totalFileSize;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return RequestId;
|
||||
}
|
||||
|
||||
public void setRequestId(String requestId) {
|
||||
RequestId = requestId;
|
||||
}
|
||||
|
||||
public long getPageRecordCount() {
|
||||
return PageRecordCount;
|
||||
}
|
||||
|
||||
public void setPageRecordCount(long pageRecordCount) {
|
||||
PageRecordCount = pageRecordCount;
|
||||
}
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.data;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午2:26
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsBackupPolicy {
|
||||
|
||||
/**
|
||||
* 数据备份保留天数(7到730天)。
|
||||
*/
|
||||
private String BackupRetentionPeriod;
|
||||
/**
|
||||
* 数据备份时间,格式:HH:mmZ- HH:mm Z。
|
||||
*/
|
||||
private String PreferredBackupTime;
|
||||
/**
|
||||
* 数据备份周期。Monday:周一;Tuesday:周二;Wednesday:周三;Thursday:周四;Friday:周五;Saturday:周六;Sunday:周日。
|
||||
*/
|
||||
private String PreferredBackupPeriod;
|
||||
/**
|
||||
* 日志备份状态。Enable:开启;Disabled:关闭。
|
||||
*/
|
||||
private boolean BackupLog;
|
||||
/**
|
||||
* 日志备份保留天数(7到730天)。
|
||||
*/
|
||||
private int LogBackupRetentionPeriod;
|
||||
|
||||
public String getBackupRetentionPeriod() {
|
||||
return BackupRetentionPeriod;
|
||||
}
|
||||
|
||||
public void setBackupRetentionPeriod(String backupRetentionPeriod) {
|
||||
BackupRetentionPeriod = backupRetentionPeriod;
|
||||
}
|
||||
|
||||
public String getPreferredBackupTime() {
|
||||
return PreferredBackupTime;
|
||||
}
|
||||
|
||||
public void setPreferredBackupTime(String preferredBackupTime) {
|
||||
PreferredBackupTime = preferredBackupTime;
|
||||
}
|
||||
|
||||
public String getPreferredBackupPeriod() {
|
||||
return PreferredBackupPeriod;
|
||||
}
|
||||
|
||||
public void setPreferredBackupPeriod(String preferredBackupPeriod) {
|
||||
PreferredBackupPeriod = preferredBackupPeriod;
|
||||
}
|
||||
|
||||
public boolean isBackupLog() {
|
||||
return BackupLog;
|
||||
}
|
||||
|
||||
public void setBackupLog(boolean backupLog) {
|
||||
BackupLog = backupLog;
|
||||
}
|
||||
|
||||
public int getLogBackupRetentionPeriod() {
|
||||
return LogBackupRetentionPeriod;
|
||||
}
|
||||
|
||||
public void setLogBackupRetentionPeriod(int logBackupRetentionPeriod) {
|
||||
LogBackupRetentionPeriod = logBackupRetentionPeriod;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午2:26
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsItem {
|
||||
private List<BinlogFile> BinLogFile;
|
||||
|
||||
public List<BinlogFile> getBinLogFile() {
|
||||
return BinLogFile;
|
||||
}
|
||||
|
||||
public void setBinLogFile(List<BinlogFile> binLogFile) {
|
||||
BinLogFile = binLogFile;
|
||||
}
|
||||
}
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.TrustStrategy;
|
||||
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.ssl.SSLContexts;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午2:26
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public abstract class AbstractRequest<T> {
|
||||
|
||||
/**
|
||||
* 要求的编码格式
|
||||
*/
|
||||
private static final String ENCODING = "UTF-8";
|
||||
/**
|
||||
* 要求的sign签名算法
|
||||
*/
|
||||
private static final String MAC_NAME = "HmacSHA1";
|
||||
|
||||
private String accessKeyId;
|
||||
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* api 版本
|
||||
*
|
||||
*/
|
||||
private String version;
|
||||
|
||||
private String endPoint = "rds.aliyuncs.com";
|
||||
|
||||
private String protocol = "http";
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
private int timeout = (int) TimeUnit.MINUTES.toMillis(1);
|
||||
|
||||
|
||||
private Map<String, String> treeMap = new TreeMap();
|
||||
|
||||
public void putQueryString(String name, String value){
|
||||
if (StringUtils.isBlank(name) || StringUtils.isBlank(value)){
|
||||
return;
|
||||
}
|
||||
treeMap.put(name, value);
|
||||
}
|
||||
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
|
||||
public void setEndPoint(String endPoint) {
|
||||
this.endPoint = endPoint;
|
||||
}
|
||||
|
||||
public void setAccessKeyId(String accessKeyId) {
|
||||
this.accessKeyId = accessKeyId;
|
||||
}
|
||||
|
||||
public void setAccessKeySecret(String accessKeySecret) {
|
||||
this.accessKeySecret = accessKeySecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 HMAC-SHA1 签名方法对对encryptText进行签名
|
||||
*
|
||||
* @param encryptText 被签名的字符串
|
||||
* @param encryptKey 密钥
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private 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 String base64(byte input[]) throws UnsupportedEncodingException {
|
||||
return new String(Base64.encodeBase64(input), ENCODING);
|
||||
}
|
||||
|
||||
private String concatQueryString(Map<String, String> parameters) throws UnsupportedEncodingException {
|
||||
if (null == parameters) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder urlBuilder = new StringBuilder("");
|
||||
for (Map.Entry<String, String> 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();
|
||||
}
|
||||
|
||||
private String encode(String value) throws UnsupportedEncodingException {
|
||||
return URLEncoder.encode(value, "UTF-8");
|
||||
}
|
||||
|
||||
private String makeSignature(TreeMap<String, String> paramMap) throws Exception {
|
||||
String cqs = concatQueryString(paramMap);
|
||||
cqs = encode(cqs);
|
||||
cqs = cqs.replaceAll("\\+", "%20");
|
||||
cqs = cqs.replaceAll("\\*", "%2A");
|
||||
cqs = cqs.replaceAll("%7E", "~");
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("GET").append("&").append(encode("/")).append("&").append(cqs);
|
||||
return base64(HmacSHA1Encrypt(stringBuilder.toString(), accessKeySecret + "&"));
|
||||
}
|
||||
|
||||
public final String formatUTCTZ(Date date) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss'Z'");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
private void fillCommonParam(Map<String, String> p) {
|
||||
p.put("Format", "JSON");
|
||||
p.put("Version", version);
|
||||
p.put("AccessKeyId", accessKeyId);
|
||||
p.put("SignatureMethod", "HMAC-SHA1"); //此处不能用变量 MAC_NAME
|
||||
p.put("Timestamp", formatUTCTZ(new Date()));
|
||||
p.put("SignatureVersion", "1.0");
|
||||
p.put("SignatureNonce", UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
private String makeRequestString(Map<String, String> param) throws Exception {
|
||||
fillCommonParam(param);
|
||||
String sign = makeSignature(new TreeMap<String, String>(param));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : param.entrySet()) {
|
||||
builder.append(encode(entry.getKey())).append("=").append(encode(entry.getValue())).append("&");
|
||||
}
|
||||
builder.append("Signature").append("=").append(sign);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行http请求
|
||||
*
|
||||
* @param getMethod
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private final HttpResponse executeHttpRequest(HttpGet getMethod, String host) throws Exception {
|
||||
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
|
||||
|
||||
@Override
|
||||
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||
return true;
|
||||
}
|
||||
}).build();
|
||||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
|
||||
new String[] { "TLSv1" },
|
||||
null,
|
||||
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
Registry registry = RegistryBuilder.create()
|
||||
.register("http", PlainConnectionSocketFactory.INSTANCE)
|
||||
.register("https", sslsf)
|
||||
.build();
|
||||
HttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create()
|
||||
.setMaxConnPerRoute(50)
|
||||
.setMaxConnTotal(100)
|
||||
.setConnectionManager(httpClientConnectionManager)
|
||||
.build();
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.build();
|
||||
getMethod.setConfig(requestConfig);
|
||||
HttpResponse response = httpClient.execute(getMethod);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != HttpResponseStatus.OK.code() && statusCode != HttpResponseStatus.PARTIAL_CONTENT.code()) {
|
||||
String result = EntityUtils.toString(response.getEntity());
|
||||
throw new RuntimeException("return error !" + response.getStatusLine().getReasonPhrase() + ", " + result);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
protected abstract T processResult(HttpResponse response) throws Exception;
|
||||
|
||||
protected void processBefore(){
|
||||
|
||||
}
|
||||
|
||||
public final T doAction() throws Exception {
|
||||
processBefore();
|
||||
String requestStr = makeRequestString(treeMap);
|
||||
HttpGet httpGet = new HttpGet(protocol + "://" +endPoint + "?" + requestStr);
|
||||
HttpResponse response = executeHttpRequest(httpGet, endPoint);
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
|
||||
String result = EntityUtils.toString(response.getEntity());
|
||||
throw new RuntimeException("http request failed! " + result);
|
||||
}
|
||||
return processResult(response);
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.request;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* rds 备份策略查询
|
||||
* @author chengjin.lyf on 2018/8/7 下午3:41
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class DescribeBackupPolicyRequest extends AbstractRequest<RdsBackupPolicy> {
|
||||
|
||||
|
||||
public DescribeBackupPolicyRequest() {
|
||||
setVersion("2014-08-15");
|
||||
putQueryString("Action", "DescribeBackupPolicy");
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setRdsInstanceId(String rdsInstanceId) {
|
||||
putQueryString("DBInstanceId", rdsInstanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RdsBackupPolicy processResult(HttpResponse response) throws Exception {
|
||||
String result = EntityUtils.toString(response.getEntity());
|
||||
JSONObject jsonObj = JSON.parseObject(result);
|
||||
RdsBackupPolicy policy = new RdsBackupPolicy();
|
||||
policy.setBackupRetentionPeriod(jsonObj.getString("BackupRetentionPeriod"));
|
||||
policy.setBackupLog(jsonObj.getString("BackupLog").equalsIgnoreCase("Enable"));
|
||||
policy.setLogBackupRetentionPeriod(jsonObj.getIntValue("LogBackupRetentionPeriod"));
|
||||
policy.setPreferredBackupPeriod(jsonObj.getString("PreferredBackupPeriod"));
|
||||
policy.setPreferredBackupTime(jsonObj.getString("PreferredBackupTime"));
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds.request;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/8/7 下午3:41
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class DescribeBinlogFilesRequest extends AbstractRequest<DescribeBinlogFileResult> {
|
||||
|
||||
|
||||
public DescribeBinlogFilesRequest() {
|
||||
setVersion("2014-08-15");
|
||||
putQueryString("Action", "DescribeBinlogFiles");
|
||||
|
||||
}
|
||||
|
||||
public void setRdsInstanceId(String rdsInstanceId) {
|
||||
putQueryString("DBInstanceId", rdsInstanceId);
|
||||
}
|
||||
|
||||
public void setPageSize(int pageSize) {
|
||||
putQueryString("PageSize", String.valueOf(pageSize));
|
||||
}
|
||||
|
||||
public void setPageNumber(int pageNumber) {
|
||||
putQueryString("PageNumber", String.valueOf(pageNumber));
|
||||
}
|
||||
|
||||
public void setStartDate(Date startDate) {
|
||||
putQueryString("StartTime" , formatUTCTZ(startDate));
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
putQueryString("EndTime" , formatUTCTZ(endDate));
|
||||
}
|
||||
|
||||
public void setResourceOwnerId(Long resourceOwnerId) {
|
||||
putQueryString("ResourceOwnerId", String.valueOf(resourceOwnerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DescribeBinlogFileResult processResult(HttpResponse response) throws Exception {
|
||||
String result = EntityUtils.toString(response.getEntity());
|
||||
DescribeBinlogFileResult describeBinlogFileResult = JSONObject.parseObject(result, new TypeReference<DescribeBinlogFileResult>() {
|
||||
});
|
||||
return describeBinlogFileResult;
|
||||
}
|
||||
}
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.CacheConnectionNull;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class HistoryTableMetaCache {
|
||||
private TableMetaStorage tableMetaStorage;
|
||||
private MysqlConnection metaConnection;
|
||||
private LoadingCache<String, Map<Long, TableMeta>> cache; // 第一层:数据库名.表名,第二层时间戳,TableMeta
|
||||
|
||||
public HistoryTableMetaCache() {
|
||||
cache = CacheBuilder.newBuilder().build(new CacheLoader<String, Map<Long, TableMeta>>() {
|
||||
@Override
|
||||
public Map<Long, TableMeta> load(String tableName) throws Exception {
|
||||
Long timestamp = new Date().getTime();
|
||||
String[] strs = tableName.split("\\.");
|
||||
String schema = strs[0];
|
||||
if (tableMetaStorage != null) {
|
||||
init(tableMetaStorage.fetchByTableName(tableName)); // 从存储中读取表的历史ddl
|
||||
}
|
||||
ResultSetPacket resultSetPacket = connectionQuery("show create table " + tableName); // 获取当前ddl
|
||||
String currentDdl = resultSetPacket.getFieldValues().get(1);
|
||||
if (cache.asMap().containsKey(tableName)) {
|
||||
Map<Long, TableMeta> tableMetaMap = cache.getUnchecked(tableName);
|
||||
if (tableMetaMap.isEmpty()) {
|
||||
put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema,取时间为当前时间-1s
|
||||
} else { // 如果table存在历史
|
||||
Iterator<Long> iterator = tableMetaMap.keySet().iterator();
|
||||
Long firstTimestamp = iterator.next();
|
||||
TableMeta first = tableMetaMap.get(firstTimestamp); // 拿第一条ddl
|
||||
if (!first.getDdl().equalsIgnoreCase(currentDdl)) { // 当前ddl与历史第一条不一致,放入当前ddl
|
||||
put(schema, tableName, currentDdl, calculateNewTimestamp(firstTimestamp)); // 计算放入的timestamp,设为第一条时间+1s
|
||||
}
|
||||
}
|
||||
} else {
|
||||
put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema
|
||||
}
|
||||
return cache.get(tableName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void init(List<TableMetaEntry> entries) throws IOException {
|
||||
if (entries == null) {
|
||||
return;
|
||||
}
|
||||
for (TableMetaEntry entry : entries) {
|
||||
try {
|
||||
put(entry.getSchema(), entry.getTable(), entry.getDdl(), entry.getTimestamp());
|
||||
} catch (CacheConnectionNull cacheConnectionNull) {
|
||||
cacheConnectionNull.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TableMeta put(String schema, String table, String ddl, Long timestamp) throws CacheConnectionNull, IOException {
|
||||
ResultSetPacket resultSetPacket;
|
||||
if (!(ddl.contains("CREATE TABLE") || ddl.contains("create table"))) { // 尝试直接从数据库拉取CREATE TABLE的DDL
|
||||
resultSetPacket = connectionQuery("show create table " + table);
|
||||
ddl = resultSetPacket.getFieldValues().get(1);
|
||||
} else { // CREATE TABLE 的 DDL
|
||||
resultSetPacket = new ResultSetPacket();
|
||||
List<String> fields = new ArrayList<String>();
|
||||
String[] strings = table.split("\\.");
|
||||
String shortTable = table;
|
||||
if (strings.length > 1) {
|
||||
shortTable = strings[1];
|
||||
}
|
||||
fields.add(0, shortTable);
|
||||
fields.add(1, ddl);
|
||||
resultSetPacket.setFieldValues(fields);
|
||||
if (metaConnection != null) {
|
||||
resultSetPacket.setSourceAddress(metaConnection.getAddress());
|
||||
}
|
||||
}
|
||||
Map<Long, TableMeta> tableMetaMap;
|
||||
if (!cache.asMap().containsKey(table)) {
|
||||
tableMetaMap = new TreeMap<Long, TableMeta>(new Comparator<Long>() {
|
||||
@Override
|
||||
public int compare(Long o1, Long o2) {
|
||||
return o2.compareTo(o1);
|
||||
}
|
||||
});
|
||||
cache.put(table, tableMetaMap);
|
||||
} else {
|
||||
tableMetaMap = cache.getUnchecked(table);
|
||||
}
|
||||
eliminate(tableMetaMap); // 淘汰旧的TableMeta
|
||||
TableMeta tableMeta = new TableMeta(schema, table, TableMetaCache.parseTableMeta(schema, table, resultSetPacket));
|
||||
if (tableMeta.getDdl() == null) { // 生成的TableMeta有时DDL为null
|
||||
tableMeta.setDdl(ddl);
|
||||
}
|
||||
tableMetaMap.put(timestamp, tableMeta);
|
||||
return tableMeta;
|
||||
}
|
||||
|
||||
public TableMeta get(String schema, String table, Long timestamp) throws NoHistoryException, CacheConnectionNull {
|
||||
Map<Long, TableMeta> tableMetaMap = cache.getUnchecked(table);
|
||||
Iterator<Long> iterator = tableMetaMap.keySet().iterator();
|
||||
Long selected = null;
|
||||
while(iterator.hasNext()) {
|
||||
Long temp = iterator.next();
|
||||
if (timestamp > temp) {
|
||||
selected = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected == null) {
|
||||
iterator = tableMetaMap.keySet().iterator();
|
||||
if (iterator.hasNext()) {
|
||||
selected = iterator.next();
|
||||
} else {
|
||||
throw new NoHistoryException(schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
return tableMetaMap.get(selected);
|
||||
}
|
||||
|
||||
public void clearTableMeta() {
|
||||
cache.invalidateAll();
|
||||
}
|
||||
|
||||
public void clearTableMetaWithSchemaName(String schema) {
|
||||
for (String tableName : cache.asMap().keySet()) {
|
||||
String[] strs = tableName.split("\\.");
|
||||
if (schema.equalsIgnoreCase(strs[0])) {
|
||||
cache.invalidate(tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearTableMeta(String schema, String table) {
|
||||
if (!table.contains(".")) {
|
||||
table = schema+"."+table;
|
||||
}
|
||||
cache.invalidate(table);
|
||||
}
|
||||
|
||||
// eliminate older table meta in cache
|
||||
private void eliminate(Map<Long, TableMeta> tableMetaMap) {
|
||||
int MAX_CAPABILITY = 20;
|
||||
if (tableMetaMap.keySet().size() < MAX_CAPABILITY) {
|
||||
return;
|
||||
}
|
||||
Iterator<Long> iterator = tableMetaMap.keySet().iterator();
|
||||
while(iterator.hasNext()) {
|
||||
iterator.next();
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
private Long calculateNewTimestamp(Long oldTimestamp) {
|
||||
return oldTimestamp + 1000;
|
||||
}
|
||||
|
||||
private ResultSetPacket connectionQuery(String query) throws CacheConnectionNull, IOException {
|
||||
if (metaConnection == null) {
|
||||
throw new CacheConnectionNull();
|
||||
}
|
||||
try {
|
||||
return metaConnection.query(query);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
metaConnection.reconnect();
|
||||
return metaConnection.query(query);
|
||||
} catch (IOException e1) {
|
||||
throw e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setMetaConnection(MysqlConnection metaConnection) {
|
||||
this.metaConnection = metaConnection;
|
||||
}
|
||||
|
||||
public void setTableMetaStorage(TableMetaStorage tableMetaStorage) {
|
||||
this.tableMetaStorage = tableMetaStorage;
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
public interface TableMetaCacheInterface {
|
||||
|
||||
TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position);
|
||||
|
||||
void clearTableMeta();
|
||||
|
||||
void clearTableMetaWithSchemaName(String schema);
|
||||
|
||||
void clearTableMeta(String schema, String table);
|
||||
|
||||
boolean apply(EntryPosition position, String schema, String ddl, String extra);
|
||||
|
||||
boolean isOnRDS();
|
||||
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class TableMetaCacheWithStorage implements TableMetaCacheInterface {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(TableMetaCacheWithStorage.class);
|
||||
private TableMetaStorage tableMetaStorage; // TableMeta存储
|
||||
private HistoryTableMetaCache cache = new HistoryTableMetaCache(); // cache
|
||||
|
||||
public TableMetaCacheWithStorage(MysqlConnection con, TableMetaStorage tableMetaStorage) {
|
||||
this.tableMetaStorage = tableMetaStorage;
|
||||
InetSocketAddress address = con.getAddress();
|
||||
this.tableMetaStorage.setDbAddress(address.getHostName()+":"+address.getPort());
|
||||
cache.setMetaConnection(con);
|
||||
cache.setTableMetaStorage(tableMetaStorage);
|
||||
if (tableMetaStorage != null) {
|
||||
try {
|
||||
cache.init(tableMetaStorage.fetch()); // 初始化,从存储拉取TableMeta
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(EntryPosition position, String fullTableName, String ddl, String extra) {
|
||||
String[] strs = fullTableName.split("\\.");
|
||||
String schema = strs[0];
|
||||
if (schema.equalsIgnoreCase("null")) { // ddl schema为null,放弃处理
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
TableMeta tableMeta = cache.get(schema, fullTableName, position.getTimestamp());
|
||||
if (!compare(tableMeta, ddl)) { // 获取最近的TableMeta,进行比对
|
||||
TableMeta result = cache.put(schema, fullTableName, ddl, calTimestamp(position.getTimestamp()));
|
||||
if (tableMetaStorage != null && result != null) { // 储存
|
||||
tableMetaStorage.store(schema, fullTableName, result.getDdl(), calTimestamp(position.getTimestamp()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.error(e.toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnRDS() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param schema dbname
|
||||
* @param table tablename
|
||||
* @param useCache unused
|
||||
* @param position timestamp
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) {
|
||||
String fulltbName = schema + "." + table;
|
||||
try {
|
||||
return cache.get(schema, fulltbName, position.getTimestamp());
|
||||
} catch (Exception e) {
|
||||
logger.error(e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTableMeta() {
|
||||
cache.clearTableMeta();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTableMetaWithSchemaName(String schema) {
|
||||
cache.clearTableMetaWithSchemaName(schema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTableMeta(String schema, String table) {
|
||||
cache.clearTableMeta(schema, table);
|
||||
}
|
||||
|
||||
private boolean compare(TableMeta tableMeta, String ddl) {
|
||||
if (tableMeta == null) {
|
||||
return false;
|
||||
}
|
||||
return tableMeta.getDdl().equalsIgnoreCase(ddl);
|
||||
}
|
||||
|
||||
private Long calTimestamp(Long timestamp) {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class TableMetaEntry implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -1350200637109107904L;
|
||||
|
||||
private String dbAddress;
|
||||
private String schema;
|
||||
private String table;
|
||||
private String ddl;
|
||||
private Long timestamp;
|
||||
|
||||
|
||||
public String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
public void setSchema(String schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setTable(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public String getDdl() {
|
||||
return ddl;
|
||||
}
|
||||
|
||||
public void setDdl(String ddl) {
|
||||
this.ddl = ddl;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getDbAddress() {
|
||||
return dbAddress;
|
||||
}
|
||||
|
||||
public void setDbAddress(String dbAddress) {
|
||||
this.dbAddress = dbAddress;
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TableMetaStorage {
|
||||
|
||||
void store(String schema, String table, String ddl, Long timestamp);
|
||||
|
||||
List<TableMetaEntry> fetch();
|
||||
|
||||
List<TableMetaEntry> fetchByTableName(String tableName);
|
||||
|
||||
String getDbName();
|
||||
|
||||
String getDbAddress();
|
||||
|
||||
void setDbAddress(String address);
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
|
||||
|
||||
public interface TableMetaStorageFactory {
|
||||
|
||||
TableMetaStorage getTableMetaStorage();
|
||||
|
||||
String getDbName();
|
||||
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception;
|
||||
|
||||
public class CacheConnectionNull extends Exception{
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheConnectionNull";
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception;
|
||||
|
||||
public class NoHistoryException extends Exception{
|
||||
|
||||
private String dbName;
|
||||
private String tbName;
|
||||
|
||||
public NoHistoryException(String dbName, String tbName) {
|
||||
this.dbName = dbName;
|
||||
this.tbName = tbName;
|
||||
}
|
||||
|
||||
public void printTableName() {
|
||||
System.out.println(dbName+"."+tbName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NioHistoryException: " + dbName + " " + tbName;
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MySqlTableMetaCallback {
|
||||
|
||||
void save(String dbAddress, String schema, String table,String ddl, Long timestamp);
|
||||
|
||||
List<TableMetaEntry> fetch(String dbAddress, String dbName);
|
||||
|
||||
List<TableMetaEntry> fetch(String dbAddress, String dbName, String tableName);
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MySqlTableMetaStorage implements TableMetaStorage {
|
||||
private MySqlTableMetaCallback mySqlTableMetaCallback;
|
||||
private String dbName;
|
||||
private String dbAddress;
|
||||
|
||||
MySqlTableMetaStorage(MySqlTableMetaCallback callback, String dbName) {
|
||||
mySqlTableMetaCallback = callback;
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void store(String schema, String table, String ddl, Long timestamp) {
|
||||
mySqlTableMetaCallback.save(dbAddress, schema, table, ddl, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TableMetaEntry> fetch() {
|
||||
return mySqlTableMetaCallback.fetch(dbAddress, dbName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TableMetaEntry> fetchByTableName(String tableName) {
|
||||
return mySqlTableMetaCallback.fetch(dbAddress, dbName, tableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbAddress() {
|
||||
return dbAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDbAddress(String address) {
|
||||
this.dbAddress = address;
|
||||
}
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory;
|
||||
|
||||
public class MySqlTableMetaStorageFactory implements TableMetaStorageFactory {
|
||||
|
||||
private MySqlTableMetaCallback mySQLTableMetaCallback;
|
||||
private String dbName;
|
||||
|
||||
public MySqlTableMetaStorageFactory(MySqlTableMetaCallback callback, String dbName) {
|
||||
mySQLTableMetaCallback = callback;
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableMetaStorage getTableMetaStorage() {
|
||||
return new MySqlTableMetaStorage(mySQLTableMetaCallback, dbName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
}
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.parse.helper.TimeoutChecker;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaCallback;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaStorageFactory;
|
||||
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;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||
import com.alibaba.otter.canal.sink.exception.CanalSinkException;
|
||||
|
||||
/**
|
||||
* @author chengjin.lyf on 2018/7/21 下午5:24
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsBinlogEventParserProxyTest {
|
||||
|
||||
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 = "";
|
||||
private static final String USERNAME = "";
|
||||
private static final String PASSWORD = "";
|
||||
public static final String DBNAME = "";
|
||||
public static final String TBNAME = "";
|
||||
public static final String DDL = "";
|
||||
|
||||
|
||||
@Test
|
||||
public void test_timestamp() throws InterruptedException {
|
||||
final TimeoutChecker timeoutChecker = new TimeoutChecker(3000 * 1000);
|
||||
final AtomicLong entryCount = new AtomicLong(0);
|
||||
final EntryPosition entryPosition = new EntryPosition();
|
||||
|
||||
final RdsBinlogEventParserProxy controller = new RdsBinlogEventParserProxy();
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_YEAR, -1);
|
||||
final EntryPosition defaultPosition = buildPosition(null, null, calendar.getTimeInMillis());
|
||||
controller.setSlaveId(3344L);
|
||||
controller.setDetectingEnable(false);
|
||||
controller.setDetectingSQL(DETECTING_SQL);
|
||||
controller.setMasterInfo(buildAuthentication());
|
||||
controller.setMasterPosition(defaultPosition);
|
||||
controller.setInstanceId("");
|
||||
controller.setAccesskey("");
|
||||
controller.setSecretkey("");
|
||||
controller.setBatchSize(4);
|
||||
// controller.setRdsOpenApiUrl("https://rds.aliyuncs.com/");
|
||||
controller.setEventSink(new AbstractCanalEventSinkTest<List<CanalEntry.Entry>>() {
|
||||
|
||||
@Override
|
||||
public boolean sink(List<CanalEntry.Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||
throws CanalSinkException {
|
||||
for (CanalEntry.Entry entry : entrys) {
|
||||
if (entry.getEntryType() != CanalEntry.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;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||
|
||||
private LogPosition logPosition;
|
||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||
System.out.println(logPosition);
|
||||
this.logPosition = logPosition;
|
||||
}
|
||||
|
||||
public LogPosition getLatestIndexBy(String destination) {
|
||||
return logPosition;
|
||||
}
|
||||
});
|
||||
|
||||
controller.start();
|
||||
timeoutChecker.waitForIdle();
|
||||
|
||||
if (controller.isStart()) {
|
||||
controller.stop();
|
||||
}
|
||||
|
||||
// check
|
||||
Assert.assertTrue(entryCount.get() > 0);
|
||||
|
||||
// 对比第一条数据和起始的position相同
|
||||
Assert.assertEquals(entryPosition.getJournalName(), "mysql-bin.000001");
|
||||
Assert.assertTrue(entryPosition.getPosition() <= 6163L);
|
||||
Assert.assertTrue(entryPosition.getTimestamp() <= defaultPosition.getTimestamp());
|
||||
}
|
||||
|
||||
|
||||
// ======================== helper method =======================
|
||||
|
||||
private EntryPosition buildPosition(String binlogFile, Long offest, Long timestamp) {
|
||||
return new EntryPosition(binlogFile, offest, timestamp);
|
||||
}
|
||||
|
||||
private AuthenticationInfo buildAuthentication() {
|
||||
return new AuthenticationInfo(new InetSocketAddress(MYSQL_ADDRESS, 3306), USERNAME, PASSWORD);
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,6 @@
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<name>canal prometheus module for otter ${project.version}</name>
|
||||
<dependencies>
|
||||
<!-- load time weaver-->
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<version>1.8.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.8.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jctools</groupId>
|
||||
<artifactId>jctools-core</artifactId>
|
||||
|
||||
+54
-67
@@ -1,12 +1,7 @@
|
||||
package com.alibaba.otter.canal.prometheus;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.prometheus.impl.InstanceMetaCollector;
|
||||
import com.alibaba.otter.canal.prometheus.impl.MemoryStoreCollector;
|
||||
import com.alibaba.otter.canal.prometheus.impl.PrometheusCanalEventDownStreamHandler;
|
||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
||||
import com.alibaba.otter.canal.store.CanalStoreException;
|
||||
import com.alibaba.otter.canal.prometheus.impl.*;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CollectorRegistry;
|
||||
import org.slf4j.Logger;
|
||||
@@ -20,79 +15,71 @@ import java.util.List;
|
||||
*/
|
||||
public class CanalInstanceExports {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class);
|
||||
public static final String DEST = "destination";
|
||||
public static final String[] DEST_LABELS = {DEST};
|
||||
public static final List<String> DEST_LABELS_LIST = Collections.singletonList(DEST);
|
||||
private final Collector storeCollector;
|
||||
private final Collector entryCollector;
|
||||
private final Collector metaCollector;
|
||||
private final Collector sinkCollector;
|
||||
private final Collector parserCollector;
|
||||
|
||||
public static final String[] labels = {"destination"};
|
||||
|
||||
public static final List<String> labelList = Collections.singletonList(labels[0]);
|
||||
|
||||
private final String destination;
|
||||
|
||||
private Collector storeCollector;
|
||||
|
||||
private Collector delayCollector;
|
||||
|
||||
private Collector metaCollector;
|
||||
|
||||
private CanalInstanceExports(CanalInstance instance) {
|
||||
this.destination = instance.getDestination();
|
||||
initDelayGauge(instance);
|
||||
initStoreCollector(instance);
|
||||
initMetaCollector(instance);
|
||||
private CanalInstanceExports() {
|
||||
this.storeCollector = StoreCollector.instance();
|
||||
this.entryCollector = EntryCollector.instance();
|
||||
this.metaCollector = MetaCollector.instance();
|
||||
this.sinkCollector = SinkCollector.instance();
|
||||
this.parserCollector = ParserCollector.instance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static CanalInstanceExports forInstance(CanalInstance instance) {
|
||||
return new CanalInstanceExports(instance);
|
||||
private static class SingletonHolder {
|
||||
private static final CanalInstanceExports SINGLETON = new CanalInstanceExports();
|
||||
}
|
||||
|
||||
void register() {
|
||||
if (delayCollector != null) {
|
||||
delayCollector.register();
|
||||
}
|
||||
if (storeCollector != null) {
|
||||
storeCollector.register();
|
||||
}
|
||||
if (metaCollector != null) {
|
||||
metaCollector.register();
|
||||
}
|
||||
public static CanalInstanceExports instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
void unregister() {
|
||||
if (delayCollector != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(delayCollector);
|
||||
}
|
||||
if (storeCollector != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(storeCollector);
|
||||
}
|
||||
if (metaCollector != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(metaCollector);
|
||||
}
|
||||
public void initialize() {
|
||||
storeCollector.register();
|
||||
entryCollector.register();
|
||||
metaCollector.register();
|
||||
sinkCollector.register();
|
||||
parserCollector.register();
|
||||
}
|
||||
|
||||
private void initDelayGauge(CanalInstance instance) {
|
||||
CanalEventSink sink = instance.getEventSink();
|
||||
if (sink instanceof EntryEventSink) {
|
||||
EntryEventSink entryEventSink = (EntryEventSink) sink;
|
||||
// TODO ensure not to add handler again
|
||||
PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination);
|
||||
entryEventSink.addHandler(handler);
|
||||
delayCollector = handler.getCollector();
|
||||
} else {
|
||||
logger.warn("This impl register metrics for only EntryEventSink, skip.");
|
||||
}
|
||||
public void terminate() {
|
||||
CollectorRegistry.defaultRegistry.unregister(storeCollector);
|
||||
CollectorRegistry.defaultRegistry.unregister(entryCollector);
|
||||
CollectorRegistry.defaultRegistry.unregister(metaCollector);
|
||||
CollectorRegistry.defaultRegistry.unregister(sinkCollector);
|
||||
CollectorRegistry.defaultRegistry.unregister(parserCollector);
|
||||
}
|
||||
|
||||
private void initStoreCollector(CanalInstance instance) {
|
||||
try {
|
||||
storeCollector = new MemoryStoreCollector(instance.getEventStore(), destination);
|
||||
} catch (CanalStoreException cse) {
|
||||
logger.warn("Failed to register metrics for destination {}.", destination, cse);
|
||||
}
|
||||
void register(CanalInstance instance) {
|
||||
requiredInstanceRegistry(storeCollector).register(instance);
|
||||
requiredInstanceRegistry(entryCollector).register(instance);
|
||||
requiredInstanceRegistry(metaCollector).register(instance);
|
||||
requiredInstanceRegistry(sinkCollector).register(instance);
|
||||
requiredInstanceRegistry(parserCollector).register(instance);
|
||||
logger.info("Successfully register metrics for instance {}.", instance.getDestination());
|
||||
}
|
||||
|
||||
private void initMetaCollector(CanalInstance instance) {
|
||||
metaCollector = new InstanceMetaCollector(instance);
|
||||
void unregister(CanalInstance instance) {
|
||||
requiredInstanceRegistry(storeCollector).unregister(instance);
|
||||
requiredInstanceRegistry(entryCollector).unregister(instance);
|
||||
requiredInstanceRegistry(metaCollector).unregister(instance);
|
||||
requiredInstanceRegistry(sinkCollector).unregister(instance);
|
||||
requiredInstanceRegistry(parserCollector).unregister(instance);
|
||||
logger.info("Successfully unregister metrics for instance {}.", instance.getDestination());
|
||||
}
|
||||
|
||||
private InstanceRegistry requiredInstanceRegistry(Collector collector) {
|
||||
if (!(collector instanceof InstanceRegistry)) {
|
||||
throw new IllegalArgumentException("Canal prometheus collector need to implement InstanceRegistry.");
|
||||
}
|
||||
return (InstanceRegistry) collector;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.alibaba.otter.canal.prometheus;
|
||||
|
||||
import com.alibaba.otter.canal.prometheus.impl.InboundThroughputAspect;
|
||||
import com.alibaba.otter.canal.prometheus.impl.OutboundThroughputAspect;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class CanalServerExports {
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
public static synchronized void initialize() {
|
||||
if (!initialized) {
|
||||
InboundThroughputAspect.getCollector().register();
|
||||
OutboundThroughputAspect.getCollector().register();
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.alibaba.otter.canal.prometheus;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public interface InstanceRegistry {
|
||||
|
||||
void register(CanalInstance instance);
|
||||
|
||||
void unregister(CanalInstance instance);
|
||||
|
||||
}
|
||||
+31
-26
@@ -1,6 +1,8 @@
|
||||
package com.alibaba.otter.canal.prometheus;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.prometheus.impl.PrometheusClientInstanceProfiler;
|
||||
import com.alibaba.otter.canal.server.netty.ClientInstanceProfiler;
|
||||
import com.alibaba.otter.canal.spi.CanalMetricsService;
|
||||
import io.prometheus.client.exporter.HTTPServer;
|
||||
import io.prometheus.client.hotspot.DefaultExports;
|
||||
@@ -8,23 +10,24 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.NOP;
|
||||
import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class PrometheusService implements CanalMetricsService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class);
|
||||
|
||||
private final Map<String, CanalInstanceExports> exports = new ConcurrentHashMap<String, CanalInstanceExports>();
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
private HTTPServer server;
|
||||
private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class);
|
||||
private final CanalInstanceExports instanceExports;
|
||||
private volatile boolean running = false;
|
||||
private HTTPServer server;
|
||||
private final ClientInstanceProfiler clientProfiler;
|
||||
|
||||
private PrometheusService() {
|
||||
this.instanceExports = CanalInstanceExports.instance();
|
||||
this.clientProfiler = PrometheusClientInstanceProfiler.instance();
|
||||
}
|
||||
|
||||
private static class SingletonHolder {
|
||||
@@ -48,8 +51,11 @@ public class PrometheusService implements CanalMetricsService {
|
||||
try {
|
||||
// JVM exports
|
||||
DefaultExports.initialize();
|
||||
// Canal server level exports
|
||||
CanalServerExports.initialize();
|
||||
instanceExports.initialize();
|
||||
if (!clientProfiler.isStart()) {
|
||||
clientProfiler.start();
|
||||
}
|
||||
profiler().setInstanceProfiler(clientProfiler);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unable to initialize server exports.", t);
|
||||
}
|
||||
@@ -60,14 +66,17 @@ public class PrometheusService implements CanalMetricsService {
|
||||
@Override
|
||||
public void terminate() {
|
||||
running = false;
|
||||
// Normally, service should be terminated at canal shutdown.
|
||||
// No need to unregister instance exports explicitly.
|
||||
// But for the sake of safety, unregister them.
|
||||
for (CanalInstanceExports ie : exports.values()) {
|
||||
ie.unregister();
|
||||
}
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
try {
|
||||
instanceExports.terminate();
|
||||
if (clientProfiler.isStart()) {
|
||||
clientProfiler.stop();
|
||||
}
|
||||
profiler().setInstanceProfiler(NOP);
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Something happened while terminating.", t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +92,7 @@ public class PrometheusService implements CanalMetricsService {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CanalInstanceExports export = CanalInstanceExports.forInstance(instance);
|
||||
export.register();
|
||||
exports.put(instance.getDestination(), export);
|
||||
instanceExports.register(instance);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unable to register instance exports for {}.", instance.getDestination(), t);
|
||||
}
|
||||
@@ -98,13 +105,11 @@ public class PrometheusService implements CanalMetricsService {
|
||||
logger.warn("Try unregister metrics after destination {} is stopped.", instance.getDestination());
|
||||
}
|
||||
try {
|
||||
CanalInstanceExports export = exports.remove(instance.getDestination());
|
||||
if (export != null) {
|
||||
export.unregister();
|
||||
}
|
||||
instanceExports.unregister(instance);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unable to unregister instance exports for {}.", instance.getDestination(), t);
|
||||
}
|
||||
logger.info("Unregister metrics for destination {}.", instance.getDestination());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.prometheus.InstanceRegistry;
|
||||
import com.alibaba.otter.canal.sink.CanalEventDownStreamHandler;
|
||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.Counter;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
import io.prometheus.client.GaugeMetricFamily;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class EntryCollector extends Collector implements InstanceRegistry {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class);
|
||||
private static final String DELAY = "canal_instance_traffic_delay";
|
||||
private static final String TRANSACTION = "canal_instance_transactions";
|
||||
private static final String ROW_EVENTS = "canal_instance_row_events";
|
||||
private static final String ROWS_COUNTER = "canal_instance_rows_counter";
|
||||
private static final String DELAY_HELP = "Traffic delay of canal instance in milliseconds";
|
||||
private static final String TRANSACTION_HELP = "Transactions counter of canal instance";
|
||||
private static final String ROW_EVENTS_HELP = "Rowdata events counter of canal instance";
|
||||
private static final String ROWS_COUNTER_HELP = "Rows counter of canal instance";
|
||||
private final ConcurrentMap<String, EntryMetricsHolder> instances = new ConcurrentHashMap<String, EntryMetricsHolder>();
|
||||
|
||||
private EntryCollector() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final EntryCollector SINGLETON = new EntryCollector();
|
||||
}
|
||||
|
||||
public static EntryCollector instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
GaugeMetricFamily delay = new GaugeMetricFamily(DELAY,
|
||||
DELAY_HELP, DEST_LABELS_LIST);
|
||||
CounterMetricFamily transactions = new CounterMetricFamily(TRANSACTION,
|
||||
TRANSACTION_HELP, DEST_LABELS_LIST);
|
||||
CounterMetricFamily rowEvents = new CounterMetricFamily(ROW_EVENTS,
|
||||
ROW_EVENTS_HELP, DEST_LABELS_LIST);
|
||||
CounterMetricFamily rowsCounter = new CounterMetricFamily(ROWS_COUNTER,
|
||||
ROWS_COUNTER_HELP, DEST_LABELS_LIST);
|
||||
for (EntryMetricsHolder emh : instances.values()) {
|
||||
long now = System.currentTimeMillis();
|
||||
long latest = emh.latestExecTime.get();
|
||||
if (now >= latest) {
|
||||
delay.addMetric(emh.destLabelValues, (now - latest));
|
||||
}
|
||||
transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue());
|
||||
rowEvents.addMetric(emh.destLabelValues, emh.rowEventCounter.doubleValue());
|
||||
rowsCounter.addMetric(emh.destLabelValues, emh.rowsCounter.doubleValue());
|
||||
}
|
||||
mfs.add(delay);
|
||||
mfs.add(transactions);
|
||||
mfs.add(rowEvents);
|
||||
mfs.add(rowsCounter);
|
||||
return mfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
EntryMetricsHolder holder = new EntryMetricsHolder();
|
||||
holder.destLabelValues = Collections.singletonList(destination);
|
||||
CanalEventSink sink = instance.getEventSink();
|
||||
if (!(sink instanceof EntryEventSink)) {
|
||||
throw new IllegalArgumentException("CanalEventSink must be EntryEventSink");
|
||||
}
|
||||
EntryEventSink entrySink = (EntryEventSink) sink;
|
||||
PrometheusCanalEventDownStreamHandler handler = assembleHandler(entrySink);
|
||||
holder.latestExecTime = handler.getLatestExecuteTime();
|
||||
holder.transactionCounter = handler.getTransactionCounter();
|
||||
holder.rowEventCounter = handler.getRowEventCounter();
|
||||
holder.rowsCounter = handler.getRowsCounter();
|
||||
Preconditions.checkNotNull(holder.latestExecTime);
|
||||
Preconditions.checkNotNull(holder.transactionCounter);
|
||||
Preconditions.checkNotNull(holder.rowEventCounter);
|
||||
Preconditions.checkNotNull(holder.rowsCounter);
|
||||
EntryMetricsHolder old = instances.put(destination, holder);
|
||||
if (old != null) {
|
||||
logger.warn("Remove stale EntryCollector for instance {}.", destination);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
CanalEventSink sink = instance.getEventSink();
|
||||
if (!(sink instanceof EntryEventSink)) {
|
||||
throw new IllegalArgumentException("CanalEventSink must be EntryEventSink");
|
||||
}
|
||||
unloadHandler((EntryEventSink) sink);
|
||||
instances.remove(destination);
|
||||
}
|
||||
|
||||
private PrometheusCanalEventDownStreamHandler assembleHandler(EntryEventSink entrySink) {
|
||||
PrometheusCanalEventDownStreamHandler ph = new PrometheusCanalEventDownStreamHandler();
|
||||
List<CanalEventDownStreamHandler> handlers = entrySink.getHandlers();
|
||||
for (CanalEventDownStreamHandler handler : handlers) {
|
||||
if (handler instanceof PrometheusCanalEventDownStreamHandler) {
|
||||
throw new IllegalStateException("PrometheusCanalEventDownStreamHandler already exists in handlers.");
|
||||
}
|
||||
}
|
||||
entrySink.addHandler(ph, 0);
|
||||
return ph;
|
||||
}
|
||||
|
||||
private void unloadHandler(EntryEventSink entrySink) {
|
||||
List<CanalEventDownStreamHandler> handlers = entrySink.getHandlers();
|
||||
int i = 0;
|
||||
for (; i < handlers.size(); i++) {
|
||||
if (handlers.get(i) instanceof PrometheusCanalEventDownStreamHandler) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
entrySink.removeHandler(i);
|
||||
// Ensure no PrometheusCanalEventDownStreamHandler
|
||||
handlers = entrySink.getHandlers();
|
||||
for (CanalEventDownStreamHandler handler : handlers) {
|
||||
if (handler instanceof PrometheusCanalEventDownStreamHandler) {
|
||||
throw new IllegalStateException("Multiple prometheusCanalEventDownStreamHandler exists in handlers.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EntryMetricsHolder {
|
||||
private AtomicLong latestExecTime;
|
||||
private AtomicLong transactionCounter;
|
||||
private AtomicLong rowEventCounter;
|
||||
private AtomicLong rowsCounter;
|
||||
private List<String> destLabelValues;
|
||||
}
|
||||
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.jctools.maps.ConcurrentAutoTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
@Aspect
|
||||
public class InboundThroughputAspect {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InboundThroughputAspect.class);
|
||||
|
||||
/**
|
||||
* Support highly scalable counters
|
||||
* @see ConcurrentAutoTable
|
||||
*/
|
||||
private static final ConcurrentAutoTable total = new ConcurrentAutoTable();
|
||||
|
||||
private static final Collector collector = new InboundThroughputCollector();
|
||||
|
||||
public static Collector getCollector() {
|
||||
return collector;
|
||||
}
|
||||
|
||||
@Pointcut("call(byte[] com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))")
|
||||
public void read() {}
|
||||
|
||||
@Pointcut("call(void com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..)) ")
|
||||
public void readBytes() {}
|
||||
|
||||
//nested read, just eliminate them.
|
||||
@Pointcut("withincode(* com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))")
|
||||
public void nestedCall() {}
|
||||
|
||||
@After("read() && !nestedCall() && args(len, ..)")
|
||||
public void recordRead(int len) {
|
||||
accumulateBytes(len);
|
||||
}
|
||||
|
||||
@After("readBytes() && !nestedCall() && args(.., len, timeout)")
|
||||
public void recordReadBytes(int len, int timeout) {
|
||||
accumulateBytes(len);
|
||||
}
|
||||
|
||||
private void accumulateBytes(int count) {
|
||||
try {
|
||||
total.add(count);
|
||||
} catch (Throwable t) {
|
||||
//Catch every Throwable, rather than break the business logic.
|
||||
logger.warn("Error while accumulate inbound bytes.", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InboundThroughputCollector extends Collector {
|
||||
|
||||
private InboundThroughputCollector() {}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
CounterMetricFamily bytes = new CounterMetricFamily("canal_net_inbound_bytes",
|
||||
"Total socket inbound bytes of canal server.",
|
||||
total.get());
|
||||
mfs.add(bytes);
|
||||
return mfs;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring;
|
||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||
import com.alibaba.otter.canal.prometheus.CanalInstanceExports;
|
||||
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.GaugeMetricFamily;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class InstanceMetaCollector extends Collector {
|
||||
|
||||
private static final List<String> InfoLabel = Arrays.asList("destination", "mode");
|
||||
|
||||
private CanalMetaManager metaManager;
|
||||
|
||||
private final String destination;
|
||||
|
||||
private final String mode;
|
||||
|
||||
private final String subsHelp;
|
||||
|
||||
public InstanceMetaCollector(CanalInstance instance) {
|
||||
if (instance == null) {
|
||||
throw new IllegalArgumentException("CanalInstance must not be null.");
|
||||
}
|
||||
if (instance instanceof CanalInstanceWithSpring) {
|
||||
mode = "spring";
|
||||
} else {
|
||||
mode = "manager";
|
||||
}
|
||||
this.metaManager = instance.getMetaManager();
|
||||
this.destination = instance.getDestination();
|
||||
this.subsHelp = "Subscriptions of canal instance " + destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
GaugeMetricFamily instanceInfo = new GaugeMetricFamily(
|
||||
"canal_instance",
|
||||
"Canal instance",
|
||||
InfoLabel);
|
||||
instanceInfo.addMetric(Arrays.asList(destination, mode), 1);
|
||||
mfs.add(instanceInfo);
|
||||
if (metaManager.isStart()) {
|
||||
// client id = hardcode 1001, 目前没有意义
|
||||
List<ClientIdentity> subs = metaManager.listAllSubscribeInfo(destination);
|
||||
GaugeMetricFamily subscriptions = new GaugeMetricFamily(
|
||||
"canal_instance_subscription",
|
||||
subsHelp, CanalInstanceExports.labelList);
|
||||
subscriptions.addMetric(Arrays.asList(destination), subs.size());
|
||||
mfs.add(subscriptions);
|
||||
}
|
||||
return mfs;
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.prometheus.CanalInstanceExports;
|
||||
import com.alibaba.otter.canal.store.CanalEventStore;
|
||||
import com.alibaba.otter.canal.store.CanalStoreException;
|
||||
import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class MemoryStoreCollector extends Collector {
|
||||
|
||||
private static final Class<MemoryEventStoreWithBuffer> clazz = MemoryEventStoreWithBuffer.class;
|
||||
|
||||
private final String destination;
|
||||
|
||||
private final AtomicLong putSequence;
|
||||
|
||||
private final AtomicLong ackSequence;
|
||||
|
||||
private final String putHelp;
|
||||
|
||||
private final String ackHelp;
|
||||
|
||||
public MemoryStoreCollector(CanalEventStore store, String destination) {
|
||||
this.destination = destination;
|
||||
if (!(store instanceof MemoryEventStoreWithBuffer)) {
|
||||
throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer");
|
||||
}
|
||||
MemoryEventStoreWithBuffer ms = (MemoryEventStoreWithBuffer) store;
|
||||
putSequence = getDeclaredValue(ms, "putSequence");
|
||||
ackSequence = getDeclaredValue(ms, "ackSequence");
|
||||
putHelp = "Produced sequence of canal instance " + destination;
|
||||
ackHelp = "Consumed sequence of canal instance " + destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
CounterMetricFamily put = new CounterMetricFamily("canal_instance_store_produce_seq",
|
||||
putHelp, Arrays.asList(CanalInstanceExports.labels));
|
||||
put.addMetric(Collections.singletonList(destination), putSequence.doubleValue());
|
||||
mfs.add(put);
|
||||
CounterMetricFamily ack = new CounterMetricFamily("canal_instance_store_consume_seq",
|
||||
ackHelp, Arrays.asList(CanalInstanceExports.labels));
|
||||
ack.addMetric(Collections.singletonList(destination), ackSequence.doubleValue());
|
||||
mfs.add(ack);
|
||||
return mfs;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getDeclaredValue(MemoryEventStoreWithBuffer store, String name) {
|
||||
T value;
|
||||
try {
|
||||
Field putField = clazz.getDeclaredField(name);
|
||||
putField.setAccessible(true);
|
||||
value = (T) putField.get(store);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new CanalStoreException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new CanalStoreException(e);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring;
|
||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||
import com.alibaba.otter.canal.prometheus.InstanceRegistry;
|
||||
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.GaugeMetricFamily;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class MetaCollector extends Collector implements InstanceRegistry {
|
||||
|
||||
private static final List<String> INFO_LABELS_LIST = Arrays.asList("destination", "mode");
|
||||
private static final Logger logger = LoggerFactory.getLogger(MetaCollector.class);
|
||||
private static final String INSTANCE = "canal_instance";
|
||||
private static final String INSTANCE_HELP = "Canal instance";
|
||||
private static final String SUBSCRIPTION = "canal_instance_subscriptions";
|
||||
private static final String SUBSCRIPTION_HELP = "Canal instance subscriptions";
|
||||
private final ConcurrentMap<String, MetaMetricsHolder> instances = new ConcurrentHashMap<String, MetaMetricsHolder>();
|
||||
|
||||
private MetaCollector() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final MetaCollector SINGLETON = new MetaCollector();
|
||||
}
|
||||
|
||||
public static MetaCollector instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
GaugeMetricFamily instanceInfo = new GaugeMetricFamily(INSTANCE,
|
||||
INSTANCE_HELP, INFO_LABELS_LIST);
|
||||
GaugeMetricFamily subsInfo = new GaugeMetricFamily(SUBSCRIPTION,
|
||||
SUBSCRIPTION_HELP, DEST_LABELS_LIST);
|
||||
for (Map.Entry<String, MetaMetricsHolder> nme : instances.entrySet()) {
|
||||
final String destination = nme.getKey();
|
||||
final MetaMetricsHolder nmh = nme.getValue();
|
||||
instanceInfo.addMetric(nmh.infoLabelValues, 1);
|
||||
List<ClientIdentity> subs = nmh.metaManager.listAllSubscribeInfo(destination);
|
||||
int count = subs == null ? 0 : subs.size();
|
||||
subsInfo.addMetric(nmh.destLabelValues, count);
|
||||
}
|
||||
mfs.add(instanceInfo);
|
||||
mfs.add(subsInfo);
|
||||
return mfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
MetaMetricsHolder holder = new MetaMetricsHolder();
|
||||
String mode = (instance instanceof CanalInstanceWithSpring) ? "spring" : "manager";
|
||||
holder.infoLabelValues = Arrays.asList(destination, mode);
|
||||
holder.destLabelValues = Collections.singletonList(destination);
|
||||
holder.metaManager = instance.getMetaManager();
|
||||
Preconditions.checkNotNull(holder.metaManager);
|
||||
MetaMetricsHolder old = instances.put(destination, holder);
|
||||
if (old != null) {
|
||||
logger.warn("Remove stale MetaCollector for instance {}.", destination);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
instances.remove(destination);
|
||||
}
|
||||
|
||||
private class MetaMetricsHolder {
|
||||
private List<String> infoLabelValues;
|
||||
private List<String> destLabelValues;
|
||||
private CanalMetaManager metaManager;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jctools.maps.ConcurrentAutoTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.otter.canal.server.netty.NettyUtils.HEADER_LENGTH;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
@Aspect
|
||||
public class OutboundThroughputAspect {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OutboundThroughputAspect.class);
|
||||
|
||||
/**
|
||||
* Support highly scalable counters
|
||||
* @see ConcurrentAutoTable
|
||||
*/
|
||||
private static final ConcurrentAutoTable total = new ConcurrentAutoTable();
|
||||
|
||||
private static final Collector collector = new OutboundThroughputCollector();
|
||||
|
||||
public static Collector getCollector() {
|
||||
return collector;
|
||||
}
|
||||
|
||||
@Pointcut("call(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))")
|
||||
public void write() {}
|
||||
|
||||
//nested read, just eliminate them.
|
||||
@Pointcut("withincode(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))")
|
||||
public void nestedCall() {}
|
||||
|
||||
@After("write() && !nestedCall() && args(ch, bytes, ..)")
|
||||
public void recordWriteBytes(Channel ch, byte[] bytes) {
|
||||
if (bytes != null) {
|
||||
accumulateBytes(HEADER_LENGTH + bytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
@After("write() && !nestedCall() && args(ch, buf, ..)")
|
||||
public void recordWriteBuffer(Channel ch, ByteBuffer buf) {
|
||||
if (buf != null) {
|
||||
total.add(HEADER_LENGTH + buf.limit());
|
||||
}
|
||||
}
|
||||
private void accumulateBytes(int count) {
|
||||
try {
|
||||
total.add(count);
|
||||
} catch (Throwable t) {
|
||||
//Catch every Throwable, rather than break the business logic.
|
||||
logger.warn("Error while accumulate inbound bytes.", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OutboundThroughputCollector extends Collector {
|
||||
|
||||
private OutboundThroughputCollector() {}
|
||||
|
||||
@Override public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
CounterMetricFamily bytes = new CounterMetricFamily("canal_net_outbound_bytes",
|
||||
"Total socket outbound bytes of canal server.",
|
||||
total.get());
|
||||
mfs.add(bytes);
|
||||
return mfs;
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||
import com.alibaba.otter.canal.prometheus.InstanceRegistry;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
import io.prometheus.client.GaugeMetricFamily;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST;
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST;
|
||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class ParserCollector extends Collector implements InstanceRegistry {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ParserCollector.class);
|
||||
private static final long NANO_PER_MILLI = 1000 * 1000L;
|
||||
private static final String PUBLISH_BLOCKING = "canal_instance_publish_blocking_time";
|
||||
private static final String RECEIVED_BINLOG = "canal_instance_received_binlog_bytes";
|
||||
private static final String PARSER_MODE = "canal_instance_parser_mode";
|
||||
private static final String MODE_LABEL = "parallel";
|
||||
private static final String PUBLISH_BLOCKING_HELP = "Publish blocking time of dump thread in milliseconds";
|
||||
private static final String RECEIVED_BINLOG_HELP = "Received binlog bytes";
|
||||
private static final String MODE_HELP = "Parser mode(parallel/serial) of instance";
|
||||
private final List<String> modeLabels = Arrays.asList(DEST, MODE_LABEL);
|
||||
private final ConcurrentMap<String, ParserMetricsHolder> instances = new ConcurrentHashMap<String, ParserMetricsHolder>();
|
||||
|
||||
private ParserCollector() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final ParserCollector SINGLETON = new ParserCollector();
|
||||
}
|
||||
|
||||
public static ParserCollector instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
boolean hasParallel = false;
|
||||
CounterMetricFamily bytesCounter = new CounterMetricFamily(RECEIVED_BINLOG,
|
||||
RECEIVED_BINLOG_HELP, DEST_LABELS_LIST);
|
||||
GaugeMetricFamily modeGauge = new GaugeMetricFamily(PARSER_MODE,
|
||||
MODE_HELP, modeLabels);
|
||||
CounterMetricFamily blockingCounter = new CounterMetricFamily(PUBLISH_BLOCKING,
|
||||
PUBLISH_BLOCKING_HELP, DEST_LABELS_LIST);
|
||||
for (ParserMetricsHolder emh : instances.values()) {
|
||||
if (emh.isParallel) {
|
||||
blockingCounter.addMetric(emh.destLabelValues, (emh.eventsPublishBlockingTime.doubleValue() / NANO_PER_MILLI));
|
||||
hasParallel = true;
|
||||
}
|
||||
modeGauge.addMetric(emh.modeLabelValues, 1);
|
||||
bytesCounter.addMetric(emh.destLabelValues, emh.receivedBinlogBytes.doubleValue());
|
||||
|
||||
}
|
||||
mfs.add(bytesCounter);
|
||||
mfs.add(modeGauge);
|
||||
if (hasParallel) {
|
||||
mfs.add(blockingCounter);
|
||||
}
|
||||
return mfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
ParserMetricsHolder holder = new ParserMetricsHolder();
|
||||
CanalEventParser parser = instance.getEventParser();
|
||||
if (!(parser instanceof MysqlEventParser)) {
|
||||
throw new IllegalArgumentException("CanalEventParser must be MysqlEventParser");
|
||||
}
|
||||
MysqlEventParser mysqlParser = (MysqlEventParser) parser;
|
||||
holder.destLabelValues = Collections.singletonList(destination);
|
||||
holder.modeLabelValues = Arrays.asList(destination, Boolean.toString(mysqlParser.isParallel()));
|
||||
holder.eventsPublishBlockingTime = mysqlParser.getEventsPublishBlockingTime();
|
||||
holder.receivedBinlogBytes = mysqlParser.getReceivedBinlogBytes();
|
||||
holder.isParallel = mysqlParser.isParallel();
|
||||
Preconditions.checkNotNull(holder.eventsPublishBlockingTime);
|
||||
Preconditions.checkNotNull(holder.receivedBinlogBytes);
|
||||
ParserMetricsHolder old = instances.put(destination, holder);
|
||||
if (old != null) {
|
||||
logger.warn("Remove stale ParserCollector for instance {}.", destination);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
instances.remove(destination);
|
||||
}
|
||||
|
||||
private class ParserMetricsHolder {
|
||||
private List<String> destLabelValues;
|
||||
private List<String> modeLabelValues;
|
||||
private AtomicLong receivedBinlogBytes;
|
||||
private AtomicLong eventsPublishBlockingTime;
|
||||
private boolean isParallel;
|
||||
}
|
||||
|
||||
}
|
||||
+66
-49
@@ -1,73 +1,90 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.prometheus.CanalInstanceExports;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||
import com.alibaba.otter.canal.sink.AbstractCanalEventDownStreamHandler;
|
||||
import com.alibaba.otter.canal.store.model.Event;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.GaugeMetricFamily;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler<List<Event>> {
|
||||
|
||||
private final Collector collector;
|
||||
|
||||
private long latestExecuteTime = 0L;
|
||||
|
||||
private static final String DELAY_NAME = "canal_instance_traffic_delay";
|
||||
|
||||
private final String delayHelpName;
|
||||
|
||||
private final List<String> labelValues;
|
||||
|
||||
public PrometheusCanalEventDownStreamHandler(final String destination) {
|
||||
this.delayHelpName = "Traffic delay of canal instance " + destination + " in seconds.";
|
||||
this.labelValues = Collections.singletonList(destination);
|
||||
collector = new Collector() {
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
long now = System.currentTimeMillis();
|
||||
GaugeMetricFamily delay = new GaugeMetricFamily(
|
||||
DELAY_NAME,
|
||||
delayHelpName,
|
||||
CanalInstanceExports.labelList);
|
||||
double d = 0.0;
|
||||
if (latestExecuteTime > 0) {
|
||||
d = now - latestExecuteTime;
|
||||
}
|
||||
d = d > 0.0 ? (d / 1000) : 0.0;
|
||||
delay.addMetric(labelValues, d);
|
||||
mfs.add(delay);
|
||||
return mfs;
|
||||
}
|
||||
};
|
||||
}
|
||||
private final AtomicLong latestExecuteTime = new AtomicLong(0L);
|
||||
private final AtomicLong transactionCounter = new AtomicLong(0L);
|
||||
private final AtomicLong rowEventCounter = new AtomicLong(0L);
|
||||
private final AtomicLong rowsCounter = new AtomicLong(0L);
|
||||
|
||||
@Override
|
||||
public List<Event> before(List<Event> events) {
|
||||
// TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming
|
||||
// see: https://dev.mysql.com/worklog/task/?id=342
|
||||
// heartbeats are sent by the master only if there is no
|
||||
// more unsent events in the actual binlog file for a period longer that
|
||||
// master_heartbeat_period.
|
||||
long localExecTime = 0L;
|
||||
if (events != null && !events.isEmpty()) {
|
||||
Event last = events.get(events.size() - 1);
|
||||
long ts = last.getExecuteTime();
|
||||
if (ts > latestExecuteTime) {
|
||||
latestExecuteTime = ts;
|
||||
for (Event e : events) {
|
||||
EntryType type = e.getEntryType();
|
||||
if (type == null) continue;
|
||||
switch (type) {
|
||||
case TRANSACTIONBEGIN: {
|
||||
long exec = e.getExecuteTime();
|
||||
if (exec > 0) localExecTime = exec;
|
||||
break;
|
||||
}
|
||||
case ROWDATA: {
|
||||
long exec = e.getExecuteTime();
|
||||
if (exec > 0) localExecTime = exec;
|
||||
rowEventCounter.incrementAndGet();
|
||||
rowsCounter.addAndGet(e.getRowsCount());
|
||||
break;
|
||||
}
|
||||
case TRANSACTIONEND: {
|
||||
long exec = e.getExecuteTime();
|
||||
if (exec > 0) localExecTime = exec;
|
||||
transactionCounter.incrementAndGet();
|
||||
break;
|
||||
}
|
||||
case HEARTBEAT:
|
||||
CanalEntry.EventType eventType = e.getEventType();
|
||||
if (eventType == CanalEntry.EventType.MHEARTBEAT) {
|
||||
localExecTime = System.currentTimeMillis();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (localExecTime > 0) {
|
||||
latestExecuteTime.lazySet(localExecTime);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public Collector getCollector() {
|
||||
return this.collector;
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public AtomicLong getLatestExecuteTime() {
|
||||
return latestExecuteTime;
|
||||
}
|
||||
|
||||
public AtomicLong getTransactionCounter() {
|
||||
return transactionCounter;
|
||||
}
|
||||
|
||||
public AtomicLong getRowsCounter() {
|
||||
return rowsCounter;
|
||||
}
|
||||
|
||||
public AtomicLong getRowEventCounter() {
|
||||
return rowEventCounter;
|
||||
}
|
||||
}
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.protocol.CanalPacket.PacketType;
|
||||
import com.alibaba.otter.canal.server.netty.ClientInstanceProfiler;
|
||||
import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult;
|
||||
import io.prometheus.client.CollectorRegistry;
|
||||
import io.prometheus.client.Counter;
|
||||
import io.prometheus.client.Histogram;
|
||||
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST;
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler {
|
||||
|
||||
private static final long NANO_PER_MILLI = 1000 * 1000L;
|
||||
private static final String PACKET_TYPE = "canal_instance_client_packets";
|
||||
private static final String OUTBOUND_BYTES = "canal_instance_client_bytes";
|
||||
private static final String EMPTY_BATCHES = "canal_instance_client_empty_batches";
|
||||
private static final String ERRORS = "canal_instance_client_request_error";
|
||||
private static final String LATENCY = "canal_instance_client_request_latency";
|
||||
private final Counter outboundCounter;
|
||||
private final Counter packetsCounter;
|
||||
private final Counter emptyBatchesCounter;
|
||||
private final Counter errorsCounter;
|
||||
private final Histogram responseLatency;
|
||||
private volatile boolean running = false;
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final PrometheusClientInstanceProfiler SINGLETON = new PrometheusClientInstanceProfiler();
|
||||
}
|
||||
|
||||
public static PrometheusClientInstanceProfiler instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
private PrometheusClientInstanceProfiler() {
|
||||
this.outboundCounter = Counter.build()
|
||||
.labelNames(DEST_LABELS)
|
||||
.name(OUTBOUND_BYTES)
|
||||
.help("Total bytes sent to client.")
|
||||
.create();
|
||||
this.packetsCounter = Counter.build()
|
||||
.labelNames(new String[]{DEST, "packetType"})
|
||||
.name(PACKET_TYPE)
|
||||
.help("Total packets sent to client.")
|
||||
.create();
|
||||
this.emptyBatchesCounter = Counter.build()
|
||||
.labelNames(DEST_LABELS)
|
||||
.name(EMPTY_BATCHES)
|
||||
.help("Total empty batches sent to client.")
|
||||
.create();
|
||||
this.errorsCounter = Counter.build()
|
||||
.labelNames(new String[]{DEST, "errorCode"})
|
||||
.name(ERRORS)
|
||||
.help("Total client request errors.")
|
||||
.create();
|
||||
this.responseLatency = Histogram.build()
|
||||
.labelNames(DEST_LABELS)
|
||||
.name(LATENCY)
|
||||
.help("Client request latency.")
|
||||
// buckets in milliseconds
|
||||
.buckets(2.5, 10.0, 25.0, 100.0)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profiling(ClientRequestResult result) {
|
||||
String destination = result.getDestination();
|
||||
PacketType type = result.getType();
|
||||
outboundCounter.labels(destination).inc(result.getAmount());
|
||||
packetsCounter.labels(destination, type.name()).inc();
|
||||
short errorCode = result.getErrorCode();
|
||||
if (errorCode > 0) {
|
||||
errorsCounter.labels(destination, Short.toString(errorCode)).inc();
|
||||
}
|
||||
long latency = result.getLatency();
|
||||
responseLatency.labels(destination).observe(((double) latency) / NANO_PER_MILLI);
|
||||
switch (type) {
|
||||
case GET:
|
||||
boolean empty = result.getEmpty();
|
||||
if (empty) {
|
||||
emptyBatchesCounter.labels(destination).inc();
|
||||
}
|
||||
break;
|
||||
// reserve for others
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (outboundCounter != null) {
|
||||
outboundCounter.register();
|
||||
}
|
||||
if (packetsCounter != null) {
|
||||
packetsCounter.register();
|
||||
}
|
||||
if (emptyBatchesCounter != null) {
|
||||
emptyBatchesCounter.register();
|
||||
}
|
||||
if (errorsCounter != null) {
|
||||
errorsCounter.register();
|
||||
}
|
||||
if (responseLatency != null) {
|
||||
responseLatency.register();
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (outboundCounter != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(outboundCounter);
|
||||
}
|
||||
if (packetsCounter != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(packetsCounter);
|
||||
}
|
||||
if (emptyBatchesCounter != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(emptyBatchesCounter);
|
||||
}
|
||||
if (errorsCounter != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(errorsCounter);
|
||||
}
|
||||
if (responseLatency != null) {
|
||||
CollectorRegistry.defaultRegistry.unregister(responseLatency);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStart() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.prometheus.InstanceRegistry;
|
||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST;
|
||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class SinkCollector extends Collector implements InstanceRegistry {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class);
|
||||
private static final long NANO_PER_MILLI = 1000 * 1000L;
|
||||
private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time";
|
||||
private static final String SINK_BLOCK_TIME_HELP = "Total sink blocking time in milliseconds";
|
||||
private final ConcurrentMap<String, SinkMetricsHolder> instances = new ConcurrentHashMap<String, SinkMetricsHolder>();
|
||||
|
||||
private SinkCollector() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final SinkCollector SINGLETON = new SinkCollector();
|
||||
}
|
||||
|
||||
public static SinkCollector instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
CounterMetricFamily blockingCounter = new CounterMetricFamily(SINK_BLOCKING_TIME,
|
||||
SINK_BLOCK_TIME_HELP, DEST_LABELS_LIST);
|
||||
for (SinkMetricsHolder smh : instances.values()) {
|
||||
blockingCounter.addMetric(smh.destLabelValues, (smh.eventsSinkBlockingTime.doubleValue() / NANO_PER_MILLI));
|
||||
}
|
||||
mfs.add(blockingCounter);
|
||||
return mfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
SinkMetricsHolder holder = new SinkMetricsHolder();
|
||||
holder.destLabelValues = Collections.singletonList(destination);
|
||||
CanalEventSink sink = instance.getEventSink();
|
||||
if (!(sink instanceof EntryEventSink)) {
|
||||
throw new IllegalArgumentException("CanalEventSink must be EntryEventSink");
|
||||
}
|
||||
EntryEventSink entrySink = (EntryEventSink) sink;
|
||||
holder.eventsSinkBlockingTime = entrySink.getEventsSinkBlockingTime();
|
||||
Preconditions.checkNotNull(holder.eventsSinkBlockingTime);
|
||||
SinkMetricsHolder old = instances.put(destination, holder);
|
||||
if (old != null) {
|
||||
logger.warn("Remote stale SinkCollector for instance {}.", destination);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
instances.remove(destination);
|
||||
}
|
||||
|
||||
private class SinkMetricsHolder {
|
||||
private AtomicLong eventsSinkBlockingTime;
|
||||
private List<String> destLabelValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.alibaba.otter.canal.prometheus.impl;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.prometheus.InstanceRegistry;
|
||||
import com.alibaba.otter.canal.store.CanalEventStore;
|
||||
import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer;
|
||||
import com.alibaba.otter.canal.store.model.BatchMode;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.prometheus.client.Collector;
|
||||
import io.prometheus.client.CounterMetricFamily;
|
||||
import io.prometheus.client.GaugeMetricFamily;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST;
|
||||
import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class StoreCollector extends Collector implements InstanceRegistry {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class);
|
||||
private static final String PRODUCE = "canal_instance_store_produce_seq";
|
||||
private static final String CONSUME = "canal_instance_store_consume_seq";
|
||||
private static final String STORE = "canal_instance_store";
|
||||
private static final String PRODUCE_MEM = "canal_instance_store_produce_mem";
|
||||
private static final String CONSUME_MEM = "canal_instance_store_consume_mem";
|
||||
private static final String PRODUCE_HELP = "Produced events counter of canal instance";
|
||||
private static final String CONSUME_HELP = "Consumed events counter of canal instance";
|
||||
private static final String STORE_HELP = "Canal instance info";
|
||||
private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance";
|
||||
private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance";
|
||||
private final ConcurrentMap<String, StoreMetricsHolder> instances = new ConcurrentHashMap<String, StoreMetricsHolder>();
|
||||
private final List<String> storeLabelsList = Arrays.asList(DEST, "batchMode");
|
||||
|
||||
private StoreCollector() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final StoreCollector SINGLETON = new StoreCollector();
|
||||
}
|
||||
|
||||
public static StoreCollector instance() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricFamilySamples> collect() {
|
||||
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
|
||||
CounterMetricFamily put = new CounterMetricFamily(PRODUCE,
|
||||
PRODUCE_HELP, DEST_LABELS_LIST);
|
||||
CounterMetricFamily ack = new CounterMetricFamily(CONSUME,
|
||||
CONSUME_HELP, DEST_LABELS_LIST);
|
||||
GaugeMetricFamily store = new GaugeMetricFamily(STORE,
|
||||
STORE_HELP, storeLabelsList);
|
||||
CounterMetricFamily putMem = new CounterMetricFamily(PRODUCE_MEM,
|
||||
PRODUCE_MEM_HELP, DEST_LABELS_LIST);
|
||||
CounterMetricFamily ackMem = new CounterMetricFamily(CONSUME_MEM,
|
||||
CONSUME_MEM_HELP, DEST_LABELS_LIST);
|
||||
boolean hasMem = false;
|
||||
for (StoreMetricsHolder smh : instances.values()) {
|
||||
final boolean isMem = smh.batchMode.isMemSize();
|
||||
put.addMetric(smh.destLabelValues, smh.putSeq.doubleValue());
|
||||
ack.addMetric(smh.destLabelValues, smh.ackSeq.doubleValue());
|
||||
store.addMetric(smh.storeLabelValues, 1);
|
||||
if (isMem) {
|
||||
hasMem = true;
|
||||
putMem.addMetric(smh.destLabelValues, smh.putMemSize.doubleValue());
|
||||
ackMem.addMetric(smh.destLabelValues, smh.ackMemSize.doubleValue());
|
||||
}
|
||||
}
|
||||
mfs.add(put);
|
||||
mfs.add(ack);
|
||||
mfs.add(store);
|
||||
if (hasMem) {
|
||||
mfs.add(putMem);
|
||||
mfs.add(ackMem);
|
||||
}
|
||||
return mfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
StoreMetricsHolder holder = new StoreMetricsHolder();
|
||||
CanalEventStore store = instance.getEventStore();
|
||||
if (!(store instanceof MemoryEventStoreWithBuffer)) {
|
||||
throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer");
|
||||
}
|
||||
MemoryEventStoreWithBuffer memStore = (MemoryEventStoreWithBuffer) store;
|
||||
holder.batchMode = memStore.getBatchMode();
|
||||
holder.putSeq = memStore.getPutSequence();
|
||||
holder.ackSeq = memStore.getAckSequence();
|
||||
holder.destLabelValues = Collections.singletonList(destination);
|
||||
holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name());
|
||||
Preconditions.checkNotNull(holder.batchMode);
|
||||
Preconditions.checkNotNull(holder.putSeq);
|
||||
Preconditions.checkNotNull(holder.ackSeq);
|
||||
if (holder.batchMode.isMemSize()) {
|
||||
holder.putMemSize = memStore.getPutMemSize();
|
||||
holder.ackMemSize = memStore.getAckMemSize();
|
||||
Preconditions.checkNotNull(holder.putMemSize);
|
||||
Preconditions.checkNotNull(holder.ackMemSize);
|
||||
}
|
||||
StoreMetricsHolder old = instances.putIfAbsent(destination, holder);
|
||||
if (old != null) {
|
||||
logger.warn("Remote stale StoreCollector for instance {}.", destination);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(CanalInstance instance) {
|
||||
final String destination = instance.getDestination();
|
||||
instances.remove(destination);
|
||||
}
|
||||
|
||||
private class StoreMetricsHolder {
|
||||
private AtomicLong putSeq;
|
||||
private AtomicLong ackSeq;
|
||||
private BatchMode batchMode;
|
||||
private AtomicLong putMemSize;
|
||||
private AtomicLong ackMemSize;
|
||||
private List<String> destLabelValues;
|
||||
private List<String> storeLabelValues;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<aspectj>
|
||||
|
||||
<aspects>
|
||||
<aspect name="com.alibaba.otter.canal.prometheus.impl.InboundThroughputAspect"/>
|
||||
<aspect name="com.alibaba.otter.canal.prometheus.impl.OutboundThroughputAspect"/>
|
||||
</aspects>
|
||||
<weaver options="-verbose -showWeaveInfo">
|
||||
<include within="com.alibaba.otter.canal..*"/>
|
||||
</weaver>
|
||||
|
||||
</aspectj>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -194,6 +194,8 @@ enum EventType {
|
||||
/** XA **/
|
||||
XACOMMIT = 13;
|
||||
XAROLLBACK = 14;
|
||||
/** MASTER HEARTBEAT **/
|
||||
MHEARTBEAT = 15;
|
||||
}
|
||||
|
||||
/**数据库类型**/
|
||||
@@ -201,4 +203,4 @@ enum Type {
|
||||
ORACLE = 1;
|
||||
MYSQL = 2;
|
||||
PGSQL = 3;
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.alibaba.otter.canal.server.netty;
|
||||
|
||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||
import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class CanalServerWithNettyProfiler {
|
||||
|
||||
public static final ClientInstanceProfiler NOP = new DefaultClientInstanceProfiler();
|
||||
private ClientInstanceProfiler instanceProfiler;
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static CanalServerWithNettyProfiler SINGLETON = new CanalServerWithNettyProfiler();
|
||||
}
|
||||
|
||||
private CanalServerWithNettyProfiler() {
|
||||
this.instanceProfiler = NOP;
|
||||
}
|
||||
|
||||
public static CanalServerWithNettyProfiler profiler() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
public void profiling(ClientRequestResult result) {
|
||||
instanceProfiler.profiling(result);
|
||||
}
|
||||
|
||||
public void setInstanceProfiler(ClientInstanceProfiler instanceProfiler) {
|
||||
this.instanceProfiler = instanceProfiler;
|
||||
}
|
||||
|
||||
private static class DefaultClientInstanceProfiler extends AbstractCanalLifeCycle implements ClientInstanceProfiler {
|
||||
@Override
|
||||
public void profiling(ClientRequestResult result) {}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.alibaba.otter.canal.server.netty;
|
||||
|
||||
import com.alibaba.otter.canal.common.CanalLifeCycle;
|
||||
import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public interface ClientInstanceProfiler extends CanalLifeCycle {
|
||||
|
||||
void profiling(ClientRequestResult result);
|
||||
|
||||
}
|
||||
@@ -74,4 +74,20 @@ public class NettyUtils {
|
||||
.toByteArray(),
|
||||
channelFutureListener);
|
||||
}
|
||||
|
||||
public static byte[] ackPacket() {
|
||||
return Packet.newBuilder()
|
||||
.setType(CanalPacket.PacketType.ACK)
|
||||
.setBody(Ack.newBuilder().build().toByteString())
|
||||
.build()
|
||||
.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] errorPacket(int errorCode, String errorMessage) {
|
||||
return Packet.newBuilder()
|
||||
.setType(CanalPacket.PacketType.ACK)
|
||||
.setBody(Ack.newBuilder().setErrorCode(errorCode).setErrorMessage(errorMessage).build().toByteString())
|
||||
.build()
|
||||
.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ public class ClientAuthenticationHandler extends SimpleChannelHandler {
|
||||
MDC.remove("destination");
|
||||
}
|
||||
}
|
||||
|
||||
// 鉴权一次性,暂不统计
|
||||
NettyUtils.ack(ctx.getChannel(), new ChannelFutureListener() {
|
||||
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
|
||||
+38
-37
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.server.netty.handler;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
@@ -51,6 +52,7 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||
logger.info("message receives in session handler...");
|
||||
long start = System.nanoTime();
|
||||
ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
|
||||
Packet packet = Packet.parseFrom(buffer.readBytes(buffer.readableBytes()).array());
|
||||
ClientIdentity clientIdentity = null;
|
||||
@@ -74,12 +76,13 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
|
||||
embeddedServer.subscribe(clientIdentity);
|
||||
// ctx.setAttachment(clientIdentity);// 设置状态数据
|
||||
NettyUtils.ack(ctx.getChannel(), null);
|
||||
byte[] ackBytes = NettyUtils.ackPacket();
|
||||
NettyUtils.write(ctx.getChannel(), ackBytes, new ChannelFutureAggregator(sub.getDestination(),
|
||||
sub, packet.getType(), ackBytes.length, System.nanoTime() - start));
|
||||
} else {
|
||||
NettyUtils.error(401,
|
||||
MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes ,new ChannelFutureAggregator(sub.getDestination(),
|
||||
sub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
|
||||
}
|
||||
break;
|
||||
case UNSUBSCRIPTION:
|
||||
@@ -91,12 +94,13 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
MDC.put("destination", clientIdentity.getDestination());
|
||||
embeddedServer.unsubscribe(clientIdentity);
|
||||
stopCanalInstanceIfNecessary(clientIdentity);// 尝试关闭
|
||||
NettyUtils.ack(ctx.getChannel(), null);
|
||||
byte[] ackBytes = NettyUtils.ackPacket();
|
||||
NettyUtils.write(ctx.getChannel(), ackBytes, new ChannelFutureAggregator(unsub.getDestination(),
|
||||
unsub, packet.getType(), ackBytes.length, System.nanoTime() - start));
|
||||
} else {
|
||||
NettyUtils.error(401,
|
||||
MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(unsub.getDestination(),
|
||||
unsub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
|
||||
}
|
||||
break;
|
||||
case GET:
|
||||
@@ -171,7 +175,8 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
output.writeBytes(2, rowEntries.get(i));
|
||||
}
|
||||
output.checkNoSpaceLeft();
|
||||
NettyUtils.write(ctx.getChannel(), body, null);
|
||||
NettyUtils.write(ctx.getChannel(), body, new ChannelFutureAggregator(get.getDestination(),
|
||||
get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1));
|
||||
|
||||
// output.flush();
|
||||
// byteBuffer.flip();
|
||||
@@ -192,14 +197,14 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
packetBuilder.setBody(messageBuilder.build().toByteString());
|
||||
NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据
|
||||
byte[] body = packetBuilder.setBody(messageBuilder.build().toByteString()).build().toByteArray();
|
||||
NettyUtils.write(ctx.getChannel(), body, new ChannelFutureAggregator(get.getDestination(),
|
||||
get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1));// 输出数据
|
||||
}
|
||||
} else {
|
||||
NettyUtils.error(401,
|
||||
MessageFormatter.format("destination or clientId is null", get.toString()).getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", get.toString()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(get.getDestination(),
|
||||
get, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
|
||||
}
|
||||
break;
|
||||
case CLIENTACK:
|
||||
@@ -207,10 +212,9 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
MDC.put("destination", ack.getDestination());
|
||||
if (StringUtils.isNotEmpty(ack.getDestination()) && StringUtils.isNotEmpty(ack.getClientId())) {
|
||||
if (ack.getBatchId() == 0L) {
|
||||
NettyUtils.error(402,
|
||||
MessageFormatter.format("batchId should assign value", ack.toString()).getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(402, MessageFormatter.format("batchId should assign value", ack.toString()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(),
|
||||
ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 402));
|
||||
} else if (ack.getBatchId() == -1L) { // -1代表上一次get没有数据,直接忽略之
|
||||
// donothing
|
||||
} else {
|
||||
@@ -218,10 +222,9 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
embeddedServer.ack(clientIdentity, ack.getBatchId());
|
||||
}
|
||||
} else {
|
||||
NettyUtils.error(401,
|
||||
MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(),
|
||||
ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
|
||||
}
|
||||
break;
|
||||
case CLIENTROLLBACK:
|
||||
@@ -237,25 +240,23 @@ public class SessionHandler extends SimpleChannelHandler {
|
||||
embeddedServer.rollback(clientIdentity, rollback.getBatchId()); // 只回滚单个批次
|
||||
}
|
||||
} else {
|
||||
NettyUtils.error(401,
|
||||
MessageFormatter.format("destination or clientId is null", rollback.toString())
|
||||
.getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", rollback.toString()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(rollback.getDestination(),
|
||||
rollback, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
NettyUtils.error(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType())
|
||||
.getMessage(), ctx.getChannel(), null);
|
||||
byte[] errorBytes = NettyUtils.errorPacket(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType()).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ctx.getChannel().getRemoteAddress().toString(),
|
||||
null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400));
|
||||
break;
|
||||
}
|
||||
} catch (Throwable exception) {
|
||||
NettyUtils.error(400,
|
||||
MessageFormatter.format("something goes wrong with channel:{}, exception={}",
|
||||
byte[] errorBytes = NettyUtils.errorPacket(400, MessageFormatter.format("something goes wrong with channel:{}, exception={}",
|
||||
ctx.getChannel(),
|
||||
ExceptionUtils.getStackTrace(exception)).getMessage(),
|
||||
ctx.getChannel(),
|
||||
null);
|
||||
ExceptionUtils.getStackTrace(exception)).getMessage());
|
||||
NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ctx.getChannel().getRemoteAddress().toString(),
|
||||
null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400));
|
||||
} finally {
|
||||
MDC.remove("destination");
|
||||
}
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package com.alibaba.otter.canal.server.netty.listener;
|
||||
|
||||
import com.alibaba.otter.canal.protocol.CanalPacket;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.protobuf.GeneratedMessage;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
|
||||
import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler;
|
||||
|
||||
/**
|
||||
* @author Chuanyi Li
|
||||
*/
|
||||
public class ChannelFutureAggregator implements ChannelFutureListener {
|
||||
|
||||
private ClientRequestResult result;
|
||||
|
||||
public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, boolean empty) {
|
||||
this(destination, request, type, amount, latency, empty, (short) 0);
|
||||
}
|
||||
|
||||
public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency) {
|
||||
this(destination, request, type, amount, latency, false, (short) 0);
|
||||
}
|
||||
|
||||
public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, short errorCode) {
|
||||
this(destination, request, type, amount, latency, false, errorCode);
|
||||
}
|
||||
|
||||
private ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, boolean empty, short errorCode) {
|
||||
this.result = new ClientRequestResult.Builder()
|
||||
.destination(destination)
|
||||
.type(type)
|
||||
.request(request)
|
||||
.amount(amount)
|
||||
.latency(latency)
|
||||
.errorCode(errorCode)
|
||||
.empty(empty)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
// profiling after I/O operation
|
||||
if (future.getCause() != null) {
|
||||
result.channelError = future.getCause();
|
||||
}
|
||||
profiler().profiling(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client request result pojo
|
||||
*/
|
||||
public static class ClientRequestResult {
|
||||
|
||||
private String destination;
|
||||
private CanalPacket.PacketType type;
|
||||
private GeneratedMessage request;
|
||||
private int amount;
|
||||
private long latency;
|
||||
private short errorCode;
|
||||
private boolean empty;
|
||||
private Throwable channelError;
|
||||
|
||||
private ClientRequestResult() {}
|
||||
|
||||
private ClientRequestResult(Builder builder) {
|
||||
this.destination = Preconditions.checkNotNull(builder.destination);
|
||||
this.type = Preconditions.checkNotNull(builder.type);
|
||||
this.request = builder.request;
|
||||
this.amount = builder.amount;
|
||||
this.latency = builder.latency;
|
||||
this.errorCode = builder.errorCode;
|
||||
this.empty = builder.empty;
|
||||
this.channelError = builder.channelError;
|
||||
}
|
||||
|
||||
// auto-generated
|
||||
public static class Builder {
|
||||
|
||||
private String destination;
|
||||
private CanalPacket.PacketType type;
|
||||
private GeneratedMessage request;
|
||||
private int amount;
|
||||
private long latency;
|
||||
private short errorCode;
|
||||
private boolean empty;
|
||||
private Throwable channelError;
|
||||
|
||||
Builder destination(String destination) {
|
||||
this.destination = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder type(CanalPacket.PacketType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder request(GeneratedMessage request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder amount(int amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder latency(long latency) {
|
||||
this.latency = latency;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder errorCode(short errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder empty(boolean empty) {
|
||||
this.empty = empty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder channelError(Throwable channelError) {
|
||||
this.channelError = channelError;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fromPrototype(ClientRequestResult prototype) {
|
||||
destination = prototype.destination;
|
||||
type = prototype.type;
|
||||
request = prototype.request;
|
||||
amount = prototype.amount;
|
||||
latency = prototype.latency;
|
||||
errorCode = prototype.errorCode;
|
||||
empty = prototype.empty;
|
||||
channelError = prototype.channelError;
|
||||
return this;
|
||||
}
|
||||
|
||||
ClientRequestResult build() {
|
||||
return new ClientRequestResult(this);
|
||||
}
|
||||
}
|
||||
// getters
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public CanalPacket.PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public GeneratedMessage getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public long getLatency() {
|
||||
return latency;
|
||||
}
|
||||
|
||||
public short getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public boolean getEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
public Throwable getChannelError() {
|
||||
return channelError;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
|
||||
protected long emptyTransctionThresold = 8192; // 超过1024个事务头,输出一个
|
||||
protected volatile long lastEmptyTransactionTimestamp = 0L;
|
||||
protected AtomicLong lastEmptyTransactionCount = new AtomicLong(0L);
|
||||
private AtomicLong eventsSinkBlockingTime = new AtomicLong(0L);
|
||||
|
||||
public EntryEventSink(){
|
||||
addHandler(new HeartBeatEntryEventHandler());
|
||||
@@ -147,16 +148,27 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
|
||||
for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) {
|
||||
events = handler.before(events);
|
||||
}
|
||||
|
||||
long blockingStart = 0L;
|
||||
int fullTimes = 0;
|
||||
do {
|
||||
if (eventStore.tryPut(events)) {
|
||||
if (fullTimes > 0) {
|
||||
eventsSinkBlockingTime.addAndGet(System.nanoTime() - blockingStart);
|
||||
}
|
||||
for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) {
|
||||
events = handler.after(events);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (fullTimes == 0) {
|
||||
blockingStart = System.nanoTime();
|
||||
}
|
||||
applyWait(++fullTimes);
|
||||
if (fullTimes % 100 == 0) {
|
||||
long nextStart = System.nanoTime();
|
||||
eventsSinkBlockingTime.addAndGet(nextStart - blockingStart);
|
||||
blockingStart = nextStart;
|
||||
}
|
||||
}
|
||||
|
||||
for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) {
|
||||
@@ -202,4 +214,8 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
|
||||
this.emptyTransctionThresold = emptyTransctionThresold;
|
||||
}
|
||||
|
||||
public AtomicLong getEventsSinkBlockingTime() {
|
||||
return eventsSinkBlockingTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+19
@@ -562,4 +562,23 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple
|
||||
this.ddlIsolation = ddlIsolation;
|
||||
}
|
||||
|
||||
public AtomicLong getPutSequence() {
|
||||
return putSequence;
|
||||
}
|
||||
|
||||
public AtomicLong getAckSequence() {
|
||||
return ackSequence;
|
||||
}
|
||||
|
||||
public AtomicLong getPutMemSize() {
|
||||
return putMemSize;
|
||||
}
|
||||
|
||||
public AtomicLong getAckMemSize() {
|
||||
return ackMemSize;
|
||||
}
|
||||
|
||||
public BatchMode getBatchMode() {
|
||||
return batchMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.alibaba.otter.canal.store.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
@@ -31,6 +32,7 @@ public class Event implements Serializable {
|
||||
private EventType eventType;
|
||||
private String gtid;
|
||||
private long rawLength;
|
||||
private int rowsCount;
|
||||
|
||||
public Event(){
|
||||
}
|
||||
@@ -47,6 +49,17 @@ public class Event implements Serializable {
|
||||
// build raw
|
||||
this.rawEntry = entry.toByteString();
|
||||
this.rawLength = rawEntry.size();
|
||||
if (entryType == EntryType.ROWDATA) {
|
||||
List<CanalEntry.Pair> props = entry.getHeader().getPropsList();
|
||||
if (props != null) {
|
||||
for (CanalEntry.Pair p : props) {
|
||||
if ("rowsCount".equals(p.getKey())) {
|
||||
rowsCount = Integer.parseInt(p.getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LogIdentity getLogIdentity() {
|
||||
@@ -129,7 +142,16 @@ public class Event implements Serializable {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public int getRowsCount() {
|
||||
return rowsCount;
|
||||
}
|
||||
|
||||
public void setRowsCount(int rowsCount) {
|
||||
this.rowsCount = rowsCount;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user