Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a96db39420 | ||
|
|
bac1d543f1 | ||
|
|
753fad6639 | ||
|
|
bc4dceea1a | ||
|
|
7aaee7b439 | ||
|
|
e19bf1ecff | ||
|
|
a99dc0cbe8 | ||
|
|
7b8ff04d20 | ||
|
|
7ee246ca5a | ||
|
|
54c645f315 | ||
|
|
6c81ab6fdc | ||
|
|
99aefaa118 | ||
|
|
e2248b3d35 | ||
|
|
eef1ee85fb | ||
|
|
7dfec19c68 | ||
|
|
05a52f1bd3 | ||
|
|
fb0f274ba8 | ||
|
|
d71a208123 | ||
|
|
656a1d23c2 | ||
|
|
a574b91fdf | ||
|
|
e41e903984 | ||
|
|
4a22d72ef4 | ||
|
|
b7eff723a3 | ||
|
|
9b9cfe4037 | ||
|
|
ee51f338e0 | ||
|
|
4315f203a2 | ||
|
|
b12945d4a9 | ||
|
|
885f05fb6a | ||
|
|
591126ee6b | ||
|
|
af432bf10a | ||
|
|
b532e90ff4 | ||
|
|
e48bc35f35 | ||
|
|
11a5f2a0fa | ||
|
|
c775478a56 | ||
|
|
bd1f91cd9c | ||
|
|
67e85d201c | ||
|
|
6965726dfe | ||
|
|
c0001afe8f | ||
|
|
977031a666 | ||
|
|
bd3224eea1 | ||
|
|
33733aef25 | ||
|
|
25be0259a4 | ||
|
|
8114d77a20 | ||
|
|
571e7f4e25 | ||
|
|
ead91433d7 | ||
|
|
bac016f89e | ||
|
|
8804647c30 | ||
|
|
2623d08b8c | ||
|
|
622fed84ce | ||
|
|
9f5e8fd1c4 | ||
|
|
88d472ef1e | ||
|
|
2987d08161 | ||
|
|
b54bea5e33 | ||
|
|
c42ba95eed |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.admin</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.admin</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>canal.admin-web</artifactId>
|
||||
|
||||
@@ -56,14 +56,26 @@ in
|
||||
exit;;
|
||||
esac
|
||||
|
||||
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
|
||||
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
|
||||
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
|
||||
|
||||
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"
|
||||
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 "cd to $current_path for continue"
|
||||
cd $current_path
|
||||
cd $current_path
|
||||
|
||||
+13
-1
@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.admin.connector;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.assertj.core.util.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -26,7 +27,8 @@ public class SimpleAdminConnectors {
|
||||
connector.connect();
|
||||
return function.apply(connector);
|
||||
} 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());
|
||||
} finally {
|
||||
connector.disconnect();
|
||||
@@ -34,4 +36,14 @@ public class SimpleAdminConnectors {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.otter.canal.admin.model.BaseModel;
|
||||
import com.alibaba.otter.canal.admin.model.CanalCluster;
|
||||
import com.alibaba.otter.canal.admin.model.NodeServer;
|
||||
|
||||
+5
@@ -21,6 +21,8 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
private static byte[] seeds = "canal is best!".getBytes();
|
||||
|
||||
private static final Integer PASSWORD_LENGTH = 6;
|
||||
|
||||
public User find4Login(String username, String password) {
|
||||
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
||||
return null;
|
||||
@@ -43,6 +45,9 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
|
||||
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();
|
||||
if (userTmp == null) {
|
||||
throw new ServiceException();
|
||||
|
||||
+17
-1
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal.admin</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>canal admin module for otter ${project.version}</name>
|
||||
<modules>
|
||||
@@ -18,6 +18,7 @@
|
||||
<java_source_version>1.8</java_source_version>
|
||||
<java_target_version>1.8</java_target_version>
|
||||
<file_encoding>UTF-8</file_encoding>
|
||||
<log4j_version>2.17.0</log4j_version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -91,6 +92,21 @@
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.41.1</version>
|
||||
</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>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -55,7 +55,7 @@ public class MappingConfigsLoader {
|
||||
}
|
||||
if (filePath.exists()) {
|
||||
String fileName = filePath.getName();
|
||||
if (!fileName.endsWith(".yml")) {
|
||||
if (!(fileName.endsWith(".yml") || fileName.endsWith(".yaml"))) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream in = new FileInputStream(filePath)) {
|
||||
|
||||
+2
@@ -30,6 +30,8 @@ public class Util {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Util.class);
|
||||
|
||||
public static final String AUTO_GENERATED_PREFIX = "AUTO_GENERATED_";
|
||||
|
||||
/**
|
||||
* 通过DS执行sql
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -27,22 +27,22 @@
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch</groupId>
|
||||
<artifactId>elasticsearch</artifactId>
|
||||
<version>6.4.3</version>
|
||||
<version>6.8.22</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>transport</artifactId>
|
||||
<version>6.4.3</version>
|
||||
<version>6.8.22</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-client</artifactId>
|
||||
<version>6.4.3</version>
|
||||
<version>6.8.22</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
<version>6.4.3</version>
|
||||
<version>6.8.22</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+6
-2
@@ -316,13 +316,17 @@ public class ES6xTemplate implements ESTemplate {
|
||||
}
|
||||
|
||||
@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) {
|
||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||
Object resultIdVal = null;
|
||||
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)) {
|
||||
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
|
||||
|
||||
+7
-1
@@ -150,7 +150,13 @@ public class ESConnection {
|
||||
logger.error(e.getMessage(), e);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+7
-4
@@ -287,8 +287,7 @@ public class ES7xTemplate implements ESTemplate {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData,
|
||||
Map<String, Object> esFieldData) {
|
||||
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData) {
|
||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||
Object resultIdVal = null;
|
||||
@@ -312,13 +311,17 @@ public class ES7xTemplate implements ESTemplate {
|
||||
}
|
||||
|
||||
@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) {
|
||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||
Object resultIdVal = null;
|
||||
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)) {
|
||||
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -22,6 +22,7 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+64
-45
@@ -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.service.ESSyncService;
|
||||
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.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.*;
|
||||
|
||||
/**
|
||||
* ES外部适配器
|
||||
@@ -42,6 +39,8 @@ public abstract class ESAdapter implements OuterAdapter {
|
||||
|
||||
protected Properties envProperties;
|
||||
|
||||
protected OuterAdapterConfig configuration;
|
||||
|
||||
public ESSyncService getEsSyncService() {
|
||||
return esSyncService;
|
||||
}
|
||||
@@ -58,23 +57,13 @@ public abstract class ESAdapter implements OuterAdapter {
|
||||
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||
try {
|
||||
this.envProperties = envProperties;
|
||||
this.configuration = configuration;
|
||||
Map<String, ESSyncConfig> esSyncConfigTmp = ESSyncConfigLoader.load(envProperties);
|
||||
// 过滤不匹配的key的配置
|
||||
esSyncConfigTmp.forEach((key, config) -> {
|
||||
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||
|| (config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
|
||||
.equalsIgnoreCase(configuration.getKey()))) {
|
||||
esSyncConfig.put(key, config);
|
||||
}
|
||||
addConfig(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);
|
||||
|
||||
esConfigMonitor = new ESConfigMonitor();
|
||||
@@ -103,12 +92,12 @@ public abstract class ESAdapter implements OuterAdapter {
|
||||
String table = dml.getTable();
|
||||
Map<String, ESSyncConfig> configMap;
|
||||
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
|
||||
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "-"
|
||||
+ StringUtils.trimToEmpty(dml.getGroupId()) + "_" + database + "-"
|
||||
+ table);
|
||||
configMap = dbTableEsSyncConfig
|
||||
.get(StringUtils.trimToEmpty(dml.getDestination()) + "-" + StringUtils.trimToEmpty(dml.getGroupId())
|
||||
+ "_" + database + "-" + table);
|
||||
} else {
|
||||
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-"
|
||||
+ table);
|
||||
configMap = dbTableEsSyncConfig
|
||||
.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-" + table);
|
||||
}
|
||||
|
||||
if (configMap != null && !configMap.values().isEmpty()) {
|
||||
@@ -138,7 +127,7 @@ public abstract class ESAdapter implements OuterAdapter {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addSyncConfigToCache(String configName, ESSyncConfig config) {
|
||||
private void addSyncConfigToCache(String configName, ESSyncConfig config) {
|
||||
Properties envProperties = this.envProperties;
|
||||
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
|
||||
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());
|
||||
}
|
||||
String schema = matcher.group(2);
|
||||
|
||||
schemaItem.getAliasTableItems()
|
||||
.values()
|
||||
.forEach(tableItem -> {
|
||||
Map<String, ESSyncConfig> esSyncConfigMap;
|
||||
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
|
||||
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination())
|
||||
+ "-"
|
||||
+ StringUtils.trimToEmpty(config.getGroupId())
|
||||
+ "_"
|
||||
+ schema
|
||||
+ "-"
|
||||
+ tableItem.getTableName(),
|
||||
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
|
||||
Map<String, ESSyncConfig> esSyncConfigMap;
|
||||
String schemaKey = tableItem.getSchema() == null ? schema : tableItem.getSchema();
|
||||
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
|
||||
esSyncConfigMap = dbTableEsSyncConfig
|
||||
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "-"
|
||||
+ StringUtils.trimToEmpty(config.getGroupId()) + "_" + schemaKey + "-"
|
||||
+ tableItem.getTableName(),
|
||||
k -> new ConcurrentHashMap<>());
|
||||
} else {
|
||||
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination())
|
||||
+ "_"
|
||||
+ schema
|
||||
+ "-"
|
||||
+ tableItem.getTableName(),
|
||||
k -> new ConcurrentHashMap<>());
|
||||
}
|
||||
} else {
|
||||
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(
|
||||
StringUtils.trimToEmpty(config.getDestination()) + "_" + schemaKey + "-" + 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;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-37
@@ -1,21 +1,18 @@
|
||||
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.es.core.ESAdapter;
|
||||
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.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 {
|
||||
|
||||
@@ -36,7 +33,7 @@ public class ESConfigMonitor {
|
||||
File confDir = Util.getConfDirPath(adapterName);
|
||||
try {
|
||||
FileAlterationObserver observer = new FileAlterationObserver(confDir,
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileListener listener = new FileListener();
|
||||
observer.addListener(listener);
|
||||
fileMonitor = new FileAlterationMonitor(3000, observer);
|
||||
@@ -69,9 +66,14 @@ public class ESConfigMonitor {
|
||||
null,
|
||||
envProperties);
|
||||
if (config != null) {
|
||||
// 这里要记得设置esVersion bugfix
|
||||
config.setEsVersion(adapterName);
|
||||
config.validate();
|
||||
addConfigToCache(file, config);
|
||||
logger.info("Add a new es mapping config: {} to canal adapter", file.getName());
|
||||
boolean result = esAdapter.addConfig(file.getName(), config);
|
||||
if (result) {
|
||||
logger.info("Add a new es mapping config: {} to canal adapter",
|
||||
file.getName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
@@ -99,12 +101,10 @@ public class ESConfigMonitor {
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
// 这里要记得设置esVersion bugfix
|
||||
config.setEsVersion(adapterName);
|
||||
config.validate();
|
||||
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
}
|
||||
addConfigToCache(file, config);
|
||||
|
||||
esAdapter.updateConfig(file.getName(), config);
|
||||
logger.info("Change a es mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -118,29 +118,12 @@ public class ESConfigMonitor {
|
||||
|
||||
try {
|
||||
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
|
||||
esAdapter.deleteConfig(file.getName());
|
||||
logger.info("Delete a es mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-12
@@ -1,19 +1,15 @@
|
||||
package com.alibaba.otter.canal.client.adapter.es.core.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
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.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.ESMapping;
|
||||
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem;
|
||||
@@ -79,7 +75,7 @@ public class ESSyncService {
|
||||
esSyncConfigs
|
||||
.forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" "));
|
||||
logger.debug("DML: {} \nAffected indexes: {}",
|
||||
JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue),
|
||||
JSON.toJSONString(dml, JSONWriter.Feature.WriteNulls),
|
||||
configIndexes.toString());
|
||||
}
|
||||
}
|
||||
@@ -209,7 +205,7 @@ public class ESSyncService {
|
||||
|
||||
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
|
||||
// ------单表 & 所有字段都为简单字段------
|
||||
singleTableSimpleFiledUpdate(config, dml, data, old);
|
||||
singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
|
||||
} else {
|
||||
// ------主表 查询sql来更新------
|
||||
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
|
||||
@@ -263,7 +259,7 @@ public class ESSyncService {
|
||||
|
||||
// 判断主键和所更新的字段是否全为简单字段
|
||||
if (idFieldSimple && allUpdateFieldSimple && !fkChanged) {
|
||||
singleTableSimpleFiledUpdate(config, dml, data, old);
|
||||
singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
|
||||
} else {
|
||||
mainTableUpdate(config, dml, data, old);
|
||||
}
|
||||
@@ -810,12 +806,12 @@ public class ESSyncService {
|
||||
* @param data 单行data数据
|
||||
* @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) {
|
||||
ESMapping mapping = config.getEsMapping();
|
||||
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()) {
|
||||
logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}",
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import org.joda.time.DateTime;
|
||||
import org.slf4j.Logger;
|
||||
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.SchemaItem;
|
||||
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.ColumnItem;
|
||||
|
||||
+2
-4
@@ -57,13 +57,11 @@ public interface ESTemplate {
|
||||
|
||||
Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException;
|
||||
|
||||
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
|
||||
Map<String, Object> esFieldData) throws SQLException;
|
||||
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, Map<String, Object> esFieldData) throws SQLException;
|
||||
|
||||
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> dmlOld,
|
||||
Map<String, Object> esFieldData);
|
||||
Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld, Map<String, Object> esFieldData);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+81
-38
@@ -1,9 +1,25 @@
|
||||
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.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.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||
@@ -16,18 +32,6 @@ import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
|
||||
import org.slf4j.Logger;
|
||||
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外部适配器
|
||||
*
|
||||
@@ -49,6 +53,8 @@ public class HbaseAdapter implements OuterAdapter {
|
||||
|
||||
private Properties envProperties;
|
||||
|
||||
private OuterAdapterConfig configuration;
|
||||
|
||||
public Map<String, MappingConfig> getHbaseMapping() {
|
||||
return hbaseMapping;
|
||||
}
|
||||
@@ -61,33 +67,12 @@ public class HbaseAdapter implements OuterAdapter {
|
||||
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||
try {
|
||||
this.envProperties = envProperties;
|
||||
this.configuration = configuration;
|
||||
Map<String, MappingConfig> hbaseMappingTmp = MappingConfigLoader.load(envProperties);
|
||||
// 过滤不匹配的key的配置
|
||||
hbaseMappingTmp.forEach((key, mappingConfig) -> {
|
||||
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||
|| (mappingConfig.getOuterAdapterKey() != null
|
||||
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
|
||||
hbaseMapping.put(key, mappingConfig);
|
||||
}
|
||||
hbaseMappingTmp.forEach((key, config) -> {
|
||||
addConfig(key, config);
|
||||
});
|
||||
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();
|
||||
|
||||
@@ -223,4 +208,62 @@ public class HbaseAdapter implements OuterAdapter {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-37
@@ -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.support.MappingConfigsLoader;
|
||||
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.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public class HbaseConfigMonitor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class);
|
||||
@@ -37,7 +32,7 @@ public class HbaseConfigMonitor {
|
||||
File confDir = Util.getConfDirPath(adapterName);
|
||||
try {
|
||||
FileAlterationObserver observer = new FileAlterationObserver(confDir,
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileListener listener = new FileListener();
|
||||
observer.addListener(listener);
|
||||
fileMonitor = new FileAlterationMonitor(3000, observer);
|
||||
@@ -70,9 +65,11 @@ public class HbaseConfigMonitor {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
addConfigToCache(file, config);
|
||||
|
||||
logger.info("Add a new hbase mapping config: {} to canal adapter", file.getName());
|
||||
boolean result = hbaseAdapter.addConfig(file.getName(), config);
|
||||
if (result) {
|
||||
logger.info("Add a new hbase mapping config: {} to canal adapter",
|
||||
file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -97,10 +94,7 @@ public class HbaseConfigMonitor {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
}
|
||||
addConfigToCache(file, config);
|
||||
hbaseAdapter.updateConfig(file.getName(), config);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
@@ -113,33 +107,12 @@ public class HbaseConfigMonitor {
|
||||
|
||||
try {
|
||||
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
|
||||
hbaseAdapter.deleteConfig(file.getName());
|
||||
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||
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.support.Dml;
|
||||
@@ -39,7 +39,7 @@ public class HbaseSyncService {
|
||||
delete(config, dml);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+73
-36
@@ -1,16 +1,5 @@
|
||||
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.kudu.config.KuduMappingConfig;
|
||||
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.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.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
|
||||
@@ -35,8 +35,6 @@ public class KuduAdapter implements OuterAdapter {
|
||||
private Map<String, KuduMappingConfig> kuduMapping = new ConcurrentHashMap<>(); // 文件名对应配置
|
||||
private Map<String, Map<String, KuduMappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
|
||||
|
||||
private String dataSourceKey;
|
||||
|
||||
private KuduTemplate kuduTemplate;
|
||||
|
||||
private KuduSyncService kuduSyncService;
|
||||
@@ -45,6 +43,8 @@ public class KuduAdapter implements OuterAdapter {
|
||||
|
||||
private Properties envProperties;
|
||||
|
||||
private OuterAdapterConfig configuration;
|
||||
|
||||
public Map<String, KuduMappingConfig> getKuduMapping() {
|
||||
return kuduMapping;
|
||||
}
|
||||
@@ -56,36 +56,17 @@ public class KuduAdapter implements OuterAdapter {
|
||||
@Override
|
||||
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||
this.envProperties = envProperties;
|
||||
this.configuration = configuration;
|
||||
Map<String, KuduMappingConfig> kuduMappingTmp = KuduMappingConfigLoader.load(envProperties);
|
||||
// 过滤不匹配的key的配置,获取连接key,key为配置文件名称
|
||||
kuduMappingTmp.forEach((key, mappingConfig) -> {
|
||||
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
|
||||
.equalsIgnoreCase(configuration.getKey()))) {
|
||||
kuduMapping.put(key, mappingConfig);
|
||||
dataSourceKey = mappingConfig.getDataSourceKey();
|
||||
}
|
||||
kuduMappingTmp.forEach((key, config) -> {
|
||||
addConfig(key, config);
|
||||
});
|
||||
// 判断目标字段是否为空
|
||||
if (kuduMapping.isEmpty()) {
|
||||
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();
|
||||
|
||||
String kudu_master = properties.get("kudu.master.address");
|
||||
@@ -203,4 +184,60 @@ public class KuduAdapter implements OuterAdapter {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-52
@@ -1,23 +1,18 @@
|
||||
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.kudu.KuduAdapter;
|
||||
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.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
|
||||
@@ -41,7 +36,7 @@ public class KuduConfigMonitor {
|
||||
File confDir = Util.getConfDirPath(adapterName);
|
||||
try {
|
||||
FileAlterationObserver observer = new FileAlterationObserver(confDir,
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileListener listener = new FileListener();
|
||||
observer.addListener(listener);
|
||||
fileMonitor = new FileAlterationMonitor(3000, observer);
|
||||
@@ -83,9 +78,11 @@ public class KuduConfigMonitor {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
addConfigToCache(file, config);
|
||||
|
||||
logger.info("Add a new kudu mapping config: {} to canal adapter", file.getName());
|
||||
boolean result = kuduAdapter.addConfig(file.getName(), config);
|
||||
if (result) {
|
||||
logger.info("Add a new kudu mapping config: {} to canal adapter",
|
||||
file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -113,10 +110,7 @@ public class KuduConfigMonitor {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
}
|
||||
addConfigToCache(file, config);
|
||||
kuduAdapter.updateConfig(file.getName(), config);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
@@ -129,42 +123,12 @@ public class KuduConfigMonitor {
|
||||
|
||||
try {
|
||||
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
kuduAdapter.deleteConfig(file.getName());
|
||||
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -10,8 +10,8 @@ import org.apache.kudu.client.KuduException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||
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.support.Dml;
|
||||
@@ -55,7 +55,7 @@ public class KuduSyncService {
|
||||
delete(config, dml);
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
logger.error(e.getMessage());
|
||||
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -61,7 +61,7 @@
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -207,6 +207,19 @@
|
||||
<classifier>jar-with-dependencies</classifier>
|
||||
<scope>provided</scope>
|
||||
</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>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -56,11 +56,24 @@ in
|
||||
exit;;
|
||||
esac
|
||||
|
||||
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
|
||||
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
|
||||
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
|
||||
|
||||
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
|
||||
|
||||
+2
@@ -13,6 +13,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class CanalAdapterApplication {
|
||||
public static void main(String[] args) {
|
||||
// 支持rocketmq client 配置日志路径
|
||||
System.setProperty("rocketmq.client.logUseSlf4j","true");
|
||||
|
||||
SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
|
||||
application.setBannerMode(Banner.Mode.OFF);
|
||||
|
||||
+23
@@ -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;
|
||||
}
|
||||
}
|
||||
+18
-132
@@ -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.ExtensionLoader;
|
||||
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.Group group : canalAdapter.getGroups()) {
|
||||
int autoGenId = 0;
|
||||
List<List<OuterAdapter>> canalOuterAdapterGroups = new CopyOnWriteArrayList<>();
|
||||
List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
|
||||
|
||||
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);
|
||||
}
|
||||
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||
|
||||
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(canalAdapter.getInstance()
|
||||
+ "|"
|
||||
+ StringUtils.trimToEmpty(group.getGroupId()),
|
||||
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(
|
||||
canalAdapter.getInstance() + "|" + StringUtils.trimToEmpty(group.getGroupId()),
|
||||
f -> new AdapterProcessor(canalClientConfig,
|
||||
canalAdapter.getInstance(),
|
||||
group.getGroupId(),
|
||||
canalOuterAdapterGroups));
|
||||
adapterProcessor.start();
|
||||
|
||||
logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-"
|
||||
+ group.getGroupId());
|
||||
logger.info("Start adapter for canal-client mq topic: {} succeed",
|
||||
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) {
|
||||
try {
|
||||
OuterAdapter adapter;
|
||||
adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey()));
|
||||
adapter = loader.getExtension(config.getName(), config.getKey());
|
||||
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
// 替换ClassLoader
|
||||
|
||||
+14
-10
@@ -1,15 +1,21 @@
|
||||
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.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.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
|
||||
*
|
||||
@@ -66,6 +64,9 @@ public class CommonRest {
|
||||
@PostMapping("/etl/{type}/{key}/{task}")
|
||||
public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task,
|
||||
@RequestParam(name = "params", required = false) String params) {
|
||||
if (key == null) {
|
||||
key = FileName2KeyMapping.getKey(type, task);
|
||||
}
|
||||
OuterAdapter adapter = loader.getExtension(type, key);
|
||||
String destination = adapter.getDestination(task);
|
||||
String lockKey = destination == null ? task : destination;
|
||||
@@ -133,6 +134,9 @@ public class CommonRest {
|
||||
*/
|
||||
@GetMapping("/count/{type}/{key}/{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);
|
||||
return adapter.count(task);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ canal.conf:
|
||||
flatMessage: true
|
||||
zookeeperHosts:
|
||||
syncBatchSize: 1000
|
||||
retries: 0
|
||||
retries: -1
|
||||
timeout:
|
||||
accessKey:
|
||||
secretKey:
|
||||
@@ -64,6 +64,8 @@ canal.conf:
|
||||
# jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true
|
||||
# jdbc.username: root
|
||||
# jdbc.password: 121212
|
||||
# druid.stat.enable: false
|
||||
# druid.stat.slowSqlMillis: 1000
|
||||
# - name: rdb
|
||||
# key: oracle1
|
||||
# properties:
|
||||
@@ -91,14 +93,14 @@ canal.conf:
|
||||
# mode: transport # or rest
|
||||
# # security.auth: test:123456 # only used for rest mode
|
||||
# cluster.name: elasticsearch
|
||||
# - name: kudu
|
||||
# key: kudu
|
||||
# properties:
|
||||
# kudu.master.address: 127.0.0.1 # ',' split multi address
|
||||
# - name: phoenix
|
||||
# key: phoenix
|
||||
# properties:
|
||||
# jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver
|
||||
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
|
||||
# jdbc.username:
|
||||
# jdbc.password:
|
||||
# - name: kudu
|
||||
# key: kudu
|
||||
# properties:
|
||||
# kudu.master.address: 127.0.0.1 # ',' split multi address
|
||||
# - name: phoenix
|
||||
# key: phoenix
|
||||
# properties:
|
||||
# jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver
|
||||
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
|
||||
# jdbc.username:
|
||||
# jdbc.password:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#canal:
|
||||
# manager:
|
||||
# jdbc:
|
||||
# url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
|
||||
# username: root
|
||||
# password: 121212
|
||||
canal:
|
||||
manager:
|
||||
jdbc:
|
||||
url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
|
||||
username: canal
|
||||
password: canal
|
||||
|
||||
@@ -34,6 +34,23 @@
|
||||
</sift>
|
||||
</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">
|
||||
<level value="DEBUG" />
|
||||
<appender-ref ref="STDOUT"/>
|
||||
@@ -54,6 +71,10 @@
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</logger>
|
||||
<logger name="RocketmqClient" additivity="false">
|
||||
<level value="INFO" />
|
||||
<appender-ref ref="RocketmqClientAppender" />
|
||||
</logger>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ import java.util.Properties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||
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.OuterAdapterConfig;
|
||||
@@ -36,7 +36,7 @@ public class LoggerAdapterExample implements OuterAdapter {
|
||||
}
|
||||
|
||||
public void sync(Dml dml) {
|
||||
logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.info("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<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">
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -41,50 +39,42 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.12</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>1.1.9</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.58</version>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.48</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -123,19 +113,19 @@
|
||||
<!-- <tasks>-->
|
||||
<!-- <copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">-->
|
||||
<!-- <fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">-->
|
||||
<!-- <include name="*.yml"/>-->
|
||||
<!-- <include name="*.yml" />-->
|
||||
<!-- </fileset>-->
|
||||
<!-- </copy>-->
|
||||
<!-- </tasks>-->
|
||||
<tasks>
|
||||
<copy todir="${project.basedir}/../launcher/target/canal-adapter/conf/phoenix" overwrite="true">
|
||||
<fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">
|
||||
<include name="*.yml"/>
|
||||
<include name="*.yml" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="${project.basedir}/../launcher/target/canal-adapter/plugin" overwrite="true">
|
||||
<fileset dir="${project.basedir}/target/" erroronmissingdir="true">
|
||||
<include name="*with-dependencies.jar"/>
|
||||
<include name="*with-dependencies.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">
|
||||
|
||||
+60
-25
@@ -43,7 +43,7 @@ public class PhoenixAdapter implements OuterAdapter {
|
||||
|
||||
private Properties envProperties;
|
||||
|
||||
|
||||
private OuterAdapterConfig configuration;
|
||||
|
||||
public Map<String, MappingConfig> getPhoenixMapping() {
|
||||
return phoenixMapping;
|
||||
@@ -64,14 +64,11 @@ public class PhoenixAdapter implements OuterAdapter {
|
||||
@Override
|
||||
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||
this.envProperties = envProperties;
|
||||
this.configuration = configuration;
|
||||
Map<String, MappingConfig> phoenixMappingTmp = ConfigLoader.load(envProperties);
|
||||
// 过滤不匹配的key的配置
|
||||
phoenixMappingTmp.forEach((key, mappingConfig) -> {
|
||||
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||
|| (mappingConfig.getOuterAdapterKey() != null
|
||||
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
|
||||
phoenixMapping.put(key, mappingConfig);
|
||||
}
|
||||
phoenixMappingTmp.forEach((key, config) -> {
|
||||
addConfig(key, config);
|
||||
});
|
||||
|
||||
if (phoenixMapping.isEmpty()) {
|
||||
@@ -80,24 +77,6 @@ public class PhoenixAdapter implements OuterAdapter {
|
||||
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();
|
||||
|
||||
DriverClass= properties.get("jdbc.driverClassName");
|
||||
@@ -288,4 +267,60 @@ public class PhoenixAdapter implements OuterAdapter {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-51
@@ -1,23 +1,19 @@
|
||||
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.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.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.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* phoenix config monitor
|
||||
*/
|
||||
@@ -75,11 +71,10 @@ public class PhoenixConfigMonitor {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
if ((key == null && config.getOuterAdapterKey() == null)
|
||||
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
|
||||
addConfigToCache(file, config);
|
||||
|
||||
logger.info("Add a new phoenix mapping config: {} to canal adapter", file.getName());
|
||||
boolean result = phoenixAdapter.addConfig(file.getName(), config);
|
||||
if (result) {
|
||||
logger.info("Add a new phoenix mapping config: {} to canal adapter",
|
||||
file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
@@ -105,16 +100,7 @@ public class PhoenixConfigMonitor {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
if ((key == null && config.getOuterAdapterKey() == null)
|
||||
|| (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");
|
||||
}
|
||||
phoenixAdapter.updateConfig(file.getName(), config);
|
||||
logger.info("Change a phoenix mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -128,40 +114,12 @@ public class PhoenixConfigMonitor {
|
||||
|
||||
try {
|
||||
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
|
||||
phoenixAdapter.deleteConfig(file.getName());
|
||||
logger.info("Delete a phoenix mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -6,8 +6,8 @@ import com.alibaba.druid.sql.ast.SQLStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.*;
|
||||
import com.alibaba.druid.sql.parser.ParserException;
|
||||
import com.alibaba.druid.util.JdbcConstants;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
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.MappingConfig;
|
||||
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()) {
|
||||
MappingConfig config = entry.getValue();
|
||||
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()) {
|
||||
//并行同步
|
||||
@@ -257,7 +257,7 @@ public class PhoenixSyncService {
|
||||
} else if (type != null && type.equalsIgnoreCase("TRUNCATE")) {
|
||||
truncate(batchExecutor, config);
|
||||
} else if (logger.isInfoEnabled()){
|
||||
logger.info("SingleDml: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.info("SingleDml: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
} catch (SQLException 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 {
|
||||
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();
|
||||
if (!dbMapping.isAlter()) {
|
||||
|
||||
+26
-5
@@ -4,12 +4,12 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>canal client adapter module for otter ${project.version}</name>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<java_source_version>1.8</java_source_version>
|
||||
<java_target_version>1.8</java_target_version>
|
||||
<file_encoding>UTF-8</file_encoding>
|
||||
<log4j_version>2.17.0</log4j_version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@@ -182,9 +183,9 @@
|
||||
<version>42.1.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3</version>
|
||||
<version>11.2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
@@ -212,6 +213,12 @@
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
<version>2.10.0</version>
|
||||
</dependency>
|
||||
<!-- 单独指定guava版本,兼容curator-client -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
@@ -270,7 +277,21 @@
|
||||
</exclusions>
|
||||
</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>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -30,7 +30,7 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
+91
-34
@@ -1,7 +1,9 @@
|
||||
package com.alibaba.otter.canal.client.adapter.rdb;
|
||||
|
||||
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -13,7 +15,9 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.druid.filter.stat.StatFilter;
|
||||
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.rdb.config.ConfigLoader;
|
||||
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||
@@ -53,6 +57,8 @@ public class RdbAdapter implements OuterAdapter {
|
||||
|
||||
private Properties envProperties;
|
||||
|
||||
private OuterAdapterConfig configuration;
|
||||
|
||||
public Map<String, MappingConfig> getRdbMapping() {
|
||||
return rdbMapping;
|
||||
}
|
||||
@@ -73,46 +79,22 @@ public class RdbAdapter implements OuterAdapter {
|
||||
@Override
|
||||
public void init(OuterAdapterConfig configuration, Properties 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);
|
||||
// 过滤不匹配的key的配置
|
||||
rdbMappingTmp.forEach((key, mappingConfig) -> {
|
||||
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
|
||||
.equalsIgnoreCase(configuration.getKey()))) {
|
||||
rdbMapping.put(key, mappingConfig);
|
||||
}
|
||||
rdbMappingTmp.forEach((key, config) -> {
|
||||
addConfig(key, config);
|
||||
});
|
||||
|
||||
if (rdbMapping.isEmpty()) {
|
||||
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.setDriverClassName(properties.get("jdbc.driverClassName"));
|
||||
dataSource.setUrl(properties.get("jdbc.url"));
|
||||
@@ -125,10 +107,20 @@ public class RdbAdapter implements OuterAdapter {
|
||||
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||
dataSource.setUseUnfairLock(true);
|
||||
dataSource.setDbType(dbType);
|
||||
|
||||
// List<String> array = new ArrayList<>();
|
||||
// array.add("set names utf8mb4;");
|
||||
// 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 {
|
||||
dataSource.init();
|
||||
} catch (SQLException e) {
|
||||
@@ -165,7 +157,9 @@ public class RdbAdapter implements OuterAdapter {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
|
||||
if (!mappingConfigCache.isEmpty()) {
|
||||
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
|
||||
}
|
||||
rdbMirrorDbSyncService.sync(dmls);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -226,7 +220,7 @@ public class RdbAdapter implements OuterAdapter {
|
||||
public Map<String, Object> count(String task) {
|
||||
MappingConfig config = rdbMapping.get(task);
|
||||
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;
|
||||
Map<String, Object> res = new LinkedHashMap<>();
|
||||
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;
|
||||
}
|
||||
@@ -289,4 +283,67 @@ public class RdbAdapter implements OuterAdapter {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-77
@@ -1,24 +1,18 @@
|
||||
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.rdb.RdbAdapter;
|
||||
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.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 {
|
||||
|
||||
@@ -41,7 +35,7 @@ public class RdbConfigMonitor {
|
||||
File confDir = Util.getConfDirPath(adapterName);
|
||||
try {
|
||||
FileAlterationObserver observer = new FileAlterationObserver(confDir,
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||
FileListener listener = new FileListener();
|
||||
observer.addListener(listener);
|
||||
fileMonitor = new FileAlterationMonitor(3000, observer);
|
||||
@@ -69,16 +63,15 @@ public class RdbConfigMonitor {
|
||||
// 加载新增的配置文件
|
||||
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
|
||||
MappingConfig config = YmlConfigBinder
|
||||
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
|
||||
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
if ((key == null && config.getOuterAdapterKey() == null)
|
||||
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
|
||||
addConfigToCache(file, config);
|
||||
|
||||
logger.info("Add a new rdb mapping config: {} to canal adapter", file.getName());
|
||||
boolean result = rdbAdapter.addConfig(file.getName(), config);
|
||||
if (result) {
|
||||
logger.info("Add a new rdb mapping config: {} to canal adapter",
|
||||
file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
@@ -92,28 +85,19 @@ public class RdbConfigMonitor {
|
||||
try {
|
||||
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
||||
// 加载配置文件
|
||||
String configContent = MappingConfigsLoader
|
||||
.loadConfig(adapterName + File.separator + file.getName());
|
||||
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
|
||||
if (configContent == null) {
|
||||
onFileDelete(file);
|
||||
return;
|
||||
}
|
||||
MappingConfig config = YmlConfigBinder
|
||||
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
|
||||
.bindYmlToObj(null, configContent, MappingConfig.class, null,
|
||||
envProperties);
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
config.validate();
|
||||
if ((key == null && config.getOuterAdapterKey() == null)
|
||||
|| (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");
|
||||
}
|
||||
rdbAdapter.updateConfig(file.getName(), config);
|
||||
logger.info("Change a rdb mapping config: {} of canal adapter", file.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -127,7 +111,7 @@ public class RdbConfigMonitor {
|
||||
|
||||
try {
|
||||
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
||||
deleteConfigFromCache(file);
|
||||
rdbAdapter.deleteConfig(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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
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.DbMapping;
|
||||
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
||||
@@ -56,8 +57,11 @@ public class RdbEtlService extends AbstractEtlService {
|
||||
DbMapping dbMapping = (DbMapping) mapping;
|
||||
Map<String, String> columnsMap = 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 {
|
||||
|
||||
ResultSetMetaData rsd = rs.getMetaData();
|
||||
@@ -83,7 +87,9 @@ public class RdbEtlService extends AbstractEtlService {
|
||||
boolean completed = false;
|
||||
|
||||
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(targetColumnName).append(","));
|
||||
|
||||
@@ -107,7 +113,7 @@ public class RdbEtlService extends AbstractEtlService {
|
||||
// 删除数据
|
||||
Map<String, Object> pkVal = new LinkedHashMap<>();
|
||||
StringBuilder deleteSql = new StringBuilder(
|
||||
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE ");
|
||||
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE ");
|
||||
appendCondition(dbMapping, deleteSql, pkVal, rs);
|
||||
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
||||
int k = 1;
|
||||
|
||||
+43
-39
@@ -7,17 +7,17 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
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.MirrorDbConfig;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -31,10 +31,10 @@ public class RdbMirrorDbSyncService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class);
|
||||
|
||||
private Map<String, MirrorDbConfig> mirrorDbConfigCache; // 镜像库配置
|
||||
private DataSource dataSource;
|
||||
private DruidDataSource dataSource;
|
||||
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,
|
||||
boolean skipDupException){
|
||||
this.mirrorDbConfigCache = mirrorDbConfigCache;
|
||||
@@ -45,7 +45,7 @@ public class RdbMirrorDbSyncService {
|
||||
/**
|
||||
* 批量同步方法
|
||||
*
|
||||
* @param dmls 批量 DML
|
||||
* @param dmls 批量 DML,包含DDL
|
||||
*/
|
||||
public void sync(List<Dml> dmls) {
|
||||
List<Dml> dmlList = new ArrayList<>();
|
||||
@@ -66,9 +66,13 @@ public class RdbMirrorDbSyncService {
|
||||
}
|
||||
|
||||
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
|
||||
// 确保执行DDL前DML已执行完
|
||||
syncDml(dmlList);
|
||||
dmlList.clear();
|
||||
|
||||
// DDL
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("DDL: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.debug("DDL: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
executeDdl(mirrorDbConfig, dml);
|
||||
rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable());
|
||||
@@ -79,38 +83,32 @@ public class RdbMirrorDbSyncService {
|
||||
dmlList.add(dml);
|
||||
}
|
||||
}
|
||||
if (!dmlList.isEmpty()) {
|
||||
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);
|
||||
syncDml(dmlList);
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
return false;
|
||||
}
|
||||
// 是否区分大小写
|
||||
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
|
||||
if (config.getConcurrent()) {
|
||||
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
|
||||
singleDmls.forEach(singleDml -> {
|
||||
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;
|
||||
});
|
||||
/**
|
||||
* 批量同步Dml
|
||||
*
|
||||
* @param dmlList Dml列表,不包含DDL
|
||||
*/
|
||||
private void syncDml(List<Dml> dmlList) {
|
||||
if (dmlList == null || dmlList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
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());
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
||||
+54
-36
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.service;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -15,14 +16,13 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
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.DbMapping;
|
||||
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 DruidDataSource dataSource;
|
||||
// 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType>
|
||||
private Map<String, Map<String, Integer>> columnsTypeCache;
|
||||
|
||||
@@ -59,13 +60,14 @@ public class RdbSyncService {
|
||||
return columnsTypeCache;
|
||||
}
|
||||
|
||||
public RdbSyncService(DataSource dataSource, Integer threads, boolean skipDupException){
|
||||
public RdbSyncService(DruidDataSource dataSource, Integer threads, boolean skipDupException){
|
||||
this(dataSource, threads, new ConcurrentHashMap<>(), skipDupException);
|
||||
}
|
||||
|
||||
@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){
|
||||
this.dataSource = dataSource;
|
||||
this.columnsTypeCache = columnsTypeCache;
|
||||
this.skipDupException = skipDupException;
|
||||
try {
|
||||
@@ -177,28 +179,38 @@ public class RdbSyncService {
|
||||
}
|
||||
|
||||
for (MappingConfig config : configMap.values()) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
appendDmlPartition(config, dml);
|
||||
}
|
||||
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 同步
|
||||
*
|
||||
@@ -220,7 +232,7 @@ public class RdbSyncService {
|
||||
truncate(batchExecutor, config);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -241,15 +253,15 @@ public class RdbSyncService {
|
||||
}
|
||||
|
||||
DbMapping dbMapping = config.getDbMapping();
|
||||
|
||||
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
|
||||
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
||||
|
||||
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("`")
|
||||
.append(backtick)
|
||||
.append(","));
|
||||
int len = insertSql.length();
|
||||
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||
@@ -313,13 +325,13 @@ public class RdbSyncService {
|
||||
}
|
||||
|
||||
DbMapping dbMapping = config.getDbMapping();
|
||||
|
||||
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
|
||||
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
||||
|
||||
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||
|
||||
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<>();
|
||||
boolean hasMatched = false;
|
||||
for (String srcColumnName : old.keySet()) {
|
||||
@@ -332,7 +344,7 @@ public class RdbSyncService {
|
||||
if (!targetColumnNames.isEmpty()) {
|
||||
hasMatched = true;
|
||||
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());
|
||||
if (type == null) {
|
||||
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
|
||||
@@ -369,11 +381,10 @@ public class RdbSyncService {
|
||||
}
|
||||
|
||||
DbMapping dbMapping = config.getDbMapping();
|
||||
|
||||
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||
|
||||
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<>();
|
||||
// 拼接主键
|
||||
@@ -392,7 +403,7 @@ public class RdbSyncService {
|
||||
private void truncate(BatchExecutor batchExecutor, MappingConfig config) throws SQLException {
|
||||
DbMapping dbMapping = config.getDbMapping();
|
||||
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<>());
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Truncate target table, sql: {}", sql);
|
||||
@@ -416,13 +427,18 @@ public class RdbSyncService {
|
||||
if (columnType == null) {
|
||||
columnType = new LinkedHashMap<>();
|
||||
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 -> {
|
||||
try {
|
||||
ResultSetMetaData rsd = rs.getMetaData();
|
||||
int columnCount = rsd.getColumnCount();
|
||||
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);
|
||||
} catch (SQLException e) {
|
||||
@@ -445,6 +461,8 @@ public class RdbSyncService {
|
||||
|
||||
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
|
||||
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()) {
|
||||
String targetColumnName = entry.getKey();
|
||||
@@ -452,7 +470,7 @@ public class RdbSyncService {
|
||||
if (srcColumnName == null) {
|
||||
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());
|
||||
if (type == null) {
|
||||
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
|
||||
|
||||
+28
-3
@@ -1,5 +1,6 @@
|
||||
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.support.Util;
|
||||
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 backtick = getBacktickByDbType(dbType);
|
||||
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
|
||||
result += ("`" + dbMapping.getTargetDb() + "`.");
|
||||
result += (backtick + dbMapping.getTargetDb() + backtick + ".");
|
||||
}
|
||||
result += ("`" + dbMapping.getTargetTable() + "`");
|
||||
result += (backtick + dbMapping.getTargetTable() + backtick);
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.client-adapter</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+84
-50
@@ -1,6 +1,8 @@
|
||||
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.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -55,62 +57,14 @@ public class TablestoreAdapter implements OuterAdapter {
|
||||
this.configuration = configuration;
|
||||
Map<String, MappingConfig> tablestoreMappingTmp = ConfigLoader.load(envProperties);
|
||||
// 过滤不匹配的key的配置
|
||||
tablestoreMappingTmp.forEach((key, mappingConfig) -> {
|
||||
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
|
||||
.equalsIgnoreCase(configuration.getKey()))) {
|
||||
tablestoreMapping.put(key, mappingConfig);
|
||||
mappingConfig.getDbMapping().init(mappingConfig);
|
||||
}
|
||||
tablestoreMappingTmp.forEach((key, config) -> {
|
||||
addConfig(key, config);
|
||||
});
|
||||
|
||||
if (tablestoreMapping.isEmpty()) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||
import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType;
|
||||
import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil;
|
||||
import com.alicloud.openservices.tablestore.TableStoreWriter;
|
||||
@@ -59,7 +59,7 @@ public class TablestoreSyncService {
|
||||
Future<WriterResult> future = writer.addRowChangeWithFuture(rowChanges);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
+6
-5
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -50,10 +50,6 @@
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
@@ -138,6 +134,11 @@
|
||||
<artifactId>pulsar-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pulsar</groupId>
|
||||
<artifactId>pulsar-client-admin</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
|
||||
@@ -39,6 +39,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
this.accessStrategy = accessStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws CanalClientException {
|
||||
while (currentConnector == null) {
|
||||
int times = 0;
|
||||
@@ -85,10 +86,12 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkValid() {
|
||||
return currentConnector != null && currentConnector.checkValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() throws CanalClientException {
|
||||
if (currentConnector != null) {
|
||||
currentConnector.disconnect();
|
||||
@@ -96,10 +99,12 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe() throws CanalClientException {
|
||||
subscribe(""); // 传递空字符即可
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(String filter) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -125,6 +130,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to subscribe after " + times + " times retry.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe() throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -142,6 +148,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to unsubscribe after " + times + " times retry.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message get(int batchSize) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -159,6 +166,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -176,6 +184,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getWithoutAck(int batchSize) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -193,6 +202,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -210,6 +220,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(long batchId) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -227,6 +238,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to rollback after " + times + " times retry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
@@ -245,6 +257,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("failed to rollback after " + times + " times retry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ack(long batchId) throws CanalClientException {
|
||||
int times = 0;
|
||||
while (times < retryTimes) {
|
||||
|
||||
+3
-2
@@ -63,6 +63,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
return nextNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress nextNode() {
|
||||
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
|
||||
return runningAddress;
|
||||
@@ -81,7 +82,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
for (String address : currentChilds) {
|
||||
String[] strs = StringUtils.split(address, ":");
|
||||
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);
|
||||
String[] strs = StringUtils.split(runningData.getAddress(), ':');
|
||||
if (strs.length == 2) {
|
||||
runningAddress = new InetSocketAddress(strs[0], Integer.valueOf(strs[1]));
|
||||
runningAddress = new InetSocketAddress(strs[0], Integer.parseInt(strs[1]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws CanalClientException {
|
||||
if (connected) {
|
||||
return;
|
||||
@@ -124,6 +125,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
connected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() throws CanalClientException {
|
||||
if (rollbackOnDisConnect && channel.isConnected()) {
|
||||
rollback();
|
||||
@@ -222,10 +224,12 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe() throws CanalClientException {
|
||||
subscribe(""); // 传递空字符即可
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(String filter) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
@@ -255,6 +259,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe() throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
@@ -281,20 +286,24 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message get(int batchSize) throws CanalClientException {
|
||||
return get(batchSize, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||
Message message = getWithoutAck(batchSize, timeout, unit);
|
||||
ack(message.getId());
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getWithoutAck(int batchSize) throws CanalClientException {
|
||||
return getWithoutAck(batchSize, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
@@ -331,6 +340,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
return CanalMessageDeserializer.deserializer(data, lazyParseEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ack(long batchId) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
@@ -352,6 +362,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(long batchId) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
ClientRollback ca = ClientRollback.newBuilder()
|
||||
@@ -370,9 +381,10 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws CanalClientException {
|
||||
waitClientRunning();
|
||||
rollback(0);// 0代笔未设置
|
||||
rollback(0); // 0 代表未设置
|
||||
}
|
||||
|
||||
// ==================== helper method ====================
|
||||
|
||||
@@ -24,6 +24,7 @@ public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
this.nodes.addAll(nodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress nextNode() {
|
||||
try {
|
||||
return nodes.get(index);
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
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.impl.SimpleCanalConnector;
|
||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
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.KafkaMessage;
|
||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
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.CanalMessageDeserializer;
|
||||
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
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.CanalMessageDeserializer;
|
||||
import com.alibaba.otter.canal.client.ConsumerBatchMessage;
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import org.apache.rocketmq.remoting.RPCHook;
|
||||
import org.slf4j.Logger;
|
||||
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.CanalMessageDeserializer;
|
||||
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.common</artifactId>
|
||||
@@ -39,8 +39,8 @@
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
package com.alibaba.otter.canal.common.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||
import com.alibaba.fastjson.serializer.SerializeWriter;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.*;
|
||||
import com.alibaba.fastjson2.filter.Filter;
|
||||
import com.alibaba.fastjson2.filter.PropertyFilter;
|
||||
import com.alibaba.fastjson2.writer.ObjectWriter;
|
||||
|
||||
|
||||
/**
|
||||
* 字节处理相关工具类
|
||||
@@ -23,46 +17,41 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
* @author jianghang
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
static {
|
||||
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
||||
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
||||
|
||||
ParserConfig.getGlobalInstance().addAccept("com.alibaba.otter.");
|
||||
ParserConfig.getGlobalInstance().addAccept("com.taobao.tddl.dbsync.");
|
||||
}
|
||||
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(
|
||||
"com.alibaba.otter.",
|
||||
"com.taobao.tddl.dbsync."
|
||||
);
|
||||
|
||||
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
|
||||
return (T) JSON.parseObject(bytes, targetClass, AUTO_TYPE_FILTER);// 默认为UTF-8
|
||||
}
|
||||
|
||||
public static <T> T unmarshalFromByte(byte[] bytes, TypeReference<T> type) {
|
||||
return (T) JSON.parseObject(bytes, type.getType());
|
||||
|
||||
return (T) JSON.parseObject(bytes, type.getType(), AUTO_TYPE_FILTER);
|
||||
}
|
||||
|
||||
public static byte[] marshalToByte(Object obj) {
|
||||
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
|
||||
}
|
||||
|
||||
public static <T> T unmarshalFromString(String json, Class<T> targetClass) {
|
||||
return (T) JSON.parseObject(json, targetClass);// 默认为UTF-8
|
||||
return (T) JSON.parseObject(json, targetClass, AUTO_TYPE_FILTER);// 默认为UTF-8
|
||||
}
|
||||
|
||||
public static <T> T unmarshalFromString(String json, TypeReference<T> type) {
|
||||
return (T) JSON.parseObject(json, type);// 默认为UTF-8
|
||||
return (T) JSON.parseObject(json, type.getType(), AUTO_TYPE_FILTER);// 默认为UTF-8
|
||||
}
|
||||
|
||||
public static String marshalToString(Object obj) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -71,29 +60,29 @@ public class JsonUtils {
|
||||
*/
|
||||
public static String marshalToString(Object obj, String... fliterFields) {
|
||||
final List<String> propertyFliters = Arrays.asList(fliterFields);
|
||||
try (SerializeWriter out = new SerializeWriter()) {
|
||||
JSONSerializer serializer = new JSONSerializer(out);
|
||||
serializer.getPropertyFilters().add((source, name, value) -> !propertyFliters.contains(name));
|
||||
serializer.write(obj);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
return JSON.toJSONString(obj, new PropertyFilter() {
|
||||
@Override
|
||||
public boolean apply(Object object, String name, Object value) {
|
||||
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
|
||||
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
|
||||
throws IOException {
|
||||
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
|
||||
if (object == null) {
|
||||
serializer.writeNull();
|
||||
jsonWriter.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = (InetAddress) object;
|
||||
// 优先使用name
|
||||
serializer.write(address.getHostName());
|
||||
jsonWriter.writeString(address.getHostName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.alibaba.otter.canal.common;
|
||||
|
||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JsonUtilsTest {
|
||||
@Test
|
||||
public void marshalToString() throws Exception {
|
||||
InetAddress address = InetAddress.getByName("localhost");
|
||||
|
||||
String json = JsonUtils.marshalToString(address);
|
||||
assertEquals("\"localhost\"", json);
|
||||
|
||||
InetAddress address1 = JsonUtils.unmarshalFromString(json, InetAddress.class);
|
||||
assertEquals(address, address1);
|
||||
}
|
||||
}
|
||||
@@ -106,21 +106,21 @@ public class ServerRunningTest extends AbstractZkTest {
|
||||
runningMonitor.setListener(new ServerRunningListener() {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
System.out.println(String.format("cid:%s ip:%s:%s processStop", ip, port));
|
||||
System.out.println(String.format("ip:%s:%s processStop", ip, port));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
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.consumer.CommonMessage;
|
||||
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
||||
|
||||
+4
-4
@@ -18,8 +18,8 @@ import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
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.MQDestination;
|
||||
@@ -249,13 +249,13 @@ public class CanalKafkaProducer extends AbstractMQProducer implements CanalMQPro
|
||||
FlatMessage flatMessagePart = partitionFlatMessage[i];
|
||||
if (flatMessagePart != null) {
|
||||
records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart,
|
||||
SerializerFeature.WriteMapNullValue)));
|
||||
JSONWriter.Feature.WriteNulls)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0;
|
||||
records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage,
|
||||
SerializerFeature.WriteMapNullValue)));
|
||||
JSONWriter.Feature.WriteNulls)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,13 +4,13 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>canal connector module for otter ${project.version}</name>
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<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">
|
||||
<parent>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>connector.pulsarmq</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>canal connector pulsarmq module for otter ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -38,6 +36,10 @@
|
||||
<groupId>org.apache.pulsar</groupId>
|
||||
<artifactId>pulsar-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pulsar</groupId>
|
||||
<artifactId>pulsar-client-admin</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -91,4 +93,4 @@
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+16
-14
@@ -1,7 +1,5 @@
|
||||
package com.alibaba.otter.canal.connector.pulsarmq.config;
|
||||
|
||||
import org.joda.time.Seconds;
|
||||
|
||||
/**
|
||||
* PulsarMQ配置
|
||||
*
|
||||
@@ -10,19 +8,20 @@ import org.joda.time.Seconds;
|
||||
* @since 1 by chad at 2021/9/15 新增配置文件
|
||||
*/
|
||||
public class PulsarMQConstants {
|
||||
public static final String ROOT = "pulsarmq";
|
||||
|
||||
public static final String ROOT = "pulsarmq";
|
||||
/**
|
||||
* pulsar服务连接地址
|
||||
*/
|
||||
public static final String PULSARMQ_SERVER_URL = ROOT + "." + "serverUrl";
|
||||
public static final String PULSARMQ_SERVER_URL = ROOT + "." + "serverUrl";
|
||||
/**
|
||||
* pulsar服务角色token,需要有对应token的生产者权限
|
||||
*/
|
||||
public static final String PULSARMQ_ROLE_TOKEN = ROOT + "." + "roleToken";
|
||||
public static final String PULSARMQ_ROLE_TOKEN = ROOT + "." + "roleToken";
|
||||
/**
|
||||
* 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,认为是同一个消费实例
|
||||
*/
|
||||
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超时秒数
|
||||
*/
|
||||
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队列名称后缀是否大写
|
||||
*/
|
||||
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";
|
||||
|
||||
}
|
||||
|
||||
+16
-2
@@ -4,16 +4,18 @@ import com.alibaba.otter.canal.connector.core.config.MQProperties;
|
||||
|
||||
/**
|
||||
* Pulsar生产者配置
|
||||
*
|
||||
* @author chad
|
||||
* @date 2021/9/15 11:23
|
||||
* @since 1 by chad at 2021/9/15 新增
|
||||
*/
|
||||
public class PulsarMQProducerConfig extends MQProperties {
|
||||
|
||||
/**
|
||||
* pulsar服务连接地址
|
||||
* <p>
|
||||
* broker: pulsar://localhost:6650<br/>
|
||||
* httpUrl: http://localhost:8080
|
||||
* broker: pulsar://localhost:6650<br/>
|
||||
* httpUrl: http://localhost:8080
|
||||
* </p>
|
||||
*/
|
||||
private String serverUrl;
|
||||
@@ -30,6 +32,10 @@ public class PulsarMQProducerConfig extends MQProperties {
|
||||
* 生产者角色权限,请确保该角色有canal使用的所有topic生产者权限(最低要求)
|
||||
*/
|
||||
private String roleToken;
|
||||
/**
|
||||
* admin服务器地址
|
||||
*/
|
||||
private String adminServerUrl;
|
||||
|
||||
public String getServerUrl() {
|
||||
return serverUrl;
|
||||
@@ -54,4 +60,12 @@ public class PulsarMQProducerConfig extends MQProperties {
|
||||
public void setTopicTenantPrefix(String topicTenantPrefix) {
|
||||
this.topicTenantPrefix = topicTenantPrefix;
|
||||
}
|
||||
|
||||
public String getAdminServerUrl() {
|
||||
return adminServerUrl;
|
||||
}
|
||||
|
||||
public void setAdminServerUrl(String adminServerUrl) {
|
||||
this.adminServerUrl = adminServerUrl;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-47
@@ -1,6 +1,13 @@
|
||||
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.connector.core.config.CanalConstants;
|
||||
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.exception.CanalClientException;
|
||||
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 实现
|
||||
@@ -30,19 +28,20 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
@SPI("pulsarmq")
|
||||
public class CanalPulsarMQConsumer implements CanalMsgConsumer {
|
||||
|
||||
/**
|
||||
* 连接pulsar客户端
|
||||
*/
|
||||
private PulsarClient pulsarClient;
|
||||
private Consumer<byte[]> pulsarMQConsumer;
|
||||
private PulsarClient pulsarClient;
|
||||
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
|
||||
*/
|
||||
private String serviceUrl;
|
||||
private String serviceUrl;
|
||||
/**
|
||||
* 角色认证token
|
||||
*/
|
||||
private String roleToken;
|
||||
private String roleToken;
|
||||
/**
|
||||
* 订阅客户端名称
|
||||
*/
|
||||
private String subscriptName;
|
||||
private String subscriptName;
|
||||
/**
|
||||
* 每次批量获取数据的最大条目数,默认30
|
||||
*/
|
||||
private int batchSize = 30;
|
||||
private int batchSize = 30;
|
||||
/**
|
||||
* 与{@code batchSize}一起决定批量获取的数据大小
|
||||
* 当:
|
||||
* 与{@code batchSize}一起决定批量获取的数据大小 当:
|
||||
* <p>
|
||||
* 1. {@code batchSize} 条消息未消费时<br/>
|
||||
* 2. 距上一次批量消费时间达到{@code batchTimeoutSeconds}秒时
|
||||
* </p>
|
||||
* 任一条件满足,即执行批量消费
|
||||
*/
|
||||
private int getBatchTimeoutSeconds = 30;
|
||||
private int getBatchTimeoutSeconds = 30;
|
||||
/**
|
||||
* 批量处理消息时,一次批量处理的超时时间
|
||||
* <p>
|
||||
* 该时间应该根据{@code batchSize}和{@code batchTimeoutSeconds}合理设置
|
||||
* </p>
|
||||
*/
|
||||
private long batchProcessTimeout = 60 * 1000;
|
||||
private long batchProcessTimeout = 60 * 1000;
|
||||
/**
|
||||
* 消费失败后的重试秒数,默认60秒
|
||||
*/
|
||||
private int redeliveryDelaySeconds = 60;
|
||||
private int redeliveryDelaySeconds = 60;
|
||||
/**
|
||||
* 当客户端接收到消息,30秒还没有返回ack给服务端时,ack超时,会重新消费该消息
|
||||
*/
|
||||
private int ackTimeoutSeconds = 30;
|
||||
private int ackTimeoutSeconds = 30;
|
||||
/**
|
||||
* 是否开启消息失败重试功能,默认开启
|
||||
*/
|
||||
private boolean isRetry = true;
|
||||
private boolean isRetry = true;
|
||||
/**
|
||||
* <p>
|
||||
* true重试(-RETRY)和死信队列(-DLQ)后缀为大写,有些地方创建的为小写,需确保正确
|
||||
* </p>
|
||||
*/
|
||||
private boolean isRetryDLQUpperCase = false;
|
||||
private boolean isRetryDLQUpperCase = false;
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private int maxRedeliveryCount = 128;
|
||||
private int maxRedeliveryCount = 128;
|
||||
|
||||
@Override
|
||||
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.roleToken = properties.getProperty(PulsarMQConstants.PULSARMQ_ROLE_TOKEN);
|
||||
this.subscriptName = properties.getProperty(PulsarMQConstants.PULSARMQ_SUBSCRIPT_NAME);
|
||||
// 采用groupId作为subscriptName,避免所有的都是同一个订阅者名称
|
||||
if (StringUtils.isEmpty(this.subscriptName)) {
|
||||
this.subscriptName = groupId;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(this.subscriptName)) {
|
||||
throw new RuntimeException("Pulsar Consumer subscriptName required");
|
||||
}
|
||||
@@ -157,10 +160,12 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
|
||||
}
|
||||
// 连接创建客户端
|
||||
try {
|
||||
pulsarClient = PulsarClient.builder()
|
||||
.serviceUrl(serviceUrl)
|
||||
.authentication(AuthenticationFactory.token(roleToken))
|
||||
.build();
|
||||
// AuthenticationDataProvider
|
||||
ClientBuilder builder = PulsarClient.builder().serviceUrl(serviceUrl);
|
||||
if (StringUtils.isNotEmpty(roleToken)) {
|
||||
builder.authentication(AuthenticationFactory.token(roleToken));
|
||||
}
|
||||
pulsarClient = builder.build();
|
||||
} catch (PulsarClientException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -176,15 +181,14 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
|
||||
builder.subscriptionType(SubscriptionType.Failover);
|
||||
|
||||
builder
|
||||
// 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息
|
||||
// 在指定的时间进行重新消费,默认是1分钟。
|
||||
.negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS)
|
||||
.subscriptionName(this.subscriptName)
|
||||
;
|
||||
// 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息
|
||||
// 在指定的时间进行重新消费,默认是1分钟。
|
||||
.negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS)
|
||||
.subscriptionName(this.subscriptName);
|
||||
if (this.isRetry) {
|
||||
DeadLetterPolicy.DeadLetterPolicyBuilder dlqBuilder = DeadLetterPolicy.builder()
|
||||
// 最大重试次数
|
||||
.maxRedeliverCount(this.maxRedeliveryCount);
|
||||
// 最大重试次数
|
||||
.maxRedeliverCount(this.maxRedeliveryCount);
|
||||
// 指定重试队列,不是多个或通配符topic才能判断重试队列
|
||||
if (!MQUtil.isPatternTag(this.topic)) {
|
||||
String retryTopic = this.topic + (this.isRetryDLQUpperCase ? "-RETRY" : "-retry");
|
||||
@@ -193,19 +197,17 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
|
||||
dlqBuilder.deadLetterTopic(dlqTopic);
|
||||
}
|
||||
|
||||
//默认关闭,如果需要重试则开启
|
||||
builder.enableRetry(true)
|
||||
.deadLetterPolicy(dlqBuilder.build());
|
||||
// 默认关闭,如果需要重试则开启
|
||||
builder.enableRetry(true).deadLetterPolicy(dlqBuilder.build());
|
||||
}
|
||||
|
||||
// ack超时
|
||||
builder.ackTimeout(this.ackTimeoutSeconds, TimeUnit.SECONDS);
|
||||
|
||||
// pulsar批量获取消息设置
|
||||
builder.batchReceivePolicy(new BatchReceivePolicy.Builder()
|
||||
.maxNumMessages(this.batchSize)
|
||||
.timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS)
|
||||
.build());
|
||||
builder.batchReceivePolicy(new BatchReceivePolicy.Builder().maxNumMessages(this.batchSize)
|
||||
.timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS)
|
||||
.build());
|
||||
|
||||
try {
|
||||
this.pulsarMQConsumer = builder.subscribe();
|
||||
@@ -220,7 +222,6 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
|
||||
List<CommonMessage> messageList = Lists.newArrayList();
|
||||
try {
|
||||
Messages<byte[]> messages = pulsarMQConsumer.batchReceive();
|
||||
|
||||
if (null == messages || messages.size() == 0) {
|
||||
return messageList;
|
||||
}
|
||||
@@ -278,7 +279,8 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.pulsarMQConsumer.unsubscribe();
|
||||
// 会导致暂停期间数据丢失
|
||||
// this.pulsarMQConsumer.unsubscribe();
|
||||
this.pulsarClient.close();
|
||||
} catch (PulsarClientException e) {
|
||||
throw new CanalClientException("Disconnect pulsar consumer error", e);
|
||||
|
||||
+144
-92
@@ -1,7 +1,19 @@
|
||||
package com.alibaba.otter.canal.connector.pulsarmq.producer;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import java.util.*;
|
||||
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.NamedThreadFactory;
|
||||
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.protocol.CanalEntry;
|
||||
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 实现
|
||||
@@ -37,19 +38,22 @@ import java.util.stream.Collectors;
|
||||
@SPI("pulsarmq")
|
||||
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客户端,管理连接
|
||||
*/
|
||||
protected PulsarClient client;
|
||||
protected PulsarClient client;
|
||||
/**
|
||||
* Pulsar admin 客户端
|
||||
*/
|
||||
protected PulsarAdmin pulsarAdmin;
|
||||
|
||||
@Override
|
||||
public void init(Properties properties) {
|
||||
@@ -61,25 +65,36 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
|
||||
// 初始化连接客户端
|
||||
try {
|
||||
client = PulsarClient.builder()
|
||||
// 填写pulsar的连接地址
|
||||
.serviceUrl(pulsarMQProducerConfig.getServerUrl())
|
||||
// 角色权限认证的token
|
||||
.authentication(AuthenticationFactory.token(pulsarMQProducerConfig.getRoleToken()))
|
||||
.build();
|
||||
ClientBuilder builder = PulsarClient.builder()
|
||||
// 填写pulsar的连接地址
|
||||
.serviceUrl(pulsarMQProducerConfig.getServerUrl());
|
||||
if (StringUtils.isNotEmpty(pulsarMQProducerConfig.getRoleToken())) {
|
||||
// 角色权限认证的token
|
||||
builder.authentication(AuthenticationFactory.token(pulsarMQProducerConfig.getRoleToken()));
|
||||
}
|
||||
client = builder.build();
|
||||
} catch (PulsarClientException 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();
|
||||
sendPartitionExecutor = new ThreadPoolExecutor(parallelPartitionSendThreadSize,
|
||||
parallelPartitionSendThreadSize,
|
||||
0,
|
||||
TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(parallelPartitionSendThreadSize * 2),
|
||||
new NamedThreadFactory("MQ-Parallel-Sender-Partition"),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
parallelPartitionSendThreadSize,
|
||||
0,
|
||||
TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(parallelPartitionSendThreadSize * 2),
|
||||
new NamedThreadFactory("MQ-Parallel-Sender-Partition"),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,10 +117,15 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
if (!StringUtils.isEmpty(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)) {
|
||||
tmpProperties.setTopicTenantPrefix(topicTenantPrefix);
|
||||
}
|
||||
String adminServerUrl = PropertiesUtils.getProperty(properties, PulsarMQConstants.PULSARMQ_ADMIN_SERVER_URL);
|
||||
if (!StringUtils.isEmpty(adminServerUrl)) {
|
||||
tmpProperties.setAdminServerUrl(adminServerUrl);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Load pulsar properties ==> {}", JSON.toJSON(this.mqProperties));
|
||||
}
|
||||
@@ -119,8 +139,8 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
* </p>
|
||||
*
|
||||
* @param destination 消息目标信息
|
||||
* @param message 消息
|
||||
* @param callback 消息发送结果回调
|
||||
* @param message 消息
|
||||
* @param callback 消息发送结果回调
|
||||
* @return void
|
||||
* @date 2021/9/2 22:01
|
||||
* @author chad
|
||||
@@ -133,9 +153,8 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
try {
|
||||
if (!StringUtils.isEmpty(destination.getDynamicTopic())) {
|
||||
// 动态topic
|
||||
Map<String, com.alibaba.otter.canal.protocol.Message> messageMap = MQMessageUtils.messageTopics(message,
|
||||
destination.getTopic(),
|
||||
destination.getDynamicTopic());
|
||||
Map<String, com.alibaba.otter.canal.protocol.Message> messageMap = MQMessageUtils
|
||||
.messageTopics(message, destination.getTopic(), destination.getDynamicTopic());
|
||||
|
||||
for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) {
|
||||
String topicName = entry.getKey().replace('.', '_');
|
||||
@@ -174,14 +193,20 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
* @author chad
|
||||
* @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的分区数
|
||||
Integer partitionNum = MQMessageUtils.parseDynamicTopicPartition(topicName,
|
||||
destination.getDynamicTopicPartitionNum());
|
||||
destination.getDynamicTopicPartitionNum());
|
||||
if (partitionNum == null) {
|
||||
partitionNum = destination.getPartitionsNum();
|
||||
}
|
||||
// 创建多分区topic
|
||||
if (pulsarAdmin != null && partitionNum != null && partitionNum > 0 && PRODUCERS.get(topicName) == null) {
|
||||
createMultipleTopic(topicName, partitionNum);
|
||||
}
|
||||
|
||||
ExecutorTemplate template = new ExecutorTemplate(sendPartitionExecutor);
|
||||
// 并发构造
|
||||
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,
|
||||
message.getId(),
|
||||
partitionNum,
|
||||
destination.getPartitionHash(),
|
||||
mqProperties.isDatabaseHash());
|
||||
message.getId(),
|
||||
partitionNum,
|
||||
destination.getPartitionHash(),
|
||||
mqProperties.isDatabaseHash());
|
||||
// 发送
|
||||
int len = messages.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
@@ -228,9 +253,9 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
|
||||
for (FlatMessage flatMessage : flatMessages) {
|
||||
FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage,
|
||||
partitionNum,
|
||||
destination.getPartitionHash(),
|
||||
mqProperties.isDatabaseHash());
|
||||
partitionNum,
|
||||
destination.getPartitionHash(),
|
||||
mqProperties.isDatabaseHash());
|
||||
int length = partitionFlatMessage.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
// 增加null判断,issue #3267
|
||||
@@ -264,9 +289,9 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
/**
|
||||
* 发送原始消息,需要做分区处理
|
||||
*
|
||||
* @param topic topic
|
||||
* @param topic topic
|
||||
* @param partitionNum 目标分区
|
||||
* @param msg 原始消息内容
|
||||
* @param msg 原始消息内容
|
||||
* @return void
|
||||
* @date 2021/9/10 17:55
|
||||
* @author chad
|
||||
@@ -277,8 +302,9 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
byte[] msgBytes = CanalMessageSerializerUtil.serializer(msg, mqProperties.isFilterTransactionEntry());
|
||||
try {
|
||||
MessageId msgResultId = producer.newMessage()
|
||||
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partitionNum))
|
||||
.value(msgBytes).send();
|
||||
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partitionNum))
|
||||
.value(msgBytes)
|
||||
.send();
|
||||
// todo 判断发送结果
|
||||
if (logger.isDebugEnabled()) {
|
||||
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 扁平消息
|
||||
* @return void
|
||||
* @date 2021/9/10 18:22
|
||||
@@ -302,13 +328,12 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
Producer<byte[]> producer = getProducer(topic);
|
||||
for (FlatMessage f : flatMessages) {
|
||||
try {
|
||||
MessageId msgResultId = producer
|
||||
.newMessage()
|
||||
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
|
||||
.value(JSON.toJSONBytes(f, SerializerFeature.WriteMapNullValue))
|
||||
.send()
|
||||
//
|
||||
;
|
||||
MessageId msgResultId = producer.newMessage()
|
||||
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
|
||||
.value(JSON.toJSONBytes(f, Feature.WriteNulls))
|
||||
.send()
|
||||
//
|
||||
;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Send Messages to topic:{} Result: {}", topic, msgResultId);
|
||||
}
|
||||
@@ -319,22 +344,44 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定topic的生产者,并且使用缓存
|
||||
*
|
||||
* 创建多分区topic
|
||||
*
|
||||
* @param topic
|
||||
* @return org.apache.pulsar.client.api.Producer<byte [ ]>
|
||||
* @date 2021/9/10 11:21
|
||||
* @author chad
|
||||
* @since 1 by chad at 2021/9/10 新增
|
||||
* @param partitionNum
|
||||
*/
|
||||
private void createMultipleTopic(String topic, Integer partitionNum) {
|
||||
// 拼接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) {
|
||||
Producer producer = PRODUCERS.get(topic);
|
||||
|
||||
if (null == producer) {
|
||||
if (null == producer || !producer.isConnected()) {
|
||||
try {
|
||||
synchronized (PRODUCERS) {
|
||||
producer = PRODUCERS.get(topic);
|
||||
if (null != producer) {
|
||||
if (null != producer && producer.isConnected()) {
|
||||
return producer;
|
||||
}
|
||||
|
||||
@@ -351,10 +398,10 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
|
||||
// 创建指定topic的生产者
|
||||
producer = client.newProducer()
|
||||
.topic(fullTopic)
|
||||
// 指定路由器
|
||||
.messageRouter(new MessageRouterImpl(topic))
|
||||
.create();
|
||||
.topic(fullTopic)
|
||||
// 指定路由器
|
||||
.messageRouter(new MessageRouterImpl(topic))
|
||||
.create();
|
||||
// 放入缓存
|
||||
PRODUCERS.put(topic, producer);
|
||||
}
|
||||
@@ -367,6 +414,26 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
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自定义路由策略
|
||||
*
|
||||
@@ -376,9 +443,10 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
* @since 2 by chad at 2021/9/17 修改为msg自带目标分区
|
||||
*/
|
||||
private static class MessageRouterImpl implements MessageRouter {
|
||||
|
||||
private String topicLocal;
|
||||
|
||||
public MessageRouterImpl(String topicLocal) {
|
||||
public MessageRouterImpl(String topicLocal){
|
||||
this.topicLocal = topicLocal;
|
||||
}
|
||||
|
||||
@@ -390,7 +458,8 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
try {
|
||||
partition = Integer.parseInt(partitionStr);
|
||||
} 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创建时设置的分区数
|
||||
@@ -402,21 +471,4 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
pulsarmq=com.alibaba.otter.canal.connector.pulsarmq.consumer.CanalPulsarMQConsumer
|
||||
+10
-14
@@ -1,23 +1,19 @@
|
||||
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.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
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
|
||||
@@ -26,7 +22,7 @@ import static org.junit.Assert.*;
|
||||
*/
|
||||
public class CanalPulsarMQConsumerTest {
|
||||
|
||||
private Properties properties;
|
||||
private Properties properties;
|
||||
|
||||
private CanalPulsarMQConsumer consumer;
|
||||
|
||||
@@ -73,4 +69,4 @@ public class CanalPulsarMQConsumerTest {
|
||||
consumer.ack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import com.alibaba.otter.canal.common.utils.PropertiesUtils;
|
||||
import org.slf4j.Logger;
|
||||
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.consumer.CommonMessage;
|
||||
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
||||
|
||||
+3
-3
@@ -10,8 +10,8 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.otter.canal.common.CanalException;
|
||||
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
||||
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());
|
||||
for (FlatMessage flatMessage : flatMessages) {
|
||||
byte[] message = JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue);
|
||||
byte[] message = JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import org.apache.rocketmq.remoting.RPCHook;
|
||||
import org.slf4j.Logger;
|
||||
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.consumer.CommonMessage;
|
||||
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
package com.alibaba.otter.canal.connector.rocketmq.producer;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.otter.canal.common.CanalException;
|
||||
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
||||
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||
@@ -263,7 +263,7 @@ import java.util.stream.Collectors;
|
||||
List<Message> messages = flatMessagePart.stream()
|
||||
.map(flatMessage -> new Message(topicName,
|
||||
((RocketMQProducerConfig) this.mqProperties).getTag(),
|
||||
JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue)))
|
||||
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls)))
|
||||
.collect(Collectors.toList());
|
||||
// 批量发送
|
||||
sendMessage(messages, index);
|
||||
@@ -278,7 +278,7 @@ import java.util.stream.Collectors;
|
||||
List<Message> messages = flatMessages.stream()
|
||||
.map(flatMessage -> new Message(topicName,
|
||||
((RocketMQProducerConfig) this.mqProperties).getTag(),
|
||||
JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue)))
|
||||
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls)))
|
||||
.collect(Collectors.toList());
|
||||
// 批量发送
|
||||
sendMessage(messages, partition);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>canal.connector</artifactId>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -3,42 +3,11 @@ package com.taobao.tddl.dbsync.binlog;
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.taobao.tddl.dbsync.binlog.event.*;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
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.BinlogCheckPointLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
|
||||
@@ -386,6 +355,12 @@ public final class LogDecoder {
|
||||
logPosition.position = header.getLogPos();
|
||||
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: {
|
||||
ViewChangeEvent event = new ViewChangeEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
|
||||
@@ -175,6 +175,11 @@ public abstract class LogEvent {
|
||||
*/
|
||||
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
|
||||
/* New MySQL/Sun events are to be added right above this comment */
|
||||
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_DATETIME2 = 18;
|
||||
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_NEWDECIMAL = 246;
|
||||
public static final int MYSQL_TYPE_ENUM = 247;
|
||||
@@ -369,8 +377,16 @@ public abstract class LogEvent {
|
||||
return "Previous_gtids";
|
||||
case PARTIAL_UPDATE_ROWS_EVENT:
|
||||
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:
|
||||
return "Unknown"; /* impossible */
|
||||
return "Unknown type:" + type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -733,7 +733,7 @@ public final class RowsLogBuffer {
|
||||
|
||||
String second = null;
|
||||
if (intpart == 0) {
|
||||
second = "00:00:00";
|
||||
second = frac < 0 ? "-00:00:00" : "00:00:00";
|
||||
} else {
|
||||
// 目前只记录秒,不处理us frac
|
||||
// if (cal == null) cal = Calendar.getInstance();
|
||||
@@ -1133,6 +1133,8 @@ public final class RowsLogBuffer {
|
||||
length = len;
|
||||
break;
|
||||
}
|
||||
case LogEvent.MYSQL_TYPE_BOOL :
|
||||
case LogEvent.MYSQL_TYPE_INVALID :
|
||||
default:
|
||||
logger.error(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)",
|
||||
type,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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.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
|
||||
@@ -343,12 +342,14 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
public int charset; // 可以通过CharsetUtil进行转化
|
||||
public int geoType;
|
||||
public boolean nullable;
|
||||
public boolean visibility;
|
||||
public boolean array;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ColumnInfo [type=" + type + ", meta=" + meta + ", name=" + name + ", unsigned=" + unsigned
|
||||
+ ", pk=" + pk + ", set_enum_values=" + set_enum_values + ", charset=" + charset + ", geoType="
|
||||
+ geoType + ", nullable=" + nullable + "]";
|
||||
@Override public String toString() {
|
||||
return "ColumnInfo{" + "type=" + type + ", meta=" + meta + ", name='" + name + '\'' + ", unsigned="
|
||||
+ unsigned + ", pk=" + pk + ", set_enum_values=" + set_enum_values + ", charset=" + charset
|
||||
+ ", geoType=" + geoType + ", nullable=" + nullable + ", visibility=" + visibility + ", array="
|
||||
+ array + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +383,12 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
public static final int SIMPLE_PRIMARY_KEY = 8;
|
||||
// Primary key with prefix
|
||||
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 boolean existOptionalMetaData = false;
|
||||
@@ -484,6 +491,15 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
case PRIMARY_KEY_WITH_PREFIX:
|
||||
parse_pk_with_prefix(buffer, len);
|
||||
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:
|
||||
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) {
|
||||
final int limit = buffer.limit();
|
||||
|
||||
buffer.limit(len + buffer.position());
|
||||
for (int i = 0; i < columnCnt; 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_BLOB:
|
||||
case MYSQL_TYPE_MEDIUM_BLOB:
|
||||
@@ -542,6 +562,9 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
case MYSQL_TYPE_GEOMETRY:
|
||||
case MYSQL_TYPE_TIME2:
|
||||
case MYSQL_TYPE_DATETIME2:
|
||||
case MYSQL_TYPE_TIMESTAMP2:
|
||||
case MYSQL_TYPE_JSON:
|
||||
/*
|
||||
* These types store a single byte.
|
||||
@@ -550,14 +573,6 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
break;
|
||||
case MYSQL_TYPE_SET:
|
||||
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: {
|
||||
/*
|
||||
* log_event.h : The first byte is always
|
||||
@@ -585,12 +600,6 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
info.meta = x;
|
||||
break;
|
||||
}
|
||||
case MYSQL_TYPE_TIME2:
|
||||
case MYSQL_TYPE_DATETIME2:
|
||||
case MYSQL_TYPE_TIMESTAMP2: {
|
||||
info.meta = buffer.getUint8();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
info.meta = 0;
|
||||
break;
|
||||
@@ -648,6 +657,20 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
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) {
|
||||
// stores column names extracted from field
|
||||
int limit = buffer.position() + length;
|
||||
|
||||
+16
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -63,13 +63,13 @@ public class LogEventTest {
|
||||
public void getTypeNameInputPositiveOutputNotNull4() {
|
||||
|
||||
// Arrange
|
||||
final int type = 36;
|
||||
final int type = 80;
|
||||
|
||||
// Act
|
||||
final String actual = LogEvent.getTypeName(type);
|
||||
|
||||
// Assert result
|
||||
Assert.assertEquals("Unknown", actual);
|
||||
Assert.assertTrue(actual.startsWith("Unknown"));
|
||||
}
|
||||
|
||||
// Test written by Diffblue Cover.
|
||||
|
||||
+14
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.6-SNAPSHOT</version>
|
||||
<version>1.1.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -64,6 +64,19 @@
|
||||
<classifier>jar-with-dependencies</classifier>
|
||||
<scope>provided</scope>
|
||||
</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>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -79,11 +79,24 @@ in
|
||||
exit;;
|
||||
esac
|
||||
|
||||
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
|
||||
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
|
||||
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
|
||||
|
||||
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
|
||||
|
||||
@@ -36,6 +36,9 @@ public class CanalLauncher {
|
||||
logger.info("## set default uncaught exception handler");
|
||||
setGlobalUncaughtExceptionHandler();
|
||||
|
||||
// 支持rocketmq client 配置日志路径
|
||||
System.setProperty("rocketmq.client.logUseSlf4j","true");
|
||||
|
||||
logger.info("## load canal configurations");
|
||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||
Properties properties = new Properties();
|
||||
@@ -56,6 +59,10 @@ public class CanalLauncher {
|
||||
CanalConstants.CANAL_ADMIN_AUTO_REGISTER));
|
||||
String autoCluster = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_AUTO_CLUSTER);
|
||||
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);
|
||||
if (StringUtils.isEmpty(registerIp)) {
|
||||
registerIp = AddressUtils.getHostIp();
|
||||
|
||||
@@ -49,10 +49,11 @@ canal.instance.filter.black.regex=mysql\\.slave_.*
|
||||
# mq config
|
||||
canal.mq.topic=example
|
||||
# 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
|
||||
# hash partition config
|
||||
#canal.mq.enableDynamicQueuePartition=false
|
||||
#canal.mq.partitionsNum=3
|
||||
#canal.mq.partitionHash=test.table:id^name,.*\\..*
|
||||
#canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6
|
||||
#canal.mq.partitionHash=test.table:id^name,.*\\..*
|
||||
#################################################
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user