mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
22 Commits
canal-1.1.
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1413cc9196 | ||
|
|
04c11ec8eb | ||
|
|
54145ced5e | ||
|
|
fb35dd4254 | ||
|
|
e233bec66a | ||
|
|
67e0928d80 | ||
|
|
93dac2d335 | ||
|
|
9baeff55e4 | ||
|
|
ca79258e4f | ||
|
|
0e4c5c40d7 | ||
|
|
c65477d97a | ||
|
|
fc3cfc5c0c | ||
|
|
b4074206ca | ||
|
|
98677c5bc8 | ||
|
|
2c7e918f30 | ||
|
|
163f719971 | ||
|
|
55a5e7592b | ||
|
|
259008a7cd | ||
|
|
26ba84e27e | ||
|
|
c8ef9552ec | ||
|
|
bff394042a | ||
|
|
b322201889 |
@@ -93,6 +93,10 @@ canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递
|
|||||||
|
|
||||||
- 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
|
- 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
|
||||||
|
|
||||||
|
## 基于canal开发的工具
|
||||||
|
|
||||||
|
- canal2sql(基于binlog生成SQL) : [https://github.com/zhuchao941/canal2sql]
|
||||||
|
|
||||||
## 相关开源&产品
|
## 相关开源&产品
|
||||||
|
|
||||||
- [canal 消费端开源项目: Otter](http://github.com/alibaba/otter)
|
- [canal 消费端开源项目: Otter](http://github.com/alibaba/otter)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.admin</artifactId>
|
<artifactId>canal.admin</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.admin</artifactId>
|
<artifactId>canal.admin</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>canal.admin-web</artifactId>
|
<artifactId>canal.admin-web</artifactId>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.io.PrintWriter;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -26,8 +28,24 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Value(value = "${canal.adminUser}")
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
@Value(value = "${canal.adminPasswd}")
|
||||||
|
private String passwd;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
if (StringUtils.isEmpty(user)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"canal.adminUser is empty , pls check https://github.com/alibaba/canal/issues/4941");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(passwd)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"canal.adminPasswd is empty , pls check https://github.com/alibaba/canal/issues/4941");
|
||||||
|
}
|
||||||
|
|
||||||
registry.addInterceptor(new HandlerInterceptor() {
|
registry.addInterceptor(new HandlerInterceptor() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,15 +2,11 @@ package com.alibaba.otter.canal.admin.controller;
|
|||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.admin.model.BaseModel;
|
import com.alibaba.otter.canal.admin.model.BaseModel;
|
||||||
import com.alibaba.otter.canal.admin.model.CanalConfig;
|
import com.alibaba.otter.canal.admin.model.CanalConfig;
|
||||||
@@ -28,7 +24,7 @@ import com.alibaba.otter.canal.protocol.SecurityUtil;
|
|||||||
@RequestMapping("/api/{env}/config")
|
@RequestMapping("/api/{env}/config")
|
||||||
public class PollingConfigController {
|
public class PollingConfigController {
|
||||||
|
|
||||||
private static final byte[] seeds = "canal is best!".getBytes();
|
private static final byte[] seeds = RandomStringUtils.random(16).getBytes();
|
||||||
|
|
||||||
@Value(value = "${canal.adminUser}")
|
@Value(value = "${canal.adminUser}")
|
||||||
String user;
|
String user;
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ spring.datasource:
|
|||||||
|
|
||||||
canal:
|
canal:
|
||||||
adminUser: admin
|
adminUser: admin
|
||||||
adminPasswd: admin
|
adminPasswd:
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.admin</artifactId>
|
<artifactId>canal.admin</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal admin module for otter ${project.version}</name>
|
<name>canal admin module for otter ${project.version}</name>
|
||||||
<modules>
|
<modules>
|
||||||
|
|||||||
84
client-adapter/clickhouse/pom.xml
Normal file
84
client-adapter/clickhouse/pom.xml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?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">
|
||||||
|
<parent>
|
||||||
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.clickhouse</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter clickhouse module for otter ${project.version}</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.common</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.yandex.clickhouse</groupId>
|
||||||
|
<artifactId>clickhouse-jdbc</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
<configuration>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<tasks>
|
||||||
|
<copy todir="${project.basedir}/../launcher/target/classes/clickhouse" overwrite="true">
|
||||||
|
<fileset dir="${project.basedir}/target/classes/clickhouse" erroronmissingdir="true">
|
||||||
|
<include name="*.yml" />
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
|
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.clickhouse.config.ConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MirrorDbConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.monitor.ClickHouseConfigMonitor;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.service.ClickHouseBatchSyncService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.service.ClickHouseEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.service.ClickHouseMirrorDbBatchSyncService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClickHouse Adapter implementation
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 1:13
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
*/
|
||||||
|
@SPI
|
||||||
|
public class ClickHouseAdapter implements OuterAdapter {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClickHouseAdapter.class);
|
||||||
|
|
||||||
|
// Store the mapping of filename and configuration, load yml files below
|
||||||
|
// resource path
|
||||||
|
private Map<String, MappingConfig> clickHouseMapping = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// Schema -> Table -> MappingConfig
|
||||||
|
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// Mirror DB Configuration, don't need to load column mapping
|
||||||
|
private Map<String, MirrorDbConfig> mirrorDbConfigCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private DruidDataSource dataSource;
|
||||||
|
|
||||||
|
private ClickHouseBatchSyncService clickHouseBatchSyncService;
|
||||||
|
|
||||||
|
private ClickHouseMirrorDbBatchSyncService clickHouseMirrorDbBatchSyncService;
|
||||||
|
|
||||||
|
private Properties envProperties;
|
||||||
|
|
||||||
|
// Launch configuration
|
||||||
|
private OuterAdapterConfig configuration;
|
||||||
|
|
||||||
|
private ClickHouseConfigMonitor clickHouseConfigMonitor;
|
||||||
|
|
||||||
|
public Map<String, MappingConfig> getClickHouseMapping() {
|
||||||
|
return clickHouseMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||||
|
this.envProperties = envProperties;
|
||||||
|
this.configuration = configuration;
|
||||||
|
// Load DB type from adapter.launch/bootstrap.yml
|
||||||
|
Map<String, String> properties = configuration.getProperties();
|
||||||
|
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
|
||||||
|
// 当.yml文件编码格式存在问题,此处clickhouse yml文件构建 可能会抛出异常
|
||||||
|
Map<String, MappingConfig> clickHouseMappingTmp = ConfigLoader.load(envProperties);
|
||||||
|
// 过滤不匹配的key的配置
|
||||||
|
clickHouseMappingTmp.forEach((key, config) -> {
|
||||||
|
addConfig(key, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clickHouseMapping.isEmpty()) {
|
||||||
|
throw new RuntimeException("No clickhouse adapter found for config key: " + configuration.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化连接池
|
||||||
|
dataSource = new DruidDataSource();
|
||||||
|
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
|
||||||
|
dataSource.setUrl(properties.get("jdbc.url"));
|
||||||
|
dataSource.setUsername(properties.get("jdbc.username"));
|
||||||
|
dataSource.setPassword(properties.get("jdbc.password"));
|
||||||
|
dataSource.setInitialSize(1);
|
||||||
|
dataSource.setMinIdle(1);
|
||||||
|
dataSource.setMaxActive(30);
|
||||||
|
dataSource.setMaxWait(60000);
|
||||||
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
dataSource.setUseUnfairLock(true);
|
||||||
|
dataSource.setDefaultAutoCommit(false); // disable auto commit or disable Transactional
|
||||||
|
dataSource.setDbType(dbType);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String threads = properties.get("threads");
|
||||||
|
String scheduleTime = properties.get("scheduleTime");
|
||||||
|
String batchSize = properties.get("batchSize");
|
||||||
|
|
||||||
|
boolean skipDupException = BooleanUtils.toBoolean(configuration.getProperties()
|
||||||
|
.getOrDefault("skipDupException", "true"));
|
||||||
|
clickHouseBatchSyncService = new ClickHouseBatchSyncService(dataSource,
|
||||||
|
threads != null ? Integer.valueOf(threads) : null,
|
||||||
|
batchSize != null ? Integer.valueOf(batchSize) : null,
|
||||||
|
scheduleTime != null ? Long.valueOf(scheduleTime) : null,
|
||||||
|
skipDupException);
|
||||||
|
|
||||||
|
clickHouseMirrorDbBatchSyncService = new ClickHouseMirrorDbBatchSyncService(mirrorDbConfigCache,
|
||||||
|
dataSource,
|
||||||
|
threads != null ? Integer.valueOf(threads) : null,
|
||||||
|
batchSize != null ? Integer.valueOf(batchSize) : null,
|
||||||
|
scheduleTime != null ? Long.valueOf(scheduleTime) : null,
|
||||||
|
clickHouseBatchSyncService.getColumnsTypeCache(),
|
||||||
|
skipDupException);
|
||||||
|
|
||||||
|
clickHouseConfigMonitor = new ClickHouseConfigMonitor();
|
||||||
|
clickHouseConfigMonitor.init(configuration.getKey(), this, envProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync main entrance
|
||||||
|
*
|
||||||
|
* @param dmls 数据包
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
if (dmls == null || dmls.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// If mappingConfigCache(column mapping) is empty, that must be mirroring synchronize
|
||||||
|
if (!mappingConfigCache.isEmpty()) {
|
||||||
|
clickHouseBatchSyncService.sync(mappingConfigCache, dmls, envProperties);
|
||||||
|
}
|
||||||
|
clickHouseMirrorDbBatchSyncService.sync(dmls);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (clickHouseConfigMonitor != null) {
|
||||||
|
clickHouseConfigMonitor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickHouseBatchSyncService != null) {
|
||||||
|
clickHouseBatchSyncService.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ETL方法
|
||||||
|
*
|
||||||
|
* @param task 任务名, 对应配置名
|
||||||
|
* @param params etl筛选条件
|
||||||
|
* @return ETL结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public EtlResult etl(String task, List<String> params) {
|
||||||
|
EtlResult etlResult = new EtlResult();
|
||||||
|
MappingConfig config = clickHouseMapping.get(task);
|
||||||
|
ClickHouseEtlService clickhouseEtlService = new ClickHouseEtlService(dataSource, config);
|
||||||
|
if (config != null) {
|
||||||
|
return clickhouseEtlService.importData(params);
|
||||||
|
} else {
|
||||||
|
StringBuilder resultMsg = new StringBuilder();
|
||||||
|
boolean resSucc = true;
|
||||||
|
for (MappingConfig configTmp : clickHouseMapping.values()) {
|
||||||
|
// 取所有的destination为task的配置
|
||||||
|
if (configTmp.getDestination().equals(task)) {
|
||||||
|
EtlResult etlRes = clickhouseEtlService.importData(params);
|
||||||
|
if (!etlRes.getSucceeded()) {
|
||||||
|
resSucc = false;
|
||||||
|
resultMsg.append(etlRes.getErrorMessage()).append("\n");
|
||||||
|
} else {
|
||||||
|
resultMsg.append(etlRes.getResultMessage()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resultMsg.length() > 0) {
|
||||||
|
etlResult.setSucceeded(resSucc);
|
||||||
|
if (resSucc) {
|
||||||
|
etlResult.setResultMessage(resultMsg.toString());
|
||||||
|
} else {
|
||||||
|
etlResult.setErrorMessage(resultMsg.toString());
|
||||||
|
}
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
etlResult.setSucceeded(false);
|
||||||
|
etlResult.setErrorMessage("Task not found");
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取总数方法
|
||||||
|
*
|
||||||
|
* @param task 任务名, 对应配置名
|
||||||
|
* @return 总数
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> count(String task) {
|
||||||
|
MappingConfig config = clickHouseMapping.get(task);
|
||||||
|
MappingConfig.DbMapping dbMapping = config.getDbMapping();
|
||||||
|
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
|
||||||
|
Connection conn = null;
|
||||||
|
Map<String, Object> res = new LinkedHashMap<>();
|
||||||
|
try {
|
||||||
|
conn = dataSource.getConnection();
|
||||||
|
Util.sqlRS(conn, sql, rs -> {
|
||||||
|
try {
|
||||||
|
if (rs.next()) {
|
||||||
|
Long rowCount = rs.getLong("cnt");
|
||||||
|
res.put("count", rowCount);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.put("targetTable", SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应canal instance name 或 mq topic
|
||||||
|
*
|
||||||
|
* @param task 任务名, 对应配置名
|
||||||
|
* @return destination
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getDestination(String task) {
|
||||||
|
MappingConfig config = clickHouseMapping.get(task);
|
||||||
|
if (config != null) {
|
||||||
|
return config.getDestination();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
clickHouseMapping.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");
|
||||||
|
}
|
||||||
|
clickHouseMapping.put(fileName, config);
|
||||||
|
addSyncConfigToCache(fileName, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteConfig(String fileName) {
|
||||||
|
clickHouseMapping.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.config;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLICKHOUSE表映射配置加载器
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
public class ConfigLoader {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载CLICKHOUSE表映射配置
|
||||||
|
*
|
||||||
|
* @return 配置名/配置文件名--对象
|
||||||
|
*/
|
||||||
|
public static Map<String, MappingConfig> load(Properties envProperties) {
|
||||||
|
logger.info("## Start loading clickhouse mapping config ... ");
|
||||||
|
|
||||||
|
Map<String, MappingConfig> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("clickhouse");
|
||||||
|
configContentMap.forEach((fileName, content) -> {
|
||||||
|
MappingConfig config = YamlUtils.ymlToObj(null, content, MappingConfig.class, null, envProperties);
|
||||||
|
if (config == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
config.validate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
result.put(fileName, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("## ClickHouse mapping config loaded");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.config;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLICKHOUSE表映射配置
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
public class MappingConfig implements AdapterConfig {
|
||||||
|
|
||||||
|
private String dataSourceKey; // 数据源key
|
||||||
|
|
||||||
|
private String destination; // canal实例或MQ的topic
|
||||||
|
|
||||||
|
private String groupId; // groupId
|
||||||
|
|
||||||
|
private String outerAdapterKey; // 对应适配器的key
|
||||||
|
|
||||||
|
private boolean concurrent = false; // 是否并行同步
|
||||||
|
|
||||||
|
private DbMapping dbMapping; // db映射配置
|
||||||
|
|
||||||
|
public String getDataSourceKey() {
|
||||||
|
return dataSourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSourceKey(String dataSourceKey) {
|
||||||
|
this.dataSourceKey = dataSourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOuterAdapterKey() {
|
||||||
|
return outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterAdapterKey(String outerAdapterKey) {
|
||||||
|
this.outerAdapterKey = outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getConcurrent() {
|
||||||
|
return concurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConcurrent(boolean concurrent) {
|
||||||
|
this.concurrent = concurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbMapping getDbMapping() {
|
||||||
|
return dbMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDbMapping(DbMapping dbMapping) {
|
||||||
|
this.dbMapping = dbMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdapterMapping getMapping() {
|
||||||
|
return dbMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate() {
|
||||||
|
if (dbMapping.database == null || dbMapping.database.isEmpty()) {
|
||||||
|
throw new NullPointerException("dbMapping.database");
|
||||||
|
}
|
||||||
|
if (!dbMapping.getMirrorDb() && (dbMapping.table == null || dbMapping.table.isEmpty())) {
|
||||||
|
throw new NullPointerException("dbMapping.table");
|
||||||
|
}
|
||||||
|
if (!dbMapping.getMirrorDb() && (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) {
|
||||||
|
throw new NullPointerException("dbMapping.targetTable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DbMapping implements AdapterMapping {
|
||||||
|
|
||||||
|
private boolean mirrorDb = false; // 是否镜像库
|
||||||
|
private String database; // 数据库名或schema名
|
||||||
|
private String table; // 表名
|
||||||
|
private Map<String, String> targetPk = new LinkedHashMap<>(); // 目标表主键字段
|
||||||
|
private boolean mapAll = false; // 映射所有字段
|
||||||
|
private String targetDb; // 目标库名
|
||||||
|
private String targetTable; // 目标表名
|
||||||
|
private Map<String, String> targetColumns; // 目标表字段映射
|
||||||
|
|
||||||
|
private boolean caseInsensitive = false; // 目标表不区分大小写,默认是否
|
||||||
|
|
||||||
|
private String etlCondition; // etl条件sql
|
||||||
|
|
||||||
|
private int readBatch = 5000;
|
||||||
|
private int commitBatch = 5000; // etl等批量提交大小
|
||||||
|
|
||||||
|
private Map<String, String> allMapColumns;
|
||||||
|
|
||||||
|
public boolean getMirrorDb() {
|
||||||
|
return mirrorDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMirrorDb(boolean mirrorDb) {
|
||||||
|
this.mirrorDb = mirrorDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabase() {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDatabase(String database) {
|
||||||
|
this.database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTable(String table) {
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getTargetPk() {
|
||||||
|
return targetPk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetPk(Map<String, String> targetPk) {
|
||||||
|
this.targetPk = targetPk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getMapAll() {
|
||||||
|
return mapAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMapAll(Boolean mapAll) {
|
||||||
|
this.mapAll = mapAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetDb() {
|
||||||
|
return targetDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetDb(String targetDb) {
|
||||||
|
this.targetDb = targetDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetTable() {
|
||||||
|
return targetTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetTable(String targetTable) {
|
||||||
|
this.targetTable = targetTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getTargetColumns() {
|
||||||
|
if (targetColumns != null) {
|
||||||
|
targetColumns.forEach((key, value) -> {
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
targetColumns.put(key, key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return targetColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetColumns(Map<String, String> targetColumns) {
|
||||||
|
this.targetColumns = targetColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCaseInsensitive() {
|
||||||
|
return caseInsensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaseInsensitive(boolean caseInsensitive) {
|
||||||
|
this.caseInsensitive = caseInsensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEtlCondition() {
|
||||||
|
return etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEtlCondition(String etlCondition) {
|
||||||
|
this.etlCondition = etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReadBatch() {
|
||||||
|
return readBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadBatch(int readBatch) {
|
||||||
|
this.readBatch = readBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommitBatch() {
|
||||||
|
return commitBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommitBatch(int commitBatch) {
|
||||||
|
this.commitBatch = commitBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAllMapColumns() {
|
||||||
|
return allMapColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllMapColumns(Map<String, String> allMapColumns) {
|
||||||
|
this.allMapColumns = allMapColumns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.config;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MirrorDbConfig {
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
private MappingConfig mappingConfig;
|
||||||
|
private Map<String, MappingConfig> tableConfig = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static MirrorDbConfig create(String fileName, MappingConfig mappingConfig) {
|
||||||
|
return new MirrorDbConfig(fileName, mappingConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MirrorDbConfig(String fileName, MappingConfig mappingConfig){
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.mappingConfig = mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileName(String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingConfig getMappingConfig() {
|
||||||
|
return mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMappingConfig(MappingConfig mappingConfig) {
|
||||||
|
this.mappingConfig = mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, MappingConfig> getTableConfig() {
|
||||||
|
return tableConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableConfig(Map<String, MappingConfig> tableConfig) {
|
||||||
|
this.tableConfig = tableConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.monitor;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ClickHouseConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClickHouseConfigMonitor.class);
|
||||||
|
|
||||||
|
private static final String adapterName = "clickhouse";
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private ClickHouseAdapter clickHouseAdapter;
|
||||||
|
|
||||||
|
private Properties envProperties;
|
||||||
|
|
||||||
|
private FileAlterationMonitor fileMonitor;
|
||||||
|
|
||||||
|
public void init(String key, ClickHouseAdapter clickHouseAdapter, Properties envProperties) {
|
||||||
|
this.key = key;
|
||||||
|
this.clickHouseAdapter = clickHouseAdapter;
|
||||||
|
this.envProperties = envProperties;
|
||||||
|
File confDir = Util.getConfDirPath(adapterName);
|
||||||
|
try {
|
||||||
|
FileAlterationObserver observer = new FileAlterationObserver(confDir,
|
||||||
|
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
|
||||||
|
FileListener listener = new FileListener();
|
||||||
|
observer.addListener(listener);
|
||||||
|
fileMonitor = new FileAlterationMonitor(3000, observer);
|
||||||
|
fileMonitor.start();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
try {
|
||||||
|
fileMonitor.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileListener extends FileAlterationListenerAdaptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileCreate(File file) {
|
||||||
|
super.onFileCreate(file);
|
||||||
|
try {
|
||||||
|
// 加载新增的配置文件
|
||||||
|
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
MappingConfig config = YamlUtils
|
||||||
|
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
|
||||||
|
if (config == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config.validate();
|
||||||
|
boolean result = clickHouseAdapter.addConfig(file.getName(), config);
|
||||||
|
if (result) {
|
||||||
|
logger.info("Add a new clickhouse mapping config: {} to canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileChange(File file) {
|
||||||
|
super.onFileChange(file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (clickHouseAdapter.getClickHouseMapping().containsKey(file.getName())) {
|
||||||
|
// 加载配置文件
|
||||||
|
String configContent = MappingConfigsLoader
|
||||||
|
.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
if (configContent == null) {
|
||||||
|
onFileDelete(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MappingConfig config = YamlUtils
|
||||||
|
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
|
||||||
|
if (config == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config.validate();
|
||||||
|
clickHouseAdapter.updateConfig(file.getName(), config);
|
||||||
|
logger.info("Change a clickhouse mapping config: {} of canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileDelete(File file) {
|
||||||
|
super.onFileDelete(file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (clickHouseAdapter.getClickHouseMapping().containsKey(file.getName())) {
|
||||||
|
clickHouseAdapter.deleteConfig(file.getName());
|
||||||
|
|
||||||
|
logger.info("Delete a clickhouse mapping config: {} of canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,555 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.service;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Types;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig.DbMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.BatchExecutor;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SingleDml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClickHouse batch synchronize
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
public class ClickHouseBatchSyncService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClickHouseBatchSyncService.class);
|
||||||
|
|
||||||
|
private DruidDataSource dataSource;
|
||||||
|
|
||||||
|
private Map<String, Map<String, Integer>> columnsTypeCache; // Cache of instance.schema.table -> <columnName, jdbcType>
|
||||||
|
|
||||||
|
private Map<MappingConfig, List<SingleDml>>[] bufferPools; // Data buffer pool store sync data, List<Dml> dispersed as arrays according to hash value
|
||||||
|
|
||||||
|
private BatchExecutor[] batchExecutors; // Batch Executor
|
||||||
|
|
||||||
|
private BatchExecutor alterExecutors; // Alter Single Executor(update/delete/truncate)
|
||||||
|
|
||||||
|
private ExecutorService[] executorThreads; // Be initialized once
|
||||||
|
|
||||||
|
private ScheduledExecutorService[] scheduledExecutors;
|
||||||
|
|
||||||
|
private int threads = 3; // Default parallel thread count
|
||||||
|
private int batchSize = 1000;
|
||||||
|
private long scheduleTime = 10;
|
||||||
|
private boolean skipDupException;
|
||||||
|
|
||||||
|
public Map<String, Map<String, Integer>> getColumnsTypeCache() {
|
||||||
|
return columnsTypeCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClickHouseBatchSyncService(DruidDataSource dataSource, Integer threads, Integer batchSize, Long scheduleTime, boolean skipDupException){
|
||||||
|
this(dataSource, threads, batchSize, scheduleTime, new ConcurrentHashMap<>(), skipDupException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ClickHouseBatchSyncService(DruidDataSource dataSource, Integer threads, Integer batchSize, Long scheduleTime, Map<String, Map<String, Integer>> columnsTypeCache,
|
||||||
|
boolean skipDupException){
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.columnsTypeCache = columnsTypeCache;
|
||||||
|
this.skipDupException = skipDupException;
|
||||||
|
try {
|
||||||
|
if (threads != null) {
|
||||||
|
this.threads = threads;
|
||||||
|
}
|
||||||
|
if (batchSize != null) {
|
||||||
|
this.batchSize = batchSize;
|
||||||
|
}
|
||||||
|
if (scheduleTime != null) {
|
||||||
|
this.scheduleTime = scheduleTime;
|
||||||
|
}
|
||||||
|
this.alterExecutors = new BatchExecutor(dataSource);
|
||||||
|
this.bufferPools = new ConcurrentHashMap[this.threads];
|
||||||
|
this.batchExecutors = new BatchExecutor[this.threads];
|
||||||
|
this.executorThreads = new ExecutorService[this.threads];
|
||||||
|
this.scheduledExecutors = new ScheduledExecutorService[this.threads];
|
||||||
|
for (int i = 0; i < this.threads; i++) {
|
||||||
|
bufferPools[i] = new ConcurrentHashMap<>();
|
||||||
|
batchExecutors[i] = new BatchExecutor(dataSource);
|
||||||
|
executorThreads[i] = Executors.newSingleThreadExecutor();
|
||||||
|
scheduledExecutors[i] = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
}
|
||||||
|
scheduleBatchSync();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timing-driven event
|
||||||
|
* start schedule batch sync threadPool
|
||||||
|
*/
|
||||||
|
private void scheduleBatchSync() {
|
||||||
|
for (int i = 0; i < scheduledExecutors.length; i++) {
|
||||||
|
int index = i;
|
||||||
|
scheduledExecutors[i].scheduleAtFixedRate(()->{
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
|
for (MappingConfig mappingConfig : bufferPools[index].keySet()) {
|
||||||
|
List<SingleDml> dmls = bufferPools[index].get(mappingConfig);
|
||||||
|
if (dmls == null || dmls.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SingleDml> tempDmls;
|
||||||
|
synchronized (dmls) {
|
||||||
|
tempDmls = new ArrayList<>(dmls);
|
||||||
|
dmls.clear();
|
||||||
|
}
|
||||||
|
futures.add(executorThreads[index].submit(()->{
|
||||||
|
try {
|
||||||
|
insert(batchExecutors[index], mappingConfig, tempDmls);
|
||||||
|
batchExecutors[index].commit();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e){
|
||||||
|
batchExecutors[index].rollback();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, 0, scheduleTime, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
logger.info("Schedule batch executors has started successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步回调
|
||||||
|
*
|
||||||
|
* @param dmls 批量 DML
|
||||||
|
* @param function 回调方法
|
||||||
|
*/
|
||||||
|
public void sync(List<Dml> dmls, Function<Dml, Boolean> function) {
|
||||||
|
boolean toExecute = false;
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
if (!toExecute) {
|
||||||
|
toExecute = function.apply(dml);
|
||||||
|
} else {
|
||||||
|
function.apply(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distribute dmls into different partition
|
||||||
|
*
|
||||||
|
* @param mappingConfig {@link com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter#mappingConfigCache }
|
||||||
|
* @param dmls received DML
|
||||||
|
*/
|
||||||
|
public void sync(Map<String, Map<String, MappingConfig>> mappingConfig, List<Dml> dmls, Properties envProperties) {
|
||||||
|
sync(dmls, dml -> {
|
||||||
|
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
|
||||||
|
// DDL(Cache need to update when DDL was executed)
|
||||||
|
columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable());
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// DML
|
||||||
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
|
String groupId = StringUtils.trimToEmpty(dml.getGroupId());
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, MappingConfig> configMap;
|
||||||
|
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
|
||||||
|
configMap = mappingConfig.get(destination + "-" + groupId + "_" + database + "-" + table);
|
||||||
|
} else {
|
||||||
|
configMap = mappingConfig.get(destination + "_" + database + "-" + table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configMap == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configMap.values().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MappingConfig config : configMap.values()) {
|
||||||
|
distributeDml(config, dml);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dml distributor
|
||||||
|
*/
|
||||||
|
private void distributeDml(MappingConfig config, Dml dml) {
|
||||||
|
if (config != null) {
|
||||||
|
try {
|
||||||
|
String type = dml.getType();
|
||||||
|
if (type == null) return;
|
||||||
|
|
||||||
|
if (type.equalsIgnoreCase("INSERT")) {
|
||||||
|
appendDmlBufferPartition(config, dml);
|
||||||
|
} else {
|
||||||
|
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
|
||||||
|
|
||||||
|
if (type.equalsIgnoreCase("UPDATE")) {
|
||||||
|
for (SingleDml singleDml : singleDmls) {
|
||||||
|
update(alterExecutors, config, singleDml);
|
||||||
|
}
|
||||||
|
} else if (type.equalsIgnoreCase("DELETE")) {
|
||||||
|
for (SingleDml singleDml : singleDmls) {
|
||||||
|
delete(alterExecutors, config, singleDml);
|
||||||
|
}
|
||||||
|
} else if (type.equalsIgnoreCase("TRUNCATE")) {
|
||||||
|
truncate(alterExecutors, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendDmlBufferPartition(MappingConfig config, Dml dml) {
|
||||||
|
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
|
||||||
|
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
int hash = mappingHash(config.getDbMapping());
|
||||||
|
if (!config.getConcurrent()) {
|
||||||
|
hash = 0;
|
||||||
|
}
|
||||||
|
List<SingleDml> dmls = bufferPools[hash].computeIfAbsent(config, k -> new ArrayList<>());
|
||||||
|
synchronized (dmls) {
|
||||||
|
dmls.add(singleDml);
|
||||||
|
logger.info("Append one data into pool, id {}", singleDml.getData().get("id"));
|
||||||
|
}
|
||||||
|
// Check the size of the List, achieve when it reaches the maximum value
|
||||||
|
if (dmls.size() >= batchSize) syncToClickHouse(config, hash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sync when size of list{@link #bufferPools } reaches the maximum value
|
||||||
|
*
|
||||||
|
* @param config key
|
||||||
|
* @param index parallel thread index
|
||||||
|
*/
|
||||||
|
private void syncToClickHouse(MappingConfig config, int index) {
|
||||||
|
List<SingleDml> dmls = bufferPools[index].get(config);
|
||||||
|
logger.info("schema:{} table:{} reaches the maximum value, ready to synchronize, size {}", config.getDbMapping().getDatabase(), config.getDbMapping().getTable(), dmls.size());
|
||||||
|
if (dmls ==null || dmls.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SingleDml> tempDmls;
|
||||||
|
synchronized (dmls) {
|
||||||
|
tempDmls = new ArrayList<>(dmls);
|
||||||
|
dmls.clear();
|
||||||
|
}
|
||||||
|
executorThreads[index].submit(() -> {
|
||||||
|
try {
|
||||||
|
insert(batchExecutors[index], config, tempDmls);
|
||||||
|
batchExecutors[index].commit();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
batchExecutors[index].rollback();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert
|
||||||
|
*
|
||||||
|
* @param batchExecutor batch translational executor
|
||||||
|
* @param config corresponding configuration object
|
||||||
|
* @param dmls DMLs
|
||||||
|
*/
|
||||||
|
private void insert(BatchExecutor batchExecutor, MappingConfig config, List<SingleDml> dmls) throws SQLException {
|
||||||
|
if (dmls == null || dmls.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SingleDml> clearDmls = dmls.stream().filter(e -> e.getData() != null && !e.getData().isEmpty()).collect(Collectors.toList());
|
||||||
|
if (clearDmls == null || clearDmls.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
|
||||||
|
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, clearDmls.get(0).getData());
|
||||||
|
|
||||||
|
StringBuilder insertSql = new StringBuilder();
|
||||||
|
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" (");
|
||||||
|
|
||||||
|
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
|
||||||
|
.append(targetColumnName)
|
||||||
|
.append(backtick)
|
||||||
|
.append(","));
|
||||||
|
int len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||||
|
int mapLen = columnsMap.size();
|
||||||
|
for (int i = 0; i < mapLen; i++) {
|
||||||
|
insertSql.append("?,");
|
||||||
|
}
|
||||||
|
len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(")");
|
||||||
|
|
||||||
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
|
List<List<Map<String, ?>>> values = new ArrayList<>();
|
||||||
|
boolean flag = false;
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetColumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = Util.cleanColumn(targetColumnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
|
||||||
|
if (type == null) {
|
||||||
|
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < clearDmls.size(); i++) {
|
||||||
|
Map<String, Object> dmlData = clearDmls.get(i).getData();
|
||||||
|
List<Map<String, ?>> item;
|
||||||
|
if (flag == false) {
|
||||||
|
item = new ArrayList<>();
|
||||||
|
values.add(item);
|
||||||
|
} else {
|
||||||
|
item = values.get(i);
|
||||||
|
}
|
||||||
|
Object value = dmlData.get(srcColumnName);
|
||||||
|
BatchExecutor.setValue(item, type, value);
|
||||||
|
}
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
batchExecutor.batchExecute(insertSql.toString(), values);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (skipDupException
|
||||||
|
&& (e.getMessage().contains("Duplicate entry") || e.getMessage().startsWith("ORA-00001:"))) {
|
||||||
|
// ignore
|
||||||
|
// TODO 增加更多关系数据库的主键冲突的错误码
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作
|
||||||
|
*
|
||||||
|
* @param config 配置项
|
||||||
|
* @param dml DML数据
|
||||||
|
*/
|
||||||
|
private void update(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
|
||||||
|
Map<String, Object> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> old = dml.getOld();
|
||||||
|
if (old == null || old.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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("ALTER TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" UPDATE ");
|
||||||
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
|
boolean hasMatched = false;
|
||||||
|
for (String srcColumnName : old.keySet()) {
|
||||||
|
List<String> targetColumnNames = new ArrayList<>();
|
||||||
|
columnsMap.forEach((targetColumn, srcColumn) -> {
|
||||||
|
if (srcColumnName.equalsIgnoreCase(srcColumn)) {
|
||||||
|
targetColumnNames.add(targetColumn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!targetColumnNames.isEmpty()) {
|
||||||
|
hasMatched = true;
|
||||||
|
for (String targetColumnName : targetColumnNames) {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
BatchExecutor.setValue(values, type, data.get(srcColumnName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasMatched) {
|
||||||
|
logger.warn("Did not matched any columns to update ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int len = updateSql.length();
|
||||||
|
updateSql.delete(len - 2, len).append(" WHERE ");
|
||||||
|
|
||||||
|
// 拼接主键
|
||||||
|
appendCondition(dbMapping, updateSql, ctype, values, data, old);
|
||||||
|
batchExecutor.execute(updateSql.toString(), values);
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Alter update target table, sql: {}", updateSql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除操作
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param dml
|
||||||
|
*/
|
||||||
|
private void delete(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
|
||||||
|
Map<String, Object> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("ALTER TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" DELETE WHERE ");
|
||||||
|
|
||||||
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
|
// 拼接主键
|
||||||
|
appendCondition(dbMapping, sql, ctype, values, data);
|
||||||
|
batchExecutor.execute(sql.toString(), values);
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Alter delete from target table, sql: {}", sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* truncate操作
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
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, dataSource.getDbType()));
|
||||||
|
batchExecutor.execute(sql.toString(), new ArrayList<>());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Truncate target table, sql: {}", sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标字段类型
|
||||||
|
*
|
||||||
|
* @param conn sql connection
|
||||||
|
* @param config 映射配置
|
||||||
|
* @return 字段sqlType
|
||||||
|
*/
|
||||||
|
private Map<String, Integer> getTargetColumnType(Connection conn, MappingConfig config) {
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable();
|
||||||
|
Map<String, Integer> columnType = columnsTypeCache.get(cacheKey);
|
||||||
|
if (columnType == null) {
|
||||||
|
synchronized (ClickHouseBatchSyncService.class) {
|
||||||
|
columnType = columnsTypeCache.get(cacheKey);
|
||||||
|
if (columnType == null) {
|
||||||
|
columnType = new LinkedHashMap<>();
|
||||||
|
final Map<String, Integer> columnTypeTmp = columnType;
|
||||||
|
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++) {
|
||||||
|
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) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接主键 where条件
|
||||||
|
*/
|
||||||
|
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
|
||||||
|
List<Map<String, ?>> values, Map<String, Object> d) {
|
||||||
|
appendCondition(dbMapping, sql, ctype, values, d, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = Util.cleanColumn(targetColumnName);
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
// 如果有修改主键的情况
|
||||||
|
if (o != null && o.containsKey(srcColumnName)) {
|
||||||
|
BatchExecutor.setValue(values, type, o.get(srcColumnName));
|
||||||
|
} else {
|
||||||
|
BatchExecutor.setValue(values, type, d.get(srcColumnName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make sure the same table in one index
|
||||||
|
*
|
||||||
|
* @param dbMapping
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private int mappingHash(MappingConfig.DbMapping dbMapping) {
|
||||||
|
int hash = dbMapping.getDatabase().toLowerCase().hashCode() + dbMapping.getTable().toLowerCase().hashCode();
|
||||||
|
hash = Math.abs(hash) % threads;
|
||||||
|
return Math.abs(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
for (int i = 0; i < threads; i++) {
|
||||||
|
executorThreads[i].shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.service;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig.DbMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AbstractEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClickHouse ETL 操作业务类
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
public class ClickHouseEtlService extends AbstractEtlService {
|
||||||
|
|
||||||
|
private DataSource targetDS;
|
||||||
|
private MappingConfig config;
|
||||||
|
|
||||||
|
public ClickHouseEtlService(DataSource targetDS, MappingConfig config){
|
||||||
|
super("CLICKHOUSE", config);
|
||||||
|
this.targetDS = targetDS;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*/
|
||||||
|
public EtlResult importData(List<String> params) {
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
String sql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable();
|
||||||
|
return importData(sql, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行导入
|
||||||
|
*/
|
||||||
|
protected boolean executeSqlImport(DataSource srcDS, String sql, List<Object> values,
|
||||||
|
AdapterConfig.AdapterMapping mapping, AtomicLong impCount, List<String> errMsg) {
|
||||||
|
try {
|
||||||
|
DbMapping dbMapping = (DbMapping) mapping;
|
||||||
|
Map<String, String> columnsMap = new LinkedHashMap<>();
|
||||||
|
Map<String, Integer> columnType = new LinkedHashMap<>();
|
||||||
|
DruidDataSource dataSource = (DruidDataSource) srcDS;
|
||||||
|
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
|
||||||
|
|
||||||
|
Util.sqlRS(targetDS,
|
||||||
|
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
|
||||||
|
rs -> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
ResultSetMetaData rsd = rs.getMetaData();
|
||||||
|
int columnCount = rsd.getColumnCount();
|
||||||
|
List<String> columns = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
|
||||||
|
columns.add(rsd.getColumnName(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
columnsMap.putAll(SyncUtil.getColumnsMap(dbMapping, columns));
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Util.sqlRS(srcDS, sql, values, rs -> {
|
||||||
|
int idx = 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean completed = false;
|
||||||
|
|
||||||
|
StringBuilder insertSql = new StringBuilder();
|
||||||
|
insertSql.append("INSERT INTO ")
|
||||||
|
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
|
||||||
|
.append(" (");
|
||||||
|
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
|
||||||
|
.append(targetColumnName)
|
||||||
|
.append(backtick)
|
||||||
|
.append(","));
|
||||||
|
|
||||||
|
int len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||||
|
int mapLen = columnsMap.size();
|
||||||
|
for (int i = 0; i < mapLen; i++) {
|
||||||
|
insertSql.append("?,");
|
||||||
|
}
|
||||||
|
len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(")");
|
||||||
|
logger.info("executeSqlImport sql:{}", insertSql.toString());
|
||||||
|
try (Connection connTarget = targetDS.getConnection();
|
||||||
|
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
||||||
|
connTarget.setAutoCommit(false);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
completed = false;
|
||||||
|
|
||||||
|
pstmt.clearParameters();
|
||||||
|
|
||||||
|
// 删除数据
|
||||||
|
Map<String, Object> pkVal = new LinkedHashMap<>();
|
||||||
|
StringBuilder deleteSql = new StringBuilder(
|
||||||
|
"ALTER TABLE " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())
|
||||||
|
+ " DELETE WHERE ");
|
||||||
|
appendCondition(dbMapping, deleteSql, pkVal, rs, backtick);
|
||||||
|
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
||||||
|
int k = 1;
|
||||||
|
for (Object val : pkVal.values()) {
|
||||||
|
pstmt2.setObject(k++, val);
|
||||||
|
}
|
||||||
|
pstmt2.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetClolumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetClolumnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
||||||
|
Object value = rs.getObject(srcColumnName);
|
||||||
|
if (value != null) {
|
||||||
|
SyncUtil.setPStmt(type, pstmt, value, i);
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx % dbMapping.getCommitBatch() == 0) {
|
||||||
|
connTarget.commit();
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
impCount.incrementAndGet();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("successful import count:" + impCount.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!completed) {
|
||||||
|
connTarget.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e);
|
||||||
|
errMsg.add(dbMapping.getTable() + " etl failed! ==>" + e.getMessage());
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接目标表主键where条件
|
||||||
|
*/
|
||||||
|
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values,
|
||||||
|
ResultSet rs, String backtick) throws SQLException {
|
||||||
|
// 拼接主键
|
||||||
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
|
String targetColumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetColumnName;
|
||||||
|
}
|
||||||
|
sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
|
||||||
|
values.put(targetColumnName, rs.getObject(srcColumnName));
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.service;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MirrorDbConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLICKHOUSE镜像库同步操作业务
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
public class ClickHouseMirrorDbBatchSyncService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClickHouseMirrorDbBatchSyncService.class);
|
||||||
|
|
||||||
|
// 镜像库配置
|
||||||
|
private Map<String, MirrorDbConfig> mirrorDbConfigCache;
|
||||||
|
private DruidDataSource dataSource;
|
||||||
|
// clickhouseSyncService代理
|
||||||
|
private ClickHouseBatchSyncService clickHouseBatchSyncService;
|
||||||
|
|
||||||
|
public ClickHouseMirrorDbBatchSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache,
|
||||||
|
DruidDataSource dataSource, Integer threads, Integer batchSize,
|
||||||
|
Long scheduleTime, Map<String, Map<String, Integer>> columnsTypeCache,
|
||||||
|
boolean skipDupException){
|
||||||
|
this.mirrorDbConfigCache = mirrorDbConfigCache;
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.clickHouseBatchSyncService = new ClickHouseBatchSyncService(dataSource,
|
||||||
|
threads,
|
||||||
|
batchSize,
|
||||||
|
scheduleTime,
|
||||||
|
columnsTypeCache,
|
||||||
|
skipDupException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步方法
|
||||||
|
*
|
||||||
|
* @param dmls 批量 DML,包含DDL
|
||||||
|
*/
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
List<Dml> dmlList = new ArrayList<>();
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(destination + "." + database);
|
||||||
|
if (mirrorDbConfig == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (mirrorDbConfig.getMappingConfig() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dml.getGroupId() != null && StringUtils.isNotEmpty(mirrorDbConfig.getMappingConfig().getGroupId())) {
|
||||||
|
if (!mirrorDbConfig.getMappingConfig().getGroupId().equals(dml.getGroupId())) {
|
||||||
|
continue; // 如果groupId不匹配则过滤
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Feature.WriteNulls));
|
||||||
|
}
|
||||||
|
executeDdl(mirrorDbConfig, dml);
|
||||||
|
clickHouseBatchSyncService.getColumnsTypeCache()
|
||||||
|
.remove(destination + "." + database + "." + dml.getTable());
|
||||||
|
mirrorDbConfig.getTableConfig().remove(dml.getTable()); // 删除对应库表配置
|
||||||
|
} else {
|
||||||
|
// DML
|
||||||
|
initMappingConfig(dml.getTable(), mirrorDbConfig.getMappingConfig(), mirrorDbConfig, dml);
|
||||||
|
dmlList.add(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syncDml(dmlList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步Dml
|
||||||
|
*
|
||||||
|
* @param dmlList Dml列表,不包含DDL
|
||||||
|
*/
|
||||||
|
private void syncDml(List<Dml> dmlList) {
|
||||||
|
if (dmlList == null || dmlList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clickHouseBatchSyncService.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;
|
||||||
|
}
|
||||||
|
clickHouseBatchSyncService.appendDmlBufferPartition(config, dml);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化表配置
|
||||||
|
*
|
||||||
|
* @param key 配置key: destination.database.table
|
||||||
|
* @param baseConfigMap db sync config
|
||||||
|
* @param dml DML
|
||||||
|
*/
|
||||||
|
private void initMappingConfig(String key, MappingConfig baseConfigMap, MirrorDbConfig mirrorDbConfig, Dml dml) {
|
||||||
|
MappingConfig mappingConfig = mirrorDbConfig.getTableConfig().get(key);
|
||||||
|
if (mappingConfig == null) {
|
||||||
|
// 构造表配置
|
||||||
|
mappingConfig = new MappingConfig();
|
||||||
|
mappingConfig.setDataSourceKey(baseConfigMap.getDataSourceKey());
|
||||||
|
mappingConfig.setDestination(baseConfigMap.getDestination());
|
||||||
|
mappingConfig.setGroupId(baseConfigMap.getGroupId());
|
||||||
|
mappingConfig.setOuterAdapterKey(baseConfigMap.getOuterAdapterKey());
|
||||||
|
mappingConfig.setConcurrent(baseConfigMap.getConcurrent());
|
||||||
|
MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping();
|
||||||
|
mappingConfig.setDbMapping(dbMapping);
|
||||||
|
dbMapping.setDatabase(dml.getDatabase());
|
||||||
|
dbMapping.setTable(dml.getTable());
|
||||||
|
dbMapping.setTargetDb(dml.getDatabase());
|
||||||
|
dbMapping.setTargetTable(dml.getTable());
|
||||||
|
dbMapping.setMapAll(true);
|
||||||
|
List<String> pkNames = dml.getPkNames();
|
||||||
|
Map<String, String> pkMapping = new LinkedHashMap<>();
|
||||||
|
pkNames.forEach(pkName -> pkMapping.put(pkName, pkName));
|
||||||
|
dbMapping.setTargetPk(pkMapping);
|
||||||
|
|
||||||
|
mirrorDbConfig.getTableConfig().put(key, mappingConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DDL 操作
|
||||||
|
*
|
||||||
|
* @param ddl DDL
|
||||||
|
*/
|
||||||
|
private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) {
|
||||||
|
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
|
||||||
|
// 替换反引号
|
||||||
|
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()) {
|
||||||
|
logger.trace("Execute DDL sql: {} for database: {}", ddl.getSql(), ddl.getDatabase());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.support;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql批量执行器
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-7 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class BatchExecutor implements Closeable {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class);
|
||||||
|
|
||||||
|
private DataSource dataSource;
|
||||||
|
private Connection conn;
|
||||||
|
private AtomicInteger idx = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public BatchExecutor(DataSource dataSource){
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConn() {
|
||||||
|
if (conn == null) {
|
||||||
|
try {
|
||||||
|
conn = dataSource.getConnection();
|
||||||
|
this.conn.setAutoCommit(false);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setValue(List<Map<String, ?>> values, int type, Object value) {
|
||||||
|
Map<String, Object> valueItem = new HashMap<>();
|
||||||
|
valueItem.put("type", type);
|
||||||
|
valueItem.put("value", value);
|
||||||
|
values.add(valueItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(String sql, List<Map<String, ?>> values) throws SQLException {
|
||||||
|
PreparedStatement pstmt = getConn().prepareStatement(sql);
|
||||||
|
int len = values.size();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int type = (Integer) values.get(i).get("type");
|
||||||
|
Object value = values.get(i).get("value");
|
||||||
|
SyncUtil.setPStmt(type, pstmt, value, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
idx.incrementAndGet();
|
||||||
|
pstmt.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void batchExecute(String sql, List<List<Map<String, ?>>> batchValues) throws SQLException {
|
||||||
|
PreparedStatement pstmt = getConn().prepareStatement(sql);
|
||||||
|
for (int i = 0; i < batchValues.size(); i++) {
|
||||||
|
List<Map<String, ?>> values = batchValues.get(i);
|
||||||
|
for (int j = 0; j < values.size(); j++) {
|
||||||
|
int type = (Integer) values.get(j).get("type");
|
||||||
|
Object value = values.get(j).get("value");
|
||||||
|
SyncUtil.setPStmt(type, pstmt, value, j + 1);
|
||||||
|
}
|
||||||
|
pstmt.addBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.executeBatch();
|
||||||
|
idx.addAndGet(batchValues.size());
|
||||||
|
pstmt.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commit() throws SQLException {
|
||||||
|
getConn().commit();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Batch executor commit " + idx.get() + " rows");
|
||||||
|
}
|
||||||
|
idx.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rollback() throws SQLException {
|
||||||
|
getConn().rollback();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Batch executor rollback " + idx.get() + " rows");
|
||||||
|
}
|
||||||
|
idx.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
conn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.support;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class SingleDml {
|
||||||
|
|
||||||
|
private String destination;
|
||||||
|
private String database;
|
||||||
|
private String table;
|
||||||
|
private String type;
|
||||||
|
private Map<String, Object> data;
|
||||||
|
private Map<String, Object> old;
|
||||||
|
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabase() {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDatabase(String database) {
|
||||||
|
this.database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTable(String table) {
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(Map<String, Object> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getOld() {
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOld(Map<String, Object> old) {
|
||||||
|
this.old = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<SingleDml> dml2SingleDmls(Dml dml, boolean caseInsensitive) {
|
||||||
|
List<SingleDml> singleDmls = new ArrayList<>();
|
||||||
|
if (dml.getData() != null) {
|
||||||
|
int size = dml.getData().size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
SingleDml singleDml = new SingleDml();
|
||||||
|
singleDml.setDestination(dml.getDestination());
|
||||||
|
singleDml.setDatabase(dml.getDatabase());
|
||||||
|
singleDml.setTable(dml.getTable());
|
||||||
|
singleDml.setType(dml.getType());
|
||||||
|
Map<String, Object> data = dml.getData().get(i);
|
||||||
|
if (caseInsensitive) {
|
||||||
|
data = toCaseInsensitiveMap(data);
|
||||||
|
}
|
||||||
|
singleDml.setData(data);
|
||||||
|
if (dml.getOld() != null) {
|
||||||
|
Map<String, Object> oldData = dml.getOld().get(i);
|
||||||
|
if (caseInsensitive) {
|
||||||
|
oldData = toCaseInsensitiveMap(oldData);
|
||||||
|
}
|
||||||
|
singleDml.setOld(oldData);
|
||||||
|
}
|
||||||
|
singleDmls.add(singleDml);
|
||||||
|
}
|
||||||
|
} else if ("TRUNCATE".equalsIgnoreCase(dml.getType())) {
|
||||||
|
SingleDml singleDml = new SingleDml();
|
||||||
|
singleDml.setDestination(dml.getDestination());
|
||||||
|
singleDml.setDatabase(dml.getDatabase());
|
||||||
|
singleDml.setTable(dml.getTable());
|
||||||
|
singleDml.setType(dml.getType());
|
||||||
|
singleDmls.add(singleDml);
|
||||||
|
}
|
||||||
|
return singleDmls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<SingleDml> dml2SingleDmls(Dml dml) {
|
||||||
|
return dml2SingleDmls(dml, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <V> LinkedCaseInsensitiveMap<V> toCaseInsensitiveMap(Map<String, V> data) {
|
||||||
|
LinkedCaseInsensitiveMap map = new LinkedCaseInsensitiveMap();
|
||||||
|
map.putAll(data);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.support;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.druid.DbType;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
public class SyncUtil {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SyncUtil.class);
|
||||||
|
|
||||||
|
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Map<String, Object> data) {
|
||||||
|
return getColumnsMap(dbMapping, data.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Collection<String> columns) {
|
||||||
|
Map<String, String> columnsMap;
|
||||||
|
if (dbMapping.getMapAll()) {
|
||||||
|
if (dbMapping.getAllMapColumns() != null) {
|
||||||
|
return dbMapping.getAllMapColumns();
|
||||||
|
}
|
||||||
|
columnsMap = new LinkedHashMap<>();
|
||||||
|
for (String srcColumn : columns) {
|
||||||
|
boolean flag = true;
|
||||||
|
if (dbMapping.getTargetColumns() != null) {
|
||||||
|
for (Map.Entry<String, String> entry : dbMapping.getTargetColumns().entrySet()) {
|
||||||
|
if (srcColumn.equals(entry.getValue())) {
|
||||||
|
columnsMap.put(entry.getKey(), srcColumn);
|
||||||
|
flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flag) {
|
||||||
|
columnsMap.put(srcColumn, srcColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dbMapping.setAllMapColumns(columnsMap);
|
||||||
|
} else {
|
||||||
|
columnsMap = dbMapping.getTargetColumns();
|
||||||
|
}
|
||||||
|
return columnsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 preparedStatement
|
||||||
|
*
|
||||||
|
* @param type sqlType
|
||||||
|
* @param pstmt 需要设置的preparedStatement
|
||||||
|
* @param value 值
|
||||||
|
* @param i 索引号
|
||||||
|
*/
|
||||||
|
public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException {
|
||||||
|
switch (type) {
|
||||||
|
case Types.BIT:
|
||||||
|
case Types.BOOLEAN:
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
pstmt.setBoolean(i, (Boolean) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
boolean v = !value.equals("0");
|
||||||
|
pstmt.setBoolean(i, v);
|
||||||
|
} else if (value instanceof Number) {
|
||||||
|
boolean v = ((Number) value).intValue() != 0;
|
||||||
|
pstmt.setBoolean(i, v);
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.CHAR:
|
||||||
|
case Types.NCHAR:
|
||||||
|
case Types.VARCHAR:
|
||||||
|
case Types.LONGVARCHAR:
|
||||||
|
if (value instanceof String) {
|
||||||
|
pstmt.setString(i, (String) value);
|
||||||
|
} else if (value == null) {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
} else {
|
||||||
|
pstmt.setString(i, value.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TINYINT:
|
||||||
|
// 向上提升一级,处理unsigned情况
|
||||||
|
if (value instanceof Number) {
|
||||||
|
pstmt.setShort(i, ((Number) value).shortValue());
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setShort(i, Short.parseShort((String) value));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.SMALLINT:
|
||||||
|
if (value instanceof Number) {
|
||||||
|
pstmt.setInt(i, ((Number) value).intValue());
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setInt(i, Integer.parseInt((String) value));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.INTEGER:
|
||||||
|
if (value instanceof Number) {
|
||||||
|
pstmt.setLong(i, ((Number) value).longValue());
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setLong(i, Long.parseLong((String) value));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.BIGINT:
|
||||||
|
if (value instanceof Number) {
|
||||||
|
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.DECIMAL:
|
||||||
|
case Types.NUMERIC:
|
||||||
|
if (value instanceof BigDecimal) {
|
||||||
|
pstmt.setBigDecimal(i, (BigDecimal) value);
|
||||||
|
} else if (value instanceof Byte) {
|
||||||
|
pstmt.setInt(i, ((Byte) value).intValue());
|
||||||
|
} else if (value instanceof Short) {
|
||||||
|
pstmt.setInt(i, ((Short) value).intValue());
|
||||||
|
} else if (value instanceof Integer) {
|
||||||
|
pstmt.setInt(i, (Integer) value);
|
||||||
|
} else if (value instanceof Long) {
|
||||||
|
pstmt.setLong(i, (Long) value);
|
||||||
|
} else if (value instanceof Float) {
|
||||||
|
pstmt.setBigDecimal(i, new BigDecimal((float) value));
|
||||||
|
} else if (value instanceof Double) {
|
||||||
|
pstmt.setBigDecimal(i, new BigDecimal((double) value));
|
||||||
|
} else if (value != null) {
|
||||||
|
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.REAL:
|
||||||
|
if (value instanceof Number) {
|
||||||
|
pstmt.setFloat(i, ((Number) value).floatValue());
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setFloat(i, Float.parseFloat((String) value));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.FLOAT:
|
||||||
|
case Types.DOUBLE:
|
||||||
|
if (value instanceof Number) {
|
||||||
|
pstmt.setDouble(i, ((Number) value).doubleValue());
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setDouble(i, Double.parseDouble((String) value));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.BINARY:
|
||||||
|
case Types.VARBINARY:
|
||||||
|
case Types.LONGVARBINARY:
|
||||||
|
case Types.BLOB:
|
||||||
|
if (value instanceof Blob) {
|
||||||
|
pstmt.setBlob(i, (Blob) value);
|
||||||
|
} else if (value instanceof byte[]) {
|
||||||
|
pstmt.setBytes(i, (byte[]) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.CLOB:
|
||||||
|
if (value instanceof Clob) {
|
||||||
|
pstmt.setClob(i, (Clob) value);
|
||||||
|
} else if (value instanceof byte[]) {
|
||||||
|
pstmt.setBytes(i, (byte[]) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
Reader clobReader = new StringReader((String) value);
|
||||||
|
pstmt.setCharacterStream(i, clobReader);
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.DATE:
|
||||||
|
if (value instanceof Date) {
|
||||||
|
pstmt.setDate(i, (Date) value);
|
||||||
|
} else if (value instanceof java.util.Date) {
|
||||||
|
pstmt.setDate(i, new Date(((java.util.Date) value).getTime()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
String v = (String) value;
|
||||||
|
if (!v.startsWith("0000-00-00")) {
|
||||||
|
java.util.Date date = Util.parseDate(v);
|
||||||
|
if (date != null) {
|
||||||
|
pstmt.setDate(i, new Date(date.getTime()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setObject(i, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIME:
|
||||||
|
if (value instanceof Time) {
|
||||||
|
pstmt.setTime(i, (Time) value);
|
||||||
|
} else if (value instanceof java.util.Date) {
|
||||||
|
pstmt.setTime(i, new Time(((java.util.Date) value).getTime()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
String v = (String) value;
|
||||||
|
java.util.Date date = Util.parseDate(v);
|
||||||
|
if (date != null) {
|
||||||
|
pstmt.setTime(i, new Time(date.getTime()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIMESTAMP:
|
||||||
|
if (value instanceof Timestamp) {
|
||||||
|
pstmt.setTimestamp(i, (Timestamp) value);
|
||||||
|
} else if (value instanceof java.util.Date) {
|
||||||
|
pstmt.setTimestamp(i, new Timestamp(((java.util.Date) value).getTime()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
String v = (String) value;
|
||||||
|
if (!v.startsWith("0000-00-00")) {
|
||||||
|
java.util.Date date = Util.parseDate(v);
|
||||||
|
if (date != null) {
|
||||||
|
pstmt.setTimestamp(i, new Timestamp(date.getTime()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setObject(i, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pstmt.setObject(i, value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDbTableName(MappingConfig.DbMapping dbMapping, String dbType) {
|
||||||
|
String result = "";
|
||||||
|
String backtick = getBacktickByDbType(dbType);
|
||||||
|
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
|
||||||
|
result += (backtick + dbMapping.getTargetDb() + backtick + ".");
|
||||||
|
}
|
||||||
|
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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
clickhouse=com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
groupId: g1
|
||||||
|
outerAdapterKey: clickhouse1
|
||||||
|
concurrent: true
|
||||||
|
dbMapping:
|
||||||
|
database: mytest
|
||||||
|
table: user
|
||||||
|
targetTable: mytest.user
|
||||||
|
targetPk:
|
||||||
|
id: id
|
||||||
|
# mapAll: true
|
||||||
|
targetColumns:
|
||||||
|
id:
|
||||||
|
name:
|
||||||
|
role_id:
|
||||||
|
c_time:
|
||||||
|
test1:
|
||||||
|
etlCondition: "where c_time>={}"
|
||||||
|
commitBatch: 3000 # 批量提交的大小
|
||||||
|
|
||||||
|
|
||||||
|
## Mirror schema synchronize config
|
||||||
|
#dataSourceKey: defaultDS
|
||||||
|
#destination: example
|
||||||
|
#groupId: g1
|
||||||
|
#outerAdapterKey: mysql1
|
||||||
|
#concurrent: true
|
||||||
|
#dbMapping:
|
||||||
|
# mirrorDb: true
|
||||||
|
# database: mytest
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.sync.Common;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public class ClickHouseBatchSyncServiceTest {
|
||||||
|
|
||||||
|
private ClickHouseAdapter clickHouseAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
clickHouseAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void insert() throws InterruptedException {
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("INSERT");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric");
|
||||||
|
data.put("role_id", 1L);
|
||||||
|
data.put("c_time", new Date());
|
||||||
|
data.put("test1", "sdfasdfawe中国asfwef");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
clickHouseAdapter.sync(Collections.singletonList(dml));
|
||||||
|
Thread.sleep(10000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void update() {
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("name", "Eric");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
clickHouseAdapter.sync(Collections.singletonList(dml));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delete() {
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("DELETE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
clickHouseAdapter.sync(Collections.singletonList(dml));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void truncate() throws InterruptedException {
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("TRUNCATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("name", "Eric");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
clickHouseAdapter.sync(Collections.singletonList(dml));
|
||||||
|
Thread.sleep(1000L);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.sync.Common;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/13 22:27
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @description: Testing thread safe
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public class ClickHouseBatchSyncThreadSafeTest {
|
||||||
|
|
||||||
|
private ClickHouseAdapter clickHouseAdapter;
|
||||||
|
|
||||||
|
private ExecutorService executorService;
|
||||||
|
|
||||||
|
private String[] operations = new String[] { "INSERT", "UPDATE" };
|
||||||
|
|
||||||
|
private String[] tables = new String[] { "user", "customer" };
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
clickHouseAdapter = Common.init();
|
||||||
|
Common.setLogLevel(Level.INFO);
|
||||||
|
executorService = Executors.newFixedThreadPool(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test01() throws InterruptedException, ExecutionException {
|
||||||
|
ArrayList<Future> list = new ArrayList();
|
||||||
|
AtomicInteger count = new AtomicInteger();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
list.add(executorService.submit(() -> {
|
||||||
|
for (int j = 0; j < 300; j++) {
|
||||||
|
Random random = new Random();
|
||||||
|
int cou = count.incrementAndGet();
|
||||||
|
// test insert
|
||||||
|
String dmlType = operations[random.nextInt(1)];
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType(dmlType);
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable(tables[(int) Math.round(Math.random())]);
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", cou);
|
||||||
|
data.put("name", "Eric" + cou);
|
||||||
|
data.put("role_id", cou);
|
||||||
|
data.put("c_time", new Date());
|
||||||
|
data.put("test1", "sdfasdfawe中国asfwef");
|
||||||
|
dml.setData(dataList);
|
||||||
|
clickHouseAdapter.sync(Collections.singletonList(dml));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
for (Future future : list) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
Thread.sleep(10000L); // waiting multiple threads execute successfully.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
|
||||||
|
public class TestConstant {
|
||||||
|
|
||||||
|
public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true";
|
||||||
|
public final static String jdbcUser = "root";
|
||||||
|
public final static String jdbcPassword = "121212";
|
||||||
|
|
||||||
|
public final static DruidDataSource dataSource;
|
||||||
|
|
||||||
|
static {
|
||||||
|
dataSource = new DruidDataSource();
|
||||||
|
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
|
||||||
|
dataSource.setUrl(jdbcUrl);
|
||||||
|
dataSource.setUsername(jdbcUser);
|
||||||
|
dataSource.setPassword(jdbcPassword);
|
||||||
|
dataSource.setInitialSize(1);
|
||||||
|
dataSource.setMinIdle(1);
|
||||||
|
dataSource.setMaxActive(1);
|
||||||
|
dataSource.setMaxWait(60000);
|
||||||
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
dataSource.setPoolPreparedStatements(false);
|
||||||
|
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
|
||||||
|
dataSource.setValidationQuery("select 1");
|
||||||
|
try {
|
||||||
|
dataSource.init();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.sync;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.TestConstant;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/13 0:16
|
||||||
|
* @email: zhrunxin33@gmail.com @description:
|
||||||
|
*/
|
||||||
|
public class Common {
|
||||||
|
|
||||||
|
public static ClickHouseAdapter init() {
|
||||||
|
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
|
||||||
|
|
||||||
|
OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig();
|
||||||
|
outerAdapterConfig.setName("clickhouse");
|
||||||
|
outerAdapterConfig.setKey("clickhouse1");
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
properties.put("jdbc.driveClassName", "ru.yandex.clickhouse.ClickHouseDriver");
|
||||||
|
properties.put("jdbc.url", "jdbc:clickhouse://127.0.0.1:8123/default");
|
||||||
|
properties.put("jdbc.username", "default");
|
||||||
|
properties.put("jdbc.password", "123456");
|
||||||
|
outerAdapterConfig.setProperties(properties);
|
||||||
|
|
||||||
|
ClickHouseAdapter adapter = new ClickHouseAdapter();
|
||||||
|
adapter.init(outerAdapterConfig, null);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set global log level
|
||||||
|
*
|
||||||
|
* @param logLevel
|
||||||
|
*/
|
||||||
|
public static void setLogLevel(Level logLevel) {
|
||||||
|
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory
|
||||||
|
.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
logger.setLevel(logLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
groupId: g1
|
||||||
|
outerAdapterKey: clickhouse1
|
||||||
|
concurrent: true
|
||||||
|
dbMapping:
|
||||||
|
database: mytest
|
||||||
|
table: customer
|
||||||
|
targetTable: mytest.customer
|
||||||
|
targetPk:
|
||||||
|
id: id
|
||||||
|
# mapAll: true
|
||||||
|
targetColumns:
|
||||||
|
id:
|
||||||
|
name:
|
||||||
|
role_id:
|
||||||
|
c_time:
|
||||||
|
test1:
|
||||||
|
etlCondition: "where c_time>={}"
|
||||||
|
commitBatch: 3000 # 批量提交的大小
|
||||||
|
|
||||||
|
|
||||||
|
## Mirror schema synchronize config
|
||||||
|
#dataSourceKey: defaultDS
|
||||||
|
#destination: example
|
||||||
|
#groupId: g1
|
||||||
|
#outerAdapterKey: mysql1
|
||||||
|
#concurrent: true
|
||||||
|
#dbMapping:
|
||||||
|
# mirrorDb: true
|
||||||
|
# database: mytest
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
groupId: g1
|
||||||
|
outerAdapterKey: clickhouse1
|
||||||
|
concurrent: true
|
||||||
|
dbMapping:
|
||||||
|
database: mytest
|
||||||
|
table: user
|
||||||
|
targetTable: mytest.user
|
||||||
|
targetPk:
|
||||||
|
id: id
|
||||||
|
# mapAll: true
|
||||||
|
targetColumns:
|
||||||
|
id:
|
||||||
|
name:
|
||||||
|
role_id:
|
||||||
|
c_time:
|
||||||
|
test1:
|
||||||
|
etlCondition: "where c_time>={}"
|
||||||
|
commitBatch: 3000 # 批量提交的大小
|
||||||
|
|
||||||
|
|
||||||
|
## Mirror schema synchronize config
|
||||||
|
#dataSourceKey: defaultDS
|
||||||
|
#destination: example
|
||||||
|
#groupId: g1
|
||||||
|
#outerAdapterKey: mysql1
|
||||||
|
#concurrent: true
|
||||||
|
#dbMapping:
|
||||||
|
# mirrorDb: true
|
||||||
|
# database: mytest
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -74,6 +74,10 @@
|
|||||||
<artifactId>hbase-shaded-client</artifactId>
|
<artifactId>hbase-shaded-client</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.yandex.clickhouse</groupId>
|
||||||
|
<artifactId>clickhouse-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- outer adapter jar with dependencies-->
|
<!-- outer adapter jar with dependencies-->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -167,6 +171,19 @@
|
|||||||
<classifier>jar-with-dependencies</classifier>
|
<classifier>jar-with-dependencies</classifier>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.clickhouse</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
<groupId>*</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<!-- connector plugin -->
|
<!-- connector plugin -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -104,3 +104,13 @@ canal.conf:
|
|||||||
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
|
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
|
||||||
# jdbc.username:
|
# jdbc.username:
|
||||||
# jdbc.password:
|
# jdbc.password:
|
||||||
|
# - name: clickhouse
|
||||||
|
# key: clickhouse1
|
||||||
|
# properties:
|
||||||
|
# jdbc.driverClassName: ru.yandex.clickhouse.ClickHouseDriver
|
||||||
|
# jdbc.url: jdbc:clickhouse://127.0.0.1:8123/default
|
||||||
|
# jdbc.username: default
|
||||||
|
# jdbc.password: 123456
|
||||||
|
# batchSize: 3000
|
||||||
|
# scheduleTime: 600 # second unit
|
||||||
|
# threads: 3 # parallel threads
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal client adapter module for otter ${project.version}</name>
|
<name>canal client adapter module for otter ${project.version}</name>
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
<module>kudu</module>
|
<module>kudu</module>
|
||||||
<module>phoenix</module>
|
<module>phoenix</module>
|
||||||
<module>tablestore</module>
|
<module>tablestore</module>
|
||||||
|
<module>clickhouse</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
@@ -192,6 +193,11 @@
|
|||||||
<artifactId>hbase-client</artifactId>
|
<artifactId>hbase-client</artifactId>
|
||||||
<version>1.4.8</version>
|
<version>1.4.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.yandex.clickhouse</groupId>
|
||||||
|
<artifactId>clickhouse-jdbc</artifactId>
|
||||||
|
<version>0.3.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun.openservices</groupId>
|
<groupId>com.aliyun.openservices</groupId>
|
||||||
<artifactId>tablestore</artifactId>
|
<artifactId>tablestore</artifactId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.alibaba.otter.canal.client.kafka;
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -11,6 +10,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
|
|||||||
import org.apache.kafka.common.TopicPartition;
|
import org.apache.kafka.common.TopicPartition;
|
||||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.otter.canal.client.CanalMQConnector;
|
import com.alibaba.otter.canal.client.CanalMQConnector;
|
||||||
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
||||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
@@ -43,7 +43,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
private Map<Integer, Long> currentOffsets = new ConcurrentHashMap<>();
|
private Map<Integer, Long> currentOffsets = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId, Integer batchSize,
|
public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId, Integer batchSize,
|
||||||
boolean flatMessage){
|
boolean flatMessage){
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.partition = partition;
|
this.partition = partition;
|
||||||
this.flatMessage = flatMessage;
|
this.flatMessage = flatMessage;
|
||||||
@@ -324,4 +324,3 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.common</artifactId>
|
<artifactId>canal.common</artifactId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.util.concurrent.Callable;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.utils.PropertiesUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||||
import org.apache.kafka.clients.producer.Producer;
|
import org.apache.kafka.clients.producer.Producer;
|
||||||
@@ -21,6 +20,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONWriter;
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
||||||
|
import com.alibaba.otter.canal.common.utils.PropertiesUtils;
|
||||||
import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer;
|
import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer;
|
||||||
import com.alibaba.otter.canal.connector.core.producer.MQDestination;
|
import com.alibaba.otter.canal.connector.core.producer.MQDestination;
|
||||||
import com.alibaba.otter.canal.connector.core.producer.MQMessageUtils;
|
import com.alibaba.otter.canal.connector.core.producer.MQMessageUtils;
|
||||||
@@ -249,13 +249,15 @@ public class CanalKafkaProducer extends AbstractMQProducer implements CanalMQPro
|
|||||||
FlatMessage flatMessagePart = partitionFlatMessage[i];
|
FlatMessage flatMessagePart = partitionFlatMessage[i];
|
||||||
if (flatMessagePart != null) {
|
if (flatMessagePart != null) {
|
||||||
records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart,
|
records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart,
|
||||||
JSONWriter.Feature.WriteNulls)));
|
JSONWriter.Feature.WriteNulls,
|
||||||
|
JSONWriter.Feature.LargeObject)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0;
|
final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0;
|
||||||
records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage,
|
records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage,
|
||||||
JSONWriter.Feature.WriteNulls)));
|
JSONWriter.Feature.WriteNulls,
|
||||||
|
JSONWriter.Feature.LargeObject)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal connector module for otter ${project.version}</name>
|
<name>canal connector module for otter ${project.version}</name>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
import com.alibaba.fastjson2.JSONWriter.Feature;
|
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||||
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
||||||
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
@@ -330,7 +331,7 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
try {
|
try {
|
||||||
MessageId msgResultId = producer.newMessage()
|
MessageId msgResultId = producer.newMessage()
|
||||||
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
|
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
|
||||||
.value(JSON.toJSONBytes(f, Feature.WriteNulls))
|
.value(JSON.toJSONBytes(f, Feature.WriteNulls, JSONWriter.Feature.LargeObject))
|
||||||
.send()
|
.send()
|
||||||
//
|
//
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.alibaba.otter.canal.connector.rabbitmq.producer;
|
package com.alibaba.otter.canal.connector.rabbitmq.producer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@@ -51,7 +54,13 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
|
|
||||||
ConnectionFactory factory = new ConnectionFactory();
|
ConnectionFactory factory = new ConnectionFactory();
|
||||||
String servers = rabbitMQProperties.getHost();
|
String servers = rabbitMQProperties.getHost();
|
||||||
if (servers.contains(":")) {
|
if (servers.startsWith("amqp")) {
|
||||||
|
try {
|
||||||
|
factory.setUri(servers);
|
||||||
|
} catch (URISyntaxException | NoSuchAlgorithmException | KeyManagementException ex) {
|
||||||
|
throw new CanalException("failed to parse host", ex);
|
||||||
|
}
|
||||||
|
} else if (servers.contains(":")) {
|
||||||
String[] serverHostAndPort = servers.split(":");
|
String[] serverHostAndPort = servers.split(":");
|
||||||
factory.setHost(serverHostAndPort[0]);
|
factory.setHost(serverHostAndPort[0]);
|
||||||
factory.setPort(Integer.parseInt(serverHostAndPort[1]));
|
factory.setPort(Integer.parseInt(serverHostAndPort[1]));
|
||||||
@@ -165,7 +174,8 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
// 串行分区
|
// 串行分区
|
||||||
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
|
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
|
||||||
for (FlatMessage flatMessage : flatMessages) {
|
for (FlatMessage flatMessage : flatMessages) {
|
||||||
byte[] message = JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls);
|
byte[] message = JSON
|
||||||
|
.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.LargeObject);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
|
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ import java.util.stream.Collectors;
|
|||||||
List<Message> messages = flatMessagePart.stream()
|
List<Message> messages = flatMessagePart.stream()
|
||||||
.map(flatMessage -> new Message(topicName,
|
.map(flatMessage -> new Message(topicName,
|
||||||
((RocketMQProducerConfig) this.mqProperties).getTag(),
|
((RocketMQProducerConfig) this.mqProperties).getTag(),
|
||||||
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls)))
|
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.LargeObject)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 批量发送
|
// 批量发送
|
||||||
sendMessage(messages, index);
|
sendMessage(messages, index);
|
||||||
@@ -278,7 +278,7 @@ import java.util.stream.Collectors;
|
|||||||
List<Message> messages = flatMessages.stream()
|
List<Message> messages = flatMessages.stream()
|
||||||
.map(flatMessage -> new Message(topicName,
|
.map(flatMessage -> new Message(topicName,
|
||||||
((RocketMQProducerConfig) this.mqProperties).getTag(),
|
((RocketMQProducerConfig) this.mqProperties).getTag(),
|
||||||
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls)))
|
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls,JSONWriter.Feature.LargeObject)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 批量发送
|
// 批量发送
|
||||||
sendMessage(messages, partition);
|
sendMessage(messages, partition);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.connector</artifactId>
|
<artifactId>canal.connector</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.taobao.tddl.dbsync.binlog;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value;
|
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value;
|
||||||
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_enum_type;
|
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_enum_type;
|
||||||
@@ -71,12 +72,21 @@ public class JsonDiffConversion {
|
|||||||
|
|
||||||
buffer.forward((int) value_length);
|
buffer.forward((int) value_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see https://github.com/alibaba/canal/pull/5018
|
||||||
|
if (buffer.position() - position >= len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.position() - position != len) {
|
||||||
|
throw new IllegalArgumentException("reading json diff");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print function names in reverse order.
|
// Print function names in reverse order.
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (int i = operation_names.size() - 1; i >= 0; i--) {
|
for (int i = operation_names.size() - 1; i >= 0; i--) {
|
||||||
if (i == 0 || operation_names.get(i - 1) != operation_names.get(i)) {
|
if (i == 0 || !Objects.equals(operation_names.get(i - 1), operation_names.get(i))) {
|
||||||
builder.append(operation_names.get(i)).append("(");
|
builder.append(operation_names.get(i)).append("(");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +100,7 @@ public class JsonDiffConversion {
|
|||||||
|
|
||||||
// In case this vector is empty (a no-op), make an early return
|
// In case this vector is empty (a no-op), make an early return
|
||||||
// after printing only the column name
|
// after printing only the column name
|
||||||
if (operation_names.size() == 0) {
|
if (operation_names.isEmpty()) {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +138,14 @@ public class JsonDiffConversion {
|
|||||||
builder.append(jsonBuilder);
|
builder.append(jsonBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see https://github.com/alibaba/canal/pull/5018
|
||||||
|
if (buffer.position() - position >= len) {
|
||||||
|
builder.append(")");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Print closing parenthesis
|
// Print closing parenthesis
|
||||||
if (!buffer.hasRemaining() || operation_names.get(diff_i + 1) != operation_names.get(diff_i)) {
|
if (!buffer.hasRemaining() || !Objects.equals(operation_names.get(diff_i + 1), operation_names.get(diff_i))) {
|
||||||
builder.append(")");
|
builder.append(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +155,10 @@ public class JsonDiffConversion {
|
|||||||
diff_i++;
|
diff_i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer.position() - position != len) {
|
||||||
|
throw new IllegalArgumentException("reading json diff");
|
||||||
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +27,8 @@ public final class LogContext {
|
|||||||
|
|
||||||
private boolean iterateDecode = false;
|
private boolean iterateDecode = false;
|
||||||
|
|
||||||
|
private boolean compatiablePercona = false;
|
||||||
|
|
||||||
public LogContext(){
|
public LogContext(){
|
||||||
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
}
|
}
|
||||||
@@ -92,4 +93,12 @@ public final class LogContext {
|
|||||||
public void setIterateDecode(boolean iterateDecode) {
|
public void setIterateDecode(boolean iterateDecode) {
|
||||||
this.iterateDecode = iterateDecode;
|
this.iterateDecode = iterateDecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCompatiablePercona() {
|
||||||
|
return compatiablePercona;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompatiablePercona(boolean compatiablePercona) {
|
||||||
|
this.compatiablePercona = compatiablePercona;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,8 +158,6 @@ public final class LogDecoder {
|
|||||||
} finally {
|
} finally {
|
||||||
context.setIterateDecode(false);
|
context.setIterateDecode(false);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// TODO support mariadb compress binlog
|
|
||||||
}
|
}
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
@@ -193,7 +191,10 @@ public final class LogDecoder {
|
|||||||
LogEvent gtidLogEvent = context.getGtidLogEvent();
|
LogEvent gtidLogEvent = context.getGtidLogEvent();
|
||||||
switch (header.getType()) {
|
switch (header.getType()) {
|
||||||
case LogEvent.QUERY_EVENT: {
|
case LogEvent.QUERY_EVENT: {
|
||||||
QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent);
|
QueryLogEvent event = new QueryLogEvent(header,
|
||||||
|
buffer,
|
||||||
|
descriptionEvent,
|
||||||
|
context.isCompatiablePercona());
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
header.putGtid(context.getGtidSet(), gtidLogEvent);
|
header.putGtid(context.getGtidSet(), gtidLogEvent);
|
||||||
@@ -354,7 +355,10 @@ public final class LogDecoder {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.EXECUTE_LOAD_QUERY_EVENT: {
|
case LogEvent.EXECUTE_LOAD_QUERY_EVENT: {
|
||||||
ExecuteLoadQueryLogEvent event = new ExecuteLoadQueryLogEvent(header, buffer, descriptionEvent);
|
ExecuteLoadQueryLogEvent event = new ExecuteLoadQueryLogEvent(header,
|
||||||
|
buffer,
|
||||||
|
descriptionEvent,
|
||||||
|
context.isCompatiablePercona());
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ public final class ExecuteLoadQueryLogEvent extends QueryLogEvent {
|
|||||||
public static final int ELQ_FN_POS_END_OFFSET = ELQ_FILE_ID_OFFSET + 8;
|
public static final int ELQ_FN_POS_END_OFFSET = ELQ_FILE_ID_OFFSET + 8;
|
||||||
public static final int ELQ_DUP_HANDLING_OFFSET = ELQ_FILE_ID_OFFSET + 12;
|
public static final int ELQ_DUP_HANDLING_OFFSET = ELQ_FILE_ID_OFFSET + 12;
|
||||||
|
|
||||||
public ExecuteLoadQueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent)
|
public ExecuteLoadQueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
|
||||||
throws IOException{
|
boolean compatiablePercona) throws IOException{
|
||||||
super(header, buffer, descriptionEvent);
|
super(header, buffer, descriptionEvent, compatiablePercona);
|
||||||
|
|
||||||
buffer.position(descriptionEvent.commonHeaderLen + ELQ_FILE_ID_OFFSET);
|
buffer.position(descriptionEvent.commonHeaderLen + ELQ_FILE_ID_OFFSET);
|
||||||
|
|
||||||
|
|||||||
@@ -434,8 +434,8 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
private int tvSec = -1;
|
private int tvSec = -1;
|
||||||
private BigInteger ddlXid = BigInteger.valueOf(-1L);
|
private BigInteger ddlXid = BigInteger.valueOf(-1L);
|
||||||
private Charset charset;
|
private Charset charset;
|
||||||
|
|
||||||
private String timezone;
|
private String timezone;
|
||||||
|
private boolean compatiablePercona = false;
|
||||||
|
|
||||||
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent)
|
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent)
|
||||||
throws IOException{
|
throws IOException{
|
||||||
@@ -443,8 +443,15 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
|
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
|
||||||
|
boolean compatiablePercona) throws IOException{
|
||||||
|
this(header, buffer, descriptionEvent, compatiablePercona, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
|
||||||
|
boolean compatiablePercona,
|
||||||
boolean compress) throws IOException{
|
boolean compress) throws IOException{
|
||||||
super(header);
|
super(header);
|
||||||
|
this.compatiablePercona = compatiablePercona;
|
||||||
final int commonHeaderLen = descriptionEvent.commonHeaderLen;
|
final int commonHeaderLen = descriptionEvent.commonHeaderLen;
|
||||||
final int postHeaderLen = descriptionEvent.postHeaderLen[header.type - 1];
|
final int postHeaderLen = descriptionEvent.postHeaderLen[header.type - 1];
|
||||||
/*
|
/*
|
||||||
@@ -607,6 +614,21 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
*/
|
*/
|
||||||
public static final int Q_SQL_REQUIRE_PRIMARY_KEY = 19;
|
public static final int Q_SQL_REQUIRE_PRIMARY_KEY = 19;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replicate default_table_encryption.
|
||||||
|
*/
|
||||||
|
public static final int Q_DEFAULT_TABLE_ENCRYPTION = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since percona 8.0.31 Replicate ddl_skip_rewrite.
|
||||||
|
*/
|
||||||
|
public static final int Q_DDL_SKIP_REWRITE = 21;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since percona 8.0.31 Replicate Q_WSREP_SKIP_READONLY_CHECKS.
|
||||||
|
*/
|
||||||
|
public static final int Q_WSREP_SKIP_READONLY_CHECKS = 128;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FROM MariaDB 5.5.34
|
* FROM MariaDB 5.5.34
|
||||||
*/
|
*/
|
||||||
@@ -619,6 +641,8 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
|
|
||||||
public static final int Q_GTID_FLAGS3 = 130;
|
public static final int Q_GTID_FLAGS3 = 130;
|
||||||
|
|
||||||
|
public static final int Q_CHARACTER_SET_COLLATIONS = 131;
|
||||||
|
|
||||||
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
|
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try {
|
||||||
@@ -713,9 +737,24 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
// *start++ = thd->variables.sql_require_primary_key;
|
// *start++ = thd->variables.sql_require_primary_key;
|
||||||
buffer.forward(1);
|
buffer.forward(1);
|
||||||
break;
|
break;
|
||||||
|
case Q_DEFAULT_TABLE_ENCRYPTION:
|
||||||
|
// *start++ = thd->variables.default_table_encryption;
|
||||||
|
buffer.forward(1);
|
||||||
|
case Q_DDL_SKIP_REWRITE:
|
||||||
|
// *start++ = thd->variables.binlog_ddl_skip_rewrite;
|
||||||
|
buffer.forward(1);
|
||||||
case Q_HRNOW:
|
case Q_HRNOW:
|
||||||
// int when_sec_part = buffer.getUint24();
|
// https://github.com/alibaba/canal/issues/4940
|
||||||
buffer.forward(3);
|
// percona 和 mariadb各自扩展mysql binlog的格式后有冲突
|
||||||
|
// 需要精确识别一下数据库类型做兼容处理
|
||||||
|
if (compatiablePercona) {
|
||||||
|
// percona 8.0.31
|
||||||
|
// Q_WSREP_SKIP_READONLY_CHECKS *start++ = 1;
|
||||||
|
buffer.forward(1);
|
||||||
|
} else {
|
||||||
|
// int when_sec_part = buffer.getUint24();
|
||||||
|
buffer.forward(3);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Q_XID:
|
case Q_XID:
|
||||||
// xid= uint8korr(pos);
|
// xid= uint8korr(pos);
|
||||||
@@ -728,7 +767,6 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
// sa_seq_no = uint8korr(pos);
|
// sa_seq_no = uint8korr(pos);
|
||||||
// pos+= 8;
|
// pos+= 8;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
int gtid_flags_extra = buffer.getUint8();
|
int gtid_flags_extra = buffer.getUint8();
|
||||||
final int FL_COMMIT_ALTER_E1= 4;
|
final int FL_COMMIT_ALTER_E1= 4;
|
||||||
final int FL_ROLLBACK_ALTER_E1= 8;
|
final int FL_ROLLBACK_ALTER_E1= 8;
|
||||||
@@ -736,6 +774,11 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
buffer.forward(8);
|
buffer.forward(8);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Q_CHARACTER_SET_COLLATIONS :
|
||||||
|
// mariadb
|
||||||
|
int count = buffer.getUint8();
|
||||||
|
// character_set_collations= Lex_cstring((const char *) pos0 , (const char *) pos);
|
||||||
|
buffer.forward(count * 4);
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
* That's why you must write status vars in growing
|
* That's why you must write status vars in growing
|
||||||
@@ -789,8 +832,18 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
return "Q_DEFAULT_COLLATION_FOR_UTF8MB4";
|
return "Q_DEFAULT_COLLATION_FOR_UTF8MB4";
|
||||||
case Q_SQL_REQUIRE_PRIMARY_KEY:
|
case Q_SQL_REQUIRE_PRIMARY_KEY:
|
||||||
return "Q_SQL_REQUIRE_PRIMARY_KEY";
|
return "Q_SQL_REQUIRE_PRIMARY_KEY";
|
||||||
|
case Q_DEFAULT_TABLE_ENCRYPTION:
|
||||||
|
return "Q_DEFAULT_TABLE_ENCRYPTION";
|
||||||
|
case Q_DDL_SKIP_REWRITE:
|
||||||
|
return "Q_DDL_SKIP_REWRITE";
|
||||||
case Q_HRNOW:
|
case Q_HRNOW:
|
||||||
return "Q_HRNOW";
|
return "Q_HRNOW";
|
||||||
|
case Q_XID:
|
||||||
|
return "Q_XID";
|
||||||
|
case Q_GTID_FLAGS3:
|
||||||
|
return "Q_GTID_FLAGS3";
|
||||||
|
case Q_CHARACTER_SET_COLLATIONS :
|
||||||
|
return "Q_CHARACTER_SET_COLLATIONS";
|
||||||
}
|
}
|
||||||
return "CODE#" + code;
|
return "CODE#" + code;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import java.io.Serializable;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.zone.ZoneRules;
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
@@ -32,27 +30,27 @@ public final class RowsLogBuffer {
|
|||||||
public static final Integer[] integerCache = new Integer[1024 * 128];
|
public static final Integer[] integerCache = new Integer[1024 * 128];
|
||||||
public static final int integerCacheLimit = longCache.length + 127;
|
public static final int integerCacheLimit = longCache.length + 127;
|
||||||
|
|
||||||
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
||||||
public static final long TIMEF_INT_OFS = 0x800000L;
|
public static final long TIMEF_INT_OFS = 0x800000L;
|
||||||
public static final long TIMEF_OFS = 0x800000000000L;
|
public static final long TIMEF_OFS = 0x800000000000L;
|
||||||
private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||||
|
|
||||||
private final LogBuffer buffer;
|
private final LogBuffer buffer;
|
||||||
private final int columnLen;
|
private final int columnLen;
|
||||||
private final int jsonColumnCount;
|
private final int jsonColumnCount;
|
||||||
private final Charset charset;
|
private final Charset charset;
|
||||||
|
|
||||||
private final BitSet nullBits;
|
private final BitSet nullBits;
|
||||||
private int nullBitIndex;
|
private int nullBitIndex;
|
||||||
|
|
||||||
// Read value_options if this is AI for PARTIAL_UPDATE_ROWS_EVENT
|
// Read value_options if this is AI for PARTIAL_UPDATE_ROWS_EVENT
|
||||||
private final boolean partial;
|
private final boolean partial;
|
||||||
private final BitSet partialBits;
|
private final BitSet partialBits;
|
||||||
|
|
||||||
private boolean fNull;
|
private boolean fNull;
|
||||||
private int javaType;
|
private int javaType;
|
||||||
private int length;
|
private int length;
|
||||||
private Serializable value;
|
private Serializable value;
|
||||||
|
|
||||||
public RowsLogBuffer(LogBuffer buffer, final int columnLen, Charset charset, int jsonColumnCount, boolean partial){
|
public RowsLogBuffer(LogBuffer buffer, final int columnLen, Charset charset, int jsonColumnCount, boolean partial){
|
||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
@@ -71,8 +69,7 @@ public final class RowsLogBuffer {
|
|||||||
/**
|
/**
|
||||||
* Extracting next row from packed buffer.
|
* Extracting next row from packed buffer.
|
||||||
*
|
*
|
||||||
* @see mysql-5.1.60/sql/log_event.cc -
|
* @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
|
||||||
* Rows_log_event::print_verbose_one_row
|
|
||||||
*/
|
*/
|
||||||
public final boolean nextOneRow(BitSet columns, boolean after) {
|
public final boolean nextOneRow(BitSet columns, boolean after) {
|
||||||
final boolean hasOneRow = buffer.hasRemaining();
|
final boolean hasOneRow = buffer.hasRemaining();
|
||||||
@@ -105,8 +102,7 @@ public final class RowsLogBuffer {
|
|||||||
/**
|
/**
|
||||||
* Extracting next field value from packed buffer.
|
* Extracting next field value from packed buffer.
|
||||||
*
|
*
|
||||||
* @see mysql-5.1.60/sql/log_event.cc -
|
* @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
|
||||||
* Rows_log_event::print_verbose_one_row
|
|
||||||
*/
|
*/
|
||||||
public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta) {
|
public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta) {
|
||||||
return nextValue(columName, columnIndex, type, meta, false);
|
return nextValue(columName, columnIndex, type, meta, false);
|
||||||
@@ -115,8 +111,7 @@ public final class RowsLogBuffer {
|
|||||||
/**
|
/**
|
||||||
* Extracting next field value from packed buffer.
|
* Extracting next field value from packed buffer.
|
||||||
*
|
*
|
||||||
* @see mysql-5.1.60/sql/log_event.cc -
|
* @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
|
||||||
* Rows_log_event::print_verbose_one_row
|
|
||||||
*/
|
*/
|
||||||
public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta,
|
public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta,
|
||||||
boolean isBinary) {
|
boolean isBinary) {
|
||||||
@@ -300,10 +295,8 @@ public final class RowsLogBuffer {
|
|||||||
len = byte1;
|
len = byte1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)",
|
throw new IllegalArgumentException(String
|
||||||
type,
|
.format("!! Don't know how to handle column type=%d meta=%d (%04X)", type, meta, meta));
|
||||||
meta,
|
|
||||||
meta));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -359,8 +352,8 @@ public final class RowsLogBuffer {
|
|||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_DECIMAL: {
|
case LogEvent.MYSQL_TYPE_DECIMAL: {
|
||||||
/*
|
/*
|
||||||
* log_event.h : This enumeration value is only used internally
|
* log_event.h : This enumeration value is only used internally and cannot exist
|
||||||
* and cannot exist in a binlog.
|
* in a binlog.
|
||||||
*/
|
*/
|
||||||
logger.warn("MYSQL_TYPE_DECIMAL : This enumeration value is "
|
logger.warn("MYSQL_TYPE_DECIMAL : This enumeration value is "
|
||||||
+ "only used internally and cannot exist in a binlog!");
|
+ "only used internally and cannot exist in a binlog!");
|
||||||
@@ -541,12 +534,10 @@ public final class RowsLogBuffer {
|
|||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_DATETIME2: {
|
case LogEvent.MYSQL_TYPE_DATETIME2: {
|
||||||
/*
|
/*
|
||||||
* DATETIME and DATE low-level memory and disk representation
|
* DATETIME and DATE low-level memory and disk representation routines 1 bit
|
||||||
* routines 1 bit sign (used when on disk) 17 bits year*13+month
|
* sign (used when on disk) 17 bits year*13+month (year 0-9999, month 0-12) 5
|
||||||
* (year 0-9999, month 0-12) 5 bits day (0-31) 5 bits hour
|
* bits day (0-31) 5 bits hour (0-23) 6 bits minute (0-59) 6 bits second (0-59)
|
||||||
* (0-23) 6 bits minute (0-59) 6 bits second (0-59) 24 bits
|
* 24 bits microseconds (0-999999) Total: 64 bits = 8 bytes SYYYYYYY.YYYYYYYY
|
||||||
* microseconds (0-999999) Total: 64 bits = 8 bytes
|
|
||||||
* SYYYYYYY.YYYYYYYY
|
|
||||||
* .YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
|
* .YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
|
||||||
*/
|
*/
|
||||||
long intpart = buffer.getBeUlong40() - DATETIMEF_INT_OFS; // big-endian
|
long intpart = buffer.getBeUlong40() - DATETIMEF_INT_OFS; // big-endian
|
||||||
@@ -665,12 +656,10 @@ public final class RowsLogBuffer {
|
|||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_TIME2: {
|
case LogEvent.MYSQL_TYPE_TIME2: {
|
||||||
/*
|
/*
|
||||||
* TIME low-level memory and disk representation routines
|
* TIME low-level memory and disk representation routines In-memory format: 1
|
||||||
* In-memory format: 1 bit sign (Used for sign, when on disk) 1
|
* bit sign (Used for sign, when on disk) 1 bit unused (Reserved for wider hour
|
||||||
* bit unused (Reserved for wider hour range, e.g. for
|
* range, e.g. for intervals) 10 bit hour (0-836) 6 bit minute (0-59) 6 bit
|
||||||
* intervals) 10 bit hour (0-836) 6 bit minute (0-59) 6 bit
|
* second (0-59) 24 bits microseconds (0-999999) Total: 48 bits = 6 bytes
|
||||||
* second (0-59) 24 bits microseconds (0-999999) Total: 48 bits
|
|
||||||
* = 6 bytes
|
|
||||||
* Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
|
* Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
|
||||||
*/
|
*/
|
||||||
long intpart = 0;
|
long intpart = 0;
|
||||||
@@ -687,20 +676,15 @@ public final class RowsLogBuffer {
|
|||||||
frac = buffer.getUint8();
|
frac = buffer.getUint8();
|
||||||
if (intpart < 0 && frac > 0) {
|
if (intpart < 0 && frac > 0) {
|
||||||
/*
|
/*
|
||||||
* Negative values are stored with reverse
|
* Negative values are stored with reverse fractional part order, for binary
|
||||||
* fractional part order, for binary sort
|
* sort compatibility. Disk value intpart frac Time value Memory value 800000.00
|
||||||
* compatibility. Disk value intpart frac Time value
|
* 0 0 00:00:00.00 0000000000.000000 7FFFFF.FF -1 255 -00:00:00.01
|
||||||
* Memory value 800000.00 0 0 00:00:00.00
|
* FFFFFFFFFF.FFD8F0 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0 7FFFFF.00 -1
|
||||||
* 0000000000.000000 7FFFFF.FF -1 255 -00:00:00.01
|
* 0 -00:00:01.00 FFFFFFFFFF.000000 7FFFFE.FF -1 255 -00:00:01.01
|
||||||
* FFFFFFFFFF.FFD8F0 7FFFFF.9D -1 99 -00:00:00.99
|
* FFFFFFFFFE.FFD8F0 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960 Formula to
|
||||||
* FFFFFFFFFF.F0E4D0 7FFFFF.00 -1 0 -00:00:01.00
|
* convert fractional part from disk format (now stored in "frac" variable) to
|
||||||
* FFFFFFFFFF.000000 7FFFFE.FF -1 255 -00:00:01.01
|
* absolute value: "0x100 - frac". To reconstruct in-memory value, we shift to
|
||||||
* FFFFFFFFFE.FFD8F0 7FFFFE.F6 -2 246 -00:00:01.10
|
* the next integer value and then substruct fractional part.
|
||||||
* FFFFFFFFFE.FE7960 Formula to convert fractional
|
|
||||||
* part from disk format (now stored in "frac"
|
|
||||||
* variable) to absolute value: "0x100 - frac". To
|
|
||||||
* reconstruct in-memory value, we shift to the next
|
|
||||||
* integer value and then substruct fractional part.
|
|
||||||
*/
|
*/
|
||||||
intpart++; /* Shift to the next integer value */
|
intpart++; /* Shift to the next integer value */
|
||||||
frac -= 0x100; /* -(0x100 - frac) */
|
frac -= 0x100; /* -(0x100 - frac) */
|
||||||
@@ -715,9 +699,8 @@ public final class RowsLogBuffer {
|
|||||||
frac = buffer.getBeUint16();
|
frac = buffer.getBeUint16();
|
||||||
if (intpart < 0 && frac > 0) {
|
if (intpart < 0 && frac > 0) {
|
||||||
/*
|
/*
|
||||||
* Fix reverse fractional part order:
|
* Fix reverse fractional part order: "0x10000 - frac". See comments for FSP=1
|
||||||
* "0x10000 - frac". See comments for FSP=1 and
|
* and FSP=2 above.
|
||||||
* FSP=2 above.
|
|
||||||
*/
|
*/
|
||||||
intpart++; /* Shift to the next integer value */
|
intpart++; /* Shift to the next integer value */
|
||||||
frac -= 0x10000; /* -(0x10000-frac) */
|
frac -= 0x10000; /* -(0x10000-frac) */
|
||||||
@@ -789,8 +772,8 @@ public final class RowsLogBuffer {
|
|||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_NEWDATE: {
|
case LogEvent.MYSQL_TYPE_NEWDATE: {
|
||||||
/*
|
/*
|
||||||
* log_event.h : This enumeration value is only used internally
|
* log_event.h : This enumeration value is only used internally and cannot exist
|
||||||
* and cannot exist in a binlog.
|
* in a binlog.
|
||||||
*/
|
*/
|
||||||
logger.warn("MYSQL_TYPE_NEWDATE : This enumeration value is "
|
logger.warn("MYSQL_TYPE_NEWDATE : This enumeration value is "
|
||||||
+ "only used internally and cannot exist in a binlog!");
|
+ "only used internally and cannot exist in a binlog!");
|
||||||
@@ -862,8 +845,8 @@ public final class RowsLogBuffer {
|
|||||||
case LogEvent.MYSQL_TYPE_ENUM: {
|
case LogEvent.MYSQL_TYPE_ENUM: {
|
||||||
final int int32;
|
final int int32;
|
||||||
/*
|
/*
|
||||||
* log_event.h : This enumeration value is only used internally
|
* log_event.h : This enumeration value is only used internally and cannot exist
|
||||||
* and cannot exist in a binlog.
|
* in a binlog.
|
||||||
*/
|
*/
|
||||||
switch (len) {
|
switch (len) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -929,24 +912,24 @@ public final class RowsLogBuffer {
|
|||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_TINY_BLOB: {
|
case LogEvent.MYSQL_TYPE_TINY_BLOB: {
|
||||||
/*
|
/*
|
||||||
* log_event.h : This enumeration value is only used internally
|
* log_event.h : This enumeration value is only used internally and cannot exist
|
||||||
* and cannot exist in a binlog.
|
* in a binlog.
|
||||||
*/
|
*/
|
||||||
logger.warn("MYSQL_TYPE_TINY_BLOB : This enumeration value is "
|
logger.warn("MYSQL_TYPE_TINY_BLOB : This enumeration value is "
|
||||||
+ "only used internally and cannot exist in a binlog!");
|
+ "only used internally and cannot exist in a binlog!");
|
||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_MEDIUM_BLOB: {
|
case LogEvent.MYSQL_TYPE_MEDIUM_BLOB: {
|
||||||
/*
|
/*
|
||||||
* log_event.h : This enumeration value is only used internally
|
* log_event.h : This enumeration value is only used internally and cannot exist
|
||||||
* and cannot exist in a binlog.
|
* in a binlog.
|
||||||
*/
|
*/
|
||||||
logger.warn("MYSQL_TYPE_MEDIUM_BLOB : This enumeration value is "
|
logger.warn("MYSQL_TYPE_MEDIUM_BLOB : This enumeration value is "
|
||||||
+ "only used internally and cannot exist in a binlog!");
|
+ "only used internally and cannot exist in a binlog!");
|
||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_LONG_BLOB: {
|
case LogEvent.MYSQL_TYPE_LONG_BLOB: {
|
||||||
/*
|
/*
|
||||||
* log_event.h : This enumeration value is only used internally
|
* log_event.h : This enumeration value is only used internally and cannot exist
|
||||||
* and cannot exist in a binlog.
|
* in a binlog.
|
||||||
*/
|
*/
|
||||||
logger.warn("MYSQL_TYPE_LONG_BLOB : This enumeration value is "
|
logger.warn("MYSQL_TYPE_LONG_BLOB : This enumeration value is "
|
||||||
+ "only used internally and cannot exist in a binlog!");
|
+ "only used internally and cannot exist in a binlog!");
|
||||||
@@ -1005,8 +988,7 @@ public final class RowsLogBuffer {
|
|||||||
case LogEvent.MYSQL_TYPE_VAR_STRING: {
|
case LogEvent.MYSQL_TYPE_VAR_STRING: {
|
||||||
/*
|
/*
|
||||||
* Except for the data length calculation, MYSQL_TYPE_VARCHAR,
|
* Except for the data length calculation, MYSQL_TYPE_VARCHAR,
|
||||||
* MYSQL_TYPE_VAR_STRING and MYSQL_TYPE_STRING are handled the
|
* MYSQL_TYPE_VAR_STRING and MYSQL_TYPE_STRING are handled the same way.
|
||||||
* same way.
|
|
||||||
*/
|
*/
|
||||||
len = meta;
|
len = meta;
|
||||||
if (len < 256) {
|
if (len < 256) {
|
||||||
@@ -1077,30 +1059,19 @@ public final class RowsLogBuffer {
|
|||||||
if (partialBits.get(1)) {
|
if (partialBits.get(1)) {
|
||||||
// print_json_diff
|
// print_json_diff
|
||||||
int position = buffer.position();
|
int position = buffer.position();
|
||||||
StringBuilder builder = JsonDiffConversion.print_json_diff(buffer,
|
try {
|
||||||
len,
|
// https://github.com/alibaba/canal/pull/5018
|
||||||
columnName,
|
StringBuilder builder = JsonDiffConversion
|
||||||
columnIndex,
|
.print_json_diff(buffer, len, columnName, columnIndex, charset);
|
||||||
charset);
|
|
||||||
value = builder.toString();
|
|
||||||
buffer.position(position + len);
|
|
||||||
} else {
|
|
||||||
if (0 == len) {
|
|
||||||
// fixed issue #1 by lava, json column of zero length
|
|
||||||
// has no
|
|
||||||
// value, value parsing should be skipped
|
|
||||||
value = "";
|
|
||||||
} else {
|
|
||||||
int position = buffer.position();
|
|
||||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(),
|
|
||||||
buffer,
|
|
||||||
len - 1,
|
|
||||||
charset);
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
jsonValue.toJsonString(builder, charset);
|
|
||||||
value = builder.toString();
|
value = builder.toString();
|
||||||
buffer.position(position + len);
|
buffer.position(position + len);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
buffer.position(position);
|
||||||
|
// print_json_diff failed, fallback to parse_value
|
||||||
|
parseJsonFromFullValue(len);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
parseJsonFromFullValue(len);
|
||||||
}
|
}
|
||||||
javaType = Types.VARCHAR;
|
javaType = Types.VARCHAR;
|
||||||
length = len;
|
length = len;
|
||||||
@@ -1129,24 +1100,19 @@ public final class RowsLogBuffer {
|
|||||||
/* fill binary */
|
/* fill binary */
|
||||||
byte[] binary = new byte[len];
|
byte[] binary = new byte[len];
|
||||||
buffer.fillBytes(binary, 0, len);
|
buffer.fillBytes(binary, 0, len);
|
||||||
|
// Warning unsupport cloumn type
|
||||||
/* Warning unsupport cloumn type */
|
// logger.warn(String.format("!! Unsupport column type MYSQL_TYPE_GEOMETRY:
|
||||||
// logger.warn(String.format("!! Unsupport column type MYSQL_TYPE_GEOMETRY: meta=%d (%04X), len = %d",
|
// meta=%d (%04X), len = %d", meta,meta, len));
|
||||||
// meta,
|
|
||||||
// meta,
|
|
||||||
// len));
|
|
||||||
javaType = Types.BINARY;
|
javaType = Types.BINARY;
|
||||||
value = binary;
|
value = binary;
|
||||||
length = len;
|
length = len;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_BOOL :
|
case LogEvent.MYSQL_TYPE_BOOL:
|
||||||
case LogEvent.MYSQL_TYPE_INVALID :
|
case LogEvent.MYSQL_TYPE_INVALID:
|
||||||
default:
|
default:
|
||||||
logger.error(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)",
|
logger.error(
|
||||||
type,
|
String.format("!! Don't know how to handle column type=%d meta=%d (%04X)", type, meta, meta));
|
||||||
meta,
|
|
||||||
meta));
|
|
||||||
javaType = Types.OTHER;
|
javaType = Types.OTHER;
|
||||||
value = null;
|
value = null;
|
||||||
length = 0;
|
length = 0;
|
||||||
@@ -1155,6 +1121,21 @@ public final class RowsLogBuffer {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void parseJsonFromFullValue(int len) {
|
||||||
|
if (0 == len) {
|
||||||
|
// fixed issue #1 by lava, json column of zero length has no value, value
|
||||||
|
// parsing should be skipped
|
||||||
|
value = "";
|
||||||
|
} else {
|
||||||
|
int position = buffer.position();
|
||||||
|
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charset);
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
jsonValue.toJsonString(builder, charset);
|
||||||
|
value = builder.toString();
|
||||||
|
buffer.position(position + len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final boolean isNull() {
|
public final boolean isNull() {
|
||||||
return fNull;
|
return fNull;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
package com.taobao.tddl.dbsync.binlog.event.mariadb;
|
package com.taobao.tddl.dbsync.binlog.event.mariadb;
|
||||||
@@ -8,11 +8,13 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.*;
|
import com.taobao.tddl.dbsync.binlog.event.*;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class JsonDiffConversionTest {
|
|||||||
final long len = 71L;
|
final long len = 71L;
|
||||||
final String columnName = "foo";
|
final String columnName = "foo";
|
||||||
final int columnIndex = 0;
|
final int columnIndex = 0;
|
||||||
final String charsetName = "foo";
|
final String charsetName = "gbk";
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -78,7 +78,7 @@ public class JsonDiffConversionTest {
|
|||||||
final long len = 0L;
|
final long len = 0L;
|
||||||
final String columnName = "foo";
|
final String columnName = "foo";
|
||||||
final int columnIndex = 0;
|
final int columnIndex = 0;
|
||||||
final String charsetName = "foo";
|
final String charsetName = "gbk";
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -119,7 +119,7 @@ public class JsonDiffConversionTest {
|
|||||||
final long len = 0L;
|
final long len = 0L;
|
||||||
final String columnName = "foo";
|
final String columnName = "foo";
|
||||||
final int columnIndex = 0;
|
final int columnIndex = 0;
|
||||||
final String charsetName = "foo";
|
final String charsetName = "gbk";
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -160,7 +160,7 @@ public class JsonDiffConversionTest {
|
|||||||
final long len = 0L;
|
final long len = 0L;
|
||||||
final String columnName = "foo";
|
final String columnName = "foo";
|
||||||
final int columnIndex = 0;
|
final int columnIndex = 0;
|
||||||
final String charsetName = "foo";
|
final String charsetName = "gbk";
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -191,7 +191,7 @@ public class JsonDiffConversionTest {
|
|||||||
final long len = 0L;
|
final long len = 0L;
|
||||||
final String columnName = ",";
|
final String columnName = ",";
|
||||||
final int columnIndex = 0;
|
final int columnIndex = 0;
|
||||||
final String charsetName = "1a 2b 3c";
|
final String charsetName = "gbk";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
final StringBuilder actual = JsonDiffConversion.print_json_diff(buffer,
|
final StringBuilder actual = JsonDiffConversion.print_json_diff(buffer,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
package com.alibaba.otter.canal.deployer;
|
package com.alibaba.otter.canal.deployer;
|
||||||
|
|
||||||
|
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS;
|
||||||
|
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS_EXPR;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.I0Itec.zkclient.IZkStateListener;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||||
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||||
@@ -23,26 +44,6 @@ import com.alibaba.otter.canal.server.netty.CanalServerWithNetty;
|
|||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.MapMaker;
|
import com.google.common.collect.MapMaker;
|
||||||
import com.google.common.collect.MigrateMap;
|
import com.google.common.collect.MigrateMap;
|
||||||
import org.I0Itec.zkclient.IZkStateListener;
|
|
||||||
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
|
||||||
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.MDC;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS;
|
|
||||||
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS_EXPR;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal调度控制器
|
* canal调度控制器
|
||||||
@@ -116,8 +117,14 @@ public class CanalController {
|
|||||||
|
|
||||||
this.adminUser = getProperty(properties, CanalConstants.CANAL_ADMIN_USER);
|
this.adminUser = getProperty(properties, CanalConstants.CANAL_ADMIN_USER);
|
||||||
this.adminPasswd = getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD);
|
this.adminPasswd = getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD);
|
||||||
embeddedCanalServer.setUser(getProperty(properties, CanalConstants.CANAL_USER));
|
String user = getProperty(properties, CanalConstants.CANAL_USER);
|
||||||
embeddedCanalServer.setPasswd(getProperty(properties, CanalConstants.CANAL_PASSWD));
|
String passwd = getProperty(properties, CanalConstants.CANAL_PASSWD);
|
||||||
|
if (StringUtils.isNotEmpty(user) && StringUtils.isEmpty(passwd)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"canal.user = " + user + " , but canal.passwd is empty , pls check https://github.com/alibaba/canal/issues/4941");
|
||||||
|
}
|
||||||
|
embeddedCanalServer.setUser(user);
|
||||||
|
embeddedCanalServer.setPasswd(passwd);
|
||||||
|
|
||||||
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
|
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
|
||||||
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
|
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
|
||||||
@@ -601,7 +608,7 @@ public class CanalController {
|
|||||||
ZkClientx.clearClients();
|
ZkClientx.clearClients();
|
||||||
|
|
||||||
// 需要释放 CanalServerWithEmbedded 否则主线程退出后,进程无法自动完整退出...
|
// 需要释放 CanalServerWithEmbedded 否则主线程退出后,进程无法自动完整退出...
|
||||||
if (embeddedCanalServer != null) {
|
if (embeddedCanalServer != null && embeddedCanalServer.isStart()) {
|
||||||
embeddedCanalServer.stop();
|
embeddedCanalServer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ public class CanalLauncher {
|
|||||||
if (StringUtils.isNotEmpty(managerAddress)) {
|
if (StringUtils.isNotEmpty(managerAddress)) {
|
||||||
String user = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_USER);
|
String user = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_USER);
|
||||||
String passwd = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD);
|
String passwd = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD);
|
||||||
|
if (StringUtils.isEmpty(passwd)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"canal.admin.passwd is empty , pls check https://github.com/alibaba/canal/issues/4941");
|
||||||
|
}
|
||||||
String adminPort = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PORT, "11110");
|
String adminPort = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PORT, "11110");
|
||||||
boolean autoRegister = BooleanUtils.toBoolean(CanalController.getProperty(properties,
|
boolean autoRegister = BooleanUtils.toBoolean(CanalController.getProperty(properties,
|
||||||
CanalConstants.CANAL_ADMIN_AUTO_REGISTER));
|
CanalConstants.CANAL_ADMIN_AUTO_REGISTER));
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ public class CanalStarter {
|
|||||||
CanalAdminController canalAdmin = new CanalAdminController(this);
|
CanalAdminController canalAdmin = new CanalAdminController(this);
|
||||||
canalAdmin.setUser(user);
|
canalAdmin.setUser(user);
|
||||||
canalAdmin.setPasswd(passwd);
|
canalAdmin.setPasswd(passwd);
|
||||||
|
|
||||||
String ip = CanalController.getProperty(properties, CanalConstants.CANAL_IP);
|
String ip = CanalController.getProperty(properties, CanalConstants.CANAL_IP);
|
||||||
|
|
||||||
logger.debug("canal admin port:{}, canal admin user:{}, canal admin password: {}, canal ip:{}",
|
logger.debug("canal admin port:{}, canal admin user:{}, canal admin password: {}, canal ip:{}",
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class CanalAdminController implements CanalAdmin {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(CanalAdminController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CanalAdminController.class);
|
||||||
private String user;
|
private String user;
|
||||||
private String passwd;
|
private String passwd;
|
||||||
private CanalStarter canalStater;
|
private CanalStarter canalStater;
|
||||||
|
|
||||||
public CanalAdminController(CanalStarter canalStater){
|
public CanalAdminController(CanalStarter canalStater){
|
||||||
this.canalStater = canalStater;
|
this.canalStater = canalStater;
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ canal.port = 11111
|
|||||||
canal.metrics.pull.port = 11112
|
canal.metrics.pull.port = 11112
|
||||||
# canal instance user/passwd
|
# canal instance user/passwd
|
||||||
# canal.user = canal
|
# canal.user = canal
|
||||||
# canal.passwd = E3619321C1A937C46A0D8BD1DAC39F93B27D4458
|
# canal.passwd =
|
||||||
|
|
||||||
# canal admin config
|
# canal admin config
|
||||||
#canal.admin.manager = 127.0.0.1:8089
|
#canal.admin.manager = 127.0.0.1:8089
|
||||||
canal.admin.port = 11110
|
canal.admin.port = 11110
|
||||||
canal.admin.user = admin
|
canal.admin.user = admin
|
||||||
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
|
canal.admin.passwd =
|
||||||
# admin auto register
|
# admin auto register
|
||||||
#canal.admin.register.auto = true
|
#canal.admin.register.auto = true
|
||||||
#canal.admin.register.cluster =
|
#canal.admin.register.cluster =
|
||||||
@@ -176,6 +176,8 @@ rabbitmq.virtual.host =
|
|||||||
rabbitmq.exchange =
|
rabbitmq.exchange =
|
||||||
rabbitmq.username =
|
rabbitmq.username =
|
||||||
rabbitmq.password =
|
rabbitmq.password =
|
||||||
|
rabbitmq.queue =
|
||||||
|
rabbitmq.routingKey =
|
||||||
rabbitmq.deliveryMode =
|
rabbitmq.deliveryMode =
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ canal.register.ip =
|
|||||||
canal.admin.manager = 127.0.0.1:8089
|
canal.admin.manager = 127.0.0.1:8089
|
||||||
canal.admin.port = 11110
|
canal.admin.port = 11110
|
||||||
canal.admin.user = admin
|
canal.admin.user = admin
|
||||||
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
|
canal.admin.passwd =
|
||||||
# admin auto register
|
# admin auto register
|
||||||
canal.admin.register.auto = true
|
canal.admin.register.auto = true
|
||||||
canal.admin.register.cluster =
|
canal.admin.register.cluster =
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ RUN \
|
|||||||
yum clean all && yum makecache
|
yum clean all && yum makecache
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
/usr/bin/ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||||
echo 'root:Hello1234' | chpasswd && \
|
echo 'root:Hello1234' | chpasswd && \
|
||||||
groupadd -r admin && useradd -g admin admin && \
|
groupadd -r admin && useradd -g admin admin && \
|
||||||
yum install -y man && \
|
yum install -y man && \
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ RUN \
|
|||||||
yum clean all && yum makecache
|
yum clean all && yum makecache
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
/usr/bin/ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||||
echo 'root:Hello1234' | chpasswd && \
|
echo 'root:Hello1234' | chpasswd && \
|
||||||
groupadd -r admin && useradd -g admin admin && \
|
groupadd -r admin && useradd -g admin admin && \
|
||||||
yum install -y man && \
|
yum install -y man && \
|
||||||
|
|||||||
@@ -25,5 +25,5 @@ else
|
|||||||
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
||||||
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
||||||
docker build --no-cache -t canal/canal-server $BASE/
|
docker build --no-cache -t canal/canal-server $BASE/
|
||||||
# docker build -platform linux/arm64 --no-cache -t canal/canal-server $BASE/
|
# docker build --platform linux/arm64 --no-cache -t canal/canal-server $BASE/
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.instance.core</artifactId>
|
<artifactId>canal.instance.core</artifactId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.parse</artifactId>
|
<artifactId>canal.parse</artifactId>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
@@ -185,9 +186,10 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
throw new CanalParseException(e);
|
throw new CanalParseException(e);
|
||||||
} finally {
|
} finally {
|
||||||
// reset
|
// reset
|
||||||
System.setProperty("canal.instance.tsdb.url", "");
|
Properties props = System.getProperties();
|
||||||
System.setProperty("canal.instance.tsdb.dbUsername", "");
|
props.remove("canal.instance.tsdb.url");
|
||||||
System.setProperty("canal.instance.tsdb.dbPassword", "");
|
props.remove("canal.instance.tsdb.dbUsername");
|
||||||
|
props.remove("canal.instance.tsdb.dbPassword");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,21 +42,22 @@ import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
|||||||
|
|
||||||
public class MysqlConnection implements ErosaConnection {
|
public class MysqlConnection implements ErosaConnection {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class);
|
private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class);
|
||||||
|
|
||||||
private MysqlConnector connector;
|
private MysqlConnector connector;
|
||||||
private long slaveId;
|
private long slaveId;
|
||||||
private Charset charset = Charset.forName("UTF-8");
|
private Charset charset = Charset.forName("UTF-8");
|
||||||
private BinlogFormat binlogFormat;
|
private BinlogFormat binlogFormat;
|
||||||
private BinlogImage binlogImage;
|
private BinlogImage binlogImage;
|
||||||
|
|
||||||
// tsdb releated
|
// tsdb releated
|
||||||
private AuthenticationInfo authInfo;
|
private AuthenticationInfo authInfo;
|
||||||
protected int connTimeout = 5 * 1000; // 5秒
|
protected int connTimeout = 5 * 1000; // 5秒
|
||||||
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
||||||
private int binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF;
|
private int binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF;
|
||||||
// dump binlog bytes, 暂不包括meta与TSDB
|
// dump binlog bytes, 暂不包括meta与TSDB
|
||||||
private AtomicLong receivedBinlogBytes;
|
private AtomicLong receivedBinlogBytes;
|
||||||
|
private boolean compatiablePercona = false;
|
||||||
|
|
||||||
public MysqlConnection(){
|
public MysqlConnection(){
|
||||||
}
|
}
|
||||||
@@ -122,6 +123,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException {
|
public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
loadBinlogChecksum();
|
||||||
|
loadVersionComment();
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
@@ -146,6 +148,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
context.setGtidSet(gtidSet);
|
context.setGtidSet(gtidSet);
|
||||||
}
|
}
|
||||||
|
context.setCompatiablePercona(compatiablePercona);
|
||||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
accumulateReceivedBytes(fetcher.limit());
|
accumulateReceivedBytes(fetcher.limit());
|
||||||
@@ -165,12 +168,14 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
loadBinlogChecksum();
|
||||||
|
loadVersionComment();
|
||||||
sendRegisterSlave();
|
sendRegisterSlave();
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
|
context.setCompatiablePercona(compatiablePercona);
|
||||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
accumulateReceivedBytes(fetcher.limit());
|
accumulateReceivedBytes(fetcher.limit());
|
||||||
@@ -195,12 +200,14 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException {
|
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
loadBinlogChecksum();
|
||||||
|
loadVersionComment();
|
||||||
sendBinlogDumpGTID(gtidSet);
|
sendBinlogDumpGTID(gtidSet);
|
||||||
|
|
||||||
try (DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize())) {
|
try (DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize())) {
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
|
context.setCompatiablePercona(compatiablePercona);
|
||||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
||||||
// fix bug: #890 将gtid传输至context中,供decode使用
|
// fix bug: #890 将gtid传输至context中,供decode使用
|
||||||
context.setGtidSet(gtidSet);
|
context.setGtidSet(gtidSet);
|
||||||
@@ -216,7 +223,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
List<LogEvent> iterateEvents = decoder.processIterateDecode(event, context);
|
List<LogEvent> iterateEvents = decoder.processIterateDecode(event, context);
|
||||||
if (!iterateEvents.isEmpty()) {
|
if (!iterateEvents.isEmpty()) {
|
||||||
// 处理compress event
|
// 处理compress event
|
||||||
for(LogEvent itEvent : iterateEvents) {
|
for (LogEvent itEvent : iterateEvents) {
|
||||||
if (!func.sink(event)) {
|
if (!func.sink(event)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -238,10 +245,12 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
loadBinlogChecksum();
|
||||||
|
loadVersionComment();
|
||||||
sendRegisterSlave();
|
sendRegisterSlave();
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
||||||
((MysqlMultiStageCoprocessor) coprocessor).setBinlogChecksum(binlogChecksum);
|
((MysqlMultiStageCoprocessor) coprocessor).setBinlogChecksum(binlogChecksum);
|
||||||
|
((MysqlMultiStageCoprocessor) coprocessor).setCompatiablePercona(compatiablePercona);
|
||||||
try (DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize())) {
|
try (DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize())) {
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
@@ -264,9 +273,11 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException {
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
loadBinlogChecksum();
|
||||||
|
loadVersionComment();
|
||||||
sendBinlogDumpGTID(gtidSet);
|
sendBinlogDumpGTID(gtidSet);
|
||||||
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
||||||
((MysqlMultiStageCoprocessor) coprocessor).setBinlogChecksum(binlogChecksum);
|
((MysqlMultiStageCoprocessor) coprocessor).setBinlogChecksum(binlogChecksum);
|
||||||
|
((MysqlMultiStageCoprocessor) coprocessor).setCompatiablePercona(compatiablePercona);
|
||||||
try (DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize())) {
|
try (DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize())) {
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
@@ -469,13 +480,13 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MASTER_HEARTBEAT_PERIOD sets the interval in seconds between
|
* MASTER_HEARTBEAT_PERIOD sets the interval in seconds between replication
|
||||||
* replication heartbeats. Whenever the master's binary log is updated
|
* heartbeats. Whenever the master's binary log is updated with an event, the
|
||||||
* with an event, the waiting period for the next heartbeat is reset.
|
* waiting period for the next heartbeat is reset. interval is a decimal value
|
||||||
* interval is a decimal value having the range 0 to 4294967 seconds and
|
* having the range 0 to 4294967 seconds and a resolution in milliseconds; the
|
||||||
* a resolution in milliseconds; the smallest nonzero value is 0.001.
|
* smallest nonzero value is 0.001. Heartbeats are sent by the master only if
|
||||||
* Heartbeats are sent by the master only if there are no unsent events
|
* there are no unsent events in the binary log file for a period longer than
|
||||||
* in the binary log file for a period longer than interval.
|
* interval.
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
long periodNano = TimeUnit.SECONDS.toNanos(MASTER_HEARTBEAT_PERIOD_SECONDS);
|
long periodNano = TimeUnit.SECONDS.toNanos(MASTER_HEARTBEAT_PERIOD_SECONDS);
|
||||||
@@ -498,7 +509,8 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
|
|
||||||
List<String> columnValues = rs.getFieldValues();
|
List<String> columnValues = rs.getFieldValues();
|
||||||
if (columnValues == null || columnValues.size() != 2) {
|
if (columnValues == null || columnValues.size() != 2) {
|
||||||
logger.warn("unexpected binlog format query result, this may cause unexpected result, so throw exception to request network to io shutdown.");
|
logger.warn(
|
||||||
|
"unexpected binlog format query result, this may cause unexpected result, so throw exception to request network to io shutdown.");
|
||||||
throw new IllegalStateException("unexpected binlog format query result:" + rs.getFieldValues());
|
throw new IllegalStateException("unexpected binlog format query result:" + rs.getFieldValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,6 +570,25 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 识别下mysql的几种生态版本 (percona / mariadb / mysql)
|
||||||
|
*/
|
||||||
|
private void loadVersionComment() {
|
||||||
|
ResultSetPacket rs = null;
|
||||||
|
try {
|
||||||
|
rs = query("select @@version_comment");
|
||||||
|
List<String> columnValues = rs.getFieldValues();
|
||||||
|
if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0) != null) {
|
||||||
|
logger.warn("load MySQL @@version_comment : " + columnValues.get(0));
|
||||||
|
if (StringUtils.containsIgnoreCase(columnValues.get(0), "Percona")) {
|
||||||
|
compatiablePercona = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
compatiablePercona = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void accumulateReceivedBytes(long x) {
|
private void accumulateReceivedBytes(long x) {
|
||||||
if (receivedBinlogBytes != null) {
|
if (receivedBinlogBytes != null) {
|
||||||
receivedBinlogBytes.addAndGet(x);
|
receivedBinlogBytes.addAndGet(x);
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCompatiablePercona(boolean compatiablePercona) {
|
||||||
|
logContext.setCompatiablePercona(compatiablePercona);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
// fix bug #968,对于pool与
|
// fix bug #968,对于pool与
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import com.alibaba.druid.DbType;
|
||||||
|
import com.alibaba.druid.sql.SQLUtils;
|
||||||
|
import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor;
|
||||||
|
import com.alibaba.druid.sql.visitor.VisitorFeature;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -149,7 +153,14 @@ public class MemoryTableMeta implements TableMetaTSDB {
|
|||||||
StringBuilder data = new StringBuilder(4 * 1024);
|
StringBuilder data = new StringBuilder(4 * 1024);
|
||||||
for (String table : schema.showTables()) {
|
for (String table : schema.showTables()) {
|
||||||
SchemaObject schemaObject = schema.findTable(table);
|
SchemaObject schemaObject = schema.findTable(table);
|
||||||
schemaObject.getStatement().output(data);
|
// fixed issue #4899
|
||||||
|
// snapshot输出的DDL语句未正确处理mysql keyword
|
||||||
|
// 导致canal重启回滚时会出现ddl解析失败的问题
|
||||||
|
// schemaObject.getStatement().output(data);
|
||||||
|
SQLASTOutputVisitor visitor = SQLUtils.createOutputVisitor(data, DbType.mysql);
|
||||||
|
visitor.config(VisitorFeature.OutputNameQuote, true);
|
||||||
|
schemaObject.getStatement().accept(visitor);
|
||||||
|
|
||||||
data.append("; \n");
|
data.append("; \n");
|
||||||
}
|
}
|
||||||
schemaDdls.put(schema.getName(), data.toString());
|
schemaDdls.put(schema.getName(), data.toString());
|
||||||
|
|||||||
@@ -3,8 +3,13 @@ package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.druid.DbType;
|
||||||
|
import com.alibaba.druid.sql.SQLUtils;
|
||||||
|
import com.alibaba.druid.sql.repository.Schema;
|
||||||
import com.alibaba.druid.sql.repository.SchemaObject;
|
import com.alibaba.druid.sql.repository.SchemaObject;
|
||||||
import com.alibaba.druid.sql.repository.SchemaRepository;
|
import com.alibaba.druid.sql.repository.SchemaRepository;
|
||||||
|
import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor;
|
||||||
|
import com.alibaba.druid.sql.visitor.VisitorFeature;
|
||||||
import com.alibaba.druid.util.JdbcConstants;
|
import com.alibaba.druid.util.JdbcConstants;
|
||||||
|
|
||||||
public class FastsqlSchemaTest {
|
public class FastsqlSchemaTest {
|
||||||
@@ -184,5 +189,80 @@ public class FastsqlSchemaTest {
|
|||||||
repository.console(sql4);
|
repository.console(sql4);
|
||||||
table = repository.findTable("test4");
|
table = repository.findTable("test4");
|
||||||
Assert.assertTrue(table != null);
|
Assert.assertTrue(table != null);
|
||||||
|
|
||||||
|
String sql5 = "CREATE TABLE `test5` (\n" + "\t`Id` varchar(32) NOT NULL COMMENT '',\n"
|
||||||
|
+ "\t`ExitId` varchar(32) NOT NULL COMMENT '',\n"
|
||||||
|
+ "\t`CreateTime` datetime NOT NULL COMMENT '创建时间',\n"
|
||||||
|
+ "\t`archive_date` datetime NOT NULL DEFAULT '2099-01-01 00:00:00',\n"
|
||||||
|
+ "\tPRIMARY KEY (`Id`, `archive_date`),\n"
|
||||||
|
+ "\tGLOBAL INDEX `g_i_id` (`id`) COVERING (`ExitId`) \n" + "\t\tPARTITION BY KEY(`Id`)\n"
|
||||||
|
+ "\t\tPARTITIONS 16,\n" + "\tKEY `ExitId` USING BTREE (`ExitId`),\n"
|
||||||
|
+ "\tKEY `CreateTime` (`CreateTime`),\n" + "\tKEY `i_id_ExitId` USING BTREE (`Id`, `ExitId`),\n"
|
||||||
|
+ "\tKEY `auto_shard_key_ExitId_id` USING BTREE (`ExitId`, `Id`)\n"
|
||||||
|
+ ") ENGINE = InnoDB DEFAULT CHARSET = utf8\n" + "PARTITION BY KEY(`ExitId`,`Id`)\n"
|
||||||
|
+ "PARTITIONS 16\n" + "LOCAL PARTITION BY RANGE (archive_date)\n" + "INTERVAL 1 MONTH\n"
|
||||||
|
+ "EXPIRE AFTER 27\n" + "PRE ALLOCATE 2\n" + "PIVOTDATE NOW()";
|
||||||
|
repository.console(sql5);
|
||||||
|
table = repository.findTable("test5");
|
||||||
|
Assert.assertTrue(table != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_escapse_sql() {
|
||||||
|
SchemaRepository repository = new SchemaRepository(JdbcConstants.MYSQL);
|
||||||
|
repository.setDefaultSchema("test");
|
||||||
|
String sql = "CREATE TABLE test1 (\n" + "\tid int(11) NOT NULL AUTO_INCREMENT,\n"
|
||||||
|
+ "\tcluster_id int(11) NOT NULL COMMENT '集群id',\n"
|
||||||
|
+ "\tcomponent_id int(11) NOT NULL COMMENT '组件id',\n"
|
||||||
|
+ "\tcomponent_type_code tinyint(1) NOT NULL COMMENT '组件类型',\n"
|
||||||
|
+ "\ttype varchar(128) COLLATE utf8_bin NOT NULL COMMENT '配置类型',\n"
|
||||||
|
+ "\trequired tinyint(1) NOT NULL COMMENT 'true/false',\n"
|
||||||
|
+ "\t`key` varchar(256) COLLATE utf8_bin NOT NULL COMMENT '配置键',\n"
|
||||||
|
+ "\t`value` text COLLATE utf8_bin COMMENT '默认配置项',\n"
|
||||||
|
+ "\t`values` varchar(512) COLLATE utf8_bin DEFAULT NULL COMMENT '可配置项',\n"
|
||||||
|
+ "\tdependencyKey varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT '依赖键',\n"
|
||||||
|
+ "\tdependencyValue varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT '依赖值',\n"
|
||||||
|
+ "\t`desc` varchar(512) COLLATE utf8_bin DEFAULT NULL COMMENT '描述',\n"
|
||||||
|
+ "\tgmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n"
|
||||||
|
+ "\tgmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n"
|
||||||
|
+ "\tis_deleted tinyint(1) NOT NULL DEFAULT '0' COMMENT '0正常 1逻辑删除',\n" + "\tPRIMARY KEY (id),\n"
|
||||||
|
+ "\tKEY index_cluster_id (cluster_id),\n" + "\tKEY index_componentId (component_id)\n"
|
||||||
|
+ ") ENGINE = InnoDB AUTO_INCREMENT = 1140 CHARSET = utf8 COLLATE = utf8_bin;";
|
||||||
|
repository.console(sql);
|
||||||
|
SchemaObject table = repository.findTable("test1");
|
||||||
|
Assert.assertTrue(table != null);
|
||||||
|
// 应用到新的schema
|
||||||
|
Schema schema = repository.findSchema("test");
|
||||||
|
StringBuilder data = new StringBuilder(4 * 1024);
|
||||||
|
for (String tableIn : schema.showTables()) {
|
||||||
|
SchemaObject schemaObject = schema.findTable(tableIn);
|
||||||
|
|
||||||
|
SQLASTOutputVisitor visitor = SQLUtils.createOutputVisitor(data, DbType.mysql);
|
||||||
|
visitor.config(VisitorFeature.OutputNameQuote, true);
|
||||||
|
|
||||||
|
schemaObject.getStatement().accept(visitor);
|
||||||
|
data.append("; \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.setDefaultSchema("test_new");
|
||||||
|
repository.console(data.toString());
|
||||||
|
table = repository.findTable("test1");
|
||||||
|
Assert.assertTrue(table != null);
|
||||||
|
|
||||||
|
// 打印新的schema的内容
|
||||||
|
schema = repository.findSchema("test_new");
|
||||||
|
data = new StringBuilder(4 * 1024);
|
||||||
|
for (String tableIn : schema.showTables()) {
|
||||||
|
SchemaObject schemaObject = schema.findTable(tableIn);
|
||||||
|
|
||||||
|
SQLASTOutputVisitor visitor = SQLUtils.createOutputVisitor(data, DbType.mysql);
|
||||||
|
visitor.config(VisitorFeature.OutputNameQuote, true);
|
||||||
|
|
||||||
|
schemaObject.getStatement().accept(visitor);
|
||||||
|
data.append("; \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(data.toString());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
pom.xml
8
pom.xml
@@ -4,7 +4,7 @@
|
|||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal module for otter ${project.version}</name>
|
<name>canal module for otter ${project.version}</name>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<url>https://github.com/alibaba/canal</url>
|
<url>https://github.com/alibaba/canal</url>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.sonatype.oss</groupId>
|
<groupId>org.sonatype.oss</groupId>
|
||||||
@@ -326,7 +326,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>druid</artifactId>
|
<artifactId>druid</artifactId>
|
||||||
<version>1.2.17</version>
|
<version>1.2.21</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lmax</groupId>
|
<groupId>com.lmax</groupId>
|
||||||
@@ -337,12 +337,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-core</artifactId>
|
<artifactId>logback-core</artifactId>
|
||||||
<version>1.2.8</version>
|
<version>1.2.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.2.8</version>
|
<version>1.2.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.prometheus</artifactId>
|
<artifactId>canal.prometheus</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<name>canal prometheus module for otter ${project.version}</name>
|
<name>canal prometheus module for otter ${project.version}</name>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.server</artifactId>
|
<artifactId>canal.server</artifactId>
|
||||||
|
|||||||
@@ -56,12 +56,14 @@ public class ClientAuthenticationHandler extends SimpleChannelHandler {
|
|||||||
byte[] errorBytes = NettyUtils.errorPacket(400,
|
byte[] errorBytes = NettyUtils.errorPacket(400,
|
||||||
MessageFormatter.format("auth failed for seed is null", clientAuth.getUsername()).getMessage());
|
MessageFormatter.format("auth failed for seed is null", clientAuth.getUsername()).getMessage());
|
||||||
NettyUtils.write(ctx.getChannel(), errorBytes, null);
|
NettyUtils.write(ctx.getChannel(), errorBytes, null);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!embeddedServer.auth(clientAuth.getUsername(), clientAuth.getPassword().toStringUtf8(), seed)) {
|
if (!embeddedServer.auth(clientAuth.getUsername(), clientAuth.getPassword().toStringUtf8(), seed)) {
|
||||||
byte[] errorBytes = NettyUtils.errorPacket(400,
|
byte[] errorBytes = NettyUtils.errorPacket(400,
|
||||||
MessageFormatter.format("auth failed for user:{}", clientAuth.getUsername()).getMessage());
|
MessageFormatter.format("auth failed for user:{}", clientAuth.getUsername()).getMessage());
|
||||||
NettyUtils.write(ctx.getChannel(), errorBytes, null);
|
NettyUtils.write(ctx.getChannel(), errorBytes, null);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果存在订阅信息
|
// 如果存在订阅信息
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
Reference in New Issue
Block a user