Compare commits

...

66 Commits

Author SHA1 Message Date
七锋
dd8b1ce955 [maven-release-plugin] prepare release canal-1.1.0 2018-08-20 13:28:57 +08:00
七锋
cb884094d5 fixed metrics 2018-08-20 13:01:22 +08:00
agapple
f567abdaee Merge pull request #855 from lcybo/master
Remove TPS(events) in tmpl.
2018-08-20 10:16:03 +08:00
Chuanyi Li
02757693b4 Remove TPS(events) in tmpl. 2018-08-20 08:02:20 +08:00
agapple
35797ca6ff Merge pull request #854 from lcybo/master
根据comments的改进
2018-08-19 20:48:23 +08:00
Chuanyi Li
5588da59b9 Update response time image. 2018-08-19 18:19:18 +08:00
Chuanyi Li
fbb3b814c0 Improve image. 2018-08-19 17:40:53 +08:00
Chuanyi Li
2784f33b52 Improve metrics. 2018-08-19 17:01:03 +08:00
Chuanyi Li
2656d1d2af Improve overview image. 2018-08-18 17:32:03 +08:00
Chuanyi Li
b6359d014f Add overview image. 2018-08-18 17:24:54 +08:00
agapple
a4445043c1 Merge pull request #853 from lcybo/master
修复启动时,没读到带execTime时,出现48年delay。
2018-08-18 13:59:54 +08:00
Chuanyi Li
59169876eb Update images. 2018-08-18 12:21:24 +08:00
Chuanyi Li
d06ecdef20 Introduce grafana template, update images. 2018-08-18 12:20:11 +08:00
Chuanyi Li
4a10986b5f Introduce grafana template, update images. 2018-08-18 11:15:53 +08:00
Chuanyi Li
e81b4279b8 Merge branch 'master' of https://github.com/alibaba/canal 2018-08-17 20:00:51 +08:00
Chuanyi Li
a6abe7fa82 Avoid 48 years delay at startup. 2018-08-17 19:55:08 +08:00
七锋
10a80b2752 fixed compiler 2018-08-17 19:55:08 +08:00
七锋
0131c3ad13 fixed merge code 2018-08-17 19:55:08 +08:00
mcy
62f975deeb 加上 if (logger.isDebugEnabled()) 防止message.toString每次都会被运行,message过大会卡cpu 2018-08-17 19:55:08 +08:00
winger
2accfbf5db 升级Kafka 版本至 2.11_1.1.1 2018-08-17 19:55:08 +08:00
yfpeng
7ed6fcdf04 debug memory leak 2018-08-17 19:55:08 +08:00
七锋
156fc0edbe fixed docker build 2018-08-17 19:55:08 +08:00
七锋
c421ff718b preapre for 1.1.0 release 2018-08-17 19:55:08 +08:00
七锋
cc31e2db7c fixed docker build 2018-08-17 19:55:07 +08:00
七锋
10114f4a9f fixed issue #727 , support rds binlog dump 2018-08-17 19:55:07 +08:00
七锋
e879216fa4 fixed compiler 2018-08-17 10:40:51 +08:00
agapple
f0ea893bf4 Merge pull request #845 from lcybo/master
Add header size for each packet.
2018-08-17 00:25:40 +08:00
Chuanyi Li
3053b9635a Add header size for each packet. 2018-08-16 07:59:45 +08:00
七锋
a1588e5f98 fixed merge code 2018-08-15 19:45:11 +08:00
agapple
1fd623311e Merge pull request #841 from lcybo/master
Create canal prometheus docs.
2018-08-15 17:25:43 +08:00
agapple
a480ef60ea Merge pull request #844 from rewerma/master
加上 if (logger.isDebugEnabled()) 防止message.toString每次都会被运行,message过大会卡cpu
2018-08-15 17:23:27 +08:00
mcy
4776552605 加上 if (logger.isDebugEnabled()) 防止message.toString每次都会被运行,message过大会卡cpu 2018-08-15 14:26:55 +08:00
Chuanyi Li
efb7adc967 Create canal prometheus docs. 2018-08-14 22:24:50 +08:00
agapple
feb2af060f Merge pull request #839 from payonxp/master
Debug Memory Leak
2018-08-14 16:39:41 +08:00
agapple
15526aab8c Merge pull request #840 from wingerx/master
升级Kafka 版本至 2.11_1.1.1
2018-08-14 16:37:09 +08:00
winger
679fd464f4 升级Kafka 版本至 2.11_1.1.1 2018-08-14 16:09:44 +08:00
yfpeng
c8a32b578c debug memory leak 2018-08-14 15:59:08 +08:00
Payon
84f3d92790 Merge pull request #1 from alibaba/master
merge
2018-08-14 14:04:47 +08:00
七锋
13291c8d7b fixed docker build 2018-08-14 13:38:01 +08:00
七锋
8a74504fe3 preapre for 1.1.0 release 2018-08-14 13:29:03 +08:00
七锋
11d4761bcb fixed docker build 2018-08-14 13:23:24 +08:00
agapple
72213bb80d Merge pull request #833 from lcybo/master
store增加bufferSize,metrics server端口可配置
2018-08-13 22:42:01 +08:00
Chuanyi Li
e8aee3a276 Configurable metrics server port. 2018-08-13 21:50:50 +08:00
Chuanyi Li
a3bf1d59e6 Add size label for store metrics. 2018-08-13 21:03:30 +08:00
七锋
d3f52c6d3d fixed issue #727 , support rds binlog dump 2018-08-13 10:16:28 +08:00
agapple
fb1126cf39 Merge pull request #828 from lcybo/master
canal 性能指标采集
2018-08-10 23:22:52 +08:00
Chuanyi Li
ec00792475 Enhance heartbeat, rowscount. 2018-08-09 23:54:53 +08:00
Chuanyi Li
d7bbb0d1a7 Reformat code. 2018-08-09 23:54:53 +08:00
Chuanyi Li
dfd58f4aa1 Improve sink blocking time resolution. 2018-08-09 23:54:53 +08:00
Chuanyi Li
179afb0340 Canal metrics snapshot version. 2018-08-09 23:54:52 +08:00
Chuanyi Li L
fec9803aae Refactor metrics code. 2018-08-09 23:54:00 +08:00
Chuanyi Li
75de57c102 Prometheus refactor. 2018-08-09 23:51:26 +08:00
Chuanyi Li L
2084b1fc37 Add listener to write. 2018-08-09 23:51:25 +08:00
qmzhang
07d4d2023a instance由manager管理时,CanalParameter#MetaMode增加本地文件方式,本地meta.dat存储位点信息; 2018-08-09 23:51:24 +08:00
七锋
fac278150e fixed issue #801 , support canal docker 2018-08-09 23:48:06 +08:00
Chuanyi Li
da5fb30191 canal server metrics refactor. 2018-08-09 23:45:38 +08:00
Chuanyi Li L
01e74c73e2 Reformat code. 2018-08-09 23:43:26 +08:00
Chuanyi Li L
782ef7fe14 Netty server/client profiler skeleton. 2018-08-09 23:43:26 +08:00
Chuanyi Li L
bd14848600 Builder of Aggregator. 2018-08-09 23:42:27 +08:00
Chuanyi Li
1c245280c7 netty3 future listener for metrics. 2018-08-09 23:42:27 +08:00
七锋
d7f48f534b fixed encoding example 2018-08-09 22:23:12 +08:00
七锋
8ce320148c fixed code 2018-08-09 22:20:54 +08:00
七锋
3eef7b583d Merge branch 'branch-rds-support' of github.com:alibaba/canal 2018-08-09 21:55:33 +08:00
charles.lin
b53131553e Commit test case 2018-08-09 02:01:44 +08:00
charles.lin
68d8afae3e Merge commit '9e816bc48f9955f9e2839873993cef9627c2f389' into branch-rds-support
Conflicts:
	instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java
	instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java
	parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
	parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
2018-08-09 02:01:00 +08:00
charles.lin
61c7f7738d rds binlog 下载支持 2018-08-08 19:29:52 +08:00
124 changed files with 17372 additions and 14260 deletions

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.common</artifactId> <artifactId>canal.common</artifactId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -168,7 +168,9 @@ public final class FileLogFetcher extends LogFetcher {
* @see com.taobao.tddl.dbsync.binlog.LogFetcher#close() * @see com.taobao.tddl.dbsync.binlog.LogFetcher#close()
*/ */
public void close() throws IOException { public void close() throws IOException {
if (fin != null) fin.close(); if (fin != null) {
fin.close();
}
fin = null; fin = null;
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
@@ -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

@@ -15,6 +15,7 @@ public class CanalConstants {
public static final String CANAL_ID = ROOT + "." + "id"; public static final String CANAL_ID = ROOT + "." + "id";
public static final String CANAL_IP = ROOT + "." + "ip"; public static final String CANAL_IP = ROOT + "." + "ip";
public static final String CANAL_PORT = ROOT + "." + "port"; public static final String CANAL_PORT = ROOT + "." + "port";
public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port";
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers"; public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty"; public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty";

View File

@@ -98,6 +98,14 @@ public class CanalController {
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
embededCanalServer = CanalServerWithEmbedded.instance(); embededCanalServer = CanalServerWithEmbedded.instance();
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
try {
int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT));
embededCanalServer.setMetricsPort(metricsPort);
} catch (NumberFormatException e) {
logger.info("No valid metrics server port found, use default 11112.");
embededCanalServer.setMetricsPort(11112);
}
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
canalServer = CanalServerWithNetty.instance(); canalServer = CanalServerWithNetty.instance();

View File

@@ -4,6 +4,7 @@
canal.id= 1 canal.id= 1
canal.ip= canal.ip=
canal.port=11111 canal.port=11111
canal.metrics.pull.port=11112
canal.zkServers= canal.zkServers=
# flush data to zk # flush data to zk
canal.zookeeper.flush.period = 1000 canal.zookeeper.flush.period = 1000
@@ -66,6 +67,10 @@ canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MO
canal.instance.tsdb.dbUsername=canal canal.instance.tsdb.dbUsername=canal
canal.instance.tsdb.dbPassword=canal canal.instance.tsdb.dbPassword=canal
# rds oss binlog account
canal.instance.rds.accesskey =
canal.instance.rds.secretkey =
################################################# #################################################
######### destinations ############# ######### destinations #############
################################################# #################################################
@@ -82,7 +87,6 @@ canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
canal.instance.global.mode = spring canal.instance.global.mode = spring
canal.instance.global.lazy = false canal.instance.global.lazy = false
#canal.instance.global.manager.address = 127.0.0.1:1099 #canal.instance.global.manager.address = 127.0.0.1:1099
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml #canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml #canal.instance.global.spring.xml = classpath:spring/default-instance.xml

View File

@@ -12,6 +12,11 @@ canal.instance.master.position=
canal.instance.master.timestamp= canal.instance.master.timestamp=
canal.instance.master.gtid= canal.instance.master.gtid=
# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=
# table meta tsdb info # table meta tsdb info
canal.instance.tsdb.enable=true canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb

View File

@@ -1,32 +0,0 @@
#################################################
# rds openapi binlog
canal.instance.rds.open.url=https://rds.aliyuncs.com/
canal.instance.rds.open.accesskey=
canal.instance.rds.open.secretkey=
canal.instance.rds.instanceId=
canal.instance.rds.startTime=
canal.instance.rds.endTime=
# local binlog dir
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
# position info
canal.instance.master.address=127.0.0.1:3306
# table meta tsdb info
canal.instance.tsdb.enable=true
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.defaultDatabaseName=test
canal.instance.connectionCharset=UTF-8
# table regex
canal.instance.filter.regex=test\\..*
# table black regex
canal.instance.filter.black.regex=
#################################################

View File

@@ -79,7 +79,7 @@
</logger> </logger>
<root level="WARN"> <root level="WARN">
<appender-ref ref="STDOUT"/> <!-- <appender-ref ref="STDOUT"/> -->
<appender-ref ref="CANAL-ROOT" /> <appender-ref ref="CANAL-ROOT" />
</root> </root>
</configuration> </configuration>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="baseEventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy" abstract="true">
<property name="accesskey" value="${canal.instance.rds.accesskey:}" />
<property name="secretkey" value="${canal.instance.rds.secretkey:}" />
<property name="instanceId" value="${canal.instance.rds.instanceId:}" />
</bean>
</beans>

View File

@@ -10,26 +10,7 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName"> default-autowire="byName">
<!-- properties --> <import resource="classpath:spring/base-instance.xml" />
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring"> <bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
@@ -84,7 +65,7 @@
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/> <property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean> </bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser" parent="baseEventParser" >
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->

View File

@@ -10,26 +10,7 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName"> default-autowire="byName">
<!-- properties --> <import resource="classpath:spring/base-instance.xml" />
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring"> <bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
@@ -70,7 +51,7 @@
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/> <property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean> </bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser" parent="baseEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->

View File

@@ -10,26 +10,7 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName"> default-autowire="byName">
<!-- properties --> <import resource="classpath:spring/base-instance.xml" />
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring"> <bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
@@ -76,7 +57,7 @@
</property> </property>
</bean> </bean>
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser1" parent="baseEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->
@@ -174,7 +155,7 @@
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" /> <property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean> </bean>
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser2" parent="baseEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->

View File

@@ -1,147 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" />
<property name="eventParser">
<ref local="eventParser" />
</property>
<property name="eventSink">
<ref local="eventSink" />
</property>
<property name="eventStore">
<ref local="eventStore" />
</property>
<property name="metaManager">
<ref local="metaManager" />
</property>
<property name="alarmHandler">
<ref local="alarmHandler" />
</property>
</bean>
<!-- 报警处理类 -->
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
<property name="period" value="${canal.file.flush.period:1000}" />
</bean>
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
</bean>
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="alarmHandler" ref="alarmHandler" />
<!-- 解析过滤处理 -->
<property name="eventFilter">
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
</bean>
</property>
<property name="eventBlackFilter">
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
<constructor-arg index="1" value="false" />
</bean>
</property>
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
<!-- 解析编码 -->
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
<!-- 解析位点记录 -->
<property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</constructor-arg>
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<constructor-arg ref="metaManager"/>
</bean>
</constructor-arg>
</bean>
</property>
<!-- 解析数据库信息 -->
<property name="masterInfo">
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
<property name="address" value="${canal.instance.master.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
<property name="directory" value="${canal.instance.parser.directory:}"/>
<!-- rds相关 -->
<property name="url" value="${canal.instance.rds.open.url:}"/>
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
</beans>

View File

@@ -10,26 +10,7 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName"> default-autowire="byName">
<!-- properties --> <import resource="classpath:spring/base-instance.xml" />
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring"> <bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
@@ -67,7 +48,7 @@
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/> <property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean> </bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser" parent="baseEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->

View File

@@ -1,36 +1,10 @@
FROM centos:centos6.7 FROM canal/osbase:v1
MAINTAINER agapple (jianghang115@gmail.com) MAINTAINER agapple (jianghang115@gmail.com)
# install system
RUN \
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'root:Hello1234' | chpasswd && \
groupadd -r admin && useradd -g admin admin && \
yum install -y man && \
yum install -y dstat && \
yum install -y unzip && \
yum install -y nc && \
yum install -y openssh-server && \
yum install -y tar && \
yum install -y which && \
yum install -y wget && \
yum install -y perl && \
yum install -y file && \
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
yum install -y cronie && \
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
yum clean all && \
true
# install canal # install canal
COPY image/ /tmp/docker/ COPY image/ /tmp/docker/
COPY canal.deployer-*.tar.gz /home/admin/ COPY canal.deployer-*.tar.gz /home/admin/
COPY jdk-8-linux-x64.rpm /tmp/
RUN \ RUN \
cp -R /tmp/docker/alidata /alidata && \ cp -R /tmp/docker/alidata /alidata && \
@@ -39,14 +13,6 @@ RUN \
cp -R /tmp/docker/admin/* /home/admin/ && \ cp -R /tmp/docker/admin/* /home/admin/ && \
/bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \ /bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \
touch /var/lib/rpm/* && \
yum -y install /tmp/jdk-8-linux-x64.rpm && \
/bin/rm -f /tmp/jdk-8-linux-x64.rpm && \
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
/bin/mv /home/admin/bin/clean_log /etc/cron.d && \
mkdir -p /home/admin/canal-server && \ mkdir -p /home/admin/canal-server && \
tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \ tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \
/bin/rm -f /home/admin/canal.deployer-*.tar.gz && \ /bin/rm -f /home/admin/canal.deployer-*.tar.gz && \
@@ -64,4 +30,4 @@ EXPOSE 2222 11111 8000 8080
WORKDIR /home/admin WORKDIR /home/admin
ENTRYPOINT [ "/alidata/bin/main.sh" ] ENTRYPOINT [ "/alidata/bin/main.sh" ]
CMD [ "/home/admin/app.sh" ] CMD [ "/home/admin/app.sh" ]

41
docker/base/Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
FROM centos:centos6.10
MAINTAINER agapple (jianghang115@gmail.com)
env DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.rpm"
# install system
RUN \
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'root:Hello1234' | chpasswd && \
groupadd -r admin && useradd -g admin admin && \
yum install -y man && \
yum install -y dstat && \
yum install -y unzip && \
yum install -y nc && \
yum install -y openssh-server && \
yum install -y tar && \
yum install -y which && \
yum install -y wget && \
yum install -y perl && \
yum install -y file && \
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
yum install -y cronie && \
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
true
RUN \
touch /var/lib/rpm/* && \
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O /tmp/jdk-8-linux-x64.rpm && \
yum -y install /tmp/jdk-8-linux-x64.rpm && \
/bin/rm -f /tmp/jdk-8-linux-x64.rpm && \
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
yum clean all && \
true
CMD ["/bin/bash"]

View File

@@ -14,17 +14,16 @@ case "`uname`" in
esac esac
BASE=${bin_abs_path} BASE=${bin_abs_path}
if [ ! -f $BASE/jdk*.rpm ] ; then if [ "$1" == "base" ] ; then
DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz" docker build --no-cache -t canal/osbase $BASE/base
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm
fi
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
if [ "$1" == "kafka" ] ; then
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
docker build --no-cache -t canal/canal-server $BASE/
else else
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ rm -rf $BASE/canal.*.tar.gz ;
docker build --no-cache -t canal/canal-server $BASE/ cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
if [ "$1" == "kafka" ] ; then
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
docker build --no-cache -t canal/canal-server $BASE/
else
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
docker build --no-cache -t canal/canal-server $BASE/
fi
fi fi

View File

@@ -86,7 +86,9 @@ function start_canal() {
echo "multi destination:$destination is not support" echo "multi destination:$destination is not support"
exit 1; exit 1;
else else
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination if [ "$destination" != "" ] && [ "$destination" != "example" ] ; then
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination
fi
fi fi
su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1' su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1'
sleep 5 sleep 5

View File

@@ -1,6 +1,11 @@
#!/bin/sh #!/bin/sh
CHECK_URL="http://127.0.0.1:8080/metrics" metrics_port=`perl -le 'print $ENV{"canal.metrics.pull.port"}'`
CHECK_POINT="success" if [ "$metrics_port" == "" ]; then
metrics_port="11112"
fi
CHECK_URL="http://127.0.0.1:$metrics_port/metrics"
CHECK_POINT="canal"
CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT` CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT`
if [ $CHECK_COUNT -eq 0 ]; then if [ $CHECK_COUNT -eq 0 ]; then
echo "[FAILED]" echo "[FAILED]"

View File

@@ -10,7 +10,7 @@ function usage() {
echo " -e canal.instance.connectionCharset=UTF-8 \\" echo " -e canal.instance.connectionCharset=UTF-8 \\"
echo " -e canal.instance.tsdb.enable=true \\" echo " -e canal.instance.tsdb.enable=true \\"
echo " -e canal.instance.gtidon=false \\" echo " -e canal.instance.gtidon=false \\"
echo " -e canal.instance.filter.regex=.*\\..* " echo " -e canal.instance.filter.regex=.*\\\\\\..* "
exit exit
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

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

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

BIN
images/QPS.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/TPS.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/basic.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
images/clients.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
images/delay.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/empty.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/idle.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/instance.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/latency.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/network.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/overview.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
images/remain_events.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
images/remain_mem.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/reqs.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/rows.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/store.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
images/throughput.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
images/transactions.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.instance.core</artifactId> <artifactId>canal.instance.core</artifactId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.alibaba.otter.canal.meta.FileMixedMetaManager;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -38,12 +39,7 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser; import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
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.MysqlEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; import com.alibaba.otter.canal.parse.index.*;
import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager;
import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager;
import com.alibaba.otter.canal.parse.index.MetaLogPositionManager;
import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager;
import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.protocol.position.EntryPosition;
import com.alibaba.otter.canal.sink.entry.EntryEventSink; import com.alibaba.otter.canal.sink.entry.EntryEventSink;

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
@@ -24,7 +22,7 @@
<dependency> <dependency>
<groupId>org.apache.kafka</groupId> <groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId> <artifactId>kafka-clients</artifactId>
<version>0.9.0.1</version> <version>1.1.1</version>
</dependency> </dependency>
<!-- junit --> <!-- junit -->
@@ -108,4 +106,4 @@
</build> </build>
</profile> </profile>
</profiles> </profiles>
</project> </project>

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
@@ -25,11 +23,10 @@
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.17</version> <version>1.17</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.kafka</groupId> <groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId> <artifactId>kafka_2.11</artifactId>
<version>0.9.0.1</version> <version>1.1.1</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
@@ -37,6 +34,12 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<!--kafka_2.11_1.1.1 exclusion掉了netty 的依赖,但CanalServerWithNetty 依赖 netty3,升级kafka至 1.1.1 需要显示加入,否则会启动失败 -->
<dependency>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
<version>3.2.2.Final</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -137,4 +140,4 @@
</build> </build>
</profile> </profile>
</profiles> </profiles>
</project> </project>

View File

@@ -21,7 +21,7 @@ import com.alibaba.otter.canal.protocol.Message;
*/ */
public class CanalKafkaProducer { public class CanalKafkaProducer {
private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class);
private Producer<String, Message> producer; private Producer<String, Message> producer;
@@ -83,6 +83,8 @@ public class CanalKafkaProducer {
record = new ProducerRecord<String, Message>(topic.getTopic(), message); record = new ProducerRecord<String, Message>(topic.getTopic(), message);
} }
producer.send(record); producer.send(record);
logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); if (logger.isDebugEnabled()) {
logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString());
}
} }
} }

View File

@@ -3,7 +3,7 @@ retries: 0
batchSize: 16384 batchSize: 16384
lingerMs: 1 lingerMs: 1
bufferMemory: 33554432 bufferMemory: 33554432
# canal的批次大小,单位 k # canal的批次大小,单位 k,量大建议改为1M
canalBatchSize: 50 canalBatchSize: 50
filterTransactionEntry: true filterTransactionEntry: true
@@ -13,7 +13,7 @@ canalDestinations:
partition: partition:
# 一个destination可以对应多个topic # 一个destination可以对应多个topic
# topics: # topics:
# - topics: example # - topic: example
# partition: # partition:

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.parse</artifactId> <artifactId>canal.parse</artifactId>

View File

@@ -0,0 +1,30 @@
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

@@ -0,0 +1,32 @@
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{
private static final long serialVersionUID = -6124989280379293953L;
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

@@ -21,6 +21,7 @@ import com.alibaba.otter.canal.filter.CanalEventFilter;
import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
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.TableIdNotFoundException; import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback; import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
@@ -94,6 +95,8 @@ 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();
@@ -170,11 +173,16 @@ 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 CanalParseException("can't find start position for " + destination); throw new PositionNotFoundException("can't find start position for " + destination);
} }
if (!processTableMeta(startPosition)) { if (!processTableMeta(startPosition)) {
@@ -277,6 +285,9 @@ 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();
@@ -426,7 +437,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
return logPosition; return logPosition;
} }
protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, long startPosition) { protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, Long startPosition) {
if (lastPosition != null) { if (lastPosition != null) {
logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]", logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]",
lastPosition.getPostion()), lastPosition.getPostion()),
@@ -615,4 +626,20 @@ 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,4 +40,6 @@ 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

@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound;
import com.alibaba.otter.canal.common.CanalLifeCycle; import com.alibaba.otter.canal.common.CanalLifeCycle;
import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
/** /**
* 针对解析器提供一个多阶段协同的处理 * 针对解析器提供一个多阶段协同的处理
@@ -23,7 +24,7 @@ public interface MultiStageCoprocessor extends CanalLifeCycle {
*/ */
public boolean publish(LogBuffer buffer); public boolean publish(LogBuffer buffer);
public boolean publish(LogBuffer buffer, String binlogFileName); public boolean publish(LogEvent event);
public void reset(); public void reset();
} }

View File

@@ -0,0 +1,9 @@
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 =========================
@@ -204,4 +208,8 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
this.tableMetaTSDBFactory = tableMetaTSDBFactory; this.tableMetaTSDBFactory = tableMetaTSDBFactory;
} }
public AtomicLong getEventsPublishBlockingTime() {
return this.eventsPublishBlockingTime;
}
} }

View File

@@ -10,18 +10,18 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException;
import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor; import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.SinkFunction;
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue; import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
import com.taobao.tddl.dbsync.binlog.FileLogFetcher; import com.taobao.tddl.dbsync.binlog.FileLogFetcher;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogContext;
import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogDecoder;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
import com.taobao.tddl.dbsync.binlog.LogPosition; import com.taobao.tddl.dbsync.binlog.LogPosition;
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
/** /**
* local bin log connection (not real connection) * local bin log connection (not real connection)
@@ -36,6 +36,8 @@ 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(){
} }
@@ -88,14 +90,14 @@ public class LocalBinLogConnection implements ErosaConnection {
while (running) { while (running) {
boolean needContinue = true; boolean needContinue = true;
LogEvent event = null; LogEvent event = null;
// 处理一下binlog文件名
event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
func.sink(event);
while (fetcher.fetch()) { while (fetcher.fetch()) {
event = decoder.decode(fetcher, context); event = decoder.decode(fetcher, context);
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;
@@ -103,8 +105,9 @@ public class LocalBinLogConnection implements ErosaConnection {
} }
} }
fetcher.close(); // 关闭上一个文件
parserFinish(current.getName());
if (needContinue) {// 读取下一个 if (needContinue) {// 读取下一个
fetcher.close(); // 关闭上一个文件
File nextFile; File nextFile;
if (needWait) { if (needWait) {
@@ -160,6 +163,10 @@ 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;
} }
@@ -176,6 +183,9 @@ public class LocalBinLogConnection implements ErosaConnection {
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) { } else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
lastXidLogFilename = current.getName(); lastXidLogFilename = current.getName();
lastXidLogFileOffset = event.getLogPos(); lastXidLogFileOffset = event.getLogPos();
} else if (LogEvent.FORMAT_DESCRIPTION_EVENT == event.getHeader().getType()) {
lastXidLogFilename = current.getName();
lastXidLogFileOffset = event.getLogPos();
} }
} }
} }
@@ -212,25 +222,37 @@ public class LocalBinLogConnection implements ErosaConnection {
@Override @Override
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException { public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
File current = new File(directory, binlogfilename); File current = new File(directory, binlogfilename);
if (!current.exists()) {
throw new CanalParseException("binlog:" + binlogfilename + " is not found");
}
FileLogFetcher fetcher = new FileLogFetcher(bufferSize); FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
LogContext context = new LogContext();
try { try {
fetcher.open(current, binlogPosition); fetcher.open(current, binlogPosition);
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
while (running) { while (running) {
boolean needContinue = true; boolean needContinue = true;
LogEvent event = null;
while (fetcher.fetch()) { while (fetcher.fetch()) {
LogBuffer buffer = fetcher.duplicate(); event = decoder.decode(fetcher, context);
fetcher.consume(fetcher.limit()); if (event == null) {
// set filename continue;
if (!coprocessor.publish(buffer, binlogfilename)) { }
if (serverId != 0 && event.getServerId() != serverId) {
throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !");
}
if (!coprocessor.publish(event)) {
needContinue = false; needContinue = false;
break; break;
} }
} }
fetcher.close(); // 关闭上一个文件
parserFinish(binlogfilename);
if (needContinue) {// 读取下一个 if (needContinue) {// 读取下一个
fetcher.close(); // 关闭上一个文件
File nextFile; File nextFile;
if (needWait) { if (needWait) {
nextFile = binlogs.waitForNextFile(current); nextFile = binlogs.waitForNextFile(current);
@@ -258,6 +280,12 @@ 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();
@@ -286,6 +314,10 @@ 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;
} }
@@ -302,6 +334,9 @@ public class LocalBinLogConnection implements ErosaConnection {
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) { } else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
lastXidLogFilename = current.getName(); lastXidLogFilename = current.getName();
lastXidLogFileOffset = event.getLogPos(); lastXidLogFileOffset = event.getLogPos();
} else if (LogEvent.FORMAT_DESCRIPTION_EVENT == event.getHeader().getType()) {
lastXidLogFilename = current.getName();
lastXidLogFileOffset = event.getLogPos();
} }
} }
} }
@@ -344,6 +379,11 @@ 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;
} }
@@ -368,4 +408,21 @@ 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,8 +7,10 @@ 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;
@@ -49,6 +51,8 @@ public class MysqlConnection implements ErosaConnection {
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(){
} }
@@ -124,6 +128,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);
@@ -146,6 +151,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);
@@ -174,6 +180,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);
@@ -204,6 +211,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)) {
@@ -230,6 +238,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)) {
@@ -324,6 +333,16 @@ 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 ====================
/** /**
@@ -334,7 +353,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 {
@@ -453,6 +471,12 @@ 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");
@@ -592,4 +616,8 @@ public class MysqlConnection implements ErosaConnection {
this.authInfo = authInfo; this.authInfo = authInfo;
} }
public void setReceivedBinlogBytes(AtomicLong receivedBinlogBytes) {
this.receivedBinlogBytes = receivedBinlogBytes;
}
} }

View File

@@ -47,29 +47,32 @@ import com.taobao.tddl.dbsync.binlog.LogEvent;
*/ */
public class MysqlEventParser extends AbstractMysqlEventParser implements CanalEventParser, CanalHASwitchable { public class MysqlEventParser extends AbstractMysqlEventParser implements CanalEventParser, CanalHASwitchable {
private CanalHAController haController = null; private CanalHAController haController = null;
private int defaultConnectionTimeoutInSeconds = 30; // sotimeout private int defaultConnectionTimeoutInSeconds = 30; // sotimeout
private int receiveBufferSize = 64 * 1024; private int receiveBufferSize = 64 * 1024;
private int sendBufferSize = 64 * 1024; private int sendBufferSize = 64 * 1024;
// 数据库信息 // 数据库信息
private AuthenticationInfo masterInfo; // 主库 protected AuthenticationInfo masterInfo; // 主库
private AuthenticationInfo standbyInfo; // 备库 protected AuthenticationInfo standbyInfo; // 备库
// binlog信息 // binlog信息
private EntryPosition masterPosition; protected EntryPosition masterPosition;
private EntryPosition standbyPosition; protected EntryPosition standbyPosition;
private long slaveId; // 链接到mysql的slave private long slaveId; // 链接到mysql的slave
// 心跳检查信息 // 心跳检查信息
private String detectingSQL; // 心跳sql private String detectingSQL; // 心跳sql
private MysqlConnection metaConnection; // 查询meta信息的链接 private MysqlConnection metaConnection; // 查询meta信息的链接
private TableMetaCache tableMetaCache; // 对应meta private TableMetaCache tableMetaCache; // 对应meta
private int fallbackIntervalInSeconds = 60; // 切换回退时间 private int fallbackIntervalInSeconds = 60; // 切换回退时间
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
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>() {
@@ -752,16 +756,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
Long logposTimestamp = entry.getHeader().getExecuteTime(); Long logposTimestamp = entry.getHeader().getExecuteTime();
Long serverId = entry.getHeader().getServerId(); Long serverId = entry.getHeader().getServerId();
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType()) // 如果最小的一条记录都不满足条件,可直接退出
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) { if (logposTimestamp >= startTimestamp) {
if (logger.isDebugEnabled()) { return false;
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
logfilename, logfileoffset, logposTimestamp, startTimestamp });
}
// 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出
if (logposTimestamp >= startTimestamp) {
return false;
}
} }
if (StringUtils.equals(endPosition.getJournalName(), logfilename) if (StringUtils.equals(endPosition.getJournalName(), logfilename)
@@ -910,4 +907,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
this.dumpErrorCountThreshold = dumpErrorCountThreshold; this.dumpErrorCountThreshold = dumpErrorCountThreshold;
} }
public AtomicLong getReceivedBinlogBytes() {
return this.receivedBinlogBytes;
}
} }

View File

@@ -2,10 +2,9 @@ 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 com.alibaba.otter.canal.common.AbstractCanalLifeCycle; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
import com.alibaba.otter.canal.common.utils.NamedThreadFactory; import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.exception.CanalParseException;
@@ -31,7 +30,6 @@ import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogContext;
import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogDecoder;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
import com.taobao.tddl.dbsync.binlog.LogPosition;
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
@@ -52,6 +50,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 +62,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){
@@ -138,14 +138,18 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
super.stop(); super.stop();
} }
public boolean publish(LogBuffer buffer) {
return this.publish(buffer, null);
}
/** /**
* 网络数据投递 * 网络数据投递
*/ */
public boolean publish(LogBuffer buffer) { public boolean publish(LogEvent event) {
return publish(buffer, null); return this.publish(null, event);
} }
public boolean publish(LogBuffer buffer, String binlogFileName) { private boolean publish(LogBuffer buffer, LogEvent event) {
if (!isStart()) { if (!isStart()) {
if (exception != null) { if (exception != null) {
throw exception; throw exception;
@@ -161,25 +165,51 @@ 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();
MessageEvent event = disruptorMsgBuffer.get(next); MessageEvent data = disruptorMsgBuffer.get(next);
event.setBuffer(buffer); if (buffer != null) {
if (binlogFileName != null) { data.setBuffer(buffer);
event.setBinlogFileName(binlogFileName); } else {
data.setEvent(event);
} }
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()) {
@@ -201,17 +231,13 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception { public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
try { try {
LogBuffer buffer = event.getBuffer(); LogEvent logEvent = event.getEvent();
if (StringUtils.isNotEmpty(event.getBinlogFileName()) if (logEvent == null) {
&& !context.getLogPosition().getFileName().equals(event.getBinlogFileName())) { LogBuffer buffer = event.getBuffer();
// set roate binlog file name logEvent = decoder.decode(buffer, context);
context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition() event.setEvent(logEvent);
.getPosition()));
} }
LogEvent logEvent = decoder.decode(buffer, context);
event.setEvent(logEvent);
int eventType = logEvent.getHeader().getType(); int eventType = logEvent.getHeader().getType();
TableMeta tableMeta = null; TableMeta tableMeta = null;
boolean needDmlParse = false; boolean needDmlParse = false;
@@ -312,7 +338,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
// clear for gc // clear for gc
event.setBuffer(null); event.setBuffer(null);
event.setBinlogFileName(null);
event.setEvent(null); event.setEvent(null);
event.setTable(null); event.setTable(null);
event.setEntry(null); event.setEntry(null);
@@ -336,21 +361,12 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
class MessageEvent { class MessageEvent {
private String binlogFileName; // for local binlog parse
private LogBuffer buffer; private LogBuffer buffer;
private CanalEntry.Entry entry; private CanalEntry.Entry entry;
private boolean needDmlParse = false; private boolean needDmlParse = false;
private TableMeta table; private TableMeta table;
private LogEvent event; private LogEvent event;
public String getBinlogFileName() {
return binlogFileName;
}
public void setBinlogFileName(String binlogFileName) {
this.binlogFileName = binlogFileName;
}
public LogBuffer getBuffer() { public LogBuffer getBuffer() {
return buffer; return buffer;
} }
@@ -427,4 +443,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
this.connection = connection; this.connection = connection;
} }
public void setEventsPublishBlockingTime(AtomicLong eventsPublishBlockingTime) {
this.eventsPublishBlockingTime = eventsPublishBlockingTime;
}
} }

View File

@@ -42,6 +42,7 @@ 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.DeleteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent; import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.HeartbeatLogEvent;
import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent; import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent;
import com.taobao.tddl.dbsync.binlog.event.LogHeader; import com.taobao.tddl.dbsync.binlog.event.LogHeader;
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
@@ -144,6 +145,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;
} }
@@ -158,6 +161,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();
@@ -759,6 +771,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;
@@ -873,11 +887,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) { private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) {
try { try {
return tableMetaCache.getTableMeta(dbName, tbName, useCache, position); return tableMetaCache.getTableMeta(dbName, tbName, useCache, position);
} catch (Exception e) { } catch (Throwable e) {
String message = ExceptionUtils.getRootCauseMessage(e); String message = ExceptionUtils.getRootCauseMessage(e);
if (filterTableError) { if (filterTableError) {
if (StringUtils.contains(message, "errorNumber=1146") && StringUtils.contains(message, "doesn't exist")) { if (StringUtils.contains(message, "errorNumber=1146") && StringUtils.contains(message, "doesn't exist")) {
return null; return null;
} else if (StringUtils.contains(message, "errorNumber=1142")
&& StringUtils.contains(message, "command denied")) {
return null;
} }
} }

View File

@@ -25,13 +25,14 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
*/ */
public class BinLogFileQueue { public class BinLogFileQueue {
private String baseName = "mysql-bin."; private String baseName = "mysql-bin.";
private List<File> binlogs = new ArrayList<File>(); private List<File> binlogs = new ArrayList<File>();
private File directory; private File directory;
private ReentrantLock lock = new ReentrantLock(); private ReentrantLock lock = new ReentrantLock();
private Condition nextCondition = lock.newCondition(); private Condition nextCondition = lock.newCondition();
private Timer timer = new Timer(true); private Timer timer = new Timer(true);
private long reloadInterval = 10 * 1000L; // 10秒 private long reloadInterval = 10 * 1000L; // 10秒
private CanalParseException exception = null;
public BinLogFileQueue(String directory){ public BinLogFileQueue(String directory){
this(new File(directory)); this(new File(directory));
@@ -52,9 +53,24 @@ public class BinLogFileQueue {
timer.scheduleAtFixedRate(new TimerTask() { timer.scheduleAtFixedRate(new TimerTask() {
public void run() { public void run() {
List<File> files = listBinlogFiles(); try {
for (File file : files) { // File errorFile = new File(BinLogFileQueue.this.directory,
offer(file); // errorFileName);
// if (errorFile.isFile() && errorFile.exists()) {
// String text = StringUtils.join(IOUtils.readLines(new
// FileInputStream(errorFile)), "\n");
// exception = new CanalParseException(text);
// }
List<File> files = listBinlogFiles();
for (File file : files) {
offer(file);
}
} catch (Throwable e) {
exception = new CanalParseException(e);
}
if (exception != null) {
offer(null);
} }
} }
}, reloadInterval, reloadInterval); }, reloadInterval, reloadInterval);
@@ -69,6 +85,10 @@ public class BinLogFileQueue {
public File getNextFile(File pre) { public File getNextFile(File pre) {
try { try {
lock.lockInterruptibly(); lock.lockInterruptibly();
if (exception != null) {
throw exception;
}
if (binlogs.size() == 0) { if (binlogs.size() == 0) {
return null; return null;
} else { } else {
@@ -94,6 +114,10 @@ public class BinLogFileQueue {
public File getBefore(File file) { public File getBefore(File file) {
try { try {
lock.lockInterruptibly(); lock.lockInterruptibly();
if (exception != null) {
throw exception;
}
if (binlogs.size() == 0) { if (binlogs.size() == 0) {
return null; return null;
} else { } else {
@@ -130,6 +154,9 @@ public class BinLogFileQueue {
nextCondition.await();// 等待新文件 nextCondition.await();// 等待新文件
} }
if (exception != null) {
throw exception;
}
if (pre == null) {// 第一次 if (pre == null) {// 第一次
return binlogs.get(0); return binlogs.get(0);
} else { } else {
@@ -170,13 +197,16 @@ public class BinLogFileQueue {
private boolean offer(File file) { private boolean offer(File file) {
try { try {
lock.lockInterruptibly(); lock.lockInterruptibly();
if (!binlogs.contains(file)) { if (file != null) {
binlogs.add(file); if (!binlogs.contains(file)) {
nextCondition.signalAll();// 唤醒 binlogs.add(file);
return true; nextCondition.signalAll();// 唤醒
} else { return true;
return false; }
} }
nextCondition.signalAll();// 唤醒
return false;
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
return false; return false;

View File

@@ -0,0 +1,339 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import javax.net.ssl.SSLContext;
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.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile;
/**
* @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 batchFileSize;
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 batchFileSize, String destDir) throws IOException{
this.binlogList = new LinkedList(downloadQueue);
this.batchFileSize = batchFileSize;
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(), "download-" + destDir);
downloadThread.setDaemon(true);
downloadThread.start();
}
public BinlogFile tryOne() throws Throwable {
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 < batchFileSize && !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 Throwable {
String downloadLink = binlogFile.getDownloadLink();
String fileName = binlogFile.getFileName();
downloadLink = downloadLink.trim();
CloseableHttpClient httpClient = null;
if (downloadLink.startsWith("https")) {
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setMaxConnPerRoute(50);
builder.setMaxConnTotal(100);
// 创建支持忽略证书的https
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
}).build();
httpClient = HttpClientBuilder.create()
.setSSLContext(sslContext)
.setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
.build()))
.build();
} else {
httpClient = HttpClientBuilder.create().setMaxConnPerRoute(50).setMaxConnTotal(100).build();
}
HttpGet httpGet = new HttpGet(downloadLink);
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());
if (tarFile.exists()) {
tarFile.delete();
}
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.exists()) {
file.delete();
}
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) {
BinlogFile binlogFile = null;
try {
binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS);
if (binlogFile != null) {
int retry = 1;
while (true) {
try {
download(binlogFile);
break;
} catch (Throwable e) {
if (retry % 10 == 0) {
retry = retry + 1;
try {
logger.warn("download failed + " + binlogFile.toString() + "], retry : "
+ retry, e);
// File errorFile = new File(destDir,
// "error.txt");
// FileWriter writer = new
// FileWriter(errorFile);
// writer.write(ExceptionUtils.getFullStackTrace(e));
// writer.flush();
// IOUtils.closeQuietly(writer);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100 * retry));
} catch (Throwable e1) {
logger.error("write error failed", e1);
}
} else {
retry = retry + 1;
}
}
}
}
Runnable runnable = taskQueue.poll(5000, TimeUnit.MILLISECONDS);
if (runnable != null) {
runnable.run();
}
} catch (Throwable e) {
logger.error("task process failed", e);
}
}
}
}
}

View File

@@ -155,6 +155,7 @@ public class HttpHelper {
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)) .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
.build())) .build()))
.build(); .build();
// ---------------- 创建支持https 的client成功--------- // ---------------- 创建支持https 的client成功---------
URI uri = new URIBuilder(url).build(); URI uri = new URIBuilder(url).build();

View File

@@ -0,0 +1,166 @@
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 org.apache.commons.lang.StringUtils;
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;
/**
* aliyun rds的binlog parser支持
*
* <pre>
* 注意点:aliyun的binlog会有定期清理并备份到oss上, 这里实现了一份自动下载oss+rds binlog的机制
* </pre>
*
* @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 String directory; // binlog目录
private int batchFileSize = 4; // 最多下载的binlog文件数量
private RdsLocalBinlogEventParser rdsLocalBinlogEventParser = 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() {
if (StringUtils.isNotEmpty(accesskey) && StringUtils.isNotEmpty(secretkey)
&& StringUtils.isNotEmpty(instanceId)) {
final ParserExceptionHandler targetHandler = this.getParserExceptionHandler();
if (directory == null) {
directory = System.getProperty("java.io.tmpdir", "/tmp") + "/" + destination;
}
rdsLocalBinlogEventParser.setLogPositionManager(this.getLogPositionManager());
rdsLocalBinlogEventParser.setDestination(destination);
rdsLocalBinlogEventParser.setAlarmHandler(this.getAlarmHandler());
rdsLocalBinlogEventParser.setConnectionCharset(this.connectionCharset);
rdsLocalBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber);
rdsLocalBinlogEventParser.setEnableTsdb(this.enableTsdb);
rdsLocalBinlogEventParser.setEventBlackFilter(this.eventBlackFilter);
rdsLocalBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl);
rdsLocalBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl);
rdsLocalBinlogEventParser.setFilterQueryDml(this.filterQueryDml);
rdsLocalBinlogEventParser.setFilterRows(this.filterRows);
rdsLocalBinlogEventParser.setFilterTableError(this.filterTableError);
// rdsLocalBinlogEventParser.setIsGTIDMode(this.isGTIDMode);
rdsLocalBinlogEventParser.setMasterInfo(this.masterInfo);
rdsLocalBinlogEventParser.setEventFilter(this.eventFilter);
rdsLocalBinlogEventParser.setMasterPosition(this.masterPosition);
rdsLocalBinlogEventParser.setTransactionSize(this.transactionSize);
rdsLocalBinlogEventParser.setUrl(this.rdsOpenApiUrl);
rdsLocalBinlogEventParser.setAccesskey(this.accesskey);
rdsLocalBinlogEventParser.setSecretkey(this.secretkey);
rdsLocalBinlogEventParser.setInstanceId(this.instanceId);
rdsLocalBinlogEventParser.setEventSink(eventSink);
rdsLocalBinlogEventParser.setDirectory(directory);
rdsLocalBinlogEventParser.setBatchFileSize(batchFileSize);
rdsLocalBinlogEventParser.setParallel(this.parallel);
rdsLocalBinlogEventParser.setParallelBufferSize(this.parallelBufferSize);
rdsLocalBinlogEventParser.setParallelThreadSize(this.parallelThreadSize);
rdsLocalBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() {
@Override
public void onFinish() {
executorService.execute(new Runnable() {
@Override
public void run() {
rdsLocalBinlogEventParser.stop();
RdsBinlogEventParserProxy.this.start();
}
});
}
});
this.setParserExceptionHandler(new ParserExceptionHandler() {
@Override
public void handle(Throwable e) {
handleMysqlParserException(e);
if (targetHandler != null) {
targetHandler.handle(e);
}
}
});
}
super.start();
}
private 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();
rdsLocalBinlogEventParser.setServerId(serverId);
rdsBinlogEventParserProxy.stop();
logger.info("start rds mysql binlog parser!");
rdsLocalBinlogEventParser.start();
} catch (Throwable e) {
logger.info("handle exception failed", e);
}
}
});
}
}
@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 setBatchFileSize(int batchFileSize) {
this.batchFileSize = batchFileSize;
}
}

View File

@@ -1,46 +1,21 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds; package com.alibaba.otter.canal.parse.inbound.mysql.rds;
import io.netty.handler.codec.http.HttpResponseStatus; import java.net.URI;
import java.net.URISyntaxException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile;
import com.alibaba.fastjson.JSONArray; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult;
import com.alibaba.fastjson.JSONObject; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBackupPolicyRequest;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest;
/** /**
* @author agapple 2017年10月14日 下午1:53:52 * @author agapple 2017年10月14日 下午1:53:52
@@ -48,287 +23,78 @@ import com.alibaba.fastjson.JSONObject;
*/ */
public class RdsBinlogOpenApi { public class RdsBinlogOpenApi {
protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class); protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class);
private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private static final int TIMEOUT = 10000;
private static final String ENCODING = "UTF-8";
private static final String MAC_NAME = "HmacSHA1";
private static final String API_VERSION = "2014-08-15";
private static final String SIGNATURE_VERSION = "1.0";
public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, public static List<BinlogFile> listBinlogFiles(String url, String ak, String sk, String dbInstanceId,
Date endTime, File destDir) throws Throwable { Date startTime, Date endTime) {
int pageSize = 100; DescribeBinlogFilesRequest request = new DescribeBinlogFilesRequest();
int pageNumber = 0; if (StringUtils.isNotEmpty(url)) {
int pageRecordCount = 1; try {
String hostInstanceID = null; URI uri = new URI(url);
while (pageRecordCount > 0 && pageRecordCount <= pageSize) { request.setEndPoint(uri.getHost());
pageNumber += 1; } catch (URISyntaxException e) {
String result = describeBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime, pageSize, pageNumber); logger.error("resolve url host failed, will use default rds endpoint!");
JSONObject jsobObj = JSON.parseObject(result);
pageRecordCount = jsobObj.getInteger("PageRecordCount");
if (pageRecordCount > 0) {
FileUtils.forceMkdir(destDir);
File hostIdFile = new File(destDir, "hostId");
if (hostIdFile.exists()) {
List<String> lines = IOUtils.readLines(new FileInputStream(hostIdFile));
hostInstanceID = StringUtils.join(lines, "\n");
}
String itemStr = jsobObj.getString("Items");
JSONObject binLogFileObj = JSONObject.parseObject(itemStr);
JSONArray items = binLogFileObj.getJSONArray("BinLogFile");
if (items == null || items.isEmpty()) {
continue;
}
for (int i = 0; i < items.size(); i++) {
JSONObject item = (JSONObject) items.get(i);
String oneHostInstanceID = item.getString("HostInstanceID");
if (hostInstanceID == null) {
hostInstanceID = oneHostInstanceID;
FileOutputStream hostIdFileOut = null;
try {
hostIdFileOut = new FileOutputStream(hostIdFile);
hostIdFileOut.write(oneHostInstanceID.getBytes());
hostIdFileOut.flush();
} finally {
IOUtils.closeQuietly(hostIdFileOut);
}
}
if (hostInstanceID.equals(oneHostInstanceID)) { // 只选择一个host下载
String downloadLink = item.getString("DownloadLink");
String fileName = StringUtils.substringBetween(downloadLink, "mysql-bin.", ".tar");
if (StringUtils.isNotEmpty(fileName)) {
File currentFile = new File(destDir, "mysql-bin." + fileName);
if (currentFile.isFile() && currentFile.exists()) {
// 检查一下文件是否存在,存在就就没必要下载了
continue;
}
}
HttpGet httpGet = new HttpGet(downloadLink);
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnPerRoute(50)
.setMaxConnTotal(100)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(TIMEOUT)
.setConnectionRequestTimeout(TIMEOUT)
.setSocketTimeout(TIMEOUT)
.build();
httpGet.setConfig(requestConfig);
HttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpResponseStatus.OK.code()) {
throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:"
+ statusCode);
}
saveFile(destDir, response);
}
}
} }
} }
} request.setStartDate(startTime);
request.setEndDate(endTime);
private static void saveFile(File parentFile, HttpResponse response) throws IOException { request.setPageNumber(1);
InputStream is = response.getEntity().getContent(); request.setPageSize(100);
long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue()); request.setRdsInstanceId(dbInstanceId);
String fileName = response.getFirstHeader("Content-Disposition").getValue(); request.setAccessKeyId(ak);
fileName = StringUtils.substringAfter(fileName, "filename="); request.setAccessKeySecret(sk);
boolean isTar = StringUtils.endsWith(fileName, ".tar"); DescribeBinlogFileResult result = null;
FileUtils.forceMkdir(parentFile); int retryTime = 3;
FileOutputStream fos = null; while (true) {
try { try {
if (isTar) { result = request.doAction();
TarArchiveInputStream tais = new TarArchiveInputStream(is); break;
TarArchiveEntry tarArchiveEntry = null; } catch (Exception e) {
while ((tarArchiveEntry = tais.getNextTarEntry()) != null) { if (retryTime-- <= 0) {
String name = tarArchiveEntry.getName(); throw new RuntimeException(e);
File tarFile = new File(parentFile, name);
logger.info("start to download file " + tarFile.getName());
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(tarFile));
int read = -1;
byte[] buffer = new byte[1024];
while ((read = tais.read(buffer)) != -1) {
bos.write(buffer, 0, read);
}
logger.info("download file " + tarFile.getName() + " end!");
} finally {
IOUtils.closeQuietly(bos);
}
}
tais.close();
} else {
File file = new File(parentFile, fileName);
if (!file.isFile()) {
file.createNewFile();
} }
try { try {
fos = new FileOutputStream(file); Thread.sleep(100L);
byte[] buffer = new byte[1024]; } catch (InterruptedException e1) {
int len;
long copySize = 0;
long nextPrintProgress = 0;
logger.info("start to download file " + file.getName());
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
copySize += len;
long progress = copySize * 100 / totalSize;
if (progress >= nextPrintProgress) {
logger.info("download " + file.getName() + " progress : " + progress
+ "% , download size : " + copySize + ", total size : " + totalSize);
nextPrintProgress += 10;
}
}
logger.info("download file " + file.getName() + " end!");
fos.flush();
} finally {
IOUtils.closeQuietly(fos);
} }
} }
} finally {
IOUtils.closeQuietly(fos);
} }
} if (result == null) {
return Collections.EMPTY_LIST;
public static String describeBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
Date endTime, int pageSize, int pageNumber) throws Exception {
Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put("Action", "DescribeBinlogFiles");
paramMap.put("DBInstanceId", dbInstanceId); // rds实例id
paramMap.put("StartTime", formatIso8601Date(startTime));
paramMap.put("EndTime", formatIso8601Date(endTime));
paramMap.put("PageSize", String.valueOf(pageSize));
paramMap.put("PageNumber", String.valueOf(pageNumber));
return doRequest(url, paramMap, ak, sk);
}
private static String doRequest(String domin, Map<String, String> param, String ak, String sk) throws Exception {
param.put("AccessKeyId", ak);
param.put("SignatureMethod", "HMAC-SHA1");
param.put("SignatureVersion", SIGNATURE_VERSION);
param.put("Version", API_VERSION);
param.put("SignatureNonce", UUID.randomUUID().toString());
param.put("Format", "JSON");
param.put("Timestamp", formatIso8601Date(new Date()));
String signStr = generate("POST", param, sk);
param.put("Signature", signStr);
String request = concatQueryString(param);
String url = domin + "?" + request;
String result = HttpHelper.post(url, null, Collections.EMPTY_MAP, TIMEOUT);
return result;
}
public static String concatQueryString(Map<String, String> parameters) throws UnsupportedEncodingException {
if (null == parameters) {
return null;
} }
StringBuilder urlBuilder = new StringBuilder(""); RdsItem rdsItem = result.getItems();
for (Map.Entry<String, String> entry : parameters.entrySet()) { if (rdsItem != null) {
String key = entry.getKey(); return rdsItem.getBinLogFile();
String val = entry.getValue(); }
urlBuilder.append(encode(key)); return Collections.EMPTY_LIST;
if (val != null) { }
urlBuilder.append("=").append(encode(val));
public static RdsBackupPolicy queryBinlogBackupPolicy(String url, String ak, String sk, String dbInstanceId) {
DescribeBackupPolicyRequest request = new DescribeBackupPolicyRequest();
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!");
} }
urlBuilder.append("&");
} }
int strIndex = urlBuilder.length(); request.setRdsInstanceId(dbInstanceId);
if (parameters.size() > 0) { request.setAccessKeyId(ak);
urlBuilder.deleteCharAt(strIndex - 1); request.setAccessKeySecret(sk);
} int retryTime = 3;
return urlBuilder.toString(); while (true) {
} try {
return request.doAction();
public static String encode(String value) throws UnsupportedEncodingException { } catch (Exception e) {
return URLEncoder.encode(value, "UTF-8"); if (retryTime-- <= 0) {
} throw new RuntimeException(e);
}
private static String formatIso8601Date(Date date) { try {
SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT); Thread.sleep(100L);
df.setTimeZone(TimeZone.getTimeZone("GMT")); } catch (InterruptedException e1) {
return df.format(date); }
}
/**
* 使用 HMAC-SHA1 签名方法对对encryptText进行签名
*
* @param encryptText 被签名的字符串
* @param encryptKey 密钥
* @return
* @throws Exception
*/
public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
byte[] data = encryptKey.getBytes(ENCODING);
// 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
// 生成一个指定 Mac 算法 的 Mac 对象
Mac mac = Mac.getInstance(MAC_NAME);
// 用给定密钥初始化 Mac 对象
mac.init(secretKey);
byte[] text = encryptText.getBytes(ENCODING);
// 完成 Mac 操作
return mac.doFinal(text);
}
private static String base64(byte input[]) throws UnsupportedEncodingException {
return new String(Base64.encodeBase64(input), ENCODING);
}
/** 对参数名称和参数值进行URL编码 **/
public static String generate(String method, Map<String, String> parameter, String accessKeySecret)
throws Exception {
String signString = generateSignString(method, parameter);
byte[] signBytes = HmacSHA1Encrypt(signString, accessKeySecret + "&");
String signature = base64(signBytes);
if ("POST".equals(method)) {
return signature;
}
return URLEncoder.encode(signature, "UTF-8");
}
private static String generateQueryString(TreeMap<String, String> treeMap) {
StringBuilder canonicalizedQueryString = new StringBuilder();
boolean first = true;
for (String key : treeMap.navigableKeySet()) {
String value = treeMap.get(key);
if (!first) {
canonicalizedQueryString.append("&");
} }
first = false;
canonicalizedQueryString.append(percentEncode(key)).append("=").append(percentEncode(value));
} }
return canonicalizedQueryString.toString();
}
public static String generateSignString(String httpMethod, Map<String, String> parameter) throws IOException {
TreeMap<String, String> sortParameter = new TreeMap<String, String>();
sortParameter.putAll(parameter);
String canonicalizedQueryString = generateQueryString(sortParameter);
if (null == httpMethod) {
throw new RuntimeException("httpMethod can not be empty");
}
/** 构造待签名的字符串* */
StringBuilder stringToSign = new StringBuilder();
stringToSign.append(httpMethod).append("&");
stringToSign.append(percentEncode("/")).append("&");
stringToSign.append(percentEncode(canonicalizedQueryString));
return stringToSign.toString();
}
public static String percentEncode(String value) {
try {
return value == null ? null : URLEncoder.encode(value, ENCODING)
.replaceAll("\\+", "%20")
.replaceAll("\\*", "%2A")
.replaceAll("%7E", "~");
} catch (Exception e) {
}
return "";
} }
} }

View File

@@ -2,14 +2,23 @@ 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备份文件的复制
@@ -17,47 +26,104 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition;
* @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 { public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser, LocalBinLogConnection.FileParserListener {
private String url = "https://rds.aliyuncs.com/"; // openapi地址 private String url; // 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 batchFileSize;
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();
} }
RdsBinlogOpenApi.downloadBinlogFiles(url, EntryPosition entryPosition = findStartPosition(null);
if (entryPosition == null) {
throw new PositionNotFoundException("position not found!");
}
long startTimeInMill = entryPosition.getTimestamp();
startTime = startTimeInMill;
List<BinlogFile> binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url,
accesskey, accesskey,
secretkey, secretkey,
instanceId, instanceId,
new Date(startTime), new Date(startTime),
new Date(endTime), new Date(endTime));
new File(directory)); binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchFileSize, directory);
binlogDownloadQueue.silenceDownload();
// 更新一下时间戳 needWait = true;
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.tryOne();
binlogDownloadQueue.prepare();
} catch (Throwable 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;
} }
@@ -68,44 +134,87 @@ 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);
Long timestamp = 0L;
if (logPosition != null && logPosition.getPostion() != null) {
timestamp = logPosition.getPostion().getTimestamp();
EntryPosition position = logPosition.getPostion();
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.warn("last file : " + fileName + " , timestamp : " + timestamp
+ " , all file parse complete, switch to mysql parser!");
finishListener.onFinish();
return;
} else {
logger.warn("parse local binlog file : " + fileName + " , timestamp : " + timestamp
+ " , try the next binlog !");
}
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 setBatchFileSize(int batchFileSize) {
this.batchFileSize = batchFileSize;
}
} }

View File

@@ -0,0 +1,80 @@
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;
}
@Override
public String toString() {
return "BinlogFile [FileSize=" + FileSize + ", LogBeginTime=" + LogBeginTime + ", LogEndTime=" + LogEndTime
+ ", DownloadLink=" + DownloadLink + ", HostInstanceID=" + HostInstanceID + ", LinkExpiredTime="
+ LinkExpiredTime + ", fileName=" + fileName + "]";
}
}

View File

@@ -0,0 +1,63 @@
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

@@ -0,0 +1,78 @@
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;
}
@Override
public String toString() {
return "RdsBackupPolicy [BackupRetentionPeriod=" + BackupRetentionPeriod + ", PreferredBackupTime="
+ PreferredBackupTime + ", PreferredBackupPeriod=" + PreferredBackupPeriod + ", BackupLog=" + BackupLog
+ ", LogBackupRetentionPeriod=" + LogBackupRetentionPeriod + "]";
}
}

View File

@@ -0,0 +1,26 @@
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;
}
@Override
public String toString() {
return "RdsItem [BinLogFile=" + BinLogFile + "]";
}
}

View File

@@ -0,0 +1,251 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds.request;
import io.netty.handler.codec.http.HttpResponseStatus;
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.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.UUID;
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;
/**
* @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
*/
@SuppressWarnings("deprecation")
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

@@ -0,0 +1,40 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds.request;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy;
/**
* 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

@@ -0,0 +1,55 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds.request;
import java.util.Date;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult;
/**
* @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

@@ -68,7 +68,8 @@ public class MemoryTableMeta implements TableMetaTSDB {
try { try {
// druid暂时flush privileges语法解析有问题 // druid暂时flush privileges语法解析有问题
if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")) { if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant")) {
repository.console(ddl); repository.console(ddl);
} }
} catch (Throwable e) { } catch (Throwable e) {

View File

@@ -0,0 +1,119 @@
package com.alibaba.otter.canal.parse.inbound.mysql;
import java.net.InetSocketAddress;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Assert;
import org.junit.Test;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
import com.alibaba.otter.canal.parse.helper.TimeoutChecker;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy;
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.HOUR_OF_DAY, -24 * 4);
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.setDirectory("/tmp/binlog");
controller.setEventBlackFilter(new AviaterRegexFilter("mysql\\.*"));
controller.setFilterTableError(true);
controller.setBatchFileSize(4);
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) {
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

@@ -1,11 +1,14 @@
package com.alibaba.otter.canal.parse.inbound.mysql; package com.alibaba.otter.canal.parse.inbound.mysql;
import java.io.File;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi; import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile;
import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy;
/** /**
* @author agapple 2017年10月15日 下午2:14:34 * @author agapple 2017年10月15日 下午2:14:34
@@ -13,15 +16,19 @@ import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi;
*/ */
public class RdsBinlogOpenApiTest { public class RdsBinlogOpenApiTest {
@Test
public void testSimple() throws Throwable { public void testSimple() throws Throwable {
Date startTime = DateUtils.parseDate("2017-10-13 20:56:58", new String[] { "yyyy-MM-dd HH:mm:ss" }); Date startTime = DateUtils.parseDate("2018-08-10 12:00:00", new String[] { "yyyy-MM-dd HH:mm:ss" });
Date endTime = DateUtils.parseDate("2017-10-14 02:57:59", new String[] { "yyyy-MM-dd HH:mm:ss" }); Date endTime = DateUtils.parseDate("2018-08-11 12:00:00", new String[] { "yyyy-MM-dd HH:mm:ss" });
RdsBinlogOpenApi.downloadBinlogFiles("https://rds.aliyuncs.com/", String url = "https://rds.aliyuncs.com/";
"", String ak = "";
"", String sk = "";
"rm-bp180v4mfjnm157es", String dbInstanceId = "";
startTime,
endTime, RdsBackupPolicy backupPolicy = RdsBinlogOpenApi.queryBinlogBackupPolicy(url, ak, sk, dbInstanceId);
new File("/tmp/binlog/")); System.out.println(backupPolicy);
List<BinlogFile> binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime);
System.out.println(binlogFiles);
} }
} }

View File

@@ -4,7 +4,7 @@
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>canal module for otter ${project.version}</name> <name>canal module for otter ${project.version}</name>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
<url>https://github.com/alibaba/canal</url> <url>https://github.com/alibaba/canal</url>
<parent> <parent>
<groupId>org.sonatype.oss</groupId> <groupId>org.sonatype.oss</groupId>

View File

@@ -1,30 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.prometheus</artifactId> <artifactId>canal.prometheus</artifactId>
<version>1.0.26-SNAPSHOT</version> <version>1.1.0</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,25 @@ 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 int port;
private volatile boolean running = false; private HTTPServer server;
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 {
@@ -38,9 +42,9 @@ public class PrometheusService implements CanalMetricsService {
@Override @Override
public void initialize() { public void initialize() {
try { try {
//TODO 1.Configurable port logger.info("Start prometheus HTTPServer on port {}.", port);
//TODO 2.Https //TODO 2.Https?
server = new HTTPServer(11112); server = new HTTPServer(port);
} catch (IOException e) { } catch (IOException e) {
logger.warn("Unable to start prometheus HTTPServer.", e); logger.warn("Unable to start prometheus HTTPServer.", e);
return; return;
@@ -48,8 +52,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 +67,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 +93,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 +106,16 @@ 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());
} }
@Override
public void setServerPort(int port) {
this.port = port;
}
} }

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.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.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 DELAY_HELP = "Traffic delay of canal instance in milliseconds";
private static final String TRANSACTION_HELP = "Transactions 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);
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());
}
mfs.add(delay);
mfs.add(transactions);
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();
Preconditions.checkNotNull(holder.latestExecTime);
Preconditions.checkNotNull(holder.transactionCounter);
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 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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More