Compare commits

..

46 Commits

Author SHA1 Message Date
jianghang.loujh
7ee246ca5a [maven-release-plugin] prepare release canal-1.1.6 2022-05-24 10:26:06 +08:00
jianghang.loujh
54c645f315 fixed test failed 2022-05-24 10:12:26 +08:00
jianghang.loujh
6c81ab6fdc fixed compiler error 2022-05-24 10:08:02 +08:00
jianghang.loujh
99aefaa118 support mysql 8.0 transaction_payload_event 2022-05-23 23:23:52 +08:00
jianghang.loujh
e2248b3d35 update phoenix pom.xml 2022-05-23 19:30:19 +08:00
jianghang.loujh
eef1ee85fb fixed issue #4014, support java8 & java11 G1 GC 2022-05-23 18:23:31 +08:00
温绍锦
7dfec19c68 update druid & fastjson2 version (#4183)
* update fastjson2 version, 2.0.2 -> 2.0.3

* update fastjson & druid version
2022-05-23 10:03:41 +08:00
jianghang.loujh
05a52f1bd3 upgrade log4j version 2022-05-20 16:58:20 +08:00
jianghang.loujh
fb0f274ba8 fixed logger 2022-05-20 16:09:53 +08:00
jianghang.loujh
d71a208123 fixed issue #4178 , support -00:00:00.12 2022-05-20 10:37:53 +08:00
jianghang.loujh
656a1d23c2 instance.properties demo 2022-05-19 19:56:50 +08:00
jianghang.loujh
a574b91fdf fixed #3976 , support LogEvent.MYSQL_TYPE_TYPED_ARRAY 2022-05-19 19:53:54 +08:00
jianghang.loujh
e41e903984 fixed issue #3538 , compatiable mysql8.0 filed meta 2022-05-17 17:17:32 +08:00
jianghang.loujh
4a22d72ef4 fixed issue #4018, fixed CanalInstanceWithManager thread-safe 2022-05-16 19:46:34 +08:00
jianghang.loujh
b7eff723a3 fixed issue #4027, CanalServerWithEmbedded.get support newList 2022-05-16 19:41:46 +08:00
jianghang.loujh
9b9cfe4037 fixed issue #4152, TableMetaTSDB support canal.instance.filter.query.ddl=true 2022-05-16 17:25:43 +08:00
jianghang.loujh
ee51f338e0 fixed outAdapterkey autoGen 2022-05-13 15:13:14 +08:00
zhuchao941
4315f203a2 repair issue #3401 (#3923)
* 解决OutAdapter单例导致的问题,如 1.多个es7 outAdapter的多线程问题(共享同一个es bulk request),2.多个es7 outAdapter共用一份es配置问题

* 1. 优化adapter和监听器之间的代码结构
2. 监听器只处理匹配的配置文件
3. bugfix-es adapter配置文件回调时漏写esVersion
理论上一个adapter实例对应一个config配置文件

Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 15:02:06 +08:00
jianghang.loujh
b12945d4a9 format yml 2022-05-13 14:39:57 +08:00
Treeyh
885f05fb6a 支持定义rocketmq client日志路径,及修复一些bug (#3229)
* 修复接入阿里云RocketMq no route info for this topic 问题

* 支持deployer同步rocketmq时定义rocketmq client日志路径

* 支持deployer同步rocketmq时定义rocketmq client日志路径

* 回退guava版本号,解决adapter的java.lang.NoSuchMethodError: com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor()异常

* 解决adapter启动报com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource异常。

* 支持rocketmq client 配置日志路径

Co-authored-by: hai.yu <hai.yu@snowballtech.com>
Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 14:39:29 +08:00
ChengTie(鐵手)
591126ee6b update (#3326) 2022-05-13 14:37:17 +08:00
He Wang
af432bf10a fix data truncated for column issue for type year (#3866) 2022-05-13 14:33:06 +08:00
jumao
b532e90ff4 ESAdapter Support the same instance cross -library query (#4161)
* ESAdapter Support the same instance cross -library query

* fix null point ex
2022-05-13 14:31:09 +08:00
xiaoyu.zhang
e48bc35f35 通过索引别名获取mapping,返回数据为真实索引,而不是别名,导致mappings.get(index).get(type)报空指针异常 (#4122) 2022-05-13 14:28:53 +08:00
He Wang
11a5f2a0fa add druid stat filter (#3985) 2022-05-13 14:24:37 +08:00
He Wang
c775478a56 set backtick by DbType (#3984)
Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 14:23:40 +08:00
He Wang
bd1f91cd9c fix sync order in RdbMirrorDbSyncService (#3928) 2022-05-13 14:19:40 +08:00
jianghang.loujh
67e85d201c remove client adapter Docker 2022-05-13 12:46:12 +08:00
wanglei
6965726dfe fix bug #2023 (#3871)
* fix bug #2023

* 配置canal-adapter,docker生成脚本
2022-05-13 12:39:43 +08:00
willmao
c0001afe8f use hostname if register name not set (#3968) 2022-05-13 12:24:33 +08:00
xiayingfeng
977031a666 Improve MAVEN build Performance (#4055) 2022-05-13 12:23:33 +08:00
zhangshaoning
bd3224eea1 Upgrade reference log4j package version (#4017) 2022-05-13 12:22:45 +08:00
jianghang.loujh
33733aef25 fixed compatible and code format 2022-05-13 11:59:57 +08:00
zhangjukai
25be0259a4 Some tweaks for pulsarmq-connector (#4060)
* 1. Add the com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer file to the META-INF/canal directory under the pulsarmq-connector project
2. Introduce pulsar-client-admin for Canal to automatically create multi-partition topics
3. Add the judgment that roleToken is null
4. The disconnect method in CanalPulsarMQConsumer removes this.pulsarMQConsumer.unsubscribe();, this code will cause data loss during stop
5. When getting Pulsar messages, they are all processed as flat messages, because CanalMessageSerializerUtil.deserializer(data) will deserialize exceptions
6. Use groupId as subscriptName, without the pulsarmq.subscriptName parameter, the entire adapter will be the same subscriber name using pulsarmq.subscriptName

* 逻辑优化

* 代码优化

* 针对pulsar的调整

* 恢复代码

* 恢复代码

* 处理Producer失效的情况
2022-05-13 11:41:50 +08:00
liteng2430
8114d77a20 Add verification: the user can set a password with a length of less than 6 digits when updating the password, but the password length must be greater than or equal to 6 digits when logging in to canal admin, so the length verification function when updating the login password is added (#4103) 2022-05-13 11:34:36 +08:00
ZhangJian He
571e7f4e25 fix ojdbc6 wrong corrinator (#4132) 2022-05-13 11:33:42 +08:00
温绍锦
ead91433d7 fastjson update 1.x to 2.0.2 (#4160) 2022-05-13 11:32:47 +08:00
xiaoma20082008
bac016f89e <feat>: support visibility columns (#3976) 2022-05-13 11:30:19 +08:00
willmao
8804647c30 avoid print password when logging (#3969) 2022-05-13 11:28:05 +08:00
mclubing
2623d08b8c rdb配置不支持yaml后缀文件 (#3964)
增加rdb配置支持yaml后缀文件
2022-05-13 11:26:04 +08:00
wuare
622fed84ce fixed logger name (#3887) 2022-05-13 11:24:00 +08:00
He Wang
9f5e8fd1c4 rollback guava version in adapter module (#3880) 2022-05-13 11:23:30 +08:00
Junwang Zhao
88d472ef1e move protocol test to protocol test directory (#3864)
1. move the protocol test to right directory
2. fix wrong string format params count

Signed-off-by: 元组 <zhaojunwang.zjw@alibaba-inc.com>
2022-05-13 11:16:06 +08:00
Aiden.Ao
2987d08161 fix issue #3144 (#3712) 2022-05-13 11:09:09 +08:00
tuple
b54bea5e33 polish the code thus make it more stable (#3855)
Signed-off-by: 元组 <zhaojunwang.zjw@alibaba-inc.com>
2021-10-11 14:46:22 +08:00
jianghang.loujh
c42ba95eed fixed pulsarmq package 2021-10-09 18:06:34 +08:00
130 changed files with 1737 additions and 1298 deletions

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.admin</artifactId> <artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.admin</artifactId> <artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>canal.admin-web</artifactId> <artifactId>canal.admin-web</artifactId>

View File

@@ -56,14 +56,26 @@ in
exit;; exit;;
esac esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit` str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m" JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else else
JAVA_OPTS="-server -Xms1024m -Xmx1024m" #JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
# JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi fi
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
CANAL_OPTS="-DappName=canal-admin" CANAL_OPTS="-DappName=canal-admin"
@@ -81,4 +93,4 @@ $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba
echo $! > $base/bin/admin.pid echo $! > $base/bin/admin.pid
echo "cd to $current_path for continue" echo "cd to $current_path for continue"
cd $current_path cd $current_path

View File

@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.admin.connector;
import java.util.function.Function; import java.util.function.Function;
import org.assertj.core.util.Strings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@@ -26,7 +27,8 @@ public class SimpleAdminConnectors {
connector.connect(); connector.connect();
return function.apply(connector); return function.apply(connector);
} catch (Exception e) { } catch (Exception e) {
logger.error("connect to ip:{},port:{},user:{},password:{}, failed", ip, port, user, passwd); logger.error("connect to ip:{},port:{},user:{},password:{}, failed",
ip, port, user, getDesensitizationPassword(passwd));
logger.error(e.getMessage()); logger.error(e.getMessage());
} finally { } finally {
connector.disconnect(); connector.disconnect();
@@ -34,4 +36,14 @@ public class SimpleAdminConnectors {
return null; return null;
} }
private static String getDesensitizationPassword(String password) {
String defaultPassword = "******";
if (Strings.isNullOrEmpty(password) || password.length() < 6) {
return defaultPassword;
} else {
return String.format("%s******", password.substring(0, 3));
}
}
} }

View File

@@ -13,8 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.alibaba.otter.canal.admin.model.BaseModel; import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalCluster; import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.NodeServer; import com.alibaba.otter.canal.admin.model.NodeServer;

View File

@@ -21,6 +21,8 @@ public class UserServiceImpl implements UserService {
private static byte[] seeds = "canal is best!".getBytes(); private static byte[] seeds = "canal is best!".getBytes();
private static final Integer PASSWORD_LENGTH = 6;
public User find4Login(String username, String password) { public User find4Login(String username, String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return null; return null;
@@ -43,6 +45,9 @@ public class UserServiceImpl implements UserService {
} }
public void update(User user) { public void update(User user) {
if (user.getPassword().length() < PASSWORD_LENGTH) {
throw new ServiceException("The new password is too short,must more than 6 digits");
}
User userTmp = User.find.query().where().eq("username", user.getUsername()).findOne(); User userTmp = User.find.query().where().eq("username", user.getUsername()).findOne();
if (userTmp == null) { if (userTmp == null) {
throw new ServiceException(); throw new ServiceException();

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.admin</artifactId> <artifactId>canal.admin</artifactId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>canal admin module for otter ${project.version}</name> <name>canal admin module for otter ${project.version}</name>
<modules> <modules>
@@ -18,6 +18,7 @@
<java_source_version>1.8</java_source_version> <java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version> <java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding> <file_encoding>UTF-8</file_encoding>
<log4j_version>2.17.0</log4j_version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -91,6 +92,21 @@
<artifactId>ebean</artifactId> <artifactId>ebean</artifactId>
<version>11.41.1</version> <version>11.41.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j_version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,28 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by @author zhuchao on @date 2021/11/11.
*/
public class FileName2KeyMapping {
private static Map<String, String> MAP = new ConcurrentHashMap<>();
public static void register(String type, String fileName, String key) {
MAP.putIfAbsent(join(type, fileName), key);
}
public static void unregister(String type, String fileName) {
MAP.remove(join(type, fileName));
}
public static String getKey(String type, String fileName) {
return MAP.get(join(type, fileName));
}
private static String join(String type, String fileName) {
return type + "|" + fileName;
}
}

View File

@@ -55,7 +55,7 @@ public class MappingConfigsLoader {
} }
if (filePath.exists()) { if (filePath.exists()) {
String fileName = filePath.getName(); String fileName = filePath.getName();
if (!fileName.endsWith(".yml")) { if (!(fileName.endsWith(".yml") || fileName.endsWith(".yaml"))) {
return null; return null;
} }
try (InputStream in = new FileInputStream(filePath)) { try (InputStream in = new FileInputStream(filePath)) {

View File

@@ -30,6 +30,8 @@ public class Util {
private static final Logger logger = LoggerFactory.getLogger(Util.class); private static final Logger logger = LoggerFactory.getLogger(Util.class);
public static final String AUTO_GENERATED_PREFIX = "AUTO_GENERATED_";
/** /**
* 通过DS执行sql * 通过DS执行sql
*/ */

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -27,22 +27,22 @@
<dependency> <dependency>
<groupId>org.elasticsearch</groupId> <groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId> <artifactId>elasticsearch</artifactId>
<version>6.4.3</version> <version>6.8.22</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.elasticsearch.client</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
<version>6.4.3</version> <version>6.8.22</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.elasticsearch.client</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <artifactId>elasticsearch-rest-client</artifactId>
<version>6.4.3</version> <version>6.8.22</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.elasticsearch.client</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId> <artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.4.3</version> <version>6.8.22</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -316,13 +316,17 @@ public class ES6xTemplate implements ESTemplate {
} }
@Override @Override
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping, Map<String, Object> dmlData, public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping,String owner, Map<String, Object> dmlData,
Map<String, Object> dmlOld, Map<String, Object> esFieldData) { Map<String, Object> dmlOld, Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName(); ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
if (!columnItem.getOwner().equals(owner)) {
continue;
}
String columnName = columnItem.getColumnName();
if (fieldItem.getFieldName().equals(idFieldName)) { if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName); resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);

View File

@@ -150,7 +150,13 @@ public class ESConnection {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
return null; return null;
} }
mappingMetaData = mappings.get(index).get(type);
//通过别名查询mapping返回的是真实索引名称,mappings.get(index)返回null,为兼容别名情况修改如下:
ImmutableOpenMap<String, MappingMetaData> esIndex = mappings.get(index);
if(esIndex == null){
esIndex = mappings.valuesIt().next();
}
mappingMetaData = esIndex.get(type);
} }
return mappingMetaData; return mappingMetaData;
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -287,8 +287,7 @@ public class ES7xTemplate implements ESTemplate {
} }
@Override @Override
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData) {
Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null; Object resultIdVal = null;
@@ -312,13 +311,17 @@ public class ES7xTemplate implements ESTemplate {
} }
@Override @Override
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld, public Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) { Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName(); ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
if (!columnItem.getOwner().equals(owner)) {
continue;
}
String columnName = columnItem.getColumnName();
if (fieldItem.getFieldName().equals(idFieldName)) { if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName); resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -22,6 +22,7 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid</artifactId> <artifactId>druid</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -18,10 +18,7 @@ import com.alibaba.otter.canal.client.adapter.es.core.config.SqlParser;
import com.alibaba.otter.canal.client.adapter.es.core.monitor.ESConfigMonitor; import com.alibaba.otter.canal.client.adapter.es.core.monitor.ESConfigMonitor;
import com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService; import com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService;
import com.alibaba.otter.canal.client.adapter.es.core.support.ESTemplate; import com.alibaba.otter.canal.client.adapter.es.core.support.ESTemplate;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.*;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
/** /**
* ES外部适配器 * ES外部适配器
@@ -42,6 +39,8 @@ public abstract class ESAdapter implements OuterAdapter {
protected Properties envProperties; protected Properties envProperties;
protected OuterAdapterConfig configuration;
public ESSyncService getEsSyncService() { public ESSyncService getEsSyncService() {
return esSyncService; return esSyncService;
} }
@@ -58,23 +57,13 @@ public abstract class ESAdapter implements OuterAdapter {
public void init(OuterAdapterConfig configuration, Properties envProperties) { public void init(OuterAdapterConfig configuration, Properties envProperties) {
try { try {
this.envProperties = envProperties; this.envProperties = envProperties;
this.configuration = configuration;
Map<String, ESSyncConfig> esSyncConfigTmp = ESSyncConfigLoader.load(envProperties); Map<String, ESSyncConfig> esSyncConfigTmp = ESSyncConfigLoader.load(envProperties);
// 过滤不匹配的key的配置 // 过滤不匹配的key的配置
esSyncConfigTmp.forEach((key, config) -> { esSyncConfigTmp.forEach((key, config) -> {
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null) addConfig(key, config);
|| (config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
esSyncConfig.put(key, config);
}
}); });
for (Map.Entry<String, ESSyncConfig> entry : esSyncConfig.entrySet()) {
String configName = entry.getKey();
ESSyncConfig config = entry.getValue();
addSyncConfigToCache(configName, config);
}
esSyncService = new ESSyncService(esTemplate); esSyncService = new ESSyncService(esTemplate);
esConfigMonitor = new ESConfigMonitor(); esConfigMonitor = new ESConfigMonitor();
@@ -103,12 +92,12 @@ public abstract class ESAdapter implements OuterAdapter {
String table = dml.getTable(); String table = dml.getTable();
Map<String, ESSyncConfig> configMap; Map<String, ESSyncConfig> configMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) { if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "-" configMap = dbTableEsSyncConfig
+ StringUtils.trimToEmpty(dml.getGroupId()) + "_" + database + "-" .get(StringUtils.trimToEmpty(dml.getDestination()) + "-" + StringUtils.trimToEmpty(dml.getGroupId())
+ table); + "_" + database + "-" + table);
} else { } else {
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-" configMap = dbTableEsSyncConfig
+ table); .get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-" + table);
} }
if (configMap != null && !configMap.values().isEmpty()) { if (configMap != null && !configMap.values().isEmpty()) {
@@ -138,7 +127,7 @@ public abstract class ESAdapter implements OuterAdapter {
return null; return null;
} }
public void addSyncConfigToCache(String configName, ESSyncConfig config) { private void addSyncConfigToCache(String configName, ESSyncConfig config) {
Properties envProperties = this.envProperties; Properties envProperties = this.envProperties;
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql()); SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
config.getEsMapping().setSchemaItem(schemaItem); config.getEsMapping().setSchemaItem(schemaItem);
@@ -153,30 +142,60 @@ public abstract class ESAdapter implements OuterAdapter {
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey()); throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
} }
String schema = matcher.group(2); String schema = matcher.group(2);
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
schemaItem.getAliasTableItems() Map<String, ESSyncConfig> esSyncConfigMap;
.values() String schemaKey = tableItem.getSchema() == null ? schema : tableItem.getSchema();
.forEach(tableItem -> { if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
Map<String, ESSyncConfig> esSyncConfigMap; esSyncConfigMap = dbTableEsSyncConfig
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) { .computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "-"
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + StringUtils.trimToEmpty(config.getGroupId()) + "_" + schemaKey + "-"
+ "-" + tableItem.getTableName(),
+ StringUtils.trimToEmpty(config.getGroupId())
+ "_"
+ schema
+ "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>()); k -> new ConcurrentHashMap<>());
} else { } else {
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(
+ "_" StringUtils.trimToEmpty(config.getDestination()) + "_" + schemaKey + "-" + tableItem.getTableName(),
+ schema k -> new ConcurrentHashMap<>());
+ "-" }
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
}
esSyncConfigMap.put(configName, config); esSyncConfigMap.put(configName, config);
}); });
}
public boolean addConfig(String fileName, ESSyncConfig config) {
if (match(config)) {
esSyncConfig.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName, configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, ESSyncConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey().equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
esSyncConfig.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
esSyncConfig.remove(fileName);
for (Map<String, ESSyncConfig> configMap : dbTableEsSyncConfig.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(ESSyncConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null
&& config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[] { Util.AUTO_GENERATED_PREFIX, config.getDestination(), config.getGroupId() }, '-'));
return sameMatch || prefixMatch;
} }
} }

View File

@@ -1,21 +1,18 @@
package com.alibaba.otter.canal.client.adapter.es.core.monitor; package com.alibaba.otter.canal.client.adapter.es.core.monitor;
import java.io.File;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder; import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.es.core.ESAdapter; import com.alibaba.otter.canal.client.adapter.es.core.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util; import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.File;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ESConfigMonitor { public class ESConfigMonitor {
@@ -36,7 +33,7 @@ public class ESConfigMonitor {
File confDir = Util.getConfDirPath(adapterName); File confDir = Util.getConfDirPath(adapterName);
try { try {
FileAlterationObserver observer = new FileAlterationObserver(confDir, FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener(); FileListener listener = new FileListener();
observer.addListener(listener); observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer); fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -69,9 +66,14 @@ public class ESConfigMonitor {
null, null,
envProperties); envProperties);
if (config != null) { if (config != null) {
// 这里要记得设置esVersion bugfix
config.setEsVersion(adapterName);
config.validate(); config.validate();
addConfigToCache(file, config); boolean result = esAdapter.addConfig(file.getName(), config);
logger.info("Add a new es mapping config: {} to canal adapter", file.getName()); if (result) {
logger.info("Add a new es mapping config: {} to canal adapter",
file.getName());
}
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
@@ -99,12 +101,10 @@ public class ESConfigMonitor {
if (config == null) { if (config == null) {
return; return;
} }
// 这里要记得设置esVersion bugfix
config.setEsVersion(adapterName);
config.validate(); config.validate();
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) { esAdapter.updateConfig(file.getName(), config);
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
logger.info("Change a es mapping config: {} of canal adapter", file.getName()); logger.info("Change a es mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
@@ -118,29 +118,12 @@ public class ESConfigMonitor {
try { try {
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) { if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file); esAdapter.deleteConfig(file.getName());
logger.info("Delete a es mapping config: {} of canal adapter", file.getName()); logger.info("Delete a es mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
} }
private void addConfigToCache(File file, ESSyncConfig config) {
esAdapter.getEsSyncConfig().put(file.getName(), config);
esAdapter.addSyncConfigToCache(file.getName(), config);
}
private void deleteConfigFromCache(File file) {
esAdapter.getEsSyncConfig().remove(file.getName());
for (Map<String, ESSyncConfig> configMap : esAdapter.getDbTableEsSyncConfig().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
} }
} }

View File

@@ -1,19 +1,15 @@
package com.alibaba.otter.canal.client.adapter.es.core.service; package com.alibaba.otter.canal.client.adapter.es.core.service;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping; import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem; import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem;
@@ -79,7 +75,7 @@ public class ESSyncService {
esSyncConfigs esSyncConfigs
.forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" ")); .forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" "));
logger.debug("DML: {} \nAffected indexes: {}", logger.debug("DML: {} \nAffected indexes: {}",
JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue), JSON.toJSONString(dml, JSONWriter.Feature.WriteNulls),
configIndexes.toString()); configIndexes.toString());
} }
} }
@@ -209,7 +205,7 @@ public class ESSyncService {
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) { if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
// ------单表 & 所有字段都为简单字段------ // ------单表 & 所有字段都为简单字段------
singleTableSimpleFiledUpdate(config, dml, data, old); singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
} else { } else {
// ------主表 查询sql来更新------ // ------主表 查询sql来更新------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) { if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
@@ -263,7 +259,7 @@ public class ESSyncService {
// 判断主键和所更新的字段是否全为简单字段 // 判断主键和所更新的字段是否全为简单字段
if (idFieldSimple && allUpdateFieldSimple && !fkChanged) { if (idFieldSimple && allUpdateFieldSimple && !fkChanged) {
singleTableSimpleFiledUpdate(config, dml, data, old); singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
} else { } else {
mainTableUpdate(config, dml, data, old); mainTableUpdate(config, dml, data, old);
} }
@@ -810,12 +806,12 @@ public class ESSyncService {
* @param data 单行data数据 * @param data 单行data数据
* @param old 单行old数据 * @param old 单行old数据
*/ */
private void singleTableSimpleFiledUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data, private void singleTableSimpleFiledUpdate(ESSyncConfig config, String owner, Dml dml, Map<String, Object> data,
Map<String, Object> old) { Map<String, Object> old) {
ESMapping mapping = config.getEsMapping(); ESMapping mapping = config.getEsMapping();
Map<String, Object> esFieldData = new LinkedHashMap<>(); Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = esTemplate.getESDataFromDmlData(mapping, data, old, esFieldData); Object idVal = esTemplate.getESDataFromDmlData(mapping, owner, data, old, esFieldData);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}", logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}",

View File

@@ -18,7 +18,7 @@ import org.joda.time.DateTime;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping; import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem; import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.ColumnItem; import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.ColumnItem;

View File

@@ -57,13 +57,11 @@ public interface ESTemplate {
Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException; Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException;
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, Map<String, Object> esFieldData) throws SQLException;
Map<String, Object> esFieldData) throws SQLException;
Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName); Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName);
Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData); Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData);
Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld, Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld, Map<String, Object> esFieldData);
Map<String, Object> esFieldData);
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,9 +1,25 @@
package com.alibaba.otter.canal.client.adapter.hbase; package com.alibaba.otter.canal.client.adapter.hbase;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -16,18 +32,6 @@ import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
/** /**
* HBase外部适配器 * HBase外部适配器
* *
@@ -49,6 +53,8 @@ public class HbaseAdapter implements OuterAdapter {
private Properties envProperties; private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getHbaseMapping() { public Map<String, MappingConfig> getHbaseMapping() {
return hbaseMapping; return hbaseMapping;
} }
@@ -61,33 +67,12 @@ public class HbaseAdapter implements OuterAdapter {
public void init(OuterAdapterConfig configuration, Properties envProperties) { public void init(OuterAdapterConfig configuration, Properties envProperties) {
try { try {
this.envProperties = envProperties; this.envProperties = envProperties;
this.configuration = configuration;
Map<String, MappingConfig> hbaseMappingTmp = MappingConfigLoader.load(envProperties); Map<String, MappingConfig> hbaseMappingTmp = MappingConfigLoader.load(envProperties);
// 过滤不匹配的key的配置 // 过滤不匹配的key的配置
hbaseMappingTmp.forEach((key, mappingConfig) -> { hbaseMappingTmp.forEach((key, config) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) addConfig(key, config);
|| (mappingConfig.getOuterAdapterKey() != null
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
hbaseMapping.put(key, mappingConfig);
}
}); });
for (Map.Entry<String, MappingConfig> entry : hbaseMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties(); Map<String, String> properties = configuration.getProperties();
@@ -223,4 +208,62 @@ public class HbaseAdapter implements OuterAdapter {
} }
return null; return null;
} }
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
String k;
if (envProperties != null && !"tcp"
.equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-" + StringUtils
.trimToEmpty(mappingConfig.getGroupId()) + "_" + mappingConfig.getHbaseMapping()
.getDatabase() + "-" + mappingConfig.getHbaseMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_" + mappingConfig
.getHbaseMapping().getDatabase() + "-" + mappingConfig.getHbaseMapping()
.getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache
.computeIfAbsent(k, k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
hbaseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
hbaseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
hbaseMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
} }

View File

@@ -5,20 +5,15 @@ import com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util; import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.File;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class HbaseConfigMonitor { public class HbaseConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class); private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class);
@@ -37,7 +32,7 @@ public class HbaseConfigMonitor {
File confDir = Util.getConfDirPath(adapterName); File confDir = Util.getConfDirPath(adapterName);
try { try {
FileAlterationObserver observer = new FileAlterationObserver(confDir, FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener(); FileListener listener = new FileListener();
observer.addListener(listener); observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer); fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -70,9 +65,11 @@ public class HbaseConfigMonitor {
return; return;
} }
config.validate(); config.validate();
addConfigToCache(file, config); boolean result = hbaseAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new hbase mapping config: {} to canal adapter", file.getName()); logger.info("Add a new hbase mapping config: {} to canal adapter",
file.getName());
}
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
@@ -97,10 +94,7 @@ public class HbaseConfigMonitor {
return; return;
} }
config.validate(); config.validate();
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) { hbaseAdapter.updateConfig(file.getName(), config);
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
@@ -113,33 +107,12 @@ public class HbaseConfigMonitor {
try { try {
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) { if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file); hbaseAdapter.deleteConfig(file.getName());
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName()); logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
} }
private void addConfigToCache(File file, MappingConfig config) {
hbaseAdapter.getHbaseMapping().put(file.getName(), config);
Map<String, MappingConfig> configMap = hbaseAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "_"
+ config.getHbaseMapping().getDatabase() + "-" + config.getHbaseMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}
private void deleteConfigFromCache(File file) {
hbaseAdapter.getHbaseMapping().remove(file.getName());
for (Map<String, MappingConfig> configMap : hbaseAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
} }
} }

View File

@@ -6,8 +6,8 @@ import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.support.*; import com.alibaba.otter.canal.client.adapter.hbase.support.*;
import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Dml;
@@ -39,7 +39,7 @@ public class HbaseSyncService {
delete(config, dml); delete(config, dml);
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} }
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,16 +1,5 @@
package com.alibaba.otter.canal.client.adapter.kudu; package com.alibaba.otter.canal.client.adapter.kudu;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig; import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfigLoader; import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfigLoader;
@@ -20,8 +9,19 @@ import com.alibaba.otter.canal.client.adapter.kudu.service.KuduSyncService;
import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate; import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult; import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI; import com.alibaba.otter.canal.client.adapter.support.SPI;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* @author liuyadong * @author liuyadong
@@ -35,8 +35,6 @@ public class KuduAdapter implements OuterAdapter {
private Map<String, KuduMappingConfig> kuduMapping = new ConcurrentHashMap<>(); // 文件名对应配置 private Map<String, KuduMappingConfig> kuduMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, KuduMappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置 private Map<String, Map<String, KuduMappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
private String dataSourceKey;
private KuduTemplate kuduTemplate; private KuduTemplate kuduTemplate;
private KuduSyncService kuduSyncService; private KuduSyncService kuduSyncService;
@@ -45,6 +43,8 @@ public class KuduAdapter implements OuterAdapter {
private Properties envProperties; private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, KuduMappingConfig> getKuduMapping() { public Map<String, KuduMappingConfig> getKuduMapping() {
return kuduMapping; return kuduMapping;
} }
@@ -56,36 +56,17 @@ public class KuduAdapter implements OuterAdapter {
@Override @Override
public void init(OuterAdapterConfig configuration, Properties envProperties) { public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties; this.envProperties = envProperties;
this.configuration = configuration;
Map<String, KuduMappingConfig> kuduMappingTmp = KuduMappingConfigLoader.load(envProperties); Map<String, KuduMappingConfig> kuduMappingTmp = KuduMappingConfigLoader.load(envProperties);
// 过滤不匹配的key的配置,获取连接key,key为配置文件名称 // 过滤不匹配的key的配置,获取连接key,key为配置文件名称
kuduMappingTmp.forEach((key, mappingConfig) -> { kuduMappingTmp.forEach((key, config) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) addConfig(key, config);
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
kuduMapping.put(key, mappingConfig);
dataSourceKey = mappingConfig.getDataSourceKey();
}
}); });
// 判断目标字段是否为空 // 判断目标字段是否为空
if (kuduMapping.isEmpty()) { if (kuduMapping.isEmpty()) {
throw new RuntimeException("No kudu adapter found for config key: " + configuration.getKey()); throw new RuntimeException("No kudu adapter found for config key: " + configuration.getKey());
} }
for (Map.Entry<String, KuduMappingConfig> entry : kuduMapping.entrySet()) {
String configName = entry.getKey();
KuduMappingConfig mappingConfig = entry.getValue();
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
}
Map<String, KuduMappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties(); Map<String, String> properties = configuration.getProperties();
String kudu_master = properties.get("kudu.master.address"); String kudu_master = properties.get("kudu.master.address");
@@ -203,4 +184,60 @@ public class KuduAdapter implements OuterAdapter {
} }
return null; return null;
} }
private void addSyncConfigToCache(String configName, KuduMappingConfig mappingConfig) {
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
}
Map<String, KuduMappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, KuduMappingConfig config) {
if (match(config)) {
kuduMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, KuduMappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
kuduMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
kuduMapping.remove(fileName);
for (Map<String, KuduMappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(KuduMappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
} }

View File

@@ -1,23 +1,18 @@
package com.alibaba.otter.canal.client.adapter.kudu.monitor; package com.alibaba.otter.canal.client.adapter.kudu.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder; import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.kudu.KuduAdapter; import com.alibaba.otter.canal.client.adapter.kudu.KuduAdapter;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig; import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util; import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.File;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* @author liuyadong * @author liuyadong
@@ -41,7 +36,7 @@ public class KuduConfigMonitor {
File confDir = Util.getConfDirPath(adapterName); File confDir = Util.getConfDirPath(adapterName);
try { try {
FileAlterationObserver observer = new FileAlterationObserver(confDir, FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener(); FileListener listener = new FileListener();
observer.addListener(listener); observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer); fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -83,9 +78,11 @@ public class KuduConfigMonitor {
return; return;
} }
config.validate(); config.validate();
addConfigToCache(file, config); boolean result = kuduAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new kudu mapping config: {} to canal adapter", file.getName()); logger.info("Add a new kudu mapping config: {} to canal adapter",
file.getName());
}
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
@@ -113,10 +110,7 @@ public class KuduConfigMonitor {
return; return;
} }
config.validate(); config.validate();
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) { kuduAdapter.updateConfig(file.getName(), config);
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
@@ -129,42 +123,12 @@ public class KuduConfigMonitor {
try { try {
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) { if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
deleteConfigFromCache(file); kuduAdapter.deleteConfig(file.getName());
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName()); logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
} }
/**
* 添加配置文件信息到缓存
*
* @param file
* @param config
*/
private void addConfigToCache(File file, KuduMappingConfig config) {
kuduAdapter.getKuduMapping().put(file.getName(), config);
Map<String, KuduMappingConfig> configMap = kuduAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
+ config.getKuduMapping().getDatabase() + "." + config.getKuduMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}
/**
* 从缓存中删除配置
*
* @param file 文件
*/
private void deleteConfigFromCache(File file) {
kuduAdapter.getKuduMapping().remove(file.getName());
for (Map<String, KuduMappingConfig> configMap : kuduAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
} }
} }

View File

@@ -10,8 +10,8 @@ import org.apache.kudu.client.KuduException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig; import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate; import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Dml;
@@ -55,7 +55,7 @@ public class KuduSyncService {
delete(config, dml); delete(config, dml);
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} }
} }
@@ -118,7 +118,7 @@ public class KuduSyncService {
} }
} catch (KuduException e) { } catch (KuduException e) {
logger.error(e.getMessage()); logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} }
} }
@@ -159,7 +159,7 @@ public class KuduSyncService {
} }
} catch (KuduException e) { } catch (KuduException e) {
logger.error(e.getMessage()); logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} }
@@ -201,7 +201,7 @@ public class KuduSyncService {
} }
} catch (KuduException e) { } catch (KuduException e) {
logger.error(e.getMessage()); logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} }
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -61,7 +61,7 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.oracle</groupId> <groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId> <artifactId>ojdbc6</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@@ -207,6 +207,19 @@
<classifier>jar-with-dependencies</classifier> <classifier>jar-with-dependencies</classifier>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>connector.pulsarmq</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -56,11 +56,24 @@ in
exit;; exit;;
esac esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit` str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError" JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " #JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
# JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi fi
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"

View File

@@ -13,6 +13,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class CanalAdapterApplication { public class CanalAdapterApplication {
public static void main(String[] args) { public static void main(String[] args) {
// 支持rocketmq client 配置日志路径
System.setProperty("rocketmq.client.logUseSlf4j","true");
SpringApplication application = new SpringApplication(CanalAdapterApplication.class); SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
application.setBannerMode(Banner.Mode.OFF); application.setBannerMode(Banner.Mode.OFF);

View File

@@ -0,0 +1,23 @@
package com.alibaba.otter.canal.adapter.launcher.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.support.http.StatViewServlet;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>( new StatViewServlet(),"/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("allow","");
bean.setInitParameters(initParams);
return bean;
}
}

View File

@@ -22,6 +22,7 @@ import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
/** /**
* 外部适配器的加载器 * 外部适配器的加载器
@@ -50,158 +51,43 @@ public class CanalAdapterLoader {
for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) { for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
for (CanalClientConfig.Group group : canalAdapter.getGroups()) { for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
int autoGenId = 0;
List<List<OuterAdapter>> canalOuterAdapterGroups = new CopyOnWriteArrayList<>(); List<List<OuterAdapter>> canalOuterAdapterGroups = new CopyOnWriteArrayList<>();
List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>(); List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
for (OuterAdapterConfig config : group.getOuterAdapters()) { for (OuterAdapterConfig config : group.getOuterAdapters()) {
// 保证一定有key
if (StringUtils.isEmpty(config.getKey())) {
String key = StringUtils.join(
new String[] { Util.AUTO_GENERATED_PREFIX, canalAdapter.getInstance(), group.getGroupId(),
String.valueOf(autoGenId) },
'-');
//gen keyId
config.setKey(key);
}
autoGenId++;
loadAdapter(config, canalOuterAdapters); loadAdapter(config, canalOuterAdapters);
} }
canalOuterAdapterGroups.add(canalOuterAdapters); canalOuterAdapterGroups.add(canalOuterAdapters);
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(canalAdapter.getInstance() AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(
+ "|" canalAdapter.getInstance() + "|" + StringUtils.trimToEmpty(group.getGroupId()),
+ StringUtils.trimToEmpty(group.getGroupId()),
f -> new AdapterProcessor(canalClientConfig, f -> new AdapterProcessor(canalClientConfig,
canalAdapter.getInstance(), canalAdapter.getInstance(),
group.getGroupId(), group.getGroupId(),
canalOuterAdapterGroups)); canalOuterAdapterGroups));
adapterProcessor.start(); adapterProcessor.start();
logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-" logger.info("Start adapter for canal-client mq topic: {} succeed",
+ group.getGroupId()); canalAdapter.getInstance() + "-" + group.getGroupId());
} }
} }
// if ("tcp".equalsIgnoreCase(canalClientConfig.getMode())) {
// // 初始化canal-client的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
//
// for (CanalClientConfig.Group connectorGroup :
// canalAdapter.getGroups()) {
// List<OuterAdapter> canalOutConnectors = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig c : connectorGroup.getOuterAdapters()) {
// loadAdapter(c, canalOutConnectors);
// }
// canalOuterAdapterGroups.add(canalOutConnectors);
// }
// CanalAdapterWorker worker;
// if (StringUtils.isNotEmpty(canalServerHost)) {
// worker = new CanalAdapterWorker(canalClientConfig,
// canalAdapter.getInstance(),
// canalServerHost,
// zkHosts,
// canalOuterAdapterGroups);
// } else if (zkHosts != null) {
// worker = new CanalAdapterWorker(canalClientConfig,
// canalAdapter.getInstance(),
// zkHosts,
// canalOuterAdapterGroups);
// } else {
// throw new RuntimeException("No canal server connector found");
// }
// canalWorkers.put(canalAdapter.getInstance(), worker);
// worker.start();
// logger.info("Start adapter for canal instance: {} succeed",
// canalAdapter.getInstance());
// }
// } else if ("kafka".equalsIgnoreCase(canalClientConfig.getMode())) {
// // 初始化canal-client-kafka的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
//
// CanalAdapterKafkaWorker canalKafkaWorker = new
// CanalAdapterKafkaWorker(canalClientConfig,
// canalClientConfig.getMqServers(),
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalOuterAdapterGroups,
// canalClientConfig.getFlatMessage());
// canalMQWorker.put(canalAdapter.getInstance() + "-kafka-" +
// group.getGroupId(), canalKafkaWorker);
// canalKafkaWorker.start();
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// } else if ("rocketMQ".equalsIgnoreCase(canalClientConfig.getMode()))
// {
// // 初始化canal-client-rocketMQ的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
// CanalAdapterRocketMQWorker rocketMQWorker = new
// CanalAdapterRocketMQWorker(canalClientConfig,
// canalClientConfig.getMqServers(),
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalOuterAdapterGroups,
// canalClientConfig.getAccessKey(),
// canalClientConfig.getSecretKey(),
// canalClientConfig.getFlatMessage(),
// canalClientConfig.isEnableMessageTrace(),
// canalClientConfig.getCustomizedTraceTopic(),
// canalClientConfig.getAccessChannel(),
// canalClientConfig.getNamespace());
// canalMQWorker.put(canalAdapter.getInstance() + "-rocketmq-" +
// group.getGroupId(), rocketMQWorker);
// rocketMQWorker.start();
//
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// } else if ("rabbitMQ".equalsIgnoreCase(canalClientConfig.getMode()))
// {
// // 初始化canal-client-rabbitMQ的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
// CanalAdapterRabbitMQWorker rabbitMQWork = new
// CanalAdapterRabbitMQWorker(canalClientConfig,
// canalOuterAdapterGroups,
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalClientConfig.getFlatMessage());
// canalMQWorker.put(canalAdapter.getInstance() + "-rabbitmq-" +
// group.getGroupId(), rabbitMQWork);
// rabbitMQWork.start();
//
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// // CanalAdapterRabbitMQWork
// }
} }
private void loadAdapter(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) { private void loadAdapter(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
try { try {
OuterAdapter adapter; OuterAdapter adapter;
adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey())); adapter = loader.getExtension(config.getName(), config.getKey());
ClassLoader cl = Thread.currentThread().getContextClassLoader(); ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 替换ClassLoader // 替换ClassLoader

View File

@@ -1,15 +1,21 @@
package com.alibaba.otter.canal.adapter.launcher.rest; package com.alibaba.otter.canal.adapter.launcher.rest;
import com.alibaba.otter.canal.adapter.launcher.common.EtlLock;
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.Result;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -19,14 +25,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.adapter.launcher.common.EtlLock;
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.Result;
/** /**
* 适配器操作Rest * 适配器操作Rest
* *
@@ -66,6 +64,9 @@ public class CommonRest {
@PostMapping("/etl/{type}/{key}/{task}") @PostMapping("/etl/{type}/{key}/{task}")
public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task, public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task,
@RequestParam(name = "params", required = false) String params) { @RequestParam(name = "params", required = false) String params) {
if (key == null) {
key = FileName2KeyMapping.getKey(type, task);
}
OuterAdapter adapter = loader.getExtension(type, key); OuterAdapter adapter = loader.getExtension(type, key);
String destination = adapter.getDestination(task); String destination = adapter.getDestination(task);
String lockKey = destination == null ? task : destination; String lockKey = destination == null ? task : destination;
@@ -133,6 +134,9 @@ public class CommonRest {
*/ */
@GetMapping("/count/{type}/{key}/{task}") @GetMapping("/count/{type}/{key}/{task}")
public Map<String, Object> count(@PathVariable String type, @PathVariable String key, @PathVariable String task) { public Map<String, Object> count(@PathVariable String type, @PathVariable String key, @PathVariable String task) {
if (key == null) {
key = FileName2KeyMapping.getKey(type, task);
}
OuterAdapter adapter = loader.getExtension(type, key); OuterAdapter adapter = loader.getExtension(type, key);
return adapter.count(task); return adapter.count(task);
} }

View File

@@ -11,7 +11,7 @@ canal.conf:
flatMessage: true flatMessage: true
zookeeperHosts: zookeeperHosts:
syncBatchSize: 1000 syncBatchSize: 1000
retries: 0 retries: -1
timeout: timeout:
accessKey: accessKey:
secretKey: secretKey:
@@ -64,6 +64,8 @@ canal.conf:
# jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true # jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true
# jdbc.username: root # jdbc.username: root
# jdbc.password: 121212 # jdbc.password: 121212
# druid.stat.enable: false
# druid.stat.slowSqlMillis: 1000
# - name: rdb # - name: rdb
# key: oracle1 # key: oracle1
# properties: # properties:
@@ -91,14 +93,14 @@ canal.conf:
# mode: transport # or rest # mode: transport # or rest
# # security.auth: test:123456 # only used for rest mode # # security.auth: test:123456 # only used for rest mode
# cluster.name: elasticsearch # cluster.name: elasticsearch
# - name: kudu # - name: kudu
# key: kudu # key: kudu
# properties: # properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address # kudu.master.address: 127.0.0.1 # ',' split multi address
# - name: phoenix # - name: phoenix
# key: phoenix # key: phoenix
# properties: # properties:
# jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver # jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db # jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
# jdbc.username: # jdbc.username:
# jdbc.password: # jdbc.password:

View File

@@ -1,6 +1,6 @@
#canal: canal:
# manager: manager:
# jdbc: jdbc:
# url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8 url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
# username: root username: canal
# password: 121212 password: canal

View File

@@ -34,6 +34,23 @@
</sift> </sift>
</appender> </appender>
<appender name="RocketmqClientAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../logs/adapter/rocketmq_client.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>../logs/adapter/%d{yyyy-MM-dd}/rocketmq_client-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>512MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.alibaba.otter.canal.client.adapter.hbase" additivity="false"> <logger name="com.alibaba.otter.canal.client.adapter.hbase" additivity="false">
<level value="DEBUG" /> <level value="DEBUG" />
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
@@ -54,6 +71,10 @@
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" /> <appender-ref ref="CANAL-ROOT" />
</logger> </logger>
<logger name="RocketmqClient" additivity="false">
<level value="INFO" />
<appender-ref ref="RocketmqClientAppender" />
</logger>
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -6,8 +6,8 @@ import java.util.Properties;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
@@ -36,7 +36,7 @@ public class LoggerAdapterExample implements OuterAdapter {
} }
public void sync(Dml dml) { public void sync(Dml dml) {
logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.info("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
@Override @Override

View File

@@ -1,11 +1,9 @@
<?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.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -41,50 +39,42 @@
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-lang</groupId> <groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId> <artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.4</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>18.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid</artifactId> <artifactId>druid</artifactId>
<version>1.1.9</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson2</artifactId>
<version>1.2.58</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -123,19 +113,19 @@
<!-- <tasks>--> <!-- <tasks>-->
<!-- <copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">--> <!-- <copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">-->
<!-- <fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">--> <!-- <fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">-->
<!-- <include name="*.yml"/>--> <!-- <include name="*.yml" />-->
<!-- </fileset>--> <!-- </fileset>-->
<!-- </copy>--> <!-- </copy>-->
<!-- </tasks>--> <!-- </tasks>-->
<tasks> <tasks>
<copy todir="${project.basedir}/../launcher/target/canal-adapter/conf/phoenix" overwrite="true"> <copy todir="${project.basedir}/../launcher/target/canal-adapter/conf/phoenix" overwrite="true">
<fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true"> <fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">
<include name="*.yml"/> <include name="*.yml" />
</fileset> </fileset>
</copy> </copy>
<copy todir="${project.basedir}/../launcher/target/canal-adapter/plugin" overwrite="true"> <copy todir="${project.basedir}/../launcher/target/canal-adapter/plugin" overwrite="true">
<fileset dir="${project.basedir}/target/" erroronmissingdir="true"> <fileset dir="${project.basedir}/target/" erroronmissingdir="true">
<include name="*with-dependencies.jar"/> <include name="*with-dependencies.jar" />
</fileset> </fileset>
</copy> </copy>
<copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true"> <copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">

View File

@@ -43,7 +43,7 @@ public class PhoenixAdapter implements OuterAdapter {
private Properties envProperties; private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getPhoenixMapping() { public Map<String, MappingConfig> getPhoenixMapping() {
return phoenixMapping; return phoenixMapping;
@@ -64,14 +64,11 @@ public class PhoenixAdapter implements OuterAdapter {
@Override @Override
public void init(OuterAdapterConfig configuration, Properties envProperties) { public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties; this.envProperties = envProperties;
this.configuration = configuration;
Map<String, MappingConfig> phoenixMappingTmp = ConfigLoader.load(envProperties); Map<String, MappingConfig> phoenixMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置 // 过滤不匹配的key的配置
phoenixMappingTmp.forEach((key, mappingConfig) -> { phoenixMappingTmp.forEach((key, config) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) addConfig(key, config);
|| (mappingConfig.getOuterAdapterKey() != null
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
phoenixMapping.put(key, mappingConfig);
}
}); });
if (phoenixMapping.isEmpty()) { if (phoenixMapping.isEmpty()) {
@@ -80,24 +77,6 @@ public class PhoenixAdapter implements OuterAdapter {
logger.info("[{}]phoenix config mapping: {}", this, phoenixMapping.keySet()); logger.info("[{}]phoenix config mapping: {}", this, phoenixMapping.keySet());
} }
for (Map.Entry<String, MappingConfig> entry : phoenixMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties(); Map<String, String> properties = configuration.getProperties();
DriverClass= properties.get("jdbc.driverClassName"); DriverClass= properties.get("jdbc.driverClassName");
@@ -288,4 +267,60 @@ public class PhoenixAdapter implements OuterAdapter {
phoenixSyncService.close(); phoenixSyncService.close();
} }
} }
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
phoenixMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
phoenixMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
phoenixMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
} }

View File

@@ -1,23 +1,19 @@
package com.alibaba.otter.canal.client.adapter.phoenix.monitor; package com.alibaba.otter.canal.client.adapter.phoenix.monitor;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter; import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util; import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.File;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/** /**
* phoenix config monitor * phoenix config monitor
*/ */
@@ -75,11 +71,10 @@ public class PhoenixConfigMonitor {
return; return;
} }
config.validate(); config.validate();
if ((key == null && config.getOuterAdapterKey() == null) boolean result = phoenixAdapter.addConfig(file.getName(), config);
|| (key != null && key.equals(config.getOuterAdapterKey()))) { if (result) {
addConfigToCache(file, config); logger.info("Add a new phoenix mapping config: {} to canal adapter",
file.getName());
logger.info("Add a new phoenix mapping config: {} to canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
@@ -105,16 +100,7 @@ public class PhoenixConfigMonitor {
return; return;
} }
config.validate(); config.validate();
if ((key == null && config.getOuterAdapterKey() == null) phoenixAdapter.updateConfig(file.getName(), config);
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} else {
// 不能修改outerAdapterKey
throw new RuntimeException("Outer adapter key not allowed modify");
}
logger.info("Change a phoenix mapping config: {} of canal adapter", file.getName()); logger.info("Change a phoenix mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
@@ -128,40 +114,12 @@ public class PhoenixConfigMonitor {
try { try {
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) { if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
deleteConfigFromCache(file); phoenixAdapter.deleteConfig(file.getName());
logger.info("Delete a phoenix mapping config: {} of canal adapter", file.getName()); logger.info("Delete a phoenix mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
} }
private void addConfigToCache(File file, MappingConfig mappingConfig) {
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
phoenixAdapter.getPhoenixMapping().put(file.getName(), mappingConfig);
Map<String, MappingConfig> configMap = phoenixAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-"
+ mappingConfig.getDbMapping().getTable().toLowerCase(),
k1 -> new HashMap<>());
configMap.put(file.getName(), mappingConfig);
}
private void deleteConfigFromCache(File file) {
logger.info("deleteConfigFromCache: {}", file.getName());
MappingConfig mappingConfig = phoenixAdapter.getPhoenixMapping().remove(file.getName());
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
for (Map<String, MappingConfig> configMap : phoenixAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
} }
} }

View File

@@ -6,8 +6,8 @@ import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.*; import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.parser.ParserException; import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.util.JdbcConstants; import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.phoenix.config.ConfigurationManager; import com.alibaba.otter.canal.client.adapter.phoenix.config.ConfigurationManager;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig.DbMapping; import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig.DbMapping;
@@ -208,7 +208,7 @@ public class PhoenixSyncService {
for (Map.Entry<String, MappingConfig> entry : configMap.entrySet()) { for (Map.Entry<String, MappingConfig> entry : configMap.entrySet()) {
MappingConfig config = entry.getValue(); MappingConfig config = entry.getValue();
if (config.isDebug()) { if (config.isDebug()) {
logger.info("DML: {} {}", entry.getKey(), JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.info("DML: {} {}", entry.getKey(), JSON.toJSONString(dml, Feature.WriteNulls));
} }
if (config.getConcurrent()) { if (config.getConcurrent()) {
//并行同步 //并行同步
@@ -257,7 +257,7 @@ public class PhoenixSyncService {
} else if (type != null && type.equalsIgnoreCase("TRUNCATE")) { } else if (type != null && type.equalsIgnoreCase("TRUNCATE")) {
truncate(batchExecutor, config); truncate(batchExecutor, config);
} else if (logger.isInfoEnabled()){ } else if (logger.isInfoEnabled()){
logger.info("SingleDml: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.info("SingleDml: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} catch (SQLException e) { } catch (SQLException e) {
logger.error("sync error: " + e.getMessage(), e); logger.error("sync error: " + e.getMessage(), e);
@@ -268,7 +268,7 @@ public class PhoenixSyncService {
private void alter(BatchExecutor batchExecutor, MappingConfig config, Dml dml, List<SQLStatement> stmtList, String configFile) throws SQLException { private void alter(BatchExecutor batchExecutor, MappingConfig config, Dml dml, List<SQLStatement> stmtList, String configFile) throws SQLException {
if (config.isDebug()) { if (config.isDebug()) {
logger.info("DML: {} {}", configFile, JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.info("DML: {} {}", configFile, JSON.toJSONString(dml, Feature.WriteNulls));
} }
DbMapping dbMapping = config.getDbMapping(); DbMapping dbMapping = config.getDbMapping();
if (!dbMapping.isAlter()) { if (!dbMapping.isAlter()) {

View File

@@ -4,12 +4,12 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>canal client adapter module for otter ${project.version}</name> <name>canal client adapter module for otter ${project.version}</name>
@@ -20,6 +20,7 @@
<java_source_version>1.8</java_source_version> <java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version> <java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding> <file_encoding>UTF-8</file_encoding>
<log4j_version>2.17.0</log4j_version>
</properties> </properties>
<modules> <modules>
@@ -182,9 +183,9 @@
<version>42.1.4</version> <version>42.1.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.oracle</groupId> <groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId> <artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version> <version>11.2.0.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.microsoft.sqlserver</groupId> <groupId>com.microsoft.sqlserver</groupId>
@@ -212,6 +213,12 @@
<artifactId>curator-recipes</artifactId> <artifactId>curator-recipes</artifactId>
<version>2.10.0</version> <version>2.10.0</version>
</dependency> </dependency>
<!-- 单独指定guava版本,兼容curator-client -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency> <dependency>
<groupId>joda-time</groupId> <groupId>joda-time</groupId>
<artifactId>joda-time</artifactId> <artifactId>joda-time</artifactId>
@@ -270,7 +277,21 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j_version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -30,7 +30,7 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.oracle</groupId> <groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId> <artifactId>ojdbc6</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>

View File

@@ -1,7 +1,9 @@
package com.alibaba.otter.canal.client.adapter.rdb; package com.alibaba.otter.canal.client.adapter.rdb;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -13,7 +15,9 @@ import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcUtils;
import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
@@ -53,6 +57,8 @@ public class RdbAdapter implements OuterAdapter {
private Properties envProperties; private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getRdbMapping() { public Map<String, MappingConfig> getRdbMapping() {
return rdbMapping; return rdbMapping;
} }
@@ -73,46 +79,22 @@ public class RdbAdapter implements OuterAdapter {
@Override @Override
public void init(OuterAdapterConfig configuration, Properties envProperties) { public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties; this.envProperties = envProperties;
this.configuration = configuration;
// 从jdbc url获取db类型
Map<String, String> properties = configuration.getProperties();
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load(envProperties); Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置 // 过滤不匹配的key的配置
rdbMappingTmp.forEach((key, mappingConfig) -> { rdbMappingTmp.forEach((key, config) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) addConfig(key, config);
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
rdbMapping.put(key, mappingConfig);
}
}); });
if (rdbMapping.isEmpty()) { if (rdbMapping.isEmpty()) {
throw new RuntimeException("No rdb adapter found for config key: " + configuration.getKey()); throw new RuntimeException("No rdb adapter found for config key: " + configuration.getKey());
} }
for (Map.Entry<String, MappingConfig> entry : rdbMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
if (!mappingConfig.getDbMapping().getMirrorDb()) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
} else {
// mirrorDB
String key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase();
mirrorDbConfigCache.put(key, MirrorDbConfig.create(configName, mappingConfig));
}
}
// 初始化连接池 // 初始化连接池
Map<String, String> properties = configuration.getProperties();
dataSource = new DruidDataSource(); dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.get("jdbc.driverClassName")); dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
dataSource.setUrl(properties.get("jdbc.url")); dataSource.setUrl(properties.get("jdbc.url"));
@@ -125,10 +107,20 @@ public class RdbAdapter implements OuterAdapter {
dataSource.setTimeBetweenEvictionRunsMillis(60000); dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000); dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setUseUnfairLock(true); dataSource.setUseUnfairLock(true);
dataSource.setDbType(dbType);
// List<String> array = new ArrayList<>(); // List<String> array = new ArrayList<>();
// array.add("set names utf8mb4;"); // array.add("set names utf8mb4;");
// dataSource.setConnectionInitSqls(array); // dataSource.setConnectionInitSqls(array);
if ("true".equals(properties.getOrDefault("druid.stat.enable", "true"))) {
StatFilter statFilter = new StatFilter();
statFilter.setSlowSqlMillis(Long.parseLong(properties.getOrDefault("druid.stat.slowSqlMillis", "1000")));
statFilter.setMergeSql(true);
statFilter.setLogSlowSql(true);
dataSource.setProxyFilters(Collections.singletonList(statFilter));
}
try { try {
dataSource.init(); dataSource.init();
} catch (SQLException e) { } catch (SQLException e) {
@@ -165,7 +157,9 @@ public class RdbAdapter implements OuterAdapter {
return; return;
} }
try { try {
rdbSyncService.sync(mappingConfigCache, dmls, envProperties); if (!mappingConfigCache.isEmpty()) {
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
}
rdbMirrorDbSyncService.sync(dmls); rdbMirrorDbSyncService.sync(dmls);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -226,7 +220,7 @@ public class RdbAdapter implements OuterAdapter {
public Map<String, Object> count(String task) { public Map<String, Object> count(String task) {
MappingConfig config = rdbMapping.get(task); MappingConfig config = rdbMapping.get(task);
MappingConfig.DbMapping dbMapping = config.getDbMapping(); MappingConfig.DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping); String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
Connection conn = null; Connection conn = null;
Map<String, Object> res = new LinkedHashMap<>(); Map<String, Object> res = new LinkedHashMap<>();
try { try {
@@ -252,7 +246,7 @@ public class RdbAdapter implements OuterAdapter {
} }
} }
} }
res.put("targetTable", SyncUtil.getDbTableName(dbMapping)); res.put("targetTable", SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
return res; return res;
} }
@@ -289,4 +283,67 @@ public class RdbAdapter implements OuterAdapter {
dataSource.close(); dataSource.close();
} }
} }
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
if (!mappingConfig.getDbMapping().getMirrorDb()) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
} else {
// mirrorDB
String key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase();
mirrorDbConfigCache.put(key, MirrorDbConfig.create(configName, mappingConfig));
}
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
rdbMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
rdbMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
rdbMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
} }

View File

@@ -1,24 +1,18 @@
package com.alibaba.otter.canal.client.adapter.rdb.monitor; package com.alibaba.otter.canal.client.adapter.rdb.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder; import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util; import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.File;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RdbConfigMonitor { public class RdbConfigMonitor {
@@ -41,7 +35,7 @@ public class RdbConfigMonitor {
File confDir = Util.getConfDirPath(adapterName); File confDir = Util.getConfDirPath(adapterName);
try { try {
FileAlterationObserver observer = new FileAlterationObserver(confDir, FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener(); FileListener listener = new FileListener();
observer.addListener(listener); observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer); fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -69,16 +63,15 @@ public class RdbConfigMonitor {
// 加载新增的配置文件 // 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName()); String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YmlConfigBinder MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties); .bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) { if (config == null) {
return; return;
} }
config.validate(); config.validate();
if ((key == null && config.getOuterAdapterKey() == null) boolean result = rdbAdapter.addConfig(file.getName(), config);
|| (key != null && key.equals(config.getOuterAdapterKey()))) { if (result) {
addConfigToCache(file, config); logger.info("Add a new rdb mapping config: {} to canal adapter",
file.getName());
logger.info("Add a new rdb mapping config: {} to canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
@@ -92,28 +85,19 @@ public class RdbConfigMonitor {
try { try {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) { if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
// 加载配置文件 // 加载配置文件
String configContent = MappingConfigsLoader String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) { if (configContent == null) {
onFileDelete(file); onFileDelete(file);
return; return;
} }
MappingConfig config = YmlConfigBinder MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties); .bindYmlToObj(null, configContent, MappingConfig.class, null,
envProperties);
if (config == null) { if (config == null) {
return; return;
} }
config.validate(); config.validate();
if ((key == null && config.getOuterAdapterKey() == null) rdbAdapter.updateConfig(file.getName(), config);
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} else {
// 不能修改outerAdapterKey
throw new RuntimeException("Outer adapter key not allowed modify");
}
logger.info("Change a rdb mapping config: {} of canal adapter", file.getName()); logger.info("Change a rdb mapping config: {} of canal adapter", file.getName());
} }
} catch (Exception e) { } catch (Exception e) {
@@ -127,7 +111,7 @@ public class RdbConfigMonitor {
try { try {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) { if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
deleteConfigFromCache(file); rdbAdapter.deleteConfig(file.getName());
logger.info("Delete a rdb mapping config: {} of canal adapter", file.getName()); logger.info("Delete a rdb mapping config: {} of canal adapter", file.getName());
} }
@@ -135,47 +119,5 @@ public class RdbConfigMonitor {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
} }
private void addConfigToCache(File file, MappingConfig mappingConfig) {
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
rdbAdapter.getRdbMapping().put(file.getName(), mappingConfig);
if (!mappingConfig.getDbMapping().getMirrorDb()) {
Map<String, MappingConfig> configMap = rdbAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-"
+ mappingConfig.getDbMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), mappingConfig);
} else {
Map<String, MirrorDbConfig> mirrorDbConfigCache = rdbAdapter.getMirrorDbConfigCache();
mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase(),
MirrorDbConfig.create(file.getName(), mappingConfig));
}
}
private void deleteConfigFromCache(File file) {
MappingConfig mappingConfig = rdbAdapter.getRdbMapping().remove(file.getName());
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
if (!mappingConfig.getDbMapping().getMirrorDb()) {
for (Map<String, MappingConfig> configMap : rdbAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
} else {
rdbAdapter.getMirrorDbConfigCache().forEach((key, mirrorDbConfig) -> {
if (mirrorDbConfig.getFileName().equals(file.getName())) {
rdbAdapter.getMirrorDbConfigCache().remove(key);
}
});
}
}
} }
} }

View File

@@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
@@ -56,8 +57,11 @@ public class RdbEtlService extends AbstractEtlService {
DbMapping dbMapping = (DbMapping) mapping; DbMapping dbMapping = (DbMapping) mapping;
Map<String, String> columnsMap = new LinkedHashMap<>(); Map<String, String> columnsMap = new LinkedHashMap<>();
Map<String, Integer> columnType = new LinkedHashMap<>(); Map<String, Integer> columnType = new LinkedHashMap<>();
DruidDataSource dataSource = (DruidDataSource) srcDS;
Util.sqlRS(targetDS, "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " LIMIT 1 ", rs -> { Util.sqlRS(targetDS,
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
rs -> {
try { try {
ResultSetMetaData rsd = rs.getMetaData(); ResultSetMetaData rsd = rs.getMetaData();
@@ -83,7 +87,9 @@ public class RdbEtlService extends AbstractEtlService {
boolean completed = false; boolean completed = false;
StringBuilder insertSql = new StringBuilder(); StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" ("); insertSql.append("INSERT INTO ")
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
.append(" (");
columnsMap columnsMap
.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); .forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
@@ -107,7 +113,7 @@ public class RdbEtlService extends AbstractEtlService {
// 删除数据 // 删除数据
Map<String, Object> pkVal = new LinkedHashMap<>(); Map<String, Object> pkVal = new LinkedHashMap<>();
StringBuilder deleteSql = new StringBuilder( StringBuilder deleteSql = new StringBuilder(
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE "); "DELETE FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE ");
appendCondition(dbMapping, deleteSql, pkVal, rs); appendCondition(dbMapping, deleteSql, pkVal, rs);
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) { try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
int k = 1; int k = 1;

View File

@@ -7,17 +7,17 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource;
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;
import com.alibaba.fastjson.JSON; import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml; import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Dml;
/** /**
@@ -31,10 +31,10 @@ public class RdbMirrorDbSyncService {
private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class); private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class);
private Map<String, MirrorDbConfig> mirrorDbConfigCache; // 镜像库配置 private Map<String, MirrorDbConfig> mirrorDbConfigCache; // 镜像库配置
private DataSource dataSource; private DruidDataSource dataSource;
private RdbSyncService rdbSyncService; // rdbSyncService代理 private RdbSyncService rdbSyncService; // rdbSyncService代理
public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DataSource dataSource, public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DruidDataSource dataSource,
Integer threads, Map<String, Map<String, Integer>> columnsTypeCache, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){ boolean skipDupException){
this.mirrorDbConfigCache = mirrorDbConfigCache; this.mirrorDbConfigCache = mirrorDbConfigCache;
@@ -45,7 +45,7 @@ public class RdbMirrorDbSyncService {
/** /**
* 批量同步方法 * 批量同步方法
* *
* @param dmls 批量 DML * @param dmls 批量 DML,包含DDL
*/ */
public void sync(List<Dml> dmls) { public void sync(List<Dml> dmls) {
List<Dml> dmlList = new ArrayList<>(); List<Dml> dmlList = new ArrayList<>();
@@ -66,9 +66,13 @@ public class RdbMirrorDbSyncService {
} }
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) { if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
// 确保执行DDL前DML已执行完
syncDml(dmlList);
dmlList.clear();
// DDL // DDL
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("DDL: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.debug("DDL: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
executeDdl(mirrorDbConfig, dml); executeDdl(mirrorDbConfig, dml);
rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable()); rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable());
@@ -79,38 +83,32 @@ public class RdbMirrorDbSyncService {
dmlList.add(dml); dmlList.add(dml);
} }
} }
if (!dmlList.isEmpty()) { syncDml(dmlList);
rdbSyncService.sync(dmlList, dml -> { }
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(dml.getDestination() + "." + dml.getDatabase());
if (mirrorDbConfig == null) {
return false;
}
String table = dml.getTable();
MappingConfig config = mirrorDbConfig.getTableConfig().get(table);
if (config == null) { /**
return false; * 批量同步Dml
} *
// 是否区分大小写 * @param dmlList Dml列表,不包含DDL
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive(); */
if (config.getConcurrent()) { private void syncDml(List<Dml> dmlList) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive); if (dmlList == null || dmlList.isEmpty()) {
singleDmls.forEach(singleDml -> { return;
int hash = rdbSyncService.pkHash(config.getDbMapping(), singleDml.getData());
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
});
}
return true;
});
} }
rdbSyncService.sync(dmlList, dml -> {
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(dml.getDestination() + "." + dml.getDatabase());
if (mirrorDbConfig == null) {
return false;
}
String table = dml.getTable();
MappingConfig config = mirrorDbConfig.getTableConfig().get(table);
if (config == null) {
return false;
}
rdbSyncService.appendDmlPartition(config, dml);
return true;
});
} }
/** /**
@@ -153,7 +151,13 @@ public class RdbMirrorDbSyncService {
*/ */
private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) { private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) {
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) { try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
statement.execute(ddl.getSql()); // 替换反引号
String sql = ddl.getSql();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
if (!"`".equals(backtick)) {
sql = sql.replaceAll("`", backtick);
}
statement.execute(sql);
// 移除对应配置 // 移除对应配置
mirrorDbConfig.getTableConfig().remove(ddl.getTable()); mirrorDbConfig.getTableConfig().remove(ddl.getTable());
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {

View File

@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.service;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -15,14 +16,13 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.function.Function; import java.util.function.Function;
import javax.sql.DataSource;
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;
import com.alibaba.fastjson.JSON; import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor; import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor;
@@ -41,6 +41,7 @@ public class RdbSyncService {
private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class); private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
private DruidDataSource dataSource;
// 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType> // 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType>
private Map<String, Map<String, Integer>> columnsTypeCache; private Map<String, Map<String, Integer>> columnsTypeCache;
@@ -59,13 +60,14 @@ public class RdbSyncService {
return columnsTypeCache; return columnsTypeCache;
} }
public RdbSyncService(DataSource dataSource, Integer threads, boolean skipDupException){ public RdbSyncService(DruidDataSource dataSource, Integer threads, boolean skipDupException){
this(dataSource, threads, new ConcurrentHashMap<>(), skipDupException); this(dataSource, threads, new ConcurrentHashMap<>(), skipDupException);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public RdbSyncService(DataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache, public RdbSyncService(DruidDataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){ boolean skipDupException){
this.dataSource = dataSource;
this.columnsTypeCache = columnsTypeCache; this.columnsTypeCache = columnsTypeCache;
this.skipDupException = skipDupException; this.skipDupException = skipDupException;
try { try {
@@ -177,28 +179,38 @@ public class RdbSyncService {
} }
for (MappingConfig config : configMap.values()) { for (MappingConfig config : configMap.values()) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive(); appendDmlPartition(config, dml);
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
}
} }
return true; return true;
} }
} ); } );
} }
/**
* 将Dml加入 {@link #dmlsPartition}
*
* @param config 表映射配置
* @param dml Dml对象
*/
public void appendDmlPartition(MappingConfig config, Dml dml) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
}
}
/** /**
* 单条 dml 同步 * 单条 dml 同步
* *
@@ -220,7 +232,7 @@ public class RdbSyncService {
truncate(batchExecutor, config); truncate(batchExecutor, config);
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -241,15 +253,15 @@ public class RdbSyncService {
} }
DbMapping dbMapping = config.getDbMapping(); DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data); Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
StringBuilder insertSql = new StringBuilder(); StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" ("); insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" (");
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append("`") columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
.append(targetColumnName) .append(targetColumnName)
.append("`") .append(backtick)
.append(",")); .append(","));
int len = insertSql.length(); int len = insertSql.length();
insertSql.delete(len - 1, len).append(") VALUES ("); insertSql.delete(len - 1, len).append(") VALUES (");
@@ -313,13 +325,13 @@ public class RdbSyncService {
} }
DbMapping dbMapping = config.getDbMapping(); DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data); Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config); Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder updateSql = new StringBuilder(); StringBuilder updateSql = new StringBuilder();
updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping)).append(" SET "); updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" SET ");
List<Map<String, ?>> values = new ArrayList<>(); List<Map<String, ?>> values = new ArrayList<>();
boolean hasMatched = false; boolean hasMatched = false;
for (String srcColumnName : old.keySet()) { for (String srcColumnName : old.keySet()) {
@@ -332,7 +344,7 @@ public class RdbSyncService {
if (!targetColumnNames.isEmpty()) { if (!targetColumnNames.isEmpty()) {
hasMatched = true; hasMatched = true;
for (String targetColumnName : targetColumnNames) { for (String targetColumnName : targetColumnNames) {
updateSql.append("`").append(targetColumnName).append("`").append("=?, "); updateSql.append(backtick).append(targetColumnName).append(backtick).append("=?, ");
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase()); Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
if (type == null) { if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched"); throw new RuntimeException("Target column: " + targetColumnName + " not matched");
@@ -369,11 +381,10 @@ public class RdbSyncService {
} }
DbMapping dbMapping = config.getDbMapping(); DbMapping dbMapping = config.getDbMapping();
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config); Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE "); sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" WHERE ");
List<Map<String, ?>> values = new ArrayList<>(); List<Map<String, ?>> values = new ArrayList<>();
// 拼接主键 // 拼接主键
@@ -392,7 +403,7 @@ public class RdbSyncService {
private void truncate(BatchExecutor batchExecutor, MappingConfig config) throws SQLException { private void truncate(BatchExecutor batchExecutor, MappingConfig config) throws SQLException {
DbMapping dbMapping = config.getDbMapping(); DbMapping dbMapping = config.getDbMapping();
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("TRUNCATE TABLE ").append(SyncUtil.getDbTableName(dbMapping)); sql.append("TRUNCATE TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
batchExecutor.execute(sql.toString(), new ArrayList<>()); batchExecutor.execute(sql.toString(), new ArrayList<>());
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Truncate target table, sql: {}", sql); logger.trace("Truncate target table, sql: {}", sql);
@@ -416,13 +427,18 @@ public class RdbSyncService {
if (columnType == null) { if (columnType == null) {
columnType = new LinkedHashMap<>(); columnType = new LinkedHashMap<>();
final Map<String, Integer> columnTypeTmp = columnType; final Map<String, Integer> columnTypeTmp = columnType;
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE 1=2"; String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE 1=2";
Util.sqlRS(conn, sql, rs -> { Util.sqlRS(conn, sql, rs -> {
try { try {
ResultSetMetaData rsd = rs.getMetaData(); ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount(); int columnCount = rsd.getColumnCount();
for (int i = 1; i <= columnCount; i++) { for (int i = 1; i <= columnCount; i++) {
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i)); int colType = rsd.getColumnType(i);
// 修复year类型作为date处理时的data truncated问题
if ("YEAR".equals(rsd.getColumnTypeName(i))) {
colType = Types.VARCHAR;
}
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), colType);
} }
columnsTypeCache.put(cacheKey, columnTypeTmp); columnsTypeCache.put(cacheKey, columnTypeTmp);
} catch (SQLException e) { } catch (SQLException e) {
@@ -445,6 +461,8 @@ public class RdbSyncService {
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype, private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
List<Map<String, ?>> values, Map<String, Object> d, Map<String, Object> o) { List<Map<String, ?>> values, Map<String, Object> d, Map<String, Object> o) {
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
// 拼接主键 // 拼接主键
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) { for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
String targetColumnName = entry.getKey(); String targetColumnName = entry.getKey();
@@ -452,7 +470,7 @@ public class RdbSyncService {
if (srcColumnName == null) { if (srcColumnName == null) {
srcColumnName = Util.cleanColumn(targetColumnName); srcColumnName = Util.cleanColumn(targetColumnName);
} }
sql.append("`").append(targetColumnName).append("`").append("=? AND "); sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase()); Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
if (type == null) { if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched"); throw new RuntimeException("Target column: " + targetColumnName + " not matched");

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.rdb.support; package com.alibaba.otter.canal.client.adapter.rdb.support;
import com.alibaba.druid.DbType;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util; import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -255,12 +256,36 @@ public class SyncUtil {
} }
} }
public static String getDbTableName(MappingConfig.DbMapping dbMapping) { public static String getDbTableName(MappingConfig.DbMapping dbMapping, String dbType) {
String result = ""; String result = "";
String backtick = getBacktickByDbType(dbType);
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) { if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
result += ("`" + dbMapping.getTargetDb() + "`."); result += (backtick + dbMapping.getTargetDb() + backtick + ".");
} }
result += ("`" + dbMapping.getTargetTable() + "`"); result += (backtick + dbMapping.getTargetTable() + backtick);
return result; return result;
} }
/**
* 根据DbType返回反引号或空字符串
*
* @param dbTypeName DbType名称
* @return 反引号或空字符串
*/
public static String getBacktickByDbType(String dbTypeName) {
DbType dbType = DbType.of(dbTypeName);
if (dbType == null) {
dbType = DbType.other;
}
// 只有当dbType为MySQL/MariaDB或OceanBase时返回反引号
switch (dbType) {
case mysql:
case mariadb:
case oceanbase:
return "`";
default:
return "";
}
}
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,6 +1,8 @@
package com.alibaba.otter.canal.client.adapter.tablestore; package com.alibaba.otter.canal.client.adapter.tablestore;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -55,62 +57,14 @@ public class TablestoreAdapter implements OuterAdapter {
this.configuration = configuration; this.configuration = configuration;
Map<String, MappingConfig> tablestoreMappingTmp = ConfigLoader.load(envProperties); Map<String, MappingConfig> tablestoreMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置 // 过滤不匹配的key的配置
tablestoreMappingTmp.forEach((key, mappingConfig) -> { tablestoreMappingTmp.forEach((key, config) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) addConfig(key, config);
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
tablestoreMapping.put(key, mappingConfig);
mappingConfig.getDbMapping().init(mappingConfig);
}
}); });
if (tablestoreMapping.isEmpty()) { if (tablestoreMapping.isEmpty()) {
throw new RuntimeException("No tablestore adapter found for config key: " + configuration.getKey()); throw new RuntimeException("No tablestore adapter found for config key: " + configuration.getKey());
} }
Map<String, String> properties = configuration.getProperties();
for (Map.Entry<String, MappingConfig> entry : tablestoreMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
// 构建对应的 TableStoreWriter
ServiceCredentials credentials = new DefaultCredentials(
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETID),
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETKEY)
);
WriterConfig config = getWriterConfig(mappingConfig);
TableStoreWriter writer = new DefaultTableStoreWriter(
properties.get(PropertyConstants.TABLESTORE_ENDPOINT),
credentials,
properties.get(PropertyConstants.TABLESTORE_INSTANCENAME),
mappingConfig.getDbMapping().getTargetTable(),
config,
null
);
Map<String, TableStoreWriter> config2writerMap = writerCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
config2writerMap.put(configName, writer);
}
tablestoreSyncService = new TablestoreSyncService(); tablestoreSyncService = new TablestoreSyncService();
} }
@@ -315,4 +269,84 @@ public class TablestoreAdapter implements OuterAdapter {
} }
} }
} }
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
Map<String, String> properties = configuration.getProperties();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
// 构建对应的 TableStoreWriter
ServiceCredentials credentials = new DefaultCredentials(
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETID),
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETKEY)
);
WriterConfig config = getWriterConfig(mappingConfig);
TableStoreWriter writer = new DefaultTableStoreWriter(
properties.get(PropertyConstants.TABLESTORE_ENDPOINT),
credentials,
properties.get(PropertyConstants.TABLESTORE_INSTANCENAME),
mappingConfig.getDbMapping().getTargetTable(),
config,
null
);
Map<String, TableStoreWriter> config2writerMap = writerCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
config2writerMap.put(configName, writer);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
tablestoreMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
tablestoreMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
tablestoreMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
} }

View File

@@ -6,8 +6,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType; import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType;
import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil;
import com.alicloud.openservices.tablestore.TableStoreWriter; import com.alicloud.openservices.tablestore.TableStoreWriter;
@@ -59,7 +59,7 @@ public class TablestoreSyncService {
Future<WriterResult> future = writer.addRowChangeWithFuture(rowChanges); Future<WriterResult> future = writer.addRowChangeWithFuture(rowChanges);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
} }
return future; return future;
} }

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.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
@@ -50,10 +50,6 @@
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
@@ -138,6 +134,11 @@
<artifactId>pulsar-client</artifactId> <artifactId>pulsar-client</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-admin</artifactId>
<scope>provided</scope>
</dependency>
<!-- junit --> <!-- junit -->
<dependency> <dependency>

View File

@@ -39,6 +39,7 @@ public class ClusterCanalConnector implements CanalConnector {
this.accessStrategy = accessStrategy; this.accessStrategy = accessStrategy;
} }
@Override
public void connect() throws CanalClientException { public void connect() throws CanalClientException {
while (currentConnector == null) { while (currentConnector == null) {
int times = 0; int times = 0;
@@ -85,10 +86,12 @@ public class ClusterCanalConnector implements CanalConnector {
} }
} }
@Override
public boolean checkValid() { public boolean checkValid() {
return currentConnector != null && currentConnector.checkValid(); return currentConnector != null && currentConnector.checkValid();
} }
@Override
public void disconnect() throws CanalClientException { public void disconnect() throws CanalClientException {
if (currentConnector != null) { if (currentConnector != null) {
currentConnector.disconnect(); currentConnector.disconnect();
@@ -96,10 +99,12 @@ public class ClusterCanalConnector implements CanalConnector {
} }
} }
@Override
public void subscribe() throws CanalClientException { public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可 subscribe(""); // 传递空字符即可
} }
@Override
public void subscribe(String filter) throws CanalClientException { public void subscribe(String filter) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -125,6 +130,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to subscribe after " + times + " times retry."); throw new CanalClientException("failed to subscribe after " + times + " times retry.");
} }
@Override
public void unsubscribe() throws CanalClientException { public void unsubscribe() throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -142,6 +148,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to unsubscribe after " + times + " times retry."); throw new CanalClientException("failed to unsubscribe after " + times + " times retry.");
} }
@Override
public Message get(int batchSize) throws CanalClientException { public Message get(int batchSize) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -159,6 +166,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry"); throw new CanalClientException("failed to fetch the data after " + times + " times retry");
} }
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -176,6 +184,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry"); throw new CanalClientException("failed to fetch the data after " + times + " times retry");
} }
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException { public Message getWithoutAck(int batchSize) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -193,6 +202,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry"); throw new CanalClientException("failed to fetch the data after " + times + " times retry");
} }
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -210,6 +220,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry"); throw new CanalClientException("failed to fetch the data after " + times + " times retry");
} }
@Override
public void rollback(long batchId) throws CanalClientException { public void rollback(long batchId) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -227,6 +238,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to rollback after " + times + " times retry"); throw new CanalClientException("failed to rollback after " + times + " times retry");
} }
@Override
public void rollback() throws CanalClientException { public void rollback() throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {
@@ -245,6 +257,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to rollback after " + times + " times retry"); throw new CanalClientException("failed to rollback after " + times + " times retry");
} }
@Override
public void ack(long batchId) throws CanalClientException { public void ack(long batchId) throws CanalClientException {
int times = 0; int times = 0;
while (times < retryTimes) { while (times < retryTimes) {

View File

@@ -63,6 +63,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
return nextNode(); return nextNode();
} }
@Override
public SocketAddress nextNode() { public SocketAddress nextNode() {
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点 if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
return runningAddress; return runningAddress;
@@ -81,7 +82,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
for (String address : currentChilds) { for (String address : currentChilds) {
String[] strs = StringUtils.split(address, ":"); String[] strs = StringUtils.split(address, ":");
if (strs != null && strs.length == 2) { if (strs != null && strs.length == 2) {
addresses.add(new InetSocketAddress(strs[0], Integer.valueOf(strs[1]))); addresses.add(new InetSocketAddress(strs[0], Integer.parseInt(strs[1])));
} }
} }
@@ -98,7 +99,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
ServerRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ServerRunningData.class); ServerRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ServerRunningData.class);
String[] strs = StringUtils.split(runningData.getAddress(), ':'); String[] strs = StringUtils.split(runningData.getAddress(), ':');
if (strs.length == 2) { if (strs.length == 2) {
runningAddress = new InetSocketAddress(strs[0], Integer.valueOf(strs[1])); runningAddress = new InetSocketAddress(strs[0], Integer.parseInt(strs[1]));
} }
} }

View File

@@ -98,6 +98,7 @@ public class SimpleCanalConnector implements CanalConnector {
this.clientIdentity = new ClientIdentity(destination, (short) 1001); this.clientIdentity = new ClientIdentity(destination, (short) 1001);
} }
@Override
public void connect() throws CanalClientException { public void connect() throws CanalClientException {
if (connected) { if (connected) {
return; return;
@@ -124,6 +125,7 @@ public class SimpleCanalConnector implements CanalConnector {
connected = true; connected = true;
} }
@Override
public void disconnect() throws CanalClientException { public void disconnect() throws CanalClientException {
if (rollbackOnDisConnect && channel.isConnected()) { if (rollbackOnDisConnect && channel.isConnected()) {
rollback(); rollback();
@@ -222,10 +224,12 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
@Override
public void subscribe() throws CanalClientException { public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可 subscribe(""); // 传递空字符即可
} }
@Override
public void subscribe(String filter) throws CanalClientException { public void subscribe(String filter) throws CanalClientException {
waitClientRunning(); waitClientRunning();
if (!running) { if (!running) {
@@ -255,6 +259,7 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
@Override
public void unsubscribe() throws CanalClientException { public void unsubscribe() throws CanalClientException {
waitClientRunning(); waitClientRunning();
if (!running) { if (!running) {
@@ -281,20 +286,24 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
@Override
public Message get(int batchSize) throws CanalClientException { public Message get(int batchSize) throws CanalClientException {
return get(batchSize, null, null); return get(batchSize, null, null);
} }
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
Message message = getWithoutAck(batchSize, timeout, unit); Message message = getWithoutAck(batchSize, timeout, unit);
ack(message.getId()); ack(message.getId());
return message; return message;
} }
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException { public Message getWithoutAck(int batchSize) throws CanalClientException {
return getWithoutAck(batchSize, null, null); return getWithoutAck(batchSize, null, null);
} }
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
waitClientRunning(); waitClientRunning();
if (!running) { if (!running) {
@@ -331,6 +340,7 @@ public class SimpleCanalConnector implements CanalConnector {
return CanalMessageDeserializer.deserializer(data, lazyParseEntry); return CanalMessageDeserializer.deserializer(data, lazyParseEntry);
} }
@Override
public void ack(long batchId) throws CanalClientException { public void ack(long batchId) throws CanalClientException {
waitClientRunning(); waitClientRunning();
if (!running) { if (!running) {
@@ -352,6 +362,7 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
@Override
public void rollback(long batchId) throws CanalClientException { public void rollback(long batchId) throws CanalClientException {
waitClientRunning(); waitClientRunning();
ClientRollback ca = ClientRollback.newBuilder() ClientRollback ca = ClientRollback.newBuilder()
@@ -370,9 +381,10 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
@Override
public void rollback() throws CanalClientException { public void rollback() throws CanalClientException {
waitClientRunning(); waitClientRunning();
rollback(0);// 0代笔未设置 rollback(0); // 0 代表未设置
} }
// ==================== helper method ==================== // ==================== helper method ====================

View File

@@ -24,6 +24,7 @@ public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
this.nodes.addAll(nodes); this.nodes.addAll(nodes);
} }
@Override
public SocketAddress nextNode() { public SocketAddress nextNode() {
try { try {
return nodes.get(index); return nodes.get(index);

View File

@@ -10,7 +10,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringDeserializer;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;

View File

@@ -1,6 +1,6 @@
package com.alibaba.otter.canal.client.kafka; package com.alibaba.otter.canal.client.kafka;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.kafka.protocol.KafkaFlatMessage; import com.alibaba.otter.canal.client.kafka.protocol.KafkaFlatMessage;
import com.alibaba.otter.canal.client.kafka.protocol.KafkaMessage; import com.alibaba.otter.canal.client.kafka.protocol.KafkaMessage;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;

View File

@@ -1,6 +1,6 @@
package com.alibaba.otter.canal.client.pulsarmq; package com.alibaba.otter.canal.client.pulsarmq;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.CanalMessageDeserializer; import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;

View File

@@ -1,6 +1,6 @@
package com.alibaba.otter.canal.client.rabbitmq; package com.alibaba.otter.canal.client.rabbitmq;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.CanalMessageDeserializer; import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.ConsumerBatchMessage; import com.alibaba.otter.canal.client.ConsumerBatchMessage;

View File

@@ -22,7 +22,7 @@ import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.CanalMessageDeserializer; import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;

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.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.common</artifactId> <artifactId>canal.common</artifactId>
@@ -39,8 +39,8 @@
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>

View File

@@ -1,6 +1,5 @@
package com.alibaba.otter.canal.common.utils; package com.alibaba.otter.canal.common.utils;
import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
@@ -8,14 +7,13 @@ import java.net.InetAddress;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson2.JSONFactory;
import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson2.TypeReference;
import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson2.filter.PropertyFilter;
import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson2.writer.ObjectWriter;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
/** /**
* 字节处理相关工具类 * 字节处理相关工具类
@@ -25,13 +23,12 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
public class JsonUtils { public class JsonUtils {
static { static {
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance); JSON.register(InetAddress.class, InetAddressWriter.instance);
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance); JSON.register(Inet4Address.class, InetAddressWriter.instance);
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance); JSON.register(Inet6Address.class, InetAddressWriter.instance);
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
ParserConfig.getGlobalInstance().addAccept("com.alibaba.otter."); JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept("com.alibaba.otter.");
ParserConfig.getGlobalInstance().addAccept("com.taobao.tddl.dbsync."); JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept("com.taobao.tddl.dbsync.");
} }
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) { public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
@@ -46,7 +43,7 @@ public class JsonUtils {
return JSON.toJSONBytes(obj); // 默认为UTF-8 return JSON.toJSONBytes(obj); // 默认为UTF-8
} }
public static byte[] marshalToByte(Object obj, SerializerFeature... features) { public static byte[] marshalToByte(Object obj, JSONWriter.Feature... features) {
return JSON.toJSONBytes(obj, features); // 默认为UTF-8 return JSON.toJSONBytes(obj, features); // 默认为UTF-8
} }
@@ -62,7 +59,7 @@ public class JsonUtils {
return JSON.toJSONString(obj); // 默认为UTF-8 return JSON.toJSONString(obj); // 默认为UTF-8
} }
public static String marshalToString(Object obj, SerializerFeature... features) { public static String marshalToString(Object obj, JSONWriter.Feature... features) {
return JSON.toJSONString(obj, features); // 默认为UTF-8 return JSON.toJSONString(obj, features); // 默认为UTF-8
} }
@@ -71,29 +68,29 @@ public class JsonUtils {
*/ */
public static String marshalToString(Object obj, String... fliterFields) { public static String marshalToString(Object obj, String... fliterFields) {
final List<String> propertyFliters = Arrays.asList(fliterFields); final List<String> propertyFliters = Arrays.asList(fliterFields);
try (SerializeWriter out = new SerializeWriter()) {
JSONSerializer serializer = new JSONSerializer(out); return JSON.toJSONString(obj, new PropertyFilter() {
serializer.getPropertyFilters().add((source, name, value) -> !propertyFliters.contains(name)); @Override
serializer.write(obj); public boolean process(Object object, String name, Object value) {
return out.toString(); return !propertyFliters.contains(name);
} }
});
} }
public static class InetAddressSerializer implements ObjectSerializer { public static class InetAddressWriter implements ObjectWriter {
public static InetAddressSerializer instance = new InetAddressSerializer(); public static InetAddressWriter instance = new InetAddressWriter();
@Override @Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
throws IOException {
if (object == null) { if (object == null) {
serializer.writeNull(); jsonWriter.writeNull();
return; return;
} }
InetAddress address = (InetAddress) object; InetAddress address = (InetAddress) object;
// 优先使用name // 优先使用name
serializer.write(address.getHostName()); jsonWriter.writeString(address.getHostName());
} }
} }
} }

View File

@@ -106,21 +106,21 @@ public class ServerRunningTest extends AbstractZkTest {
runningMonitor.setListener(new ServerRunningListener() { runningMonitor.setListener(new ServerRunningListener() {
public void processActiveEnter() { public void processActiveEnter() {
System.out.println(String.format("cid:%s ip:%s:%s has start", ip, port)); System.out.println(String.format("ip:%s:%s has start", ip, port));
countLatch.countDown(); countLatch.countDown();
} }
public void processActiveExit() { public void processActiveExit() {
System.out.println(String.format("cid:%s ip:%s:%s has stop", ip, port)); System.out.println(String.format("ip:%s:%s has stop", ip, port));
countLatch.countDown(); countLatch.countDown();
} }
public void processStart() { public void processStart() {
System.out.println(String.format("cid:%s ip:%s:%s processStart", ip, port)); System.out.println(String.format("ip:%s:%s processStart", ip, port));
} }
public void processStop() { public void processStop() {
System.out.println(String.format("cid:%s ip:%s:%s processStop", ip, port)); System.out.println(String.format("ip:%s:%s processStop", ip, port));
} }
}); });

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

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

View File

@@ -16,7 +16,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringDeserializer;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.connector.core.config.CanalConstants; import com.alibaba.otter.canal.connector.core.config.CanalConstants;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage; import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer; import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;

View File

@@ -18,8 +18,8 @@ import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate; import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer; import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer;
import com.alibaba.otter.canal.connector.core.producer.MQDestination; import com.alibaba.otter.canal.connector.core.producer.MQDestination;
@@ -249,13 +249,13 @@ public class CanalKafkaProducer extends AbstractMQProducer implements CanalMQPro
FlatMessage flatMessagePart = partitionFlatMessage[i]; FlatMessage flatMessagePart = partitionFlatMessage[i];
if (flatMessagePart != null) { if (flatMessagePart != null) {
records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart, records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart,
SerializerFeature.WriteMapNullValue))); JSONWriter.Feature.WriteNulls)));
} }
} }
} else { } else {
final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0; final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0;
records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage, records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage,
SerializerFeature.WriteMapNullValue))); JSONWriter.Feature.WriteNulls)));
} }
} }
} }

View File

@@ -4,13 +4,13 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>canal connector module for otter ${project.version}</name> <name>canal connector module for otter ${project.version}</name>

View File

@@ -1,18 +1,16 @@
<?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.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>connector.pulsarmq</artifactId> <artifactId>connector.pulsarmq</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>canal connector pulsarmq module for otter ${project.version}</name>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -38,6 +36,10 @@
<groupId>org.apache.pulsar</groupId> <groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client</artifactId> <artifactId>pulsar-client</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-admin</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -91,4 +93,4 @@
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@@ -1,7 +1,5 @@
package com.alibaba.otter.canal.connector.pulsarmq.config; package com.alibaba.otter.canal.connector.pulsarmq.config;
import org.joda.time.Seconds;
/** /**
* PulsarMQ配置 * PulsarMQ配置
* *
@@ -10,19 +8,20 @@ import org.joda.time.Seconds;
* @since 1 by chad at 2021/9/15 新增配置文件 * @since 1 by chad at 2021/9/15 新增配置文件
*/ */
public class PulsarMQConstants { public class PulsarMQConstants {
public static final String ROOT = "pulsarmq";
public static final String ROOT = "pulsarmq";
/** /**
* pulsar服务连接地址 * pulsar服务连接地址
*/ */
public static final String PULSARMQ_SERVER_URL = ROOT + "." + "serverUrl"; public static final String PULSARMQ_SERVER_URL = ROOT + "." + "serverUrl";
/** /**
* pulsar服务角色token,需要有对应token的生产者权限 * pulsar服务角色token,需要有对应token的生产者权限
*/ */
public static final String PULSARMQ_ROLE_TOKEN = ROOT + "." + "roleToken"; public static final String PULSARMQ_ROLE_TOKEN = ROOT + "." + "roleToken";
/** /**
* topic前缀 * topic前缀
*/ */
public static final String PULSARMQ_TOPIC_TENANT_PREFIX = ROOT + "." + "topicTenantPrefix"; public static final String PULSARMQ_TOPIC_TENANT_PREFIX = ROOT + "." + "topicTenantPrefix";
/**** 消费者 *****/ /**** 消费者 *****/
/** /**
@@ -32,31 +31,34 @@ public class PulsarMQConstants {
/** /**
* 批量处理超时时间 * 批量处理超时时间
*/ */
public static final String PULSARMQ_BATCH_PROCESS_TIMEOUT = ROOT + "." + "batchProcessTimeout"; public static final String PULSARMQ_BATCH_PROCESS_TIMEOUT = ROOT + "." + "batchProcessTimeout";
/** /**
* 消费都订阅名称,将以该名称为消费者身份标识,同一个subscriptName,认为是同一个消费实例 * 消费都订阅名称,将以该名称为消费者身份标识,同一个subscriptName,认为是同一个消费实例
*/ */
public static final String PULSARMQ_SUBSCRIPT_NAME = ROOT + "." + "subscriptName"; public static final String PULSARMQ_SUBSCRIPT_NAME = ROOT + "." + "subscriptName";
/** /**
* 重试间隔秒数 * 重试间隔秒数
*/ */
public static final String PULSARMQ_REDELIVERY_DELAY_SECONDS = ROOT + "." + "redeliveryDelaySeconds"; public static final String PULSARMQ_REDELIVERY_DELAY_SECONDS = ROOT + "." + "redeliveryDelaySeconds";
/** /**
* ACK超时秒数 * ACK超时秒数
*/ */
public static final String PULSARMQ_ACK_TIMEOUT_SECONDS = ROOT + "." + "ackTimeoutSeconds"; public static final String PULSARMQ_ACK_TIMEOUT_SECONDS = ROOT + "." + "ackTimeoutSeconds";
/** /**
* 是否开启消费重试 * 是否开启消费重试
*/ */
public static final String PULSARMQ_IS_RETRY = ROOT + "." + "isRetry"; public static final String PULSARMQ_IS_RETRY = ROOT + "." + "isRetry";
/** /**
* 自动生成的 retry dlq队列名称后缀是否大写 * 自动生成的 retry dlq队列名称后缀是否大写
*/ */
public static final String PULSARMQ_IS_RETRY_DLQ_UPPERCASE = ROOT + "." + "isRetryDLQUpperCase"; public static final String PULSARMQ_IS_RETRY_DLQ_UPPERCASE = ROOT + "." + "isRetryDLQUpperCase";
/** /**
* 最大重试次数 * 最大重试次数
*/ */
public static final String PULSARMQ_MAX_REDELIVERY_COUNT = ROOT + "." + "maxRedeliveryCount"; public static final String PULSARMQ_MAX_REDELIVERY_COUNT = ROOT + "." + "maxRedeliveryCount";
/**
* Pulsar admin服务器地址
*/
public static final String PULSARMQ_ADMIN_SERVER_URL = ROOT + "." + "adminServerUrl";
} }

View File

@@ -4,16 +4,18 @@ import com.alibaba.otter.canal.connector.core.config.MQProperties;
/** /**
* Pulsar生产者配置 * Pulsar生产者配置
*
* @author chad * @author chad
* @date 2021/9/15 11:23 * @date 2021/9/15 11:23
* @since 1 by chad at 2021/9/15 新增 * @since 1 by chad at 2021/9/15 新增
*/ */
public class PulsarMQProducerConfig extends MQProperties { public class PulsarMQProducerConfig extends MQProperties {
/** /**
* pulsar服务连接地址 * pulsar服务连接地址
* <p> * <p>
* broker: pulsar://localhost:6650<br/> * broker: pulsar://localhost:6650<br/>
* httpUrl: http://localhost:8080 * httpUrl: http://localhost:8080
* </p> * </p>
*/ */
private String serverUrl; private String serverUrl;
@@ -30,6 +32,10 @@ public class PulsarMQProducerConfig extends MQProperties {
* 生产者角色权限,请确保该角色有canal使用的所有topic生产者权限(最低要求) * 生产者角色权限,请确保该角色有canal使用的所有topic生产者权限(最低要求)
*/ */
private String roleToken; private String roleToken;
/**
* admin服务器地址
*/
private String adminServerUrl;
public String getServerUrl() { public String getServerUrl() {
return serverUrl; return serverUrl;
@@ -54,4 +60,12 @@ public class PulsarMQProducerConfig extends MQProperties {
public void setTopicTenantPrefix(String topicTenantPrefix) { public void setTopicTenantPrefix(String topicTenantPrefix) {
this.topicTenantPrefix = topicTenantPrefix; this.topicTenantPrefix = topicTenantPrefix;
} }
public String getAdminServerUrl() {
return adminServerUrl;
}
public void setAdminServerUrl(String adminServerUrl) {
this.adminServerUrl = adminServerUrl;
}
} }

View File

@@ -1,6 +1,13 @@
package com.alibaba.otter.canal.connector.pulsarmq.consumer; package com.alibaba.otter.canal.connector.pulsarmq.consumer;
import com.alibaba.fastjson.JSON; import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.api.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.common.utils.MQUtil; import com.alibaba.otter.canal.common.utils.MQUtil;
import com.alibaba.otter.canal.connector.core.config.CanalConstants; import com.alibaba.otter.canal.connector.core.config.CanalConstants;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage; import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
@@ -12,15 +19,6 @@ import com.alibaba.otter.canal.connector.pulsarmq.config.PulsarMQConstants;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException; import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.api.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** /**
* Pulsar consumer SPI 实现 * Pulsar consumer SPI 实现
@@ -30,19 +28,20 @@ import java.util.concurrent.TimeUnit;
*/ */
@SPI("pulsarmq") @SPI("pulsarmq")
public class CanalPulsarMQConsumer implements CanalMsgConsumer { public class CanalPulsarMQConsumer implements CanalMsgConsumer {
/** /**
* 连接pulsar客户端 * 连接pulsar客户端
*/ */
private PulsarClient pulsarClient; private PulsarClient pulsarClient;
private Consumer<byte[]> pulsarMQConsumer; private Consumer<byte[]> pulsarMQConsumer;
/** /**
* 是否为扁平消息 * 是否为扁平消息
*/ */
private boolean flatMessage = false; private boolean flatMessage = false;
/** /**
* 主题名称 * 主题名称
*/ */
private String topic; private String topic;
/** /**
* 单线程控制 * 单线程控制
*/ */
@@ -50,58 +49,57 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
/** /**
* 环境连接URL * 环境连接URL
*/ */
private String serviceUrl; private String serviceUrl;
/** /**
* 角色认证token * 角色认证token
*/ */
private String roleToken; private String roleToken;
/** /**
* 订阅客户端名称 * 订阅客户端名称
*/ */
private String subscriptName; private String subscriptName;
/** /**
* 每次批量获取数据的最大条目数,默认30 * 每次批量获取数据的最大条目数,默认30
*/ */
private int batchSize = 30; private int batchSize = 30;
/** /**
* 与{@code batchSize}一起决定批量获取的数据大小 * 与{@code batchSize}一起决定批量获取的数据大小 当:
* 当:
* <p> * <p>
* 1. {@code batchSize} 条消息未消费时<br/> * 1. {@code batchSize} 条消息未消费时<br/>
* 2. 距上一次批量消费时间达到{@code batchTimeoutSeconds}秒时 * 2. 距上一次批量消费时间达到{@code batchTimeoutSeconds}秒时
* </p> * </p>
* 任一条件满足,即执行批量消费 * 任一条件满足,即执行批量消费
*/ */
private int getBatchTimeoutSeconds = 30; private int getBatchTimeoutSeconds = 30;
/** /**
* 批量处理消息时,一次批量处理的超时时间 * 批量处理消息时,一次批量处理的超时时间
* <p> * <p>
* 该时间应该根据{@code batchSize}和{@code batchTimeoutSeconds}合理设置 * 该时间应该根据{@code batchSize}和{@code batchTimeoutSeconds}合理设置
* </p> * </p>
*/ */
private long batchProcessTimeout = 60 * 1000; private long batchProcessTimeout = 60 * 1000;
/** /**
* 消费失败后的重试秒数,默认60秒 * 消费失败后的重试秒数,默认60秒
*/ */
private int redeliveryDelaySeconds = 60; private int redeliveryDelaySeconds = 60;
/** /**
* 当客户端接收到消息,30秒还没有返回ack给服务端时,ack超时,会重新消费该消息 * 当客户端接收到消息,30秒还没有返回ack给服务端时,ack超时,会重新消费该消息
*/ */
private int ackTimeoutSeconds = 30; private int ackTimeoutSeconds = 30;
/** /**
* 是否开启消息失败重试功能,默认开启 * 是否开启消息失败重试功能,默认开启
*/ */
private boolean isRetry = true; private boolean isRetry = true;
/** /**
* <p> * <p>
* true重试(-RETRY)和死信队列(-DLQ)后缀为大写,有些地方创建的为小写,需确保正确 * true重试(-RETRY)和死信队列(-DLQ)后缀为大写,有些地方创建的为小写,需确保正确
* </p> * </p>
*/ */
private boolean isRetryDLQUpperCase = false; private boolean isRetryDLQUpperCase = false;
/** /**
* 最大重试次数 * 最大重试次数
*/ */
private int maxRedeliveryCount = 128; private int maxRedeliveryCount = 128;
@Override @Override
public void init(Properties properties, String topic, String groupId) { public void init(Properties properties, String topic, String groupId) {
@@ -113,6 +111,11 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
this.serviceUrl = properties.getProperty(PulsarMQConstants.PULSARMQ_SERVER_URL); this.serviceUrl = properties.getProperty(PulsarMQConstants.PULSARMQ_SERVER_URL);
this.roleToken = properties.getProperty(PulsarMQConstants.PULSARMQ_ROLE_TOKEN); this.roleToken = properties.getProperty(PulsarMQConstants.PULSARMQ_ROLE_TOKEN);
this.subscriptName = properties.getProperty(PulsarMQConstants.PULSARMQ_SUBSCRIPT_NAME); this.subscriptName = properties.getProperty(PulsarMQConstants.PULSARMQ_SUBSCRIPT_NAME);
// 采用groupId作为subscriptName,避免所有的都是同一个订阅者名称
if (StringUtils.isEmpty(this.subscriptName)) {
this.subscriptName = groupId;
}
if (StringUtils.isEmpty(this.subscriptName)) { if (StringUtils.isEmpty(this.subscriptName)) {
throw new RuntimeException("Pulsar Consumer subscriptName required"); throw new RuntimeException("Pulsar Consumer subscriptName required");
} }
@@ -157,10 +160,12 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
} }
// 连接创建客户端 // 连接创建客户端
try { try {
pulsarClient = PulsarClient.builder() // AuthenticationDataProvider
.serviceUrl(serviceUrl) ClientBuilder builder = PulsarClient.builder().serviceUrl(serviceUrl);
.authentication(AuthenticationFactory.token(roleToken)) if (StringUtils.isNotEmpty(roleToken)) {
.build(); builder.authentication(AuthenticationFactory.token(roleToken));
}
pulsarClient = builder.build();
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -176,15 +181,14 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
builder.subscriptionType(SubscriptionType.Failover); builder.subscriptionType(SubscriptionType.Failover);
builder builder
// 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息 // 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息
// 在指定的时间进行重新消费,默认是1分钟。 // 在指定的时间进行重新消费,默认是1分钟。
.negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS) .negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS)
.subscriptionName(this.subscriptName) .subscriptionName(this.subscriptName);
;
if (this.isRetry) { if (this.isRetry) {
DeadLetterPolicy.DeadLetterPolicyBuilder dlqBuilder = DeadLetterPolicy.builder() DeadLetterPolicy.DeadLetterPolicyBuilder dlqBuilder = DeadLetterPolicy.builder()
// 最大重试次数 // 最大重试次数
.maxRedeliverCount(this.maxRedeliveryCount); .maxRedeliverCount(this.maxRedeliveryCount);
// 指定重试队列,不是多个或通配符topic才能判断重试队列 // 指定重试队列,不是多个或通配符topic才能判断重试队列
if (!MQUtil.isPatternTag(this.topic)) { if (!MQUtil.isPatternTag(this.topic)) {
String retryTopic = this.topic + (this.isRetryDLQUpperCase ? "-RETRY" : "-retry"); String retryTopic = this.topic + (this.isRetryDLQUpperCase ? "-RETRY" : "-retry");
@@ -193,19 +197,17 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
dlqBuilder.deadLetterTopic(dlqTopic); dlqBuilder.deadLetterTopic(dlqTopic);
} }
//默认关闭,如果需要重试则开启 // 默认关闭,如果需要重试则开启
builder.enableRetry(true) builder.enableRetry(true).deadLetterPolicy(dlqBuilder.build());
.deadLetterPolicy(dlqBuilder.build());
} }
// ack超时 // ack超时
builder.ackTimeout(this.ackTimeoutSeconds, TimeUnit.SECONDS); builder.ackTimeout(this.ackTimeoutSeconds, TimeUnit.SECONDS);
// pulsar批量获取消息设置 // pulsar批量获取消息设置
builder.batchReceivePolicy(new BatchReceivePolicy.Builder() builder.batchReceivePolicy(new BatchReceivePolicy.Builder().maxNumMessages(this.batchSize)
.maxNumMessages(this.batchSize) .timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS)
.timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS) .build());
.build());
try { try {
this.pulsarMQConsumer = builder.subscribe(); this.pulsarMQConsumer = builder.subscribe();
@@ -220,7 +222,6 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
List<CommonMessage> messageList = Lists.newArrayList(); List<CommonMessage> messageList = Lists.newArrayList();
try { try {
Messages<byte[]> messages = pulsarMQConsumer.batchReceive(); Messages<byte[]> messages = pulsarMQConsumer.batchReceive();
if (null == messages || messages.size() == 0) { if (null == messages || messages.size() == 0) {
return messageList; return messageList;
} }
@@ -278,7 +279,8 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
return; return;
} }
try { try {
this.pulsarMQConsumer.unsubscribe(); // 会导致暂停期间数据丢失
// this.pulsarMQConsumer.unsubscribe();
this.pulsarClient.close(); this.pulsarClient.close();
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new CanalClientException("Disconnect pulsar consumer error", e); throw new CanalClientException("Disconnect pulsar consumer error", e);

View File

@@ -1,7 +1,19 @@
package com.alibaba.otter.canal.connector.pulsarmq.producer; package com.alibaba.otter.canal.connector.pulsarmq.producer;
import com.alibaba.fastjson.JSON; import java.util.*;
import com.alibaba.fastjson.serializer.SerializerFeature; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.admin.PulsarAdmin;
import org.apache.pulsar.client.admin.PulsarAdminException;
import org.apache.pulsar.client.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate; import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.common.utils.NamedThreadFactory; import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
import com.alibaba.otter.canal.common.utils.PropertiesUtils; import com.alibaba.otter.canal.common.utils.PropertiesUtils;
@@ -16,17 +28,6 @@ import com.alibaba.otter.canal.connector.pulsarmq.config.PulsarMQConstants;
import com.alibaba.otter.canal.connector.pulsarmq.config.PulsarMQProducerConfig; import com.alibaba.otter.canal.connector.pulsarmq.config.PulsarMQProducerConfig;
import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.api.*;
import org.apache.pulsar.shade.com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* PulsarMQ Producer SPI 实现 * PulsarMQ Producer SPI 实现
@@ -37,19 +38,22 @@ import java.util.stream.Collectors;
@SPI("pulsarmq") @SPI("pulsarmq")
public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQProducer { public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQProducer {
private static final Logger logger = LoggerFactory.getLogger(CanalPulsarMQProducer.class);
private static final Map<String, Producer<byte[]>> PRODUCERS = new HashMap<>();
protected ThreadPoolExecutor sendPartitionExecutor;
/** /**
* 消息体分区属性名称 * 消息体分区属性名称
*/ */
public static final String MSG_PROPERTY_PARTITION_NAME = "partitionNum"; public static final String MSG_PROPERTY_PARTITION_NAME = "partitionNum";
private static final Logger logger = LoggerFactory
.getLogger(CanalPulsarMQProducer.class);
private static final Map<String, Producer<byte[]>> PRODUCERS = new HashMap<>();
protected ThreadPoolExecutor sendPartitionExecutor;
/** /**
* pulsar客户端,管理连接 * pulsar客户端,管理连接
*/ */
protected PulsarClient client; protected PulsarClient client;
/**
* Pulsar admin 客户端
*/
protected PulsarAdmin pulsarAdmin;
@Override @Override
public void init(Properties properties) { public void init(Properties properties) {
@@ -61,25 +65,36 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
// 初始化连接客户端 // 初始化连接客户端
try { try {
client = PulsarClient.builder() ClientBuilder builder = PulsarClient.builder()
// 填写pulsar的连接地址 // 填写pulsar的连接地址
.serviceUrl(pulsarMQProducerConfig.getServerUrl()) .serviceUrl(pulsarMQProducerConfig.getServerUrl());
// 角色权限认证的token if (StringUtils.isNotEmpty(pulsarMQProducerConfig.getRoleToken())) {
.authentication(AuthenticationFactory.token(pulsarMQProducerConfig.getRoleToken())) // 角色权限认证的token
.build(); builder.authentication(AuthenticationFactory.token(pulsarMQProducerConfig.getRoleToken()));
}
client = builder.build();
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
// 加载所有生产者 --> topic可能有正则或表名,无法确认所有topic,在使用时再加载
// 初始化Pulsar admin
if (StringUtils.isNotEmpty(pulsarMQProducerConfig.getAdminServerUrl())) {
try {
pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsarMQProducerConfig.getAdminServerUrl()).build();
} catch (PulsarClientException e) {
throw new RuntimeException(e);
}
}
// 加载所有生产者 --> topic可能有正则或表名,无法确认所有topic,在使用时再加载
int parallelPartitionSendThreadSize = mqProperties.getParallelSendThreadSize(); int parallelPartitionSendThreadSize = mqProperties.getParallelSendThreadSize();
sendPartitionExecutor = new ThreadPoolExecutor(parallelPartitionSendThreadSize, sendPartitionExecutor = new ThreadPoolExecutor(parallelPartitionSendThreadSize,
parallelPartitionSendThreadSize, parallelPartitionSendThreadSize,
0, 0,
TimeUnit.SECONDS, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(parallelPartitionSendThreadSize * 2), new ArrayBlockingQueue<>(parallelPartitionSendThreadSize * 2),
new NamedThreadFactory("MQ-Parallel-Sender-Partition"), new NamedThreadFactory("MQ-Parallel-Sender-Partition"),
new ThreadPoolExecutor.CallerRunsPolicy()); new ThreadPoolExecutor.CallerRunsPolicy());
} }
/** /**
@@ -102,10 +117,15 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
if (!StringUtils.isEmpty(roleToken)) { if (!StringUtils.isEmpty(roleToken)) {
tmpProperties.setRoleToken(roleToken); tmpProperties.setRoleToken(roleToken);
} }
String topicTenantPrefix = PropertiesUtils.getProperty(properties, PulsarMQConstants.PULSARMQ_TOPIC_TENANT_PREFIX); String topicTenantPrefix = PropertiesUtils.getProperty(properties,
PulsarMQConstants.PULSARMQ_TOPIC_TENANT_PREFIX);
if (!StringUtils.isEmpty(topicTenantPrefix)) { if (!StringUtils.isEmpty(topicTenantPrefix)) {
tmpProperties.setTopicTenantPrefix(topicTenantPrefix); tmpProperties.setTopicTenantPrefix(topicTenantPrefix);
} }
String adminServerUrl = PropertiesUtils.getProperty(properties, PulsarMQConstants.PULSARMQ_ADMIN_SERVER_URL);
if (!StringUtils.isEmpty(adminServerUrl)) {
tmpProperties.setAdminServerUrl(adminServerUrl);
}
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Load pulsar properties ==> {}", JSON.toJSON(this.mqProperties)); logger.debug("Load pulsar properties ==> {}", JSON.toJSON(this.mqProperties));
} }
@@ -119,8 +139,8 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
* </p> * </p>
* *
* @param destination 消息目标信息 * @param destination 消息目标信息
* @param message 消息 * @param message 消息
* @param callback 消息发送结果回调 * @param callback 消息发送结果回调
* @return void * @return void
* @date 2021/9/2 22:01 * @date 2021/9/2 22:01
* @author chad * @author chad
@@ -133,9 +153,8 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
try { try {
if (!StringUtils.isEmpty(destination.getDynamicTopic())) { if (!StringUtils.isEmpty(destination.getDynamicTopic())) {
// 动态topic // 动态topic
Map<String, com.alibaba.otter.canal.protocol.Message> messageMap = MQMessageUtils.messageTopics(message, Map<String, com.alibaba.otter.canal.protocol.Message> messageMap = MQMessageUtils
destination.getTopic(), .messageTopics(message, destination.getTopic(), destination.getDynamicTopic());
destination.getDynamicTopic());
for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) { for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) {
String topicName = entry.getKey().replace('.', '_'); String topicName = entry.getKey().replace('.', '_');
@@ -174,14 +193,20 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
* @author chad * @author chad
* @since 1.0.0 by chad at 2021/9/2: 新增 * @since 1.0.0 by chad at 2021/9/2: 新增
*/ */
public void send(final MQDestination destination, String topicName, com.alibaba.otter.canal.protocol.Message message) { public void send(final MQDestination destination, String topicName,
com.alibaba.otter.canal.protocol.Message message) {
// 获取当前topic的分区数 // 获取当前topic的分区数
Integer partitionNum = MQMessageUtils.parseDynamicTopicPartition(topicName, Integer partitionNum = MQMessageUtils.parseDynamicTopicPartition(topicName,
destination.getDynamicTopicPartitionNum()); destination.getDynamicTopicPartitionNum());
if (partitionNum == null) { if (partitionNum == null) {
partitionNum = destination.getPartitionsNum(); partitionNum = destination.getPartitionsNum();
} }
// 创建多分区topic
if (pulsarAdmin != null && partitionNum != null && partitionNum > 0 && PRODUCERS.get(topicName) == null) {
createMultipleTopic(topicName, partitionNum);
}
ExecutorTemplate template = new ExecutorTemplate(sendPartitionExecutor); ExecutorTemplate template = new ExecutorTemplate(sendPartitionExecutor);
// 并发构造 // 并发构造
MQMessageUtils.EntryRowData[] datas = MQMessageUtils.buildMessageData(message, buildExecutor); MQMessageUtils.EntryRowData[] datas = MQMessageUtils.buildMessageData(message, buildExecutor);
@@ -195,10 +220,10 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
} }
// 串行分区 // 串行分区
com.alibaba.otter.canal.protocol.Message[] messages = MQMessageUtils.messagePartition(datas, com.alibaba.otter.canal.protocol.Message[] messages = MQMessageUtils.messagePartition(datas,
message.getId(), message.getId(),
partitionNum, partitionNum,
destination.getPartitionHash(), destination.getPartitionHash(),
mqProperties.isDatabaseHash()); mqProperties.isDatabaseHash());
// 发送 // 发送
int len = messages.length; int len = messages.length;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@@ -228,9 +253,9 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
for (FlatMessage flatMessage : flatMessages) { for (FlatMessage flatMessage : flatMessages) {
FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage, FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage,
partitionNum, partitionNum,
destination.getPartitionHash(), destination.getPartitionHash(),
mqProperties.isDatabaseHash()); mqProperties.isDatabaseHash());
int length = partitionFlatMessage.length; int length = partitionFlatMessage.length;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
// 增加null判断,issue #3267 // 增加null判断,issue #3267
@@ -264,9 +289,9 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
/** /**
* 发送原始消息,需要做分区处理 * 发送原始消息,需要做分区处理
* *
* @param topic topic * @param topic topic
* @param partitionNum 目标分区 * @param partitionNum 目标分区
* @param msg 原始消息内容 * @param msg 原始消息内容
* @return void * @return void
* @date 2021/9/10 17:55 * @date 2021/9/10 17:55
* @author chad * @author chad
@@ -277,8 +302,9 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
byte[] msgBytes = CanalMessageSerializerUtil.serializer(msg, mqProperties.isFilterTransactionEntry()); byte[] msgBytes = CanalMessageSerializerUtil.serializer(msg, mqProperties.isFilterTransactionEntry());
try { try {
MessageId msgResultId = producer.newMessage() MessageId msgResultId = producer.newMessage()
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partitionNum)) .property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partitionNum))
.value(msgBytes).send(); .value(msgBytes)
.send();
// todo 判断发送结果 // todo 判断发送结果
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Send Message to topic:{} Result: {}", topic, msgResultId); logger.debug("Send Message to topic:{} Result: {}", topic, msgResultId);
@@ -291,7 +317,7 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
/** /**
* 发送扁平消息 * 发送扁平消息
* *
* @param topic topic主题 * @param topic topic主题
* @param flatMessages 扁平消息 * @param flatMessages 扁平消息
* @return void * @return void
* @date 2021/9/10 18:22 * @date 2021/9/10 18:22
@@ -302,13 +328,12 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
Producer<byte[]> producer = getProducer(topic); Producer<byte[]> producer = getProducer(topic);
for (FlatMessage f : flatMessages) { for (FlatMessage f : flatMessages) {
try { try {
MessageId msgResultId = producer MessageId msgResultId = producer.newMessage()
.newMessage() .property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition)) .value(JSON.toJSONBytes(f, Feature.WriteNulls))
.value(JSON.toJSONBytes(f, SerializerFeature.WriteMapNullValue)) .send()
.send() //
// ;
;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Send Messages to topic:{} Result: {}", topic, msgResultId); logger.debug("Send Messages to topic:{} Result: {}", topic, msgResultId);
} }
@@ -319,22 +344,44 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
} }
/** /**
* 获取指定topic的生产者,并且使用缓存 * 创建多分区topic
* *
* @param topic * @param topic
* @return org.apache.pulsar.client.api.Producer<byte [ ]> * @param partitionNum
* @date 2021/9/10 11:21 */
* @author chad private void createMultipleTopic(String topic, Integer partitionNum) {
* @since 1 by chad at 2021/9/10 新增 // 拼接topic前缀
PulsarMQProducerConfig pulsarMQProperties = (PulsarMQProducerConfig) this.mqProperties;
String prefix = pulsarMQProperties.getTopicTenantPrefix();
String fullTopic = topic;
if (!StringUtils.isEmpty(prefix)) {
if (!prefix.endsWith("/")) {
fullTopic = "/" + fullTopic;
}
fullTopic = pulsarMQProperties.getTopicTenantPrefix() + fullTopic;
}
// 创建分区topic
try {
pulsarAdmin.topics().createPartitionedTopic(fullTopic, partitionNum);
} catch (PulsarAdminException e) {
// TODO 无论是否报错,都继续后续的操作,此处不进行阻塞
}
}
/**
* 获取topic
*
* @param topic
* @return
*/ */
private Producer<byte[]> getProducer(String topic) { private Producer<byte[]> getProducer(String topic) {
Producer producer = PRODUCERS.get(topic); Producer producer = PRODUCERS.get(topic);
if (null == producer || !producer.isConnected()) {
if (null == producer) {
try { try {
synchronized (PRODUCERS) { synchronized (PRODUCERS) {
producer = PRODUCERS.get(topic); producer = PRODUCERS.get(topic);
if (null != producer) { if (null != producer && producer.isConnected()) {
return producer; return producer;
} }
@@ -351,10 +398,10 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
// 创建指定topic的生产者 // 创建指定topic的生产者
producer = client.newProducer() producer = client.newProducer()
.topic(fullTopic) .topic(fullTopic)
// 指定路由器 // 指定路由器
.messageRouter(new MessageRouterImpl(topic)) .messageRouter(new MessageRouterImpl(topic))
.create(); .create();
// 放入缓存 // 放入缓存
PRODUCERS.put(topic, producer); PRODUCERS.put(topic, producer);
} }
@@ -367,6 +414,26 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
return producer; return producer;
} }
@Override
public void stop() {
logger.info("## Stop PulsarMQ producer##");
for (Producer p : PRODUCERS.values()) {
try {
if (null != p && p.isConnected()) {
p.close();
}
} catch (PulsarClientException e) {
logger.warn("close producer name: {}, topic: {}, error: {}",
p.getProducerName(),
p.getTopic(),
e.getMessage());
}
}
super.stop();
}
/** /**
* Pulsar自定义路由策略 * Pulsar自定义路由策略
* *
@@ -376,9 +443,10 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
* @since 2 by chad at 2021/9/17 修改为msg自带目标分区 * @since 2 by chad at 2021/9/17 修改为msg自带目标分区
*/ */
private static class MessageRouterImpl implements MessageRouter { private static class MessageRouterImpl implements MessageRouter {
private String topicLocal; private String topicLocal;
public MessageRouterImpl(String topicLocal) { public MessageRouterImpl(String topicLocal){
this.topicLocal = topicLocal; this.topicLocal = topicLocal;
} }
@@ -390,7 +458,8 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
try { try {
partition = Integer.parseInt(partitionStr); partition = Integer.parseInt(partitionStr);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
logger.warn("Parse msg {} property failed for value: {}", MSG_PROPERTY_PARTITION_NAME, partitionStr); logger
.warn("Parse msg {} property failed for value: {}", MSG_PROPERTY_PARTITION_NAME, partitionStr);
} }
} }
// topic创建时设置的分区数 // topic创建时设置的分区数
@@ -402,21 +471,4 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
return partition; return partition;
} }
} }
@Override
public void stop() {
logger.info("## Stop RocketMQ producer##");
for (Producer p : PRODUCERS.values()) {
try {
if (null != p && p.isConnected()) {
p.close();
}
} catch (PulsarClientException e) {
logger.warn("close producer name: {}, topic: {}, error: {}", p.getProducerName(), p.getTopic(), e.getMessage());
}
}
super.stop();
}
} }

View File

@@ -0,0 +1 @@
pulsarmq=com.alibaba.otter.canal.connector.pulsarmq.consumer.CanalPulsarMQConsumer

View File

@@ -1,23 +1,19 @@
package com.alibaba.otter.canal.connector.pulsarmq.consumer; package com.alibaba.otter.canal.connector.pulsarmq.consumer;
import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.connector.core.config.CanalConstants;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
import com.alibaba.otter.canal.connector.pulsarmq.config.PulsarMQConstants;
import org.apache.commons.lang.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*; import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.connector.core.config.CanalConstants;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
import com.alibaba.otter.canal.connector.pulsarmq.config.PulsarMQConstants;
/** /**
* @author chad * @author chad
@@ -26,7 +22,7 @@ import static org.junit.Assert.*;
*/ */
public class CanalPulsarMQConsumerTest { public class CanalPulsarMQConsumerTest {
private Properties properties; private Properties properties;
private CanalPulsarMQConsumer consumer; private CanalPulsarMQConsumer consumer;
@@ -73,4 +69,4 @@ public class CanalPulsarMQConsumerTest {
consumer.ack(); consumer.ack();
} }
} }
} }

View File

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

View File

@@ -13,7 +13,7 @@ import com.alibaba.otter.canal.common.utils.PropertiesUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.connector.core.config.CanalConstants; import com.alibaba.otter.canal.connector.core.config.CanalConstants;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage; import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer; import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;

View File

@@ -10,8 +10,8 @@ import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.otter.canal.common.CanalException; import com.alibaba.otter.canal.common.CanalException;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate; import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.common.utils.PropertiesUtils; import com.alibaba.otter.canal.common.utils.PropertiesUtils;
@@ -151,7 +151,7 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
// 串行分区 // 串行分区
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId()); List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
for (FlatMessage flatMessage : flatMessages) { for (FlatMessage flatMessage : flatMessages) {
byte[] message = JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue); byte[] message = JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination()); logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
} }

View File

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

View File

@@ -21,7 +21,7 @@ import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.connector.core.config.CanalConstants; import com.alibaba.otter.canal.connector.core.config.CanalConstants;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage; import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer; import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;

View File

@@ -1,7 +1,7 @@
package com.alibaba.otter.canal.connector.rocketmq.producer; package com.alibaba.otter.canal.connector.rocketmq.producer;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.otter.canal.common.CanalException; import com.alibaba.otter.canal.common.CanalException;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate; import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.common.utils.NamedThreadFactory; import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
@@ -263,7 +263,7 @@ import java.util.stream.Collectors;
List<Message> messages = flatMessagePart.stream() List<Message> messages = flatMessagePart.stream()
.map(flatMessage -> new Message(topicName, .map(flatMessage -> new Message(topicName,
((RocketMQProducerConfig) this.mqProperties).getTag(), ((RocketMQProducerConfig) this.mqProperties).getTag(),
JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue))) JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls)))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 批量发送 // 批量发送
sendMessage(messages, index); sendMessage(messages, index);
@@ -278,7 +278,7 @@ import java.util.stream.Collectors;
List<Message> messages = flatMessages.stream() List<Message> messages = flatMessages.stream()
.map(flatMessage -> new Message(topicName, .map(flatMessage -> new Message(topicName,
((RocketMQProducerConfig) this.mqProperties).getTag(), ((RocketMQProducerConfig) this.mqProperties).getTag(),
JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue))) JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls)))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 批量发送 // 批量发送
sendMessage(messages, partition); sendMessage(messages, partition);

View File

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

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.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,42 +3,11 @@ package com.taobao.tddl.dbsync.binlog;
import java.io.IOException; import java.io.IOException;
import java.util.BitSet; import java.util.BitSet;
import com.taobao.tddl.dbsync.binlog.event.*;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent;
import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent;
import com.taobao.tddl.dbsync.binlog.event.DeleteFileLogEvent;
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.ExecuteLoadLogEvent;
import com.taobao.tddl.dbsync.binlog.event.ExecuteLoadQueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.HeartbeatLogEvent;
import com.taobao.tddl.dbsync.binlog.event.IgnorableLogEvent;
import com.taobao.tddl.dbsync.binlog.event.IncidentLogEvent;
import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent;
import com.taobao.tddl.dbsync.binlog.event.LoadLogEvent;
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
import com.taobao.tddl.dbsync.binlog.event.PreviousGtidsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RandLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.StartLogEventV3;
import com.taobao.tddl.dbsync.binlog.event.StopLogEvent;
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
import com.taobao.tddl.dbsync.binlog.event.TransactionContextLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
import com.taobao.tddl.dbsync.binlog.event.ViewChangeEvent;
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.XaPrepareLogEvent;
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
@@ -386,6 +355,12 @@ public final class LogDecoder {
logPosition.position = header.getLogPos(); logPosition.position = header.getLogPos();
return event; return event;
} }
case LogEvent.TRANSACTION_PAYLOAD_EVENT: {
TransactionPayloadLogEvent event = new TransactionPayloadLogEvent(header, buffer, descriptionEvent);
/* updating position in context */
logPosition.position = header.getLogPos();
return event;
}
case LogEvent.VIEW_CHANGE_EVENT: { case LogEvent.VIEW_CHANGE_EVENT: {
ViewChangeEvent event = new ViewChangeEvent(header, buffer, descriptionEvent); ViewChangeEvent event = new ViewChangeEvent(header, buffer, descriptionEvent);
/* updating position in context */ /* updating position in context */

View File

@@ -175,6 +175,11 @@ public abstract class LogEvent {
*/ */
public static final int PARTIAL_UPDATE_ROWS_EVENT = 39; public static final int PARTIAL_UPDATE_ROWS_EVENT = 39;
/* mysql 8.0.20 */
public static final int TRANSACTION_PAYLOAD_EVENT = 40;
public static final int MYSQL_ENUM_END_EVENT = 41;
// mariaDb 5.5.34 // mariaDb 5.5.34
/* New MySQL/Sun events are to be added right above this comment */ /* New MySQL/Sun events are to be added right above this comment */
public static final int MYSQL_EVENTS_END = 49; public static final int MYSQL_EVENTS_END = 49;
@@ -283,6 +288,9 @@ public abstract class LogEvent {
public static final int MYSQL_TYPE_TIMESTAMP2 = 17; public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
public static final int MYSQL_TYPE_DATETIME2 = 18; public static final int MYSQL_TYPE_DATETIME2 = 18;
public static final int MYSQL_TYPE_TIME2 = 19; public static final int MYSQL_TYPE_TIME2 = 19;
public static final int MYSQL_TYPE_TYPED_ARRAY = 20;
public static final int MYSQL_TYPE_INVALID = 243;
public static final int MYSQL_TYPE_BOOL = 244;
public static final int MYSQL_TYPE_JSON = 245; public static final int MYSQL_TYPE_JSON = 245;
public static final int MYSQL_TYPE_NEWDECIMAL = 246; public static final int MYSQL_TYPE_NEWDECIMAL = 246;
public static final int MYSQL_TYPE_ENUM = 247; public static final int MYSQL_TYPE_ENUM = 247;
@@ -369,8 +377,16 @@ public abstract class LogEvent {
return "Previous_gtids"; return "Previous_gtids";
case PARTIAL_UPDATE_ROWS_EVENT: case PARTIAL_UPDATE_ROWS_EVENT:
return "Update_rows_partial"; return "Update_rows_partial";
case TRANSACTION_CONTEXT_EVENT :
return "Transaction_context";
case VIEW_CHANGE_EVENT :
return "view_change";
case XA_PREPARE_LOG_EVENT :
return "Xa_prepare";
case TRANSACTION_PAYLOAD_EVENT :
return "transaction_payload";
default: default:
return "Unknown"; /* impossible */ return "Unknown type:" + type;
} }
} }

View File

@@ -733,7 +733,7 @@ public final class RowsLogBuffer {
String second = null; String second = null;
if (intpart == 0) { if (intpart == 0) {
second = "00:00:00"; second = frac < 0 ? "-00:00:00" : "00:00:00";
} else { } else {
// 目前只记录秒,不处理us frac // 目前只记录秒,不处理us frac
// if (cal == null) cal = Calendar.getInstance(); // if (cal == null) cal = Calendar.getInstance();
@@ -1133,6 +1133,8 @@ public final class RowsLogBuffer {
length = len; length = len;
break; break;
} }
case LogEvent.MYSQL_TYPE_BOOL :
case LogEvent.MYSQL_TYPE_INVALID :
default: default:
logger.error(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)", logger.error(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)",
type, type,

View File

@@ -1,11 +1,10 @@
package com.taobao.tddl.dbsync.binlog.event; package com.taobao.tddl.dbsync.binlog.event;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
/** /**
* In row-based mode, every row operation event is preceded by a * In row-based mode, every row operation event is preceded by a
@@ -343,12 +342,14 @@ public final class TableMapLogEvent extends LogEvent {
public int charset; // 可以通过CharsetUtil进行转化 public int charset; // 可以通过CharsetUtil进行转化
public int geoType; public int geoType;
public boolean nullable; public boolean nullable;
public boolean visibility;
public boolean array;
@Override @Override public String toString() {
public String toString() { return "ColumnInfo{" + "type=" + type + ", meta=" + meta + ", name='" + name + '\'' + ", unsigned="
return "ColumnInfo [type=" + type + ", meta=" + meta + ", name=" + name + ", unsigned=" + unsigned + unsigned + ", pk=" + pk + ", set_enum_values=" + set_enum_values + ", charset=" + charset
+ ", pk=" + pk + ", set_enum_values=" + set_enum_values + ", charset=" + charset + ", geoType=" + ", geoType=" + geoType + ", nullable=" + nullable + ", visibility=" + visibility + ", array="
+ geoType + ", nullable=" + nullable + "]"; + array + '}';
} }
} }
@@ -382,6 +383,12 @@ public final class TableMapLogEvent extends LogEvent {
public static final int SIMPLE_PRIMARY_KEY = 8; public static final int SIMPLE_PRIMARY_KEY = 8;
// Primary key with prefix // Primary key with prefix
public static final int PRIMARY_KEY_WITH_PREFIX = 9; public static final int PRIMARY_KEY_WITH_PREFIX = 9;
// Character set of enum and set columns, optimized to minimize space when many columns have the same charset.
public static final int ENUM_AND_SET_DEFAULT_CHARSET = 10;
// Character set of enum and set columns, optimized to minimize space when many columns have the same charset.
public static final int ENUM_AND_SET_COLUMN_CHARSET = 11;
// Flag to indicate column visibility attribute
public static final int COLUMN_VISIBILITY = 12;
private int default_charset; private int default_charset;
private boolean existOptionalMetaData = false; private boolean existOptionalMetaData = false;
@@ -484,6 +491,15 @@ public final class TableMapLogEvent extends LogEvent {
case PRIMARY_KEY_WITH_PREFIX: case PRIMARY_KEY_WITH_PREFIX:
parse_pk_with_prefix(buffer, len); parse_pk_with_prefix(buffer, len);
break; break;
case ENUM_AND_SET_DEFAULT_CHARSET:
parse_default_charset(buffer, len);
break;
case ENUM_AND_SET_COLUMN_CHARSET:
parse_column_charset(buffer, len);
break;
case COLUMN_VISIBILITY:
parse_column_visibility(buffer, len);
break;
default: default:
throw new IllegalArgumentException("unknow type : " + type); throw new IllegalArgumentException("unknow type : " + type);
} }
@@ -529,12 +545,16 @@ public final class TableMapLogEvent extends LogEvent {
*/ */
private final void decodeFields(LogBuffer buffer, final int len) { private final void decodeFields(LogBuffer buffer, final int len) {
final int limit = buffer.limit(); final int limit = buffer.limit();
buffer.limit(len + buffer.position()); buffer.limit(len + buffer.position());
for (int i = 0; i < columnCnt; i++) { for (int i = 0; i < columnCnt; i++) {
ColumnInfo info = columnInfo[i]; ColumnInfo info = columnInfo[i];
switch (info.type) { int binlogType = info.type;
if (binlogType == MYSQL_TYPE_TYPED_ARRAY) {
binlogType = buffer.getUint8();
}
switch (binlogType) {
case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_BLOB: case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_MEDIUM_BLOB:
@@ -542,6 +562,9 @@ public final class TableMapLogEvent extends LogEvent {
case MYSQL_TYPE_DOUBLE: case MYSQL_TYPE_DOUBLE:
case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_GEOMETRY: case MYSQL_TYPE_GEOMETRY:
case MYSQL_TYPE_TIME2:
case MYSQL_TYPE_DATETIME2:
case MYSQL_TYPE_TIMESTAMP2:
case MYSQL_TYPE_JSON: case MYSQL_TYPE_JSON:
/* /*
* These types store a single byte. * These types store a single byte.
@@ -550,14 +573,6 @@ public final class TableMapLogEvent extends LogEvent {
break; break;
case MYSQL_TYPE_SET: case MYSQL_TYPE_SET:
case MYSQL_TYPE_ENUM: case MYSQL_TYPE_ENUM:
/*
* log_event.h : MYSQL_TYPE_SET & MYSQL_TYPE_ENUM : This
* enumeration value is only used internally and cannot
* exist in a binlog.
*/
logger.warn("This enumeration value is only used internally "
+ "and cannot exist in a binlog: type=" + info.type);
break;
case MYSQL_TYPE_STRING: { case MYSQL_TYPE_STRING: {
/* /*
* log_event.h : The first byte is always * log_event.h : The first byte is always
@@ -585,12 +600,6 @@ public final class TableMapLogEvent extends LogEvent {
info.meta = x; info.meta = x;
break; break;
} }
case MYSQL_TYPE_TIME2:
case MYSQL_TYPE_DATETIME2:
case MYSQL_TYPE_TIMESTAMP2: {
info.meta = buffer.getUint8();
break;
}
default: default:
info.meta = 0; info.meta = 0;
break; break;
@@ -648,6 +657,20 @@ public final class TableMapLogEvent extends LogEvent {
return datas; return datas;
} }
private void parse_column_visibility(LogBuffer buffer, int length) {
List<Boolean> data = new ArrayList<>(columnInfo.length);
for (int i = 0; i < length; i++) {
int ut = buffer.getUint8();
for (int c = 0x80; c != 0; c >>= 1) {
data.add((ut & c) > 0);
}
}
for (int i = 0; i < columnCnt; i++) {
columnInfo[i].visibility = data.get(i);
}
}
private void parse_column_name(LogBuffer buffer, int length) { private void parse_column_name(LogBuffer buffer, int length) {
// stores column names extracted from field // stores column names extracted from field
int limit = buffer.position() + length; int limit = buffer.position() + length;

View File

@@ -0,0 +1,16 @@
package com.taobao.tddl.dbsync.binlog.event;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
/**
* @author agapple 2022年5月23日 下午7:05:39
* @version 1.1.6
* @since mysql 8.0.20
*/
public class TransactionPayloadLogEvent extends LogEvent {
public TransactionPayloadLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header);
}
}

View File

@@ -63,13 +63,13 @@ public class LogEventTest {
public void getTypeNameInputPositiveOutputNotNull4() { public void getTypeNameInputPositiveOutputNotNull4() {
// Arrange // Arrange
final int type = 36; final int type = 80;
// Act // Act
final String actual = LogEvent.getTypeName(type); final String actual = LogEvent.getTypeName(type);
// Assert result // Assert result
Assert.assertEquals("Unknown", actual); Assert.assertTrue(actual.startsWith("Unknown"));
} }
// Test written by Diffblue Cover. // Test written by Diffblue Cover.

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.1.6-SNAPSHOT</version> <version>1.1.6</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
@@ -64,6 +64,19 @@
<classifier>jar-with-dependencies</classifier> <classifier>jar-with-dependencies</classifier>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>connector.pulsarmq</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -79,11 +79,24 @@ in
exit;; exit;;
esac esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit` str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " #JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
# JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi fi
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"

View File

@@ -36,6 +36,9 @@ public class CanalLauncher {
logger.info("## set default uncaught exception handler"); logger.info("## set default uncaught exception handler");
setGlobalUncaughtExceptionHandler(); setGlobalUncaughtExceptionHandler();
// 支持rocketmq client 配置日志路径
System.setProperty("rocketmq.client.logUseSlf4j","true");
logger.info("## load canal configurations"); logger.info("## load canal configurations");
String conf = System.getProperty("canal.conf", "classpath:canal.properties"); String conf = System.getProperty("canal.conf", "classpath:canal.properties");
Properties properties = new Properties(); Properties properties = new Properties();
@@ -56,6 +59,10 @@ public class CanalLauncher {
CanalConstants.CANAL_ADMIN_AUTO_REGISTER)); CanalConstants.CANAL_ADMIN_AUTO_REGISTER));
String autoCluster = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_AUTO_CLUSTER); String autoCluster = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_AUTO_CLUSTER);
String name = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_REGISTER_NAME); String name = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_REGISTER_NAME);
if (StringUtils.isEmpty(name)) {
name = AddressUtils.getHostName();
}
String registerIp = CanalController.getProperty(properties, CanalConstants.CANAL_REGISTER_IP); String registerIp = CanalController.getProperty(properties, CanalConstants.CANAL_REGISTER_IP);
if (StringUtils.isEmpty(registerIp)) { if (StringUtils.isEmpty(registerIp)) {
registerIp = AddressUtils.getHostIp(); registerIp = AddressUtils.getHostIp();

View File

@@ -49,10 +49,11 @@ canal.instance.filter.black.regex=mysql\\.slave_.*
# mq config # mq config
canal.mq.topic=example canal.mq.topic=example
# dynamic topic route by schema or table regex # dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,mytest2\\..*,.*\\..* #canal.mq.dynamicTopic=mytest1.user,topic2:mytest2\\..*,.*\\..*
canal.mq.partition=0 canal.mq.partition=0
# hash partition config # hash partition config
#canal.mq.enableDynamicQueuePartition=false
#canal.mq.partitionsNum=3 #canal.mq.partitionsNum=3
#canal.mq.partitionHash=test.table:id^name,.*\\..*
#canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6 #canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6
#canal.mq.partitionHash=test.table:id^name,.*\\..*
################################################# #################################################

View File

@@ -1,96 +1,117 @@
<configuration scan="true" scanPeriod=" 5 seconds"> <configuration scan="true" scanPeriod=" 5 seconds">
<jmxConfigurator /> <jmxConfigurator />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern> </pattern>
</encoder> </encoder>
</appender> </appender>
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender"> <appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator> <discriminator>
<Key>destination</Key> <Key>destination</Key>
<DefaultValue>canal</DefaultValue> <DefaultValue>canal</DefaultValue>
</discriminator> </discriminator>
<sift> <sift>
<appender name="FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>../logs/${destination}/${destination}.log</File> <File>../logs/${destination}/${destination}.log</File>
<rollingPolicy <rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily --> <!-- rollover daily -->
<fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern> <fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB --> <!-- or whenever the file size reaches 100MB -->
<maxFileSize>512MB</maxFileSize> <maxFileSize>512MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy> </timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory> <maxHistory>60</maxHistory>
</rollingPolicy> </rollingPolicy>
<encoder> <encoder>
<pattern> <pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern> </pattern>
</encoder> </encoder>
</appender> </appender>
</sift> </sift>
</appender> </appender>
<appender name="CANAL-META" class="ch.qos.logback.classic.sift.SiftingAppender"> <appender name="CANAL-META" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator> <discriminator>
<Key>destination</Key> <Key>destination</Key>
<DefaultValue>canal</DefaultValue> <DefaultValue>canal</DefaultValue>
</discriminator> </discriminator>
<sift> <sift>
<appender name="META-FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="META-FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>../logs/${destination}/meta.log</File> <File>../logs/${destination}/meta.log</File>
<rollingPolicy <rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily --> <!-- rollover daily -->
<fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/meta-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern> <fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/meta-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB --> <!-- or whenever the file size reaches 100MB -->
<maxFileSize>32MB</maxFileSize> <maxFileSize>32MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy> </timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory> <maxHistory>60</maxHistory>
</rollingPolicy> </rollingPolicy>
<encoder> <encoder>
<pattern> <pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n %d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n
</pattern> </pattern>
</encoder> </encoder>
</appender> </appender>
</sift> </sift>
</appender> </appender>
<logger name="com.alibaba.otter.canal.instance" additivity="false"> <appender name="RocketmqClientAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<level value="INFO" /> <file>../logs/canal/rocketmq_client.log</file>
<appender-ref ref="CANAL-ROOT" /> <rollingPolicy
</logger> class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<logger name="com.alibaba.otter.canal.deployer" additivity="false"> <!-- rollover daily -->
<level value="INFO" /> <fileNamePattern>../logs/canal/%d{yyyy-MM-dd}/rocketmq_client-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<appender-ref ref="CANAL-ROOT" /> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
</logger> <!-- or whenever the file size reaches 100MB -->
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false"> <maxFileSize>512MB</maxFileSize>
<level value="INFO" /> </timeBasedFileNamingAndTriggeringPolicy>
<appender-ref ref="CANAL-META" /> <maxHistory>60</maxHistory>
</logger> </rollingPolicy>
<logger name="com.alibaba.otter.canal.connector.kafka" additivity="false"> <encoder charset="UTF-8">
<level value="INFO" /> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n</pattern>
<appender-ref ref="CANAL-ROOT" /> </encoder>
</logger> </appender>
<logger name="com.alibaba.otter.canal.connector.rocketmq" additivity="false">
<level value="INFO" /> <logger name="com.alibaba.otter.canal.instance" additivity="false">
<appender-ref ref="CANAL-ROOT" /> <level value="INFO" />
</logger> <appender-ref ref="CANAL-ROOT" />
<logger name="com.alibaba.otter.canal.connector.rabbitmq" additivity="false"> </logger>
<level value="INFO" /> <logger name="com.alibaba.otter.canal.deployer" additivity="false">
<appender-ref ref="CANAL-ROOT" /> <level value="INFO" />
</logger> <appender-ref ref="CANAL-ROOT" />
<logger name="com.alibaba.otter.canal.connector.pulsarmq" additivity="false"> </logger>
<level value="INFO" /> <logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
<appender-ref ref="CANAL-ROOT" /> <level value="INFO" />
</logger> <appender-ref ref="CANAL-META" />
<root level="WARN"> </logger>
<!-- <appender-ref ref="STDOUT"/> --> <logger name="com.alibaba.otter.canal.connector.kafka" additivity="false">
<appender-ref ref="CANAL-ROOT" /> <level value="INFO" />
</root> <appender-ref ref="CANAL-ROOT" />
</configuration> </logger>
<logger name="com.alibaba.otter.canal.connector.rocketmq" additivity="false">
<level value="INFO" />
<appender-ref ref="CANAL-ROOT" />
</logger>
<logger name="com.alibaba.otter.canal.connector.rabbitmq" additivity="false">
<level value="INFO" />
<appender-ref ref="CANAL-ROOT" />
</logger>
<logger name="RocketmqClient" additivity="false">
<level value="INFO" />
<appender-ref ref="RocketmqClientAppender" />
</logger>
<logger name="com.alibaba.otter.canal.connector.pulsarmq" additivity="false">
<level value="INFO" />
<appender-ref ref="CANAL-ROOT" />
</logger>
<root level="WARN">
<!-- <appender-ref ref="STDOUT"/> -->
<appender-ref ref="CANAL-ROOT" />
</root>
</configuration>

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