Compare commits

..

30 Commits

Author SHA1 Message Date
lcybo
67a3a1d955 Merge pull request #827 from lcybo/metrics_support
Merge master updates
2018-08-09 22:50:32 +08:00
Chuanyi Li
a857b87388 Merge branch 'master' into metrics_support 2018-08-09 22:44:39 +08:00
七锋
b57ac0bcdf fixed merge and blob encoding example 2018-08-09 16:56:13 +08:00
charles.lin
ea6391d1c3 Merge branch 'branch-rds-support' 2018-08-09 16:44:12 +08:00
agapple
89a32aaabd Merge pull request #825 from lcybo/master
Remove dirty test case
2018-08-09 11:38:48 +08:00
lcybo
a2eb128698 Merge pull request #826 from lcybo/metrics_support
Metrics support
2018-08-08 22:56:44 +08:00
Chuanyi Li
760f2dda80 Enhance heartbeat, rowscount. 2018-08-08 22:53:08 +08:00
Chuanyi Li
bf9dc0073d Merge branch 'master' into metrics_support 2018-08-08 21:01:22 +08:00
Chuanyi Li
92f24bd02e Remove dirty test case 2018-08-08 20:59:09 +08:00
Chuanyi Li
d4797a8fb9 Merge master branch. 2018-08-08 20:35:14 +08:00
Chuanyi Li
060bd8e31d Reformat code. 2018-08-08 20:10:34 +08:00
Chuanyi Li
88de747ed5 Improve sink blocking time resolution. 2018-08-08 07:53:44 +08:00
lcybo
725d36aa98 Merge pull request #819 from lcybo/metrics_support
canal 性能监控改动版,merge到开发分支(metrics_support)
2018-08-07 23:02:37 +08:00
Chuanyi Li
d24e10fb3e Canal metrics snapshot version. 2018-08-07 22:52:30 +08:00
Chuanyi Li
fdae646e26 Merge branch 'master' into metrics_support 2018-08-07 19:46:30 +08:00
Chuanyi Li L
40c3e65341 Refactor metrics code. 2018-08-07 17:17:33 +08:00
Chuanyi Li
b5a1898cec Prometheus refactor. 2018-08-07 08:18:01 +08:00
Chuanyi Li L
680faea2bb Add listener to write. 2018-08-06 17:22:28 +08:00
winger
1cd77bf551 fix bug: kafka get row data for performance 2018-08-06 15:29:14 +08:00
qmzhang
9786faba9a instance由manager管理时,CanalParameter#MetaMode增加本地文件方式,本地meta.dat存储位点信息; 2018-08-06 15:29:14 +08:00
七锋
325fe89aed fixed issue #801 , optimizer docker 2018-08-06 15:29:13 +08:00
七锋
ffeec8fc86 fixed issue #801 , support canal docker 2018-08-06 15:29:13 +08:00
winger
7662c9241e 支持GTID模式下,通过获取当前gtid值 2018-08-06 15:29:13 +08:00
Chuanyi Li L
d01195ac82 Merge branch 'master' into metrics_support 2018-08-06 15:26:44 +08:00
Chuanyi Li
8b186d1330 canal server metrics refactor. 2018-08-06 08:12:59 +08:00
Chuanyi Li L
b7431eb7d0 Reformat code. 2018-08-03 17:42:57 +08:00
Chuanyi Li L
c7fc149e7e Netty server/client profiler skeleton. 2018-08-03 17:32:48 +08:00
Chuanyi Li L
368554166c Builder of Aggregator. 2018-08-02 17:36:22 +08:00
Chuanyi Li L
c4ee350717 Merge branch 'master' of https://github.com/alibaba/canal 2018-08-02 14:55:01 +08:00
Chuanyi Li
ae0f43d1c7 netty3 future listener for metrics. 2018-08-01 08:17:55 +08:00
68 changed files with 14090 additions and 15470 deletions

View File

@@ -18,12 +18,12 @@
</dependency> </dependency>
<!-- 这里指定runtime的metrics provider--> <!-- 这里指定runtime的metrics provider-->
<!--<dependency>--> <dependency>
<!--<groupId>com.alibaba.otter</groupId>--> <groupId>com.alibaba.otter</groupId>
<!--<artifactId>canal.prometheus</artifactId>--> <artifactId>canal.prometheus</artifactId>
<!--<version>${project.version}</version>--> <version>${project.version}</version>
<!--<scope>runtime</scope>--> <scope>runtime</scope>
<!--</dependency>--> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -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

View File

@@ -94,12 +94,7 @@ then
echo LOG CONFIGURATION : $logback_configurationFile echo LOG CONFIGURATION : $logback_configurationFile
echo canal conf : $canal_conf echo canal conf : $canal_conf
echo CLASSPATH :$CLASSPATH echo CLASSPATH :$CLASSPATH
# metrics support options $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 &
# 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 &
echo $! > $base/bin/canal.pid echo $! > $base/bin/canal.pid
echo "cd to $current_path for continue" echo "cd to $current_path for continue"

View File

@@ -1,9 +1,9 @@
package com.alibaba.otter.canal.example; package com.alibaba.otter.canal.example;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils; import org.apache.commons.lang.SystemUtils;
@@ -254,7 +254,18 @@ public class AbstractCanalClientTest {
protected void printColumn(List<Column> columns) { protected void printColumn(List<Column> columns) {
for (Column column : columns) { for (Column column : columns) {
StringBuilder builder = new StringBuilder(); 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()); builder.append(" type=" + column.getMysqlType());
if (column.getUpdated()) { if (column.getUpdated()) {
builder.append(" update=" + column.getUpdated()); builder.append(" update=" + column.getUpdated());

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -8,7 +8,6 @@ import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; 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.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.math.RandomUtils; import org.apache.commons.lang.math.RandomUtils;
@@ -95,9 +94,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
.availableProcessors() * 60 / 100; // 60%的能力跑解析,剩余部分处理网络 .availableProcessors() * 60 / 100; // 60%的能力跑解析,剩余部分处理网络
protected int parallelBufferSize = 256; // 必须为2的幂 protected int parallelBufferSize = 256; // 必须为2的幂
protected MultiStageCoprocessor multiStageCoprocessor; protected MultiStageCoprocessor multiStageCoprocessor;
protected ParserExceptionHandler parserExceptionHandler;
protected long serverId;
protected abstract BinlogParser buildParser(); protected abstract BinlogParser buildParser();
@@ -175,16 +171,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
preDump(erosaConnection); preDump(erosaConnection);
erosaConnection.connect();// 链接 erosaConnection.connect();// 链接
long queryServerId = erosaConnection.queryServerId();
if (queryServerId != 0){
serverId = queryServerId;
}
// 4. 获取最后的位置信息 // 4. 获取最后的位置信息
EntryPosition position = findStartPosition(erosaConnection); EntryPosition position = findStartPosition(erosaConnection);
final EntryPosition startPosition = position; final EntryPosition startPosition = position;
if (startPosition == null) { 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)) { if (!processTableMeta(startPosition)) {
@@ -287,9 +278,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
runningInfo.getAddress().toString()), e); runningInfo.getAddress().toString()), e);
sendAlarm(destination, ExceptionUtils.getFullStackTrace(e)); sendAlarm(destination, ExceptionUtils.getFullStackTrace(e));
} }
if (parserExceptionHandler!=null){
parserExceptionHandler.handle(e);
}
} finally { } finally {
// 重新置为中断状态 // 重新置为中断状态
Thread.interrupted(); Thread.interrupted();
@@ -628,19 +616,5 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
this.parallelBufferSize = parallelBufferSize; 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;
}
} }

View File

@@ -40,6 +40,4 @@ public interface ErosaConnection {
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException; public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException;
ErosaConnection fork(); ErosaConnection fork();
public long queryServerId() throws IOException;
} }

View File

@@ -80,6 +80,11 @@ public class EventTransactionBuffer extends AbstractCanalLifeCycle {
flush(); flush();
} }
break; break;
case HEARTBEAT:
// master过来的heartbeat,说明binlog已经读完了,是idle状态
put(entry);
flush();
break;
default: default:
break; break;
} }

View File

@@ -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);
}

View File

@@ -1,6 +1,7 @@
package com.alibaba.otter.canal.parse.inbound.mysql; package com.alibaba.otter.canal.parse.inbound.mysql;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -21,23 +22,24 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition;
public abstract class AbstractMysqlEventParser extends AbstractEventParser { public abstract class AbstractMysqlEventParser extends AbstractEventParser {
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected static final long BINLOG_START_OFFEST = 4L; protected static final long BINLOG_START_OFFEST = 4L;
protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory(); protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory();
protected boolean enableTsdb = false; protected boolean enableTsdb = false;
protected String tsdbSpringXml; protected String tsdbSpringXml;
protected TableMetaTSDB tableMetaTSDB; protected TableMetaTSDB tableMetaTSDB;
// 编码信息 // 编码信息
protected byte connectionCharsetNumber = (byte) 33; protected byte connectionCharsetNumber = (byte) 33;
protected Charset connectionCharset = Charset.forName("UTF-8"); protected Charset connectionCharset = Charset.forName("UTF-8");
protected boolean filterQueryDcl = false; protected boolean filterQueryDcl = false;
protected boolean filterQueryDml = false; protected boolean filterQueryDml = false;
protected boolean filterQueryDdl = false; protected boolean filterQueryDdl = false;
protected boolean filterRows = false; protected boolean filterRows = false;
protected boolean filterTableError = false; protected boolean filterTableError = false;
protected boolean useDruidDdlFilter = true; protected boolean useDruidDdlFilter = true;
private final AtomicLong eventsPublishBlockingTime = new AtomicLong(0L);
protected BinlogParser buildParser() { protected BinlogParser buildParser() {
LogEventConvert convert = new LogEventConvert(); LogEventConvert convert = new LogEventConvert();
@@ -131,11 +133,13 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
} }
protected MultiStageCoprocessor buildMultiStageCoprocessor() { protected MultiStageCoprocessor buildMultiStageCoprocessor() {
return new MysqlMultiStageCoprocessor(parallelBufferSize, MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize,
parallelThreadSize, parallelThreadSize,
(LogEventConvert) binlogParser, (LogEventConvert) binlogParser,
transactionBuffer, transactionBuffer,
destination); destination);
mysqlMultiStageCoprocessor.setEventsPublishBlockingTime(eventsPublishBlockingTime);
return mysqlMultiStageCoprocessor;
} }
// ============================ setter / getter ========================= // ============================ setter / getter =========================
@@ -200,6 +204,10 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
} }
} }
public AtomicLong getEventsPublishBlockingTime() {
return this.eventsPublishBlockingTime;
}
public void setTableMetaTSDBFactory(TableMetaTSDBFactory tableMetaTSDBFactory) { public void setTableMetaTSDBFactory(TableMetaTSDBFactory tableMetaTSDBFactory) {
this.tableMetaTSDBFactory = tableMetaTSDBFactory; this.tableMetaTSDBFactory = tableMetaTSDBFactory;
} }

View File

@@ -4,7 +4,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException;
import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -37,9 +36,6 @@ public class LocalBinLogConnection implements ErosaConnection {
private String directory; private String directory;
private int bufferSize = 16 * 1024; private int bufferSize = 16 * 1024;
private boolean running = false; private boolean running = false;
private long serverId;
private FileParserListener parserListener;
public LocalBinLogConnection(){ public LocalBinLogConnection(){
} }
@@ -100,9 +96,6 @@ public class LocalBinLogConnection implements ErosaConnection {
if (event == null) { if (event == null) {
continue; continue;
} }
if (serverId != 0 && event.getServerId() != serverId){
throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !");
}
if (!func.sink(event)) { if (!func.sink(event)) {
needContinue = false; needContinue = false;
@@ -110,9 +103,8 @@ public class LocalBinLogConnection implements ErosaConnection {
} }
} }
fetcher.close(); // 关闭上一个文件
parserFinish(current.getName());
if (needContinue) {// 读取下一个 if (needContinue) {// 读取下一个
fetcher.close(); // 关闭上一个文件
File nextFile; File nextFile;
if (needWait) { if (needWait) {
@@ -168,11 +160,6 @@ public class LocalBinLogConnection implements ErosaConnection {
while (fetcher.fetch()) { while (fetcher.fetch()) {
LogEvent event = decoder.decode(fetcher, context); LogEvent event = decoder.decode(fetcher, context);
if (event != null) { if (event != null) {
if (serverId != 0 && event.getServerId() != serverId){
throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !");
}
if (event.getWhen() > timestampSeconds) { if (event.getWhen() > timestampSeconds) {
break; break;
} }
@@ -241,9 +228,8 @@ public class LocalBinLogConnection implements ErosaConnection {
} }
} }
fetcher.close(); // 关闭上一个文件
parserFinish(binlogfilename);
if (needContinue) {// 读取下一个 if (needContinue) {// 读取下一个
fetcher.close(); // 关闭上一个文件
File nextFile; File nextFile;
if (needWait) { if (needWait) {
@@ -272,12 +258,6 @@ public class LocalBinLogConnection implements ErosaConnection {
} }
} }
private void parserFinish(String fileName){
if (parserListener != null){
parserListener.onFinish(fileName);
}
}
@Override @Override
public void dump(long timestampMills, MultiStageCoprocessor coprocessor) throws IOException { public void dump(long timestampMills, MultiStageCoprocessor coprocessor) throws IOException {
List<File> currentBinlogs = binlogs.currentBinlogs(); List<File> currentBinlogs = binlogs.currentBinlogs();
@@ -306,11 +286,6 @@ public class LocalBinLogConnection implements ErosaConnection {
while (fetcher.fetch()) { while (fetcher.fetch()) {
LogEvent event = decoder.decode(fetcher, context); LogEvent event = decoder.decode(fetcher, context);
if (event != null) { if (event != null) {
if (serverId != 0 && event.getServerId() != serverId){
throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !");
}
if (event.getWhen() > timestampSeconds) { if (event.getWhen() > timestampSeconds) {
break; break;
} }
@@ -369,11 +344,6 @@ public class LocalBinLogConnection implements ErosaConnection {
return connection; return connection;
} }
@Override
public long queryServerId() {
return 0;
}
public boolean isNeedWait() { public boolean isNeedWait() {
return needWait; return needWait;
} }
@@ -398,20 +368,4 @@ public class LocalBinLogConnection implements ErosaConnection {
this.bufferSize = bufferSize; 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);
}
} }

View File

@@ -7,12 +7,11 @@ import java.net.InetSocketAddress;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.MysqlConnector;
import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor; 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 { 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 MysqlConnector connector;
private long slaveId; private long slaveId;
private Charset charset = Charset.forName("UTF-8"); private Charset charset = Charset.forName("UTF-8");
private BinlogFormat binlogFormat; private BinlogFormat binlogFormat;
private BinlogImage binlogImage; private BinlogImage binlogImage;
// tsdb releated // tsdb releated
private AuthenticationInfo authInfo; private AuthenticationInfo authInfo;
protected int connTimeout = 5 * 1000; // 5秒 protected int connTimeout = 5 * 1000; // 5秒
protected int soTimeout = 60 * 60 * 1000; // 1小时 protected int soTimeout = 60 * 60 * 1000; // 1小时
// dump binlog bytes, 暂不包括meta与TSDB
private AtomicLong receivedBinlogBytes;
public MysqlConnection(){ public MysqlConnection(){
} }
@@ -126,6 +127,7 @@ public class MysqlConnection implements ErosaConnection {
decoder.handle(LogEvent.XID_EVENT); decoder.handle(LogEvent.XID_EVENT);
LogContext context = new LogContext(); LogContext context = new LogContext();
while (fetcher.fetch()) { while (fetcher.fetch()) {
accumulateReceivedBytes(fetcher.limit());
LogEvent event = null; LogEvent event = null;
event = decoder.decode(fetcher, context); 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); LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
LogContext context = new LogContext(); LogContext context = new LogContext();
while (fetcher.fetch()) { while (fetcher.fetch()) {
accumulateReceivedBytes(fetcher.limit());
LogEvent event = null; LogEvent event = null;
event = decoder.decode(fetcher, context); 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); LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
LogContext context = new LogContext(); LogContext context = new LogContext();
while (fetcher.fetch()) { while (fetcher.fetch()) {
accumulateReceivedBytes(fetcher.limit());
LogEvent event = null; LogEvent event = null;
event = decoder.decode(fetcher, context); event = decoder.decode(fetcher, context);
@@ -206,6 +210,7 @@ public class MysqlConnection implements ErosaConnection {
try { try {
fetcher.start(connector.getChannel()); fetcher.start(connector.getChannel());
while (fetcher.fetch()) { while (fetcher.fetch()) {
accumulateReceivedBytes(fetcher.limit());
LogBuffer buffer = fetcher.duplicate(); LogBuffer buffer = fetcher.duplicate();
fetcher.consume(fetcher.limit()); fetcher.consume(fetcher.limit());
if (!coprocessor.publish(buffer)) { if (!coprocessor.publish(buffer)) {
@@ -232,6 +237,7 @@ public class MysqlConnection implements ErosaConnection {
try { try {
fetcher.start(connector.getChannel()); fetcher.start(connector.getChannel());
while (fetcher.fetch()) { while (fetcher.fetch()) {
accumulateReceivedBytes(fetcher.limit());
LogBuffer buffer = fetcher.duplicate(); LogBuffer buffer = fetcher.duplicate();
fetcher.consume(fetcher.limit()); fetcher.consume(fetcher.limit());
if (!coprocessor.publish(buffer)) { if (!coprocessor.publish(buffer)) {
@@ -326,16 +332,6 @@ public class MysqlConnection implements ErosaConnection {
return connection; 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 ==================== // ====================== help method ====================
/** /**
@@ -346,7 +342,6 @@ public class MysqlConnection implements ErosaConnection {
* <li>net_read_timeout</li> * <li>net_read_timeout</li>
* </ol> * </ol>
* *
* @param channel
* @throws IOException * @throws IOException
*/ */
private void updateSettings() 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 { public static enum BinlogFormat {
STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED"); STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED");
@@ -604,4 +607,8 @@ public class MysqlConnection implements ErosaConnection {
this.authInfo = authInfo; this.authInfo = authInfo;
} }
public void setReceivedBinlogBytes(AtomicLong receivedBinlogBytes) {
this.receivedBinlogBytes = receivedBinlogBytes;
}
} }

View File

@@ -53,11 +53,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
private int receiveBufferSize = 64 * 1024; private int receiveBufferSize = 64 * 1024;
private int sendBufferSize = 64 * 1024; private int sendBufferSize = 64 * 1024;
// 数据库信息 // 数据库信息
protected AuthenticationInfo masterInfo; // 主库 private AuthenticationInfo masterInfo; // 主库
protected AuthenticationInfo standbyInfo; // 备库 private AuthenticationInfo standbyInfo; // 备库
// binlog信息 // binlog信息
protected EntryPosition masterPosition; private EntryPosition masterPosition;
protected EntryPosition standbyPosition; private EntryPosition standbyPosition;
private long slaveId; // 链接到mysql的slave private long slaveId; // 链接到mysql的slave
// 心跳检查信息 // 心跳检查信息
private String detectingSQL; // 心跳sql private String detectingSQL; // 心跳sql
@@ -68,8 +68,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
// update by yishun.chen,特殊异常处理参数 // update by yishun.chen,特殊异常处理参数
private int dumpErrorCount = 0; // binlogDump失败异常计数 private int dumpErrorCount = 0; // binlogDump失败异常计数
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
// instance received binlog bytes
private final AtomicLong receivedBinlogBytes = new AtomicLong(0L);
protected ErosaConnection buildErosaConnection() { protected ErosaConnection buildErosaConnection() {
return buildMysqlConnection(this.runningInfo); return buildMysqlConnection(this.runningInfo);
@@ -313,6 +316,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
connection.getConnector().setSendBufferSize(sendBufferSize); connection.getConnector().setSendBufferSize(sendBufferSize);
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000); connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
connection.setCharset(connectionCharset); connection.setCharset(connectionCharset);
connection.setReceivedBinlogBytes(receivedBinlogBytes);
// 随机生成slaveId // 随机生成slaveId
if (this.slaveId <= 0) { if (this.slaveId <= 0) {
this.slaveId = generateUniqueServerId(); this.slaveId = generateUniqueServerId();
@@ -511,7 +515,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition) private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
throws IOException { throws IOException {
// 针对开始的第一条为非Begin记录,需要从该binlog扫描 // 针对开始的第一条为非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.reconnect();
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() { mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
@@ -910,4 +914,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
this.dumpErrorCountThreshold = dumpErrorCountThreshold; this.dumpErrorCountThreshold = dumpErrorCountThreshold;
} }
public AtomicLong getReceivedBinlogBytes() {
return this.receivedBinlogBytes;
}
} }

View File

@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import org.apache.commons.lang.StringUtils; 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 { public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor {
private static final int maxFullTimes = 10;
private LogEventConvert logEventConvert; private LogEventConvert logEventConvert;
private EventTransactionBuffer transactionBuffer; private EventTransactionBuffer transactionBuffer;
private ErosaConnection connection; private ErosaConnection connection;
@@ -63,6 +65,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
private ExecutorService stageExecutor; private ExecutorService stageExecutor;
private String destination; private String destination;
private volatile CanalParseException exception; private volatile CanalParseException exception;
private AtomicLong eventsPublishBlockingTime;
public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert,
EventTransactionBuffer transactionBuffer, String destination){ EventTransactionBuffer transactionBuffer, String destination){
@@ -161,6 +164,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
throw exception; throw exception;
} }
boolean interupted = false; boolean interupted = false;
long blockingStart = 0L;
int fullTimes = 0;
do { do {
try { try {
long next = disruptorMsgBuffer.tryNext(); long next = disruptorMsgBuffer.tryNext();
@@ -170,16 +175,39 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
event.setBinlogFileName(binlogFileName); event.setBinlogFileName(binlogFileName);
} }
disruptorMsgBuffer.publish(next); disruptorMsgBuffer.publish(next);
if (fullTimes > 0) {
eventsPublishBlockingTime.addAndGet(System.nanoTime() - blockingStart);
}
break; break;
} catch (InsufficientCapacityException e) { } catch (InsufficientCapacityException e) {
if (fullTimes == 0) {
blockingStart = System.nanoTime();
}
// park // park
LockSupport.parkNanos(1L); //LockSupport.parkNanos(1L);
applyWait(++fullTimes);
interupted = Thread.interrupted(); interupted = Thread.interrupted();
if (fullTimes % 1000 == 0) {
long nextStart = System.nanoTime();
eventsPublishBlockingTime.addAndGet(nextStart - blockingStart);
blockingStart = nextStart;
}
} }
} while (!interupted && isStart()); } while (!interupted && isStart());
return 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 @Override
public void reset() { public void reset() {
if (isStart()) { if (isStart()) {
@@ -189,6 +217,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
start(); start();
} }
private class SimpleParserStage implements EventHandler<MessageEvent>, LifecycleAware { private class SimpleParserStage implements EventHandler<MessageEvent>, LifecycleAware {
private LogDecoder decoder; private LogDecoder decoder;
@@ -203,15 +232,10 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
try { try {
LogBuffer buffer = event.getBuffer(); LogBuffer buffer = event.getBuffer();
if (StringUtils.isNotEmpty(event.getBinlogFileName()) 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 // set roate binlog file name
if (context.getLogPosition() == null){ context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition()
context.setLogPosition(new LogPosition(event.getBinlogFileName(), 0)); .getPosition()));
}else{
context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition()
.getPosition()));
}
} }
LogEvent logEvent = decoder.decode(buffer, context); LogEvent logEvent = decoder.decode(buffer, context);
@@ -432,4 +456,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
this.connection = connection; this.connection = connection;
} }
public void setEventsPublishBlockingTime(AtomicLong eventsPublishBlockingTime) {
this.eventsPublishBlockingTime = eventsPublishBlockingTime;
}
} }

View File

@@ -10,9 +10,7 @@ import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; import java.util.List;
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; import com.taobao.tddl.dbsync.binlog.event.*;
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage;
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger; 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.alibaba.otter.canal.protocol.position.EntryPosition;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.taobao.tddl.dbsync.binlog.LogEvent; 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.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; 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 nameFilter; // 运行时引用可能会有变化,比如规则发生变化时
private volatile AviaterRegexFilter nameBlackFilter; private volatile AviaterRegexFilter nameBlackFilter;
private TableMetaCache tableMetaCache;
private TableMetaCacheInterface tableMetaCache;
private String binlogFileName = "mysql-bin.000001";
private Charset charset = Charset.defaultCharset(); private Charset charset = Charset.defaultCharset();
private boolean filterQueryDcl = false; private boolean filterQueryDcl = false;
private boolean filterQueryDml = false; private boolean filterQueryDml = false;
@@ -150,6 +130,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
return parseRandLogEvent((RandLogEvent) logEvent); return parseRandLogEvent((RandLogEvent) logEvent);
case LogEvent.GTID_LOG_EVENT: case LogEvent.GTID_LOG_EVENT:
return parseGTIDLogEvent((GtidLogEvent) logEvent); return parseGTIDLogEvent((GtidLogEvent) logEvent);
case LogEvent.HEARTBEAT_LOG_EVENT:
return parseHeartbeatLogEvent((HeartbeatLogEvent) logEvent);
default: default:
break; 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) { private Entry parseGTIDLogEvent(GtidLogEvent logEvent) {
LogHeader logHeader = logEvent.getHeader(); LogHeader logHeader = logEvent.getHeader();
String value = logEvent.getSid().toString() + ":" + logEvent.getGno(); String value = logEvent.getSid().toString() + ":" + logEvent.getGno();
@@ -268,8 +259,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
if (!isSeek) { if (!isSeek) {
// 使用新的表结构元数据管理方式 // 使用新的表结构元数据管理方式
EntryPosition position = createPosition(event.getHeader()); EntryPosition position = createPosition(event.getHeader());
String fulltbName = schemaName+"."+tableName; tableMetaCache.apply(position, event.getDbName(), queryString, null);
tableMetaCache.apply(position, fulltbName, queryString, null);
} }
Header header = createHeader(event.getHeader(), schemaName, tableName, type); Header header = createHeader(event.getHeader(), schemaName, tableName, type);
@@ -766,6 +756,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} else { } else {
// byte数组,直接使用iso-8859-1保留对应编码,浪费内存 // byte数组,直接使用iso-8859-1保留对应编码,浪费内存
columnBuilder.setValue(new String((byte[]) value, ISO_8859_1)); columnBuilder.setValue(new String((byte[]) value, ISO_8859_1));
// columnBuilder.setValueBytes(ByteString.copyFrom((byte[])
// value));
javaType = Types.BLOB; javaType = Types.BLOB;
} }
break; break;
@@ -944,7 +936,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
this.nameBlackFilter = nameBlackFilter; this.nameBlackFilter = nameBlackFilter;
} }
public void setTableMetaCache(TableMetaCacheInterface tableMetaCache) { public void setTableMetaCache(TableMetaCache tableMetaCache) {
this.tableMetaCache = tableMetaCache; this.tableMetaCache = tableMetaCache;
} }

View File

@@ -6,7 +6,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; 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 * @author jianghang 2013-1-17 下午10:15:16
* @version 1.0.0 * @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_NAME = "COLUMN_NAME";
public static final String COLUMN_TYPE = "COLUMN_TYPE"; public static final String COLUMN_TYPE = "COLUMN_TYPE";
@@ -100,10 +99,6 @@ public class TableMetaCache implements TableMetaCacheInterface {
String createDDL = packet.getFieldValues().get(1); String createDDL = packet.getFieldValues().get(1);
MemoryTableMeta memoryTableMeta = new MemoryTableMeta(); MemoryTableMeta memoryTableMeta = new MemoryTableMeta();
memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null); 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); TableMeta tableMeta = memoryTableMeta.find(schema, table);
return tableMeta.getFields(); return tableMeta.getFields();
} else { } else {

View File

@@ -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();
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -1,9 +1,5 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds; 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 io.netty.handler.codec.http.HttpResponseStatus;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@@ -13,11 +9,16 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.text.SimpleDateFormat; 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.Mac;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@@ -55,52 +56,6 @@ public class RdsBinlogOpenApi {
private static final String API_VERSION = "2014-08-15"; private static final String API_VERSION = "2014-08-15";
private static final String SIGNATURE_VERSION = "1.0"; 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, public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
Date endTime, File destDir) throws Throwable { Date endTime, File destDir) throws Throwable {
int pageSize = 100; int pageSize = 100;

View File

@@ -2,23 +2,14 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds;
import java.io.File; import java.io.File;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.exception.CanalParseException; 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.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.EntryPosition;
import com.alibaba.otter.canal.protocol.position.LogPosition;
/** /**
* 基于rds binlog备份文件的复制 * 基于rds binlog备份文件的复制
@@ -26,102 +17,47 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
* @author agapple 2017年10月15日 下午1:27:36 * @author agapple 2017年10月15日 下午1:27:36
* @since 1.0.25 * @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 url = "https://rds.aliyuncs.com/"; // openapi地址
private String accesskey; // 云账号的ak private String accesskey; // 云账号的ak
private String secretkey; // 云账号sk private String secretkey; // 云账号sk
private String instanceId; // rds实例id private String instanceId; // rds实例id
private Long startTime; private Long startTime;
private Long endTime; private Long endTime;
private BinlogDownloadQueue binlogDownloadQueue;
private ParseFinishListener finishListener;
private int batchSize;
public RdsLocalBinlogEventParser(){ public RdsLocalBinlogEventParser(){
} }
public void start() throws CanalParseException { public void start() throws CanalParseException {
try { try {
Assert.notNull(startTime);
Assert.notNull(accesskey); Assert.notNull(accesskey);
Assert.notNull(secretkey); Assert.notNull(secretkey);
Assert.notNull(instanceId); Assert.notNull(instanceId);
Assert.notNull(url); Assert.notNull(url);
Assert.notNull(directory);
if (endTime == null) { if (endTime == null) {
endTime = System.currentTimeMillis(); endTime = System.currentTimeMillis();
} }
EntryPosition entryPosition = findStartPosition(null); RdsBinlogOpenApi.downloadBinlogFiles(url,
if (entryPosition == null) { accesskey,
throw new PositionNotFoundException("position not found!");
}
long startTimeInMill = entryPosition.getTimestamp();
startTime = startTimeInMill;
List<BinlogFile> binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, accesskey,
secretkey, secretkey,
instanceId, instanceId,
new Date(startTime), new Date(startTime),
new Date(endTime)); new Date(endTime),
binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchSize, directory); new File(directory));
binlogDownloadQueue.silenceDownload();
needWait = true; // 更新一下时间戳
parallel = false; masterPosition = new EntryPosition(startTime);
// try to download one file,use to test server id
binlogDownloadQueue.tryOne();
} catch (Throwable e) { } catch (Throwable e) {
logger.error("download binlog failed", e); logger.error("download binlog failed", e);
throw new CanalParseException(e); throw new CanalParseException(e);
} }
setParserExceptionHandler(new ParserExceptionHandler() {
@Override
public void handle(Throwable e) {
handleMysqlParserException(e);
}
});
super.start(); 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() { public String getUrl() {
return url; return url;
} }
@@ -132,81 +68,44 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements
} }
} }
public String getAccesskey() {
return accesskey;
}
public void setAccesskey(String accesskey) { public void setAccesskey(String accesskey) {
this.accesskey = accesskey; this.accesskey = accesskey;
} }
public String getSecretkey() {
return secretkey;
}
public void setSecretkey(String secretkey) { public void setSecretkey(String secretkey) {
this.secretkey = secretkey; this.secretkey = secretkey;
} }
public String getInstanceId() {
return instanceId;
}
public void setInstanceId(String instanceId) { public void setInstanceId(String instanceId) {
this.instanceId = instanceId; this.instanceId = instanceId;
} }
public Long getStartTime() {
return startTime;
}
public void setStartTime(Long startTime) { public void setStartTime(Long startTime) {
this.startTime = startTime; this.startTime = startTime;
} }
public Long getEndTime() {
return endTime;
}
public void setEndTime(Long endTime) { public void setEndTime(Long endTime) {
this.endTime = 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;
}
} }

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -1,9 +0,0 @@
package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta;
public interface TableMetaStorageFactory {
TableMetaStorage getTableMetaStorage();
String getDbName();
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -14,17 +14,6 @@
<version>1.0.26-SNAPSHOT</version> <version>1.0.26-SNAPSHOT</version>
<name>canal prometheus module for otter ${project.version}</name> <name>canal prometheus module for otter ${project.version}</name>
<dependencies> <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> <dependency>
<groupId>org.jctools</groupId> <groupId>org.jctools</groupId>
<artifactId>jctools-core</artifactId> <artifactId>jctools-core</artifactId>

View File

@@ -1,12 +1,7 @@
package com.alibaba.otter.canal.prometheus; package com.alibaba.otter.canal.prometheus;
import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.instance.core.CanalInstance;
import com.alibaba.otter.canal.prometheus.impl.InstanceMetaCollector; import com.alibaba.otter.canal.prometheus.impl.*;
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 io.prometheus.client.Collector; import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -20,79 +15,71 @@ import java.util.List;
*/ */
public class CanalInstanceExports { 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"}; private CanalInstanceExports() {
this.storeCollector = StoreCollector.instance();
public static final List<String> labelList = Collections.singletonList(labels[0]); this.entryCollector = EntryCollector.instance();
this.metaCollector = MetaCollector.instance();
private final String destination; this.sinkCollector = SinkCollector.instance();
this.parserCollector = ParserCollector.instance();
private Collector storeCollector;
private Collector delayCollector;
private Collector metaCollector;
private CanalInstanceExports(CanalInstance instance) {
this.destination = instance.getDestination();
initDelayGauge(instance);
initStoreCollector(instance);
initMetaCollector(instance);
} }
private static class SingletonHolder {
private static final CanalInstanceExports SINGLETON = new CanalInstanceExports();
static CanalInstanceExports forInstance(CanalInstance instance) {
return new CanalInstanceExports(instance);
} }
void register() { public static CanalInstanceExports instance() {
if (delayCollector != null) { return SingletonHolder.SINGLETON;
delayCollector.register();
}
if (storeCollector != null) {
storeCollector.register();
}
if (metaCollector != null) {
metaCollector.register();
}
} }
void unregister() { public void initialize() {
if (delayCollector != null) { storeCollector.register();
CollectorRegistry.defaultRegistry.unregister(delayCollector); entryCollector.register();
} metaCollector.register();
if (storeCollector != null) { sinkCollector.register();
CollectorRegistry.defaultRegistry.unregister(storeCollector); parserCollector.register();
}
if (metaCollector != null) {
CollectorRegistry.defaultRegistry.unregister(metaCollector);
}
} }
private void initDelayGauge(CanalInstance instance) { public void terminate() {
CanalEventSink sink = instance.getEventSink(); CollectorRegistry.defaultRegistry.unregister(storeCollector);
if (sink instanceof EntryEventSink) { CollectorRegistry.defaultRegistry.unregister(entryCollector);
EntryEventSink entryEventSink = (EntryEventSink) sink; CollectorRegistry.defaultRegistry.unregister(metaCollector);
// TODO ensure not to add handler again CollectorRegistry.defaultRegistry.unregister(sinkCollector);
PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination); CollectorRegistry.defaultRegistry.unregister(parserCollector);
entryEventSink.addHandler(handler);
delayCollector = handler.getCollector();
} else {
logger.warn("This impl register metrics for only EntryEventSink, skip.");
}
} }
private void initStoreCollector(CanalInstance instance) { void register(CanalInstance instance) {
try { requiredInstanceRegistry(storeCollector).register(instance);
storeCollector = new MemoryStoreCollector(instance.getEventStore(), destination); requiredInstanceRegistry(entryCollector).register(instance);
} catch (CanalStoreException cse) { requiredInstanceRegistry(metaCollector).register(instance);
logger.warn("Failed to register metrics for destination {}.", destination, cse); requiredInstanceRegistry(sinkCollector).register(instance);
} requiredInstanceRegistry(parserCollector).register(instance);
logger.info("Successfully register metrics for instance {}.", instance.getDestination());
} }
private void initMetaCollector(CanalInstance instance) { void unregister(CanalInstance instance) {
metaCollector = new InstanceMetaCollector(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;
}
} }

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -1,6 +1,8 @@
package com.alibaba.otter.canal.prometheus; package com.alibaba.otter.canal.prometheus;
import com.alibaba.otter.canal.instance.core.CanalInstance; 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 com.alibaba.otter.canal.spi.CanalMetricsService;
import io.prometheus.client.exporter.HTTPServer; import io.prometheus.client.exporter.HTTPServer;
import io.prometheus.client.hotspot.DefaultExports; import io.prometheus.client.hotspot.DefaultExports;
@@ -8,23 +10,24 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; 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 * @author Chuanyi Li
*/ */
public class PrometheusService implements CanalMetricsService { public class PrometheusService implements CanalMetricsService {
private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class);
private final CanalInstanceExports instanceExports;
private final Map<String, CanalInstanceExports> exports = new ConcurrentHashMap<String, CanalInstanceExports>(); private volatile boolean running = false;
private HTTPServer server;
private volatile boolean running = false; private final ClientInstanceProfiler clientProfiler;
private HTTPServer server;
private PrometheusService() { private PrometheusService() {
this.instanceExports = CanalInstanceExports.instance();
this.clientProfiler = PrometheusClientInstanceProfiler.instance();
} }
private static class SingletonHolder { private static class SingletonHolder {
@@ -48,8 +51,11 @@ public class PrometheusService implements CanalMetricsService {
try { try {
// JVM exports // JVM exports
DefaultExports.initialize(); DefaultExports.initialize();
// Canal server level exports instanceExports.initialize();
CanalServerExports.initialize(); if (!clientProfiler.isStart()) {
clientProfiler.start();
}
profiler().setInstanceProfiler(clientProfiler);
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("Unable to initialize server exports.", t); logger.warn("Unable to initialize server exports.", t);
} }
@@ -60,14 +66,17 @@ public class PrometheusService implements CanalMetricsService {
@Override @Override
public void terminate() { public void terminate() {
running = false; running = false;
// Normally, service should be terminated at canal shutdown. try {
// No need to unregister instance exports explicitly. instanceExports.terminate();
// But for the sake of safety, unregister them. if (clientProfiler.isStart()) {
for (CanalInstanceExports ie : exports.values()) { clientProfiler.stop();
ie.unregister(); }
} profiler().setInstanceProfiler(NOP);
if (server != null) { if (server != null) {
server.stop(); server.stop();
}
} catch (Throwable t) {
logger.warn("Something happened while terminating.", t);
} }
} }
@@ -83,9 +92,7 @@ public class PrometheusService implements CanalMetricsService {
return; return;
} }
try { try {
CanalInstanceExports export = CanalInstanceExports.forInstance(instance); instanceExports.register(instance);
export.register();
exports.put(instance.getDestination(), export);
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("Unable to register instance exports for {}.", instance.getDestination(), 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()); logger.warn("Try unregister metrics after destination {} is stopped.", instance.getDestination());
} }
try { try {
CanalInstanceExports export = exports.remove(instance.getDestination()); instanceExports.unregister(instance);
if (export != null) {
export.unregister();
}
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("Unable to unregister instance exports for {}.", instance.getDestination(), t); logger.warn("Unable to unregister instance exports for {}.", instance.getDestination(), t);
} }
logger.info("Unregister metrics for destination {}.", instance.getDestination()); logger.info("Unregister metrics for destination {}.", instance.getDestination());
} }
} }

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -1,73 +1,90 @@
package com.alibaba.otter.canal.prometheus.impl; 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.sink.AbstractCanalEventDownStreamHandler;
import com.alibaba.otter.canal.store.model.Event; 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.List;
import java.util.concurrent.atomic.AtomicLong;
/** /**
* @author Chuanyi Li * @author Chuanyi Li
*/ */
public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler<List<Event>> { public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler<List<Event>> {
private final Collector collector; private final AtomicLong latestExecuteTime = new AtomicLong(0L);
private final AtomicLong transactionCounter = new AtomicLong(0L);
private long latestExecuteTime = 0L; private final AtomicLong rowEventCounter = new AtomicLong(0L);
private final AtomicLong rowsCounter = new AtomicLong(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;
}
};
}
@Override @Override
public List<Event> before(List<Event> events) { public List<Event> before(List<Event> events) {
// TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming long localExecTime = 0L;
// 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.
if (events != null && !events.isEmpty()) { if (events != null && !events.isEmpty()) {
Event last = events.get(events.size() - 1); for (Event e : events) {
long ts = last.getExecuteTime(); EntryType type = e.getEntryType();
if (ts > latestExecuteTime) { if (type == null) continue;
latestExecuteTime = ts; 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; return events;
} }
public Collector getCollector() { @Override
return this.collector; 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;
}
} }

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -194,6 +194,8 @@ enum EventType {
/** XA **/ /** XA **/
XACOMMIT = 13; XACOMMIT = 13;
XAROLLBACK = 14; XAROLLBACK = 14;
/** MASTER HEARTBEAT **/
MHEARTBEAT = 15;
} }
/**数据库类型**/ /**数据库类型**/
@@ -201,4 +203,4 @@ enum Type {
ORACLE = 1; ORACLE = 1;
MYSQL = 2; MYSQL = 2;
PGSQL = 3; PGSQL = 3;
} }

View File

@@ -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) {}
}
}

View File

@@ -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);
}

View File

@@ -74,4 +74,20 @@ public class NettyUtils {
.toByteArray(), .toByteArray(),
channelFutureListener); 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();
}
} }

View File

@@ -73,7 +73,7 @@ public class ClientAuthenticationHandler extends SimpleChannelHandler {
MDC.remove("destination"); MDC.remove("destination");
} }
} }
// 鉴权一次性,暂不统计
NettyUtils.ack(ctx.getChannel(), new ChannelFutureListener() { NettyUtils.ack(ctx.getChannel(), new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {

View File

@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.server.netty.handler;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; 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.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.exception.ExceptionUtils;
import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffer;
@@ -51,6 +52,7 @@ public class SessionHandler extends SimpleChannelHandler {
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
logger.info("message receives in session handler..."); logger.info("message receives in session handler...");
long start = System.nanoTime();
ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
Packet packet = Packet.parseFrom(buffer.readBytes(buffer.readableBytes()).array()); Packet packet = Packet.parseFrom(buffer.readBytes(buffer.readableBytes()).array());
ClientIdentity clientIdentity = null; ClientIdentity clientIdentity = null;
@@ -74,12 +76,13 @@ public class SessionHandler extends SimpleChannelHandler {
embeddedServer.subscribe(clientIdentity); embeddedServer.subscribe(clientIdentity);
// ctx.setAttachment(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 { } else {
NettyUtils.error(401, byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage());
MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage(), NettyUtils.write(ctx.getChannel(), errorBytes ,new ChannelFutureAggregator(sub.getDestination(),
ctx.getChannel(), sub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
null);
} }
break; break;
case UNSUBSCRIPTION: case UNSUBSCRIPTION:
@@ -91,12 +94,13 @@ public class SessionHandler extends SimpleChannelHandler {
MDC.put("destination", clientIdentity.getDestination()); MDC.put("destination", clientIdentity.getDestination());
embeddedServer.unsubscribe(clientIdentity); embeddedServer.unsubscribe(clientIdentity);
stopCanalInstanceIfNecessary(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 { } else {
NettyUtils.error(401, byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage());
MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage(), NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(unsub.getDestination(),
ctx.getChannel(), unsub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
null);
} }
break; break;
case GET: case GET:
@@ -171,7 +175,8 @@ public class SessionHandler extends SimpleChannelHandler {
output.writeBytes(2, rowEntries.get(i)); output.writeBytes(2, rowEntries.get(i));
} }
output.checkNoSpaceLeft(); 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(); // output.flush();
// byteBuffer.flip(); // byteBuffer.flip();
@@ -192,14 +197,14 @@ public class SessionHandler extends SimpleChannelHandler {
} }
} }
} }
packetBuilder.setBody(messageBuilder.build().toByteString()); byte[] body = packetBuilder.setBody(messageBuilder.build().toByteString()).build().toByteArray();
NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据 NettyUtils.write(ctx.getChannel(), body, new ChannelFutureAggregator(get.getDestination(),
get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1));// 输出数据
} }
} else { } else {
NettyUtils.error(401, byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", get.toString()).getMessage());
MessageFormatter.format("destination or clientId is null", get.toString()).getMessage(), NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(get.getDestination(),
ctx.getChannel(), get, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
null);
} }
break; break;
case CLIENTACK: case CLIENTACK:
@@ -207,10 +212,9 @@ public class SessionHandler extends SimpleChannelHandler {
MDC.put("destination", ack.getDestination()); MDC.put("destination", ack.getDestination());
if (StringUtils.isNotEmpty(ack.getDestination()) && StringUtils.isNotEmpty(ack.getClientId())) { if (StringUtils.isNotEmpty(ack.getDestination()) && StringUtils.isNotEmpty(ack.getClientId())) {
if (ack.getBatchId() == 0L) { if (ack.getBatchId() == 0L) {
NettyUtils.error(402, byte[] errorBytes = NettyUtils.errorPacket(402, MessageFormatter.format("batchId should assign value", ack.toString()).getMessage());
MessageFormatter.format("batchId should assign value", ack.toString()).getMessage(), NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(),
ctx.getChannel(), ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 402));
null);
} else if (ack.getBatchId() == -1L) { // -1代表上一次get没有数据,直接忽略之 } else if (ack.getBatchId() == -1L) { // -1代表上一次get没有数据,直接忽略之
// donothing // donothing
} else { } else {
@@ -218,10 +222,9 @@ public class SessionHandler extends SimpleChannelHandler {
embeddedServer.ack(clientIdentity, ack.getBatchId()); embeddedServer.ack(clientIdentity, ack.getBatchId());
} }
} else { } else {
NettyUtils.error(401, byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage());
MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage(), NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(),
ctx.getChannel(), ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
null);
} }
break; break;
case CLIENTROLLBACK: case CLIENTROLLBACK:
@@ -237,25 +240,23 @@ public class SessionHandler extends SimpleChannelHandler {
embeddedServer.rollback(clientIdentity, rollback.getBatchId()); // 只回滚单个批次 embeddedServer.rollback(clientIdentity, rollback.getBatchId()); // 只回滚单个批次
} }
} else { } else {
NettyUtils.error(401, byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", rollback.toString()).getMessage());
MessageFormatter.format("destination or clientId is null", rollback.toString()) NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(rollback.getDestination(),
.getMessage(), rollback, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401));
ctx.getChannel(),
null);
} }
break; break;
default: default:
NettyUtils.error(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType()) byte[] errorBytes = NettyUtils.errorPacket(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType()).getMessage());
.getMessage(), ctx.getChannel(), null); NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ctx.getChannel().getRemoteAddress().toString(),
null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400));
break; break;
} }
} catch (Throwable exception) { } catch (Throwable exception) {
NettyUtils.error(400, byte[] errorBytes = NettyUtils.errorPacket(400, MessageFormatter.format("something goes wrong with channel:{}, exception={}",
MessageFormatter.format("something goes wrong with channel:{}, exception={}",
ctx.getChannel(), ctx.getChannel(),
ExceptionUtils.getStackTrace(exception)).getMessage(), ExceptionUtils.getStackTrace(exception)).getMessage());
ctx.getChannel(), NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ctx.getChannel().getRemoteAddress().toString(),
null); null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400));
} finally { } finally {
MDC.remove("destination"); MDC.remove("destination");
} }

View File

@@ -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;
}
}
}

View File

@@ -39,6 +39,7 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
protected long emptyTransctionThresold = 8192; // 超过1024个事务头,输出一个 protected long emptyTransctionThresold = 8192; // 超过1024个事务头,输出一个
protected volatile long lastEmptyTransactionTimestamp = 0L; protected volatile long lastEmptyTransactionTimestamp = 0L;
protected AtomicLong lastEmptyTransactionCount = new AtomicLong(0L); protected AtomicLong lastEmptyTransactionCount = new AtomicLong(0L);
private AtomicLong eventsSinkBlockingTime = new AtomicLong(0L);
public EntryEventSink(){ public EntryEventSink(){
addHandler(new HeartBeatEntryEventHandler()); addHandler(new HeartBeatEntryEventHandler());
@@ -147,16 +148,27 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) { for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) {
events = handler.before(events); events = handler.before(events);
} }
long blockingStart = 0L;
int fullTimes = 0; int fullTimes = 0;
do { do {
if (eventStore.tryPut(events)) { if (eventStore.tryPut(events)) {
if (fullTimes > 0) {
eventsSinkBlockingTime.addAndGet(System.nanoTime() - blockingStart);
}
for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) { for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) {
events = handler.after(events); events = handler.after(events);
} }
return true; return true;
} else { } else {
if (fullTimes == 0) {
blockingStart = System.nanoTime();
}
applyWait(++fullTimes); applyWait(++fullTimes);
if (fullTimes % 100 == 0) {
long nextStart = System.nanoTime();
eventsSinkBlockingTime.addAndGet(nextStart - blockingStart);
blockingStart = nextStart;
}
} }
for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) { for (CanalEventDownStreamHandler<List<Event>> handler : getHandlers()) {
@@ -202,4 +214,8 @@ public class EntryEventSink extends AbstractCanalEventSink<List<CanalEntry.Entry
this.emptyTransctionThresold = emptyTransctionThresold; this.emptyTransctionThresold = emptyTransctionThresold;
} }
public AtomicLong getEventsSinkBlockingTime() {
return eventsSinkBlockingTime;
}
} }

View File

@@ -562,4 +562,23 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple
this.ddlIsolation = ddlIsolation; 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;
}
} }

View File

@@ -1,6 +1,7 @@
package com.alibaba.otter.canal.store.model; package com.alibaba.otter.canal.store.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
@@ -31,6 +32,7 @@ public class Event implements Serializable {
private EventType eventType; private EventType eventType;
private String gtid; private String gtid;
private long rawLength; private long rawLength;
private int rowsCount;
public Event(){ public Event(){
} }
@@ -47,6 +49,17 @@ public class Event implements Serializable {
// build raw // build raw
this.rawEntry = entry.toByteString(); this.rawEntry = entry.toByteString();
this.rawLength = rawEntry.size(); 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() { public LogIdentity getLogIdentity() {
@@ -129,7 +142,16 @@ public class Event implements Serializable {
this.eventType = eventType; this.eventType = eventType;
} }
public int getRowsCount() {
return rowsCount;
}
public void setRowsCount(int rowsCount) {
this.rowsCount = rowsCount;
}
public String toString() { public String toString() {
return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE);
} }
} }