mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
41 Commits
canal-1.1.
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f8a9fc98 | ||
|
|
f5bf1667a9 | ||
|
|
a9cccb3017 | ||
|
|
47ac10ca18 | ||
|
|
d4a70d5eea | ||
|
|
e78de8568e | ||
|
|
728550cac3 | ||
|
|
81c216f1c1 | ||
|
|
17f345bfe5 | ||
|
|
ebf9139762 | ||
|
|
7de40091a3 | ||
|
|
ee5775ce05 | ||
|
|
21ef2e5dd4 | ||
|
|
283956ee93 | ||
|
|
06275a46ed | ||
|
|
ea81b1dc92 | ||
|
|
fd5cfe23db | ||
|
|
bcf30a9a05 | ||
|
|
d5122dcf50 | ||
|
|
b5e05c8c77 | ||
|
|
9434ed513e | ||
|
|
6fe3ef43d8 | ||
|
|
c1f92858a2 | ||
|
|
00b5ade787 | ||
|
|
6b94f396ff | ||
|
|
b7de1e5070 | ||
|
|
4fe6c36c49 | ||
|
|
899592ce2a | ||
|
|
8585761f42 | ||
|
|
1aa2e1a409 | ||
|
|
9e3d4a7ffe | ||
|
|
fc3de4b7fd | ||
|
|
e7f07d7d3e | ||
|
|
6e07021223 | ||
|
|
3c80b6c1f7 | ||
|
|
20b0b39b14 | ||
|
|
de59d22429 | ||
|
|
1bef1b72bc | ||
|
|
47348e2e96 | ||
|
|
0f3c427e28 | ||
|
|
9bde69b6d6 |
33
client-adapter/common/pom.xml
Normal file
33
client-adapter/common/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.common</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter common module for otter ${project.version}</name>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.protocol</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>1.7.12</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>joda-time</groupId>
|
||||||
|
<artifactId>joda-time</artifactId>
|
||||||
|
<version>2.9.4</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器接口
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-18 下午10:14:02
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("logger")
|
||||||
|
public interface CanalOuterAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器初始化接口
|
||||||
|
*
|
||||||
|
* @param configuration 外部适配器配置信息
|
||||||
|
*/
|
||||||
|
void init(CanalOuterAdapterConfiguration configuration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 往适配器中写入数据
|
||||||
|
*
|
||||||
|
* @param message message数据包
|
||||||
|
*/
|
||||||
|
void writeOut(Message message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器销毁接口
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-18 下午10:40:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalClientConfig {
|
||||||
|
|
||||||
|
private String canalServerHost;
|
||||||
|
|
||||||
|
private String zookeeperHosts;
|
||||||
|
|
||||||
|
private Properties properties;
|
||||||
|
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
private List<KafkaTopic> kafkaTopics;
|
||||||
|
|
||||||
|
private List<CanalInstance> canalInstances;
|
||||||
|
|
||||||
|
public String getCanalServerHost() {
|
||||||
|
return canalServerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalServerHost(String canalServerHost) {
|
||||||
|
this.canalServerHost = canalServerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZookeeperHosts() {
|
||||||
|
return zookeeperHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZookeeperHosts(String zookeeperHosts) {
|
||||||
|
this.zookeeperHosts = zookeeperHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBootstrapServers() {
|
||||||
|
return bootstrapServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBootstrapServers(String bootstrapServers) {
|
||||||
|
this.bootstrapServers = bootstrapServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KafkaTopic> getKafkaTopics() {
|
||||||
|
return kafkaTopics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKafkaTopics(List<KafkaTopic> kafkaTopics) {
|
||||||
|
this.kafkaTopics = kafkaTopics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CanalInstance> getCanalInstances() {
|
||||||
|
return canalInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalInstances(List<CanalInstance> canalInstances) {
|
||||||
|
this.canalInstances = canalInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CanalInstance {
|
||||||
|
|
||||||
|
private String instance;
|
||||||
|
|
||||||
|
private List<AdapterGroup> adapterGroups;
|
||||||
|
|
||||||
|
public String getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstance(String instance) {
|
||||||
|
if (instance != null) {
|
||||||
|
this.instance = instance.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AdapterGroup> getAdapterGroups() {
|
||||||
|
return adapterGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdapterGroups(List<AdapterGroup> adapterGroups) {
|
||||||
|
this.adapterGroups = adapterGroups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AdapterGroup {
|
||||||
|
|
||||||
|
private List<CanalOuterAdapterConfiguration> outAdapters;
|
||||||
|
|
||||||
|
public List<CanalOuterAdapterConfiguration> getOutAdapters() {
|
||||||
|
return outAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutAdapters(List<CanalOuterAdapterConfiguration> outAdapters) {
|
||||||
|
this.outAdapters = outAdapters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KafkaTopic {
|
||||||
|
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
private List<Group> groups = new ArrayList<>();
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Group> getGroups() {
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroups(List<Group> groups) {
|
||||||
|
this.groups = groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Group {
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
|
||||||
|
// private List<Adaptor> adapters = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<CanalOuterAdapterConfiguration> outAdapters;
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CanalOuterAdapterConfiguration> getOutAdapters() {
|
||||||
|
return outAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutAdapters(List<CanalOuterAdapterConfiguration> outAdapters) {
|
||||||
|
this.outAdapters = outAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public List<Adaptor> getAdapters() {
|
||||||
|
// return adapters;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void setAdapters(List<Adaptor> adapters) {
|
||||||
|
// this.adapters = adapters;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static class Adaptor {
|
||||||
|
// private List<CanalOuterAdapterConfiguration> outAdapters;
|
||||||
|
//
|
||||||
|
// public List<CanalOuterAdapterConfiguration> getOutAdapters() {
|
||||||
|
// return outAdapters;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void setOutAdapters(List<CanalOuterAdapterConfiguration> outAdapters)
|
||||||
|
// {
|
||||||
|
// this.outAdapters = outAdapters;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器配置信息类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-18 下午10:15:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalOuterAdapterConfiguration {
|
||||||
|
|
||||||
|
private String name; // 适配器名称, 如: logger, hbase, es
|
||||||
|
|
||||||
|
private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址
|
||||||
|
|
||||||
|
private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址
|
||||||
|
|
||||||
|
private Properties properties; // 其余参数, 可填写适配器中的所需的配置信息
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHosts() {
|
||||||
|
return hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHosts(String hosts) {
|
||||||
|
this.hosts = hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZkHosts() {
|
||||||
|
return zkHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZkHosts(String zkHosts) {
|
||||||
|
this.zkHosts = zkHosts;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DML操作转换对象
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class Dml implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2611556444074013268L;
|
||||||
|
|
||||||
|
private String database;
|
||||||
|
private String table;
|
||||||
|
private String type;
|
||||||
|
private Long ts;
|
||||||
|
private String sql;
|
||||||
|
private List<Map<String, Object>> data;
|
||||||
|
private List<Map<String, Object>> old;
|
||||||
|
|
||||||
|
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 Long getTs() {
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTs(Long ts) {
|
||||||
|
this.ts = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSql() {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSql(String sql) {
|
||||||
|
this.sql = sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<Map<String, Object>> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getOld() {
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOld(List<Map<String, Object>> old) {
|
||||||
|
this.old = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
database = null;
|
||||||
|
table = null;
|
||||||
|
type = null;
|
||||||
|
ts = null;
|
||||||
|
data = null;
|
||||||
|
old = null;
|
||||||
|
sql = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Dml{" + "database='" + database + '\'' + ", table='" + table + '\'' + ", type='" + type + '\'' + ", ts="
|
||||||
|
+ ts + ", sql='" + sql + '\'' + ", data=" + data + ", old=" + old + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,563 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPI 类加载器
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ExtensionLoader<T> {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory
|
||||||
|
.getLogger(ExtensionLoader.class);
|
||||||
|
|
||||||
|
private static final String SERVICES_DIRECTORY = "META-INF/services/";
|
||||||
|
|
||||||
|
private static final String CANAL_DIRECTORY = "META-INF/canal/";
|
||||||
|
|
||||||
|
private static final String DEFAULT_CLASSLOADER_POLICY = "internal";
|
||||||
|
|
||||||
|
private static final Pattern NAME_SEPARATOR = Pattern
|
||||||
|
.compile("\\s*[,]+\\s*");
|
||||||
|
|
||||||
|
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Class<?> type;
|
||||||
|
|
||||||
|
private final String classLoaderPolicy;
|
||||||
|
|
||||||
|
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
|
||||||
|
|
||||||
|
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private String cachedDefaultName;
|
||||||
|
|
||||||
|
private ConcurrentHashMap<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static <T> boolean withExtensionAnnotation(Class<T> type) {
|
||||||
|
return type.isAnnotationPresent(SPI.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
|
||||||
|
return getExtensionLoader(type, DEFAULT_CLASSLOADER_POLICY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, String classLoaderPolicy) {
|
||||||
|
if (type == null) throw new IllegalArgumentException("Extension type == null");
|
||||||
|
if (!type.isInterface()) {
|
||||||
|
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
|
||||||
|
}
|
||||||
|
if (!withExtensionAnnotation(type)) {
|
||||||
|
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @"
|
||||||
|
+ SPI.class.getSimpleName() + " Annotation!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
|
||||||
|
if (loader == null) {
|
||||||
|
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type, classLoaderPolicy));
|
||||||
|
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtensionLoader(Class<?> type, String classLoaderPolicy){
|
||||||
|
this.type = type;
|
||||||
|
this.classLoaderPolicy = classLoaderPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtensionName(T extensionInstance) {
|
||||||
|
return getExtensionName(extensionInstance.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtensionName(Class<?> extensionClass) {
|
||||||
|
return cachedNames.get(extensionClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcurrentHashMap<String, IllegalStateException> getExceptions() {
|
||||||
|
return exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回扩展点实例,如果没有指定的扩展点或是还没加载(即实例化)则返回<code>null</code>注意:此方法不会触发扩展点的加载
|
||||||
|
* <p/>
|
||||||
|
* 一般应该调用{@link #getExtension(String)}方法获得扩展,这个方法会触发扩展点加载
|
||||||
|
*
|
||||||
|
* @see #getExtension(String)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getLoadedExtension(String name) {
|
||||||
|
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
Holder<Object> holder = cachedInstances.get(name);
|
||||||
|
if (holder == null) {
|
||||||
|
cachedInstances.putIfAbsent(name, new Holder<>());
|
||||||
|
holder = cachedInstances.get(name);
|
||||||
|
}
|
||||||
|
return (T) holder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回已经加载的扩展点的名字
|
||||||
|
* <p/>
|
||||||
|
* 一般应该调用{@link #getSupportedExtensions()}方法获得扩展,这个方法会返回所有的扩展点
|
||||||
|
*
|
||||||
|
* @see #getSupportedExtensions()
|
||||||
|
*/
|
||||||
|
public Set<String> getLoadedExtensions() {
|
||||||
|
return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回指定名字的扩展
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getExtension(String name) {
|
||||||
|
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
if ("true".equals(name)) {
|
||||||
|
return getDefaultExtension();
|
||||||
|
}
|
||||||
|
Holder<Object> holder = cachedInstances.get(name);
|
||||||
|
if (holder == null) {
|
||||||
|
cachedInstances.putIfAbsent(name, new Holder<>());
|
||||||
|
holder = cachedInstances.get(name);
|
||||||
|
}
|
||||||
|
Object instance = holder.get();
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (holder) {
|
||||||
|
instance = holder.get();
|
||||||
|
if (instance == null) {
|
||||||
|
instance = createExtension(name);
|
||||||
|
holder.set(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T) instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回缺省的扩展,如果没有设置则返回<code>null</code>
|
||||||
|
*/
|
||||||
|
public T getDefaultExtension() {
|
||||||
|
getExtensionClasses();
|
||||||
|
if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getExtension(cachedDefaultName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasExtension(String name) {
|
||||||
|
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
try {
|
||||||
|
return getExtensionClass(name) != null;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getSupportedExtensions() {
|
||||||
|
Map<String, Class<?>> clazzes = getExtensionClasses();
|
||||||
|
return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回缺省的扩展点名,如果没有设置缺省则返回<code>null</code>
|
||||||
|
*/
|
||||||
|
public String getDefaultExtensionName() {
|
||||||
|
getExtensionClasses();
|
||||||
|
return cachedDefaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编程方式添加新扩展点
|
||||||
|
*
|
||||||
|
* @param name 扩展点名
|
||||||
|
* @param clazz 扩展点类
|
||||||
|
* @throws IllegalStateException 要添加扩展点名已经存在
|
||||||
|
*/
|
||||||
|
public void addExtension(String name, Class<?> clazz) {
|
||||||
|
getExtensionClasses(); // load classes
|
||||||
|
|
||||||
|
if (!type.isAssignableFrom(clazz)) {
|
||||||
|
throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
|
||||||
|
}
|
||||||
|
if (clazz.isInterface()) {
|
||||||
|
throw new IllegalStateException("Input type " + clazz + "can not be interface!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null || "".equals(name)) {
|
||||||
|
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
|
||||||
|
}
|
||||||
|
if (cachedClasses.get().containsKey(name)) {
|
||||||
|
throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!");
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedNames.put(clazz, name);
|
||||||
|
cachedClasses.get().put(name, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编程方式添加替换已有扩展点
|
||||||
|
*
|
||||||
|
* @param name 扩展点名
|
||||||
|
* @param clazz 扩展点类
|
||||||
|
* @throws IllegalStateException 要添加扩展点名已经存在
|
||||||
|
* @deprecated 不推荐应用使用,一般只在测试时可以使用
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void replaceExtension(String name, Class<?> clazz) {
|
||||||
|
getExtensionClasses(); // load classes
|
||||||
|
|
||||||
|
if (!type.isAssignableFrom(clazz)) {
|
||||||
|
throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
|
||||||
|
}
|
||||||
|
if (clazz.isInterface()) {
|
||||||
|
throw new IllegalStateException("Input type " + clazz + "can not be interface!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null || "".equals(name)) {
|
||||||
|
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
|
||||||
|
}
|
||||||
|
if (!cachedClasses.get().containsKey(name)) {
|
||||||
|
throw new IllegalStateException("Extension name " + name + " not existed(Extension " + type + ")!");
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedNames.put(clazz, name);
|
||||||
|
cachedClasses.get().put(name, clazz);
|
||||||
|
cachedInstances.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private T createExtension(String name) {
|
||||||
|
Class<?> clazz = getExtensionClasses().get(name);
|
||||||
|
if (clazz == null) {
|
||||||
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
|
+ ") could not be instantiated: class could not be found");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
T instance = (T) EXTENSION_INSTANCES.get(clazz);
|
||||||
|
if (instance == null) {
|
||||||
|
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
|
||||||
|
instance = (T) EXTENSION_INSTANCES.get(clazz);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
|
+ ") could not be instantiated: " + t.getMessage(),
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> getExtensionClass(String name) {
|
||||||
|
if (type == null) throw new IllegalArgumentException("Extension type == null");
|
||||||
|
if (name == null) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
Class<?> clazz = getExtensionClasses().get(name);
|
||||||
|
if (clazz == null)
|
||||||
|
throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<?>> getExtensionClasses() {
|
||||||
|
Map<String, Class<?>> classes = cachedClasses.get();
|
||||||
|
if (classes == null) {
|
||||||
|
synchronized (cachedClasses) {
|
||||||
|
classes = cachedClasses.get();
|
||||||
|
if (classes == null) {
|
||||||
|
classes = loadExtensionClasses();
|
||||||
|
cachedClasses.set(classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJarDirectoryPath() {
|
||||||
|
URL url = Thread.currentThread().getContextClassLoader().getResource("");
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalStateException("failed to get class loader resource");
|
||||||
|
}
|
||||||
|
String dirtyPath = url.toString();
|
||||||
|
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes file:/ and everything before it
|
||||||
|
jarPath = jarPath.replaceAll("jar!.*", "jar"); // removes everything after .jar, if .jar exists in dirtyPath
|
||||||
|
jarPath = jarPath.replaceAll("%20", " "); // necessary if path has spaces within
|
||||||
|
if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run the app using Spring Tools Suit play
|
||||||
|
// button.
|
||||||
|
jarPath = jarPath.replaceAll("/classes/.*", "/classes/");
|
||||||
|
}
|
||||||
|
return Paths.get(jarPath).getParent().toString(); // Paths - from java 8
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<?>> loadExtensionClasses() {
|
||||||
|
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
|
||||||
|
if (defaultAnnotation != null) {
|
||||||
|
String value = defaultAnnotation.value();
|
||||||
|
if ((value = value.trim()).length() > 0) {
|
||||||
|
String[] names = NAME_SEPARATOR.split(value);
|
||||||
|
if (names.length > 1) {
|
||||||
|
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
|
||||||
|
+ ": " + Arrays.toString(names));
|
||||||
|
}
|
||||||
|
if (names.length == 1) cachedDefaultName = names[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
|
||||||
|
|
||||||
|
// 1. lib folder,customized extension classLoader (jar_dir/lib)
|
||||||
|
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib";
|
||||||
|
logger.info("extension classpath dir: " + dir);
|
||||||
|
File externalLibDir = new File(dir);
|
||||||
|
if (!externalLibDir.exists()) {
|
||||||
|
externalLibDir = new File(
|
||||||
|
File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" + File.separator + "lib");
|
||||||
|
}
|
||||||
|
if (externalLibDir.exists()) {
|
||||||
|
File[] files = externalLibDir.listFiles(new FilenameFilter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return name.endsWith(".jar");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (files != null) {
|
||||||
|
for (File f : files) {
|
||||||
|
URL url = null;
|
||||||
|
try {
|
||||||
|
url = f.toURI().toURL();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException("load extension jar failed!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader parent = Thread.currentThread().getContextClassLoader();
|
||||||
|
URLClassLoader localClassLoader;
|
||||||
|
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|
||||||
|
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
|
||||||
|
localClassLoader = new URLClassLoader(new URL[] { url }, parent) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||||
|
Class<?> c = findLoadedClass(name);
|
||||||
|
if (c != null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith("java.") || name.startsWith("org.slf4j.")
|
||||||
|
|| name.startsWith("org.apache.logging")
|
||||||
|
|| name.startsWith("org.apache.commons.logging.")) {
|
||||||
|
// || name.startsWith("org.apache.hadoop.")) {
|
||||||
|
c = super.loadClass(name);
|
||||||
|
}
|
||||||
|
if (c != null) return c;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先加载jar内的class,可避免jar冲突
|
||||||
|
c = findClass(name);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
c = null;
|
||||||
|
}
|
||||||
|
if (c != null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.loadClass(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<URL> getResources(String name) throws IOException {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
|
||||||
|
|
||||||
|
tmp[0] = findResources(name); // local class path first
|
||||||
|
// tmp[1] = super.getResources(name);
|
||||||
|
|
||||||
|
return new CompoundEnumeration<>(tmp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
localClassLoader = new URLClassLoader(new URL[] { url }, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFile(extensionClasses, CANAL_DIRECTORY, localClassLoader);
|
||||||
|
loadFile(extensionClasses, SERVICES_DIRECTORY, localClassLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. load inner extension class with default classLoader
|
||||||
|
ClassLoader classLoader = findClassLoader();
|
||||||
|
loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
|
||||||
|
loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
|
||||||
|
|
||||||
|
return extensionClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CompoundEnumeration<E> implements Enumeration<E> {
|
||||||
|
|
||||||
|
private Enumeration<E>[] enums;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
public CompoundEnumeration(Enumeration<E>[] enums){
|
||||||
|
this.enums = enums;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean next() {
|
||||||
|
while (this.index < this.enums.length) {
|
||||||
|
if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
++this.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoreElements() {
|
||||||
|
return this.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public E nextElement() {
|
||||||
|
if (!this.next()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
} else {
|
||||||
|
return this.enums[this.index].nextElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFile(Map<String, Class<?>> extensionClasses, String dir, ClassLoader classLoader) {
|
||||||
|
String fileName = dir + type.getName();
|
||||||
|
try {
|
||||||
|
Enumeration<URL> urls;
|
||||||
|
if (classLoader != null) {
|
||||||
|
urls = classLoader.getResources(fileName);
|
||||||
|
} else {
|
||||||
|
urls = ClassLoader.getSystemResources(fileName);
|
||||||
|
}
|
||||||
|
if (urls != null) {
|
||||||
|
while (urls.hasMoreElements()) {
|
||||||
|
URL url = urls.nextElement();
|
||||||
|
try {
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
|
||||||
|
String line = null;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
final int ci = line.indexOf('#');
|
||||||
|
if (ci >= 0) line = line.substring(0, ci);
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() > 0) {
|
||||||
|
try {
|
||||||
|
String name = null;
|
||||||
|
int i = line.indexOf('=');
|
||||||
|
if (i > 0) {
|
||||||
|
name = line.substring(0, i).trim();
|
||||||
|
line = line.substring(i + 1).trim();
|
||||||
|
}
|
||||||
|
if (line.length() > 0) {
|
||||||
|
Class<?> clazz = classLoader.loadClass(line);
|
||||||
|
// Class<?> clazz = Class.forName(line, true, classLoader);
|
||||||
|
if (!type.isAssignableFrom(clazz)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Error when load extension class(interface: " + type
|
||||||
|
+ ", class line: " + clazz.getName()
|
||||||
|
+ "), class " + clazz.getName()
|
||||||
|
+ "is not subtype of interface.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
clazz.getConstructor(type);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz.getConstructor();
|
||||||
|
String[] names = NAME_SEPARATOR.split(name);
|
||||||
|
if (names != null && names.length > 0) {
|
||||||
|
for (String n : names) {
|
||||||
|
if (!cachedNames.containsKey(clazz)) {
|
||||||
|
cachedNames.put(clazz, n);
|
||||||
|
}
|
||||||
|
Class<?> c = extensionClasses.get(n);
|
||||||
|
if (c == null) {
|
||||||
|
extensionClasses.put(n, clazz);
|
||||||
|
} else if (c != clazz) {
|
||||||
|
cachedNames.remove(clazz);
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Duplicate extension " + type.getName() + " name "
|
||||||
|
+ n + " on "
|
||||||
|
+ c.getName() + " and "
|
||||||
|
+ clazz.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
IllegalStateException e = new IllegalStateException(
|
||||||
|
"Failed to load extension class(interface: " + type + ", class line: "
|
||||||
|
+ line + ") in " + url
|
||||||
|
+ ", cause: "
|
||||||
|
+ t.getMessage(),
|
||||||
|
t);
|
||||||
|
exceptions.put(line, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end of while read lines
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url
|
||||||
|
+ ") in " + url,
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
} // end of while urls
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error(
|
||||||
|
"Exception when load extension class(interface: " + type + ", description file: " + fileName + ").",
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassLoader findClassLoader() {
|
||||||
|
return ExtensionLoader.class.getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getClass().getName() + "[" + type.getName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Holder<T> {
|
||||||
|
|
||||||
|
private volatile T value;
|
||||||
|
|
||||||
|
private void set(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.sql.Types;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型转换工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午06:14:23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class JdbcTypeUtil {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class);
|
||||||
|
|
||||||
|
public static Object typeConvert(String tableName, String columnName, String value, int sqlType, String mysqlType) {
|
||||||
|
if (value == null || value.equals("")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object res;
|
||||||
|
switch (sqlType) {
|
||||||
|
case Types.INTEGER:
|
||||||
|
res = Integer.parseInt(value);
|
||||||
|
break;
|
||||||
|
case Types.SMALLINT:
|
||||||
|
res = Short.parseShort(value);
|
||||||
|
break;
|
||||||
|
case Types.BIT:
|
||||||
|
case Types.TINYINT:
|
||||||
|
res = Byte.parseByte(value);
|
||||||
|
break;
|
||||||
|
case Types.BIGINT:
|
||||||
|
if (mysqlType.startsWith("bigint") && mysqlType.endsWith("unsigned")) {
|
||||||
|
res = new BigInteger(value);
|
||||||
|
} else {
|
||||||
|
res = Long.parseLong(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// case Types.BIT:
|
||||||
|
case Types.BOOLEAN:
|
||||||
|
res = !"0".equals(value);
|
||||||
|
break;
|
||||||
|
case Types.DOUBLE:
|
||||||
|
case Types.FLOAT:
|
||||||
|
res = Double.parseDouble(value);
|
||||||
|
break;
|
||||||
|
case Types.REAL:
|
||||||
|
res = Float.parseFloat(value);
|
||||||
|
break;
|
||||||
|
case Types.DECIMAL:
|
||||||
|
case Types.NUMERIC:
|
||||||
|
res = new BigDecimal(value);
|
||||||
|
break;
|
||||||
|
case Types.BINARY:
|
||||||
|
case Types.VARBINARY:
|
||||||
|
case Types.LONGVARBINARY:
|
||||||
|
case Types.BLOB:
|
||||||
|
res = value.getBytes("ISO-8859-1");
|
||||||
|
break;
|
||||||
|
case Types.DATE:
|
||||||
|
if (!value.startsWith("0000-00-00")) {
|
||||||
|
value = value.trim().replace(" ", "T");
|
||||||
|
DateTime dt = new DateTime(value);
|
||||||
|
res = new Date(dt.toDate().getTime());
|
||||||
|
} else {
|
||||||
|
res = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIME:
|
||||||
|
value = "T" + value;
|
||||||
|
DateTime dt = new DateTime(value);
|
||||||
|
res = new Time(dt.toDate().getTime());
|
||||||
|
break;
|
||||||
|
case Types.TIMESTAMP:
|
||||||
|
if (!value.startsWith("0000-00-00")) {
|
||||||
|
value = value.trim().replace(" ", "T");
|
||||||
|
dt = new DateTime(value);
|
||||||
|
res = new Timestamp(dt.toDate().getTime());
|
||||||
|
} else {
|
||||||
|
res = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.CLOB:
|
||||||
|
default:
|
||||||
|
res = value;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message对象解析工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午06:14:23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MessageUtil {
|
||||||
|
|
||||||
|
public static void parse4Dml(Message message, Consumer<Dml> consumer) {
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<CanalEntry.Entry> entries = message.getEntries();
|
||||||
|
|
||||||
|
for (CanalEntry.Entry entry : entries) {
|
||||||
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.RowChange rowChange;
|
||||||
|
try {
|
||||||
|
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.EventType eventType = rowChange.getEventType();
|
||||||
|
|
||||||
|
final Dml dml = new Dml();
|
||||||
|
dml.setDatabase(entry.getHeader().getSchemaName());
|
||||||
|
dml.setTable(entry.getHeader().getTableName());
|
||||||
|
dml.setType(eventType.toString());
|
||||||
|
dml.setTs(System.currentTimeMillis());
|
||||||
|
dml.setSql(rowChange.getSql());
|
||||||
|
List<Map<String, Object>> data = new ArrayList<>();
|
||||||
|
List<Map<String, Object>> old = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!rowChange.getIsDdl()) {
|
||||||
|
Set<String> updateSet = new HashSet<>();
|
||||||
|
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
||||||
|
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
|
||||||
|
&& eventType != CanalEntry.EventType.DELETE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
List<CanalEntry.Column> columns;
|
||||||
|
|
||||||
|
if (eventType == CanalEntry.EventType.DELETE) {
|
||||||
|
columns = rowData.getBeforeColumnsList();
|
||||||
|
} else {
|
||||||
|
columns = rowData.getAfterColumnsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CanalEntry.Column column : columns) {
|
||||||
|
row.put(column.getName(),
|
||||||
|
JdbcTypeUtil.typeConvert(dml.getTable(),
|
||||||
|
column.getName(),
|
||||||
|
column.getValue(),
|
||||||
|
column.getSqlType(),
|
||||||
|
column.getMysqlType()));
|
||||||
|
// 获取update为true的字段
|
||||||
|
if (column.getUpdated()) {
|
||||||
|
updateSet.add(column.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!row.isEmpty()) {
|
||||||
|
data.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType == CanalEntry.EventType.UPDATE) {
|
||||||
|
Map<String, Object> rowOld = new LinkedHashMap<>();
|
||||||
|
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
|
||||||
|
if (updateSet.contains(column.getName())) {
|
||||||
|
rowOld.put(column.getName(),
|
||||||
|
JdbcTypeUtil.typeConvert(dml.getTable(),
|
||||||
|
column.getName(),
|
||||||
|
column.getValue(),
|
||||||
|
column.getSqlType(),
|
||||||
|
column.getMysqlType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update操作将记录修改前的值
|
||||||
|
if (!rowOld.isEmpty()) {
|
||||||
|
old.add(rowOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
dml.setData(data);
|
||||||
|
}
|
||||||
|
if (!old.isEmpty()) {
|
||||||
|
dml.setOld(old);
|
||||||
|
}
|
||||||
|
consumer.accept(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Consumer<T> {
|
||||||
|
|
||||||
|
void accept(T t);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE })
|
||||||
|
public @interface SPI {
|
||||||
|
|
||||||
|
// Default SPI name
|
||||||
|
String value() default "";
|
||||||
|
}
|
||||||
101
client-adapter/hbase/pom.xml
Normal file
101
client-adapter/hbase/pom.xml
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?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.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.hbase</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter hbase 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>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.17</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.hbase</groupId>
|
||||||
|
<artifactId>hbase-client</artifactId>
|
||||||
|
<version>1.1.2</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>12.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<addMavenDescriptor>true</addMavenDescriptor>
|
||||||
|
</archive>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/hbase-mapping/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<tasks>
|
||||||
|
<copy todir="${project.basedir}/../../client-launcher/target/canal_client/conf/hbase-mapping" overwrite="true" >
|
||||||
|
<fileset dir="${project.basedir}/src/main/resources/hbase-mapping" erroronmissingdir="true">
|
||||||
|
<include name="*.conf"/>
|
||||||
|
<include name="*.yml"/>
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<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>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase外部适配器
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午8:45:38
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("hbase")
|
||||||
|
public class HbaseAdapter implements CanalOuterAdapter {
|
||||||
|
|
||||||
|
private static volatile Map<String, MappingConfig> mappingConfigCache = null;
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
private HbaseSyncService hbaseSyncService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CanalOuterAdapterConfiguration configuration) {
|
||||||
|
try {
|
||||||
|
if (mappingConfigCache == null) {
|
||||||
|
synchronized (MappingConfig.class) {
|
||||||
|
if (mappingConfigCache == null) {
|
||||||
|
Map<String, MappingConfig> hbaseMapping = MappingConfigLoader.load();
|
||||||
|
mappingConfigCache = new HashMap<>();
|
||||||
|
for (MappingConfig mappingConfig : hbaseMapping.values()) {
|
||||||
|
mappingConfigCache.put(mappingConfig.getHbaseOrm().getDatabase() + "-"
|
||||||
|
+ mappingConfig.getHbaseOrm().getTable(),
|
||||||
|
mappingConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String hosts = configuration.getZkHosts();
|
||||||
|
if (StringUtils.isEmpty(hosts)) {
|
||||||
|
hosts = configuration.getHosts();
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(hosts)) {
|
||||||
|
throw new RuntimeException("Empty zookeeper hosts");
|
||||||
|
}
|
||||||
|
String[] zkHosts = StringUtils.split(hosts, ",");
|
||||||
|
int zkPort = 0;
|
||||||
|
StringBuilder hostsWithoutPort = new StringBuilder();
|
||||||
|
for (String host : zkHosts) {
|
||||||
|
int i = host.indexOf(":");
|
||||||
|
hostsWithoutPort.append(host, 0, i);
|
||||||
|
hostsWithoutPort.append(",");
|
||||||
|
if (zkPort == 0) zkPort = Integer.parseInt(host.substring(i + 1));
|
||||||
|
}
|
||||||
|
hostsWithoutPort.deleteCharAt(hostsWithoutPort.length() - 1);
|
||||||
|
|
||||||
|
String znode = configuration.getProperties().getProperty("znodeParent");
|
||||||
|
if (StringUtils.isEmpty(znode)) {
|
||||||
|
znode = "/hbase";
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration hbaseConfig = HBaseConfiguration.create();
|
||||||
|
hbaseConfig.set("hbase.zookeeper.quorum", hostsWithoutPort.toString());
|
||||||
|
hbaseConfig.set("hbase.zookeeper.property.clientPort", Integer.toString(zkPort));
|
||||||
|
hbaseConfig.set("zookeeper.znode.parent", znode);
|
||||||
|
conn = ConnectionFactory.createConnection(hbaseConfig);
|
||||||
|
hbaseSyncService = new HbaseSyncService(conn);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeOut(Message message) {
|
||||||
|
MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Dml dml) {
|
||||||
|
if (dml == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
MappingConfig config = mappingConfigCache.get(database + "-" + table);
|
||||||
|
hbaseSyncService.sync(config, dml);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.config;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase表映射配置
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MappingConfig {
|
||||||
|
|
||||||
|
private HbaseOrm hbaseOrm;
|
||||||
|
|
||||||
|
public HbaseOrm getHbaseOrm() {
|
||||||
|
return hbaseOrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHbaseOrm(HbaseOrm hbaseOrm) {
|
||||||
|
this.hbaseOrm = hbaseOrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate() {
|
||||||
|
if (hbaseOrm.database == null || hbaseOrm.database.isEmpty()) {
|
||||||
|
throw new NullPointerException("hbaseOrm.database");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.table == null || hbaseOrm.table.isEmpty()) {
|
||||||
|
throw new NullPointerException("hbaseOrm.table");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.hbaseTable == null || hbaseOrm.hbaseTable.isEmpty()) {
|
||||||
|
throw new NullPointerException("hbaseOrm.hbaseTable");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.mode == null) {
|
||||||
|
throw new NullPointerException("hbaseOrm.mode");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.rowKey != null && hbaseOrm.rowKeyColumn != null) {
|
||||||
|
throw new RuntimeException("已配置了复合主键作为RowKey,无需再指定RowKey列");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
MappingConfig config = (MappingConfig) o;
|
||||||
|
|
||||||
|
return hbaseOrm != null ? hbaseOrm.equals(config.hbaseOrm) : config.hbaseOrm == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hbaseOrm != null ? hbaseOrm.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ColumnItem {
|
||||||
|
|
||||||
|
private boolean isRowKey = false;
|
||||||
|
private String column;
|
||||||
|
private String family;
|
||||||
|
private String qualifier;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public boolean isRowKey() {
|
||||||
|
return isRowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKey(boolean rowKey) {
|
||||||
|
isRowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColumn() {
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumn(String column) {
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFamily() {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamily(String family) {
|
||||||
|
this.family = family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQualifier() {
|
||||||
|
return qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQualifier(String qualifier) {
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ColumnItem that = (ColumnItem) o;
|
||||||
|
return Objects.equals(column, that.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
|
||||||
|
return Objects.hash(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Mode {
|
||||||
|
STRING("STRING"), NATIVE("NATIVE"), PHOENIX("PHOENIX");
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode(String type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HbaseOrm {
|
||||||
|
|
||||||
|
private Mode mode = Mode.STRING;
|
||||||
|
private String database;
|
||||||
|
private String table;
|
||||||
|
private String hbaseTable;
|
||||||
|
private String family = "CF";
|
||||||
|
private boolean uppercaseQualifier = true;
|
||||||
|
private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表
|
||||||
|
private String rowKey; // 指定复合主键为rowKey
|
||||||
|
private Map<String, String> columns;
|
||||||
|
private ColumnItem rowKeyColumn;
|
||||||
|
private String etlCondition;
|
||||||
|
|
||||||
|
private Map<String, ColumnItem> columnItems = new LinkedHashMap<>();
|
||||||
|
private Set<String> families = new LinkedHashSet<>();
|
||||||
|
private int readBatch = 5000;
|
||||||
|
private int commitBatch = 5000;
|
||||||
|
|
||||||
|
public Mode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getHbaseTable() {
|
||||||
|
return hbaseTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHbaseTable(String hbaseTable) {
|
||||||
|
this.hbaseTable = hbaseTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getColumns() {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoCreateTable() {
|
||||||
|
return autoCreateTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoCreateTable(boolean autoCreateTable) {
|
||||||
|
this.autoCreateTable = autoCreateTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String getRowKey() {
|
||||||
|
return rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKey(String rowKey) {
|
||||||
|
this.rowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEtlCondition() {
|
||||||
|
return etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEtlCondition(String etlCondition) {
|
||||||
|
this.etlCondition = etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumns(Map<String, String> columns) {
|
||||||
|
this.columns = columns;
|
||||||
|
|
||||||
|
if (columns != null) {
|
||||||
|
for (Map.Entry<String, String> columnField : columns.entrySet()) {
|
||||||
|
String field = columnField.getValue();
|
||||||
|
String type = null;
|
||||||
|
if (field != null) {
|
||||||
|
// 解析类型
|
||||||
|
int i = field.indexOf("$");
|
||||||
|
if (i > -1) {
|
||||||
|
type = field.substring(i + 1);
|
||||||
|
field = field.substring(0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnItem columnItem = new ColumnItem();
|
||||||
|
columnItem.setColumn(columnField.getKey());
|
||||||
|
columnItem.setType(type);
|
||||||
|
if ("rowKey".equalsIgnoreCase(field)) {
|
||||||
|
columnItem.setRowKey(true);
|
||||||
|
rowKeyColumn = columnItem;
|
||||||
|
} else {
|
||||||
|
if (field == null || field.equals("")) {
|
||||||
|
columnItem.setFamily(family);
|
||||||
|
columnItem.setQualifier(columnField.getKey());
|
||||||
|
} else {
|
||||||
|
int len = field.indexOf(":");
|
||||||
|
if (len > -1) {
|
||||||
|
columnItem.setFamily(field.substring(0, len));
|
||||||
|
columnItem.setQualifier(field.substring(len + 1));
|
||||||
|
} else {
|
||||||
|
columnItem.setFamily(family);
|
||||||
|
columnItem.setQualifier(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uppercaseQualifier) {
|
||||||
|
columnItem.setQualifier(columnItem.getQualifier().toUpperCase());
|
||||||
|
}
|
||||||
|
families.add(columnItem.getFamily());
|
||||||
|
}
|
||||||
|
|
||||||
|
columnItems.put(columnField.getKey(), columnItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.columns = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFamily() {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamily(String family) {
|
||||||
|
this.family = family;
|
||||||
|
if (family == null) {
|
||||||
|
this.family = "CF";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUppercaseQualifier() {
|
||||||
|
return uppercaseQualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUppercaseQualifier(boolean uppercaseQualifier) {
|
||||||
|
this.uppercaseQualifier = uppercaseQualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnItem getRowKeyColumn() {
|
||||||
|
return rowKeyColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKeyColumn(ColumnItem rowKeyColumn) {
|
||||||
|
this.rowKeyColumn = rowKeyColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ColumnItem> getColumnItems() {
|
||||||
|
return columnItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnItems(Map<String, ColumnItem> columnItems) {
|
||||||
|
this.columnItems = columnItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getFamilies() {
|
||||||
|
return families;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamilies(Set<String> families) {
|
||||||
|
this.families = families;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
HbaseOrm hbaseOrm = (HbaseOrm) o;
|
||||||
|
|
||||||
|
if (table != null ? !table.equals(hbaseOrm.table) : hbaseOrm.table != null) return false;
|
||||||
|
return hbaseTable != null ? hbaseTable.equals(hbaseOrm.hbaseTable) : hbaseOrm.hbaseTable == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = table != null ? table.hashCode() : 0;
|
||||||
|
result = 31 * result + (hbaseTable != null ? hbaseTable.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.config;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase表映射配置加载器
|
||||||
|
* <p>
|
||||||
|
* 配置统一从hbase-mapping/configs.conf文件作为入口, 该文件包含所有表映射配置的名称或者文件名列表。
|
||||||
|
* 每个对应的表配置可以yml配置文件或者以database.table为配置名的简化形式
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MappingConfigLoader {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
|
||||||
|
|
||||||
|
private static final String BASE_PATH = "hbase-mapping/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载HBase表映射配置
|
||||||
|
*
|
||||||
|
* @return 配置名/配置文件名--对象
|
||||||
|
*/
|
||||||
|
public static Map<String, MappingConfig> load() {
|
||||||
|
logger.info("## Start loading mapping config ... ");
|
||||||
|
String mappingConfigContent = readConfigContent(BASE_PATH + "configs.conf");
|
||||||
|
|
||||||
|
Map<String, MappingConfig> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
String[] configLines = mappingConfigContent.split("\n");
|
||||||
|
for (String c : configLines) {
|
||||||
|
if (c == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
c = c.trim();
|
||||||
|
if (c.equals("") || c.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig config;
|
||||||
|
String configContent = null;
|
||||||
|
|
||||||
|
if (c.endsWith(".yml")) {
|
||||||
|
configContent = readConfigContent(BASE_PATH + "/" + c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单配置database.table@datasourcekey?rowKey=key1,key2
|
||||||
|
if (StringUtils.isEmpty(configContent)) {
|
||||||
|
String[] mapping = c.split("\\?");
|
||||||
|
String params = mapping.length == 2 ? mapping[1] : null;
|
||||||
|
String rowKey = null;
|
||||||
|
String srcMeta = mapping[0];
|
||||||
|
//
|
||||||
|
if (params != null) {
|
||||||
|
for (String entry : params.split("&")) {
|
||||||
|
if ("rowKey".equals(entry.split("=")[0])) {
|
||||||
|
rowKey = entry.split("=")[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String dsKey = srcMeta.split("@").length == 2 ? srcMeta.split("@")[1] : null;
|
||||||
|
String[] dbTable;
|
||||||
|
if (dsKey == null) {
|
||||||
|
dbTable = srcMeta.split("\\.");
|
||||||
|
} else {
|
||||||
|
dbTable = srcMeta.split("@")[0].split("\\.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbTable.length == 2) {
|
||||||
|
config = new MappingConfig();
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = new MappingConfig.HbaseOrm();
|
||||||
|
hbaseOrm.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase());
|
||||||
|
hbaseOrm.setAutoCreateTable(true);
|
||||||
|
hbaseOrm.setDatabase(dbTable[0]);
|
||||||
|
hbaseOrm.setTable(dbTable[1]);
|
||||||
|
hbaseOrm.setMode(MappingConfig.Mode.STRING);
|
||||||
|
hbaseOrm.setRowKey(rowKey);
|
||||||
|
// 有定义rowKey
|
||||||
|
if (rowKey != null) {
|
||||||
|
MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem();
|
||||||
|
columnItem.setRowKey(true);
|
||||||
|
columnItem.setColumn(rowKey);
|
||||||
|
hbaseOrm.setRowKeyColumn(columnItem);
|
||||||
|
}
|
||||||
|
config.setHbaseOrm(hbaseOrm);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // 配置文件配置
|
||||||
|
config = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config.validate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
result.put(c, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("## Mapping config loaded");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readConfigContent(String config) {
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
// 先取本地文件,再取类路径
|
||||||
|
File configFile = new File("config/" + config);
|
||||||
|
if (configFile.exists()) {
|
||||||
|
in = new FileInputStream(configFile);
|
||||||
|
} else {
|
||||||
|
in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config);
|
||||||
|
}
|
||||||
|
if (in == null) {
|
||||||
|
throw new RuntimeException("Config file not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[in.available()];
|
||||||
|
in.read(bytes);
|
||||||
|
return new String(bytes, "UTF-8");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (in != null) {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase同步操作业务
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class HbaseSyncService {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private HbaseTemplate hbaseTemplate; // HBase操作模板
|
||||||
|
|
||||||
|
public HbaseSyncService(Connection conn){
|
||||||
|
hbaseTemplate = new HbaseTemplate(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(MappingConfig config, Dml dml) {
|
||||||
|
try {
|
||||||
|
if (config != null) {
|
||||||
|
String type = dml.getType();
|
||||||
|
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
||||||
|
insert(config, dml);
|
||||||
|
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
|
||||||
|
update(config, dml);
|
||||||
|
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
|
||||||
|
delete(config, dml);
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
String res = dml.toString();
|
||||||
|
logger.debug(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入操作
|
||||||
|
*
|
||||||
|
* @param config 配置项
|
||||||
|
* @param dml DML数据
|
||||||
|
*/
|
||||||
|
private void insert(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
// if (!validHTable(config)) {
|
||||||
|
// logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
int i = 1;
|
||||||
|
boolean complete = false;
|
||||||
|
List<HRow> rows = new ArrayList<>();
|
||||||
|
for (Map<String, Object> r : data) {
|
||||||
|
HRow hRow = new HRow();
|
||||||
|
|
||||||
|
// 拼接复合rowKey
|
||||||
|
if (hbaseOrm.getRowKey() != null) {
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
String rowKeyVale = getRowKeys(rowKeyColumns, r);
|
||||||
|
// params.put("rowKey", Bytes.toBytes(rowKeyVale));
|
||||||
|
hRow.setRowKey(Bytes.toBytes(rowKeyVale));
|
||||||
|
}
|
||||||
|
|
||||||
|
convertData2Row(hbaseOrm, hRow, r);
|
||||||
|
if (hRow.getRowKey() == null) {
|
||||||
|
throw new RuntimeException("empty rowKey");
|
||||||
|
}
|
||||||
|
rows.add(hRow);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
rows.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!complete && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Map数据转换为HRow行数据
|
||||||
|
*
|
||||||
|
* @param hbaseOrm hbase映射配置
|
||||||
|
* @param hRow 行对象
|
||||||
|
* @param data Map数据
|
||||||
|
*/
|
||||||
|
private static void convertData2Row(MappingConfig.HbaseOrm hbaseOrm, HRow hRow, Map<String, Object> data) {
|
||||||
|
Map<String, MappingConfig.ColumnItem> columnItems = hbaseOrm.getColumnItems();
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||||
|
if (entry.getValue() != null) {
|
||||||
|
MappingConfig.ColumnItem columnItem = columnItems.get(entry.getKey());
|
||||||
|
|
||||||
|
byte[] bytes = typeConvert(columnItem, hbaseOrm, entry.getValue());
|
||||||
|
|
||||||
|
if (columnItem == null) {
|
||||||
|
String familyName = hbaseOrm.getFamily();
|
||||||
|
String qualifier = entry.getKey();
|
||||||
|
if (hbaseOrm.isUppercaseQualifier()) {
|
||||||
|
qualifier = qualifier.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hbaseOrm.getRowKey() == null && i == 0) {
|
||||||
|
hRow.setRowKey(bytes);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(familyName, qualifier, bytes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (columnItem.isRowKey()) {
|
||||||
|
// row.put("rowKey", bytes);
|
||||||
|
hRow.setRowKey(bytes);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param dml
|
||||||
|
*/
|
||||||
|
private void update(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
List<Map<String, Object>> old = dml.getOld();
|
||||||
|
if (old == null || old.isEmpty() || data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
// if (!validHTable(config)) {
|
||||||
|
// logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn();
|
||||||
|
int index = 0;
|
||||||
|
int i = 1;
|
||||||
|
boolean complete = false;
|
||||||
|
List<HRow> rows = new ArrayList<>();
|
||||||
|
out: for (Map<String, Object> r : data) {
|
||||||
|
byte[] rowKeyBytes;
|
||||||
|
|
||||||
|
if (hbaseOrm.getRowKey() != null) {
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
|
||||||
|
// 判断是否有复合主键修改
|
||||||
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
|
||||||
|
// 调用删除插入操作
|
||||||
|
deleteAndInsert(config, dml);
|
||||||
|
continue out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String rowKeyVale = getRowKeys(rowKeyColumns, r);
|
||||||
|
rowKeyBytes = Bytes.toBytes(rowKeyVale);
|
||||||
|
} else if (rowKeyColumn == null) {
|
||||||
|
Map<String, Object> rowKey = data.get(0);
|
||||||
|
rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next());
|
||||||
|
} else {
|
||||||
|
rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, r.get(rowKeyColumn.getColumn()));
|
||||||
|
}
|
||||||
|
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
||||||
|
|
||||||
|
Map<String, MappingConfig.ColumnItem> columnItems = hbaseOrm.getColumnItems();
|
||||||
|
HRow hRow = new HRow(rowKeyBytes);
|
||||||
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
|
MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn);
|
||||||
|
if (columnItem == null) {
|
||||||
|
String family = hbaseOrm.getFamily();
|
||||||
|
String qualifier = updateColumn;
|
||||||
|
if (hbaseOrm.isUppercaseQualifier()) {
|
||||||
|
qualifier = qualifier.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object newVal = r.get(updateColumn);
|
||||||
|
|
||||||
|
if (newVal == null) {
|
||||||
|
hRow.addCell(family, qualifier, null);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(family, qualifier, typeConvert(null, hbaseOrm, newVal));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 排除修改id的情况
|
||||||
|
if (columnItem.isRowKey()) continue;
|
||||||
|
|
||||||
|
Object newVal = r.get(updateColumn);
|
||||||
|
if (newVal == null) {
|
||||||
|
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), null);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(columnItem.getFamily(),
|
||||||
|
columnItem.getQualifier(),
|
||||||
|
typeConvert(columnItem, hbaseOrm, newVal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.add(hRow);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
rows.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (!complete && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
// if (!validHTable(config)) {
|
||||||
|
// logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn();
|
||||||
|
boolean complete = false;
|
||||||
|
int i = 1;
|
||||||
|
Set<byte[]> rowKeys = new HashSet<>();
|
||||||
|
for (Map<String, Object> r : data) {
|
||||||
|
byte[] rowKeyBytes;
|
||||||
|
|
||||||
|
if (hbaseOrm.getRowKey() != null) {
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
String rowKeyVale = getRowKeys(rowKeyColumns, r);
|
||||||
|
rowKeyBytes = Bytes.toBytes(rowKeyVale);
|
||||||
|
} else if (rowKeyColumn == null) {
|
||||||
|
// 如果不需要类型转换
|
||||||
|
Map<String, Object> rowKey = data.get(0);
|
||||||
|
rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next());
|
||||||
|
} else {
|
||||||
|
Object val = r.get(rowKeyColumn.getColumn());
|
||||||
|
rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, val);
|
||||||
|
}
|
||||||
|
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
||||||
|
rowKeys.add(rowKeyBytes);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rowKeys.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
rowKeys.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!complete && !rowKeys.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAndInsert(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
List<Map<String, Object>> old = dml.getOld();
|
||||||
|
if (old == null || old.isEmpty() || data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int i = 1;
|
||||||
|
boolean complete = false;
|
||||||
|
Set<byte[]> rowKeys = new HashSet<>();
|
||||||
|
List<HRow> rows = new ArrayList<>();
|
||||||
|
for (Map<String, Object> r : data) {
|
||||||
|
// 拼接老的rowKey
|
||||||
|
List<String> updateSubRowKey = new ArrayList<>();
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
|
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
|
||||||
|
updateSubRowKey.add(rowKeyColumnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateSubRowKey.isEmpty()) {
|
||||||
|
throw new RuntimeException("没有更新复合主键的RowKey");
|
||||||
|
}
|
||||||
|
StringBuilder oldRowKey = new StringBuilder();
|
||||||
|
StringBuilder newRowKey = new StringBuilder();
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
newRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
|
||||||
|
if (!updateSubRowKey.contains(rowKeyColumnName)) {
|
||||||
|
// 从data取
|
||||||
|
oldRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
|
||||||
|
} else {
|
||||||
|
// 从old取
|
||||||
|
oldRowKey.append(old.get(index).get(rowKeyColumnName).toString()).append("|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int len = newRowKey.length();
|
||||||
|
newRowKey.delete(len - 1, len);
|
||||||
|
len = oldRowKey.length();
|
||||||
|
oldRowKey.delete(len - 1, len);
|
||||||
|
byte[] newRowKeyBytes = Bytes.toBytes(newRowKey.toString());
|
||||||
|
byte[] oldRowKeyBytes = Bytes.toBytes(oldRowKey.toString());
|
||||||
|
|
||||||
|
rowKeys.add(oldRowKeyBytes);
|
||||||
|
HRow row = new HRow(newRowKeyBytes);
|
||||||
|
convertData2Row(hbaseOrm, row, r);
|
||||||
|
rows.add(row);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
rowKeys.clear();
|
||||||
|
rows.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (!complete && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据对应的类型进行转换
|
||||||
|
*
|
||||||
|
* @param columnItem 列项配置
|
||||||
|
* @param hbaseOrm hbase映射配置
|
||||||
|
* @param value 值
|
||||||
|
* @return 复合字段rowKey
|
||||||
|
*/
|
||||||
|
private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseOrm hbaseOrm,
|
||||||
|
Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] bytes = null;
|
||||||
|
if (columnItem == null || columnItem.getType() == null || "".equals(columnItem.getType())) {
|
||||||
|
if (MappingConfig.Mode.STRING == hbaseOrm.getMode()) {
|
||||||
|
bytes = Bytes.toBytes(value.toString());
|
||||||
|
} else if (MappingConfig.Mode.NATIVE == hbaseOrm.getMode()) {
|
||||||
|
bytes = TypeUtil.toBytes(value);
|
||||||
|
} else if (MappingConfig.Mode.PHOENIX == hbaseOrm.getMode()) {
|
||||||
|
PhType phType = PhType.getType(value.getClass());
|
||||||
|
bytes = PhTypeUtil.toBytes(value, phType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hbaseOrm.getMode() == MappingConfig.Mode.STRING) {
|
||||||
|
bytes = Bytes.toBytes(value.toString());
|
||||||
|
} else if (hbaseOrm.getMode() == MappingConfig.Mode.NATIVE) {
|
||||||
|
Type type = Type.getType(columnItem.getType());
|
||||||
|
bytes = TypeUtil.toBytes(value, type);
|
||||||
|
} else if (hbaseOrm.getMode() == MappingConfig.Mode.PHOENIX) {
|
||||||
|
PhType phType = PhType.getType(columnItem.getType());
|
||||||
|
bytes = PhTypeUtil.toBytes(value, phType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取复合字段作为rowKey的拼接
|
||||||
|
*
|
||||||
|
* @param rowKeyColumns 复合rowK对应的字段
|
||||||
|
* @param data 数据
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getRowKeys(String[] rowKeyColumns, Map<String, Object> data) {
|
||||||
|
StringBuilder rowKeyValue = new StringBuilder();
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
Object obj = data.get(rowKeyColumnName);
|
||||||
|
if (obj != null) {
|
||||||
|
rowKeyValue.append(obj.toString());
|
||||||
|
}
|
||||||
|
rowKeyValue.append("|");
|
||||||
|
}
|
||||||
|
int len = rowKeyValue.length();
|
||||||
|
if (len > 0) {
|
||||||
|
rowKeyValue.delete(len - 1, len);
|
||||||
|
}
|
||||||
|
return rowKeyValue.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase操作对象类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午10:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class HRow {
|
||||||
|
|
||||||
|
private byte[] rowKey;
|
||||||
|
private List<HCell> cells = new ArrayList<>();
|
||||||
|
|
||||||
|
public HRow(){
|
||||||
|
}
|
||||||
|
|
||||||
|
public HRow(byte[] rowKey){
|
||||||
|
this.rowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRowKey() {
|
||||||
|
return rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKey(byte[] rowKey) {
|
||||||
|
this.rowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HCell> getCells() {
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCells(List<HCell> cells) {
|
||||||
|
this.cells = cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCell(String family, String qualifier, byte[] value) {
|
||||||
|
HCell hCell = new HCell(family, qualifier, value);
|
||||||
|
cells.add(hCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HCell {
|
||||||
|
|
||||||
|
private String family;
|
||||||
|
private String qualifier;
|
||||||
|
private byte[] value;
|
||||||
|
|
||||||
|
public HCell(){
|
||||||
|
}
|
||||||
|
|
||||||
|
public HCell(String family, String qualifier, byte[] value){
|
||||||
|
this.family = family;
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFamily() {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamily(String family) {
|
||||||
|
this.family = family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQualifier() {
|
||||||
|
return qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQualifier(String qualifier) {
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(byte[] value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.HColumnDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.client.*;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase操作模板
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午10:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class HbaseTemplate {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
|
||||||
|
public HbaseTemplate(Connection conn){
|
||||||
|
this.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tableExists(String tableName) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
|
||||||
|
return admin.tableExists(TableName.valueOf(tableName));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTable(String tableName, String... familyNames) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
|
||||||
|
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
|
||||||
|
// 添加列簇
|
||||||
|
if (familyNames != null) {
|
||||||
|
for (String familyName : familyNames) {
|
||||||
|
HColumnDescriptor hcd = new HColumnDescriptor(familyName);
|
||||||
|
desc.addFamily(hcd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
admin.createTable(desc);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableTable(String tableName) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
admin.disableTable(tableName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTable(String tableName) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
if (admin.isTableEnabled(tableName)) {
|
||||||
|
disableTable(tableName);
|
||||||
|
}
|
||||||
|
admin.deleteTable(tableName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入一行数据
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @param hRow 行数据对象
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public Boolean put(String tableName, HRow hRow) {
|
||||||
|
boolean flag = false;
|
||||||
|
try {
|
||||||
|
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
||||||
|
Put put = new Put(hRow.getRowKey());
|
||||||
|
for (HRow.HCell hCell : hRow.getCells()) {
|
||||||
|
put.addColumn(Bytes.toBytes(hCell.getFamily()), Bytes.toBytes(hCell.getQualifier()), hCell.getValue());
|
||||||
|
}
|
||||||
|
table.put(put);
|
||||||
|
flag = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @param rows 行数据对象集合
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public Boolean puts(String tableName, List<HRow> rows) {
|
||||||
|
boolean flag = false;
|
||||||
|
try {
|
||||||
|
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
||||||
|
List<Put> puts = new ArrayList<>();
|
||||||
|
for (HRow hRow : rows) {
|
||||||
|
Put put = new Put(hRow.getRowKey());
|
||||||
|
for (HRow.HCell hCell : hRow.getCells()) {
|
||||||
|
put.addColumn(Bytes.toBytes(hCell.getFamily()),
|
||||||
|
Bytes.toBytes(hCell.getQualifier()),
|
||||||
|
hCell.getValue());
|
||||||
|
}
|
||||||
|
puts.add(put);
|
||||||
|
}
|
||||||
|
if (!puts.isEmpty()) {
|
||||||
|
table.put(puts);
|
||||||
|
}
|
||||||
|
flag = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除数据
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @param rowKeys rowKey集合
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public Boolean deletes(String tableName, Set<byte[]> rowKeys) {
|
||||||
|
boolean flag = false;
|
||||||
|
try {
|
||||||
|
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
||||||
|
List<Delete> deletes = new ArrayList<>();
|
||||||
|
for (byte[] rowKey : rowKeys) {
|
||||||
|
Delete delete = new Delete(rowKey);
|
||||||
|
deletes.add(delete);
|
||||||
|
}
|
||||||
|
if (!deletes.isEmpty()) {
|
||||||
|
table.delete(deletes);
|
||||||
|
}
|
||||||
|
flag = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phoenix类型
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public enum PhType {
|
||||||
|
DEFAULT(-1, "VARCHAR"),
|
||||||
|
UNSIGNED_INT(4, "UNSIGNED_INT"),
|
||||||
|
UNSIGNED_LONG(8, "UNSIGNED_LONG"),
|
||||||
|
UNSIGNED_TINYINT(1, "UNSIGNED_TINYINT"),
|
||||||
|
UNSIGNED_SMALLINT(2, "UNSIGNED_SMALLINT"),
|
||||||
|
UNSIGNED_FLOAT(4, "UNSIGNED_FLOAT"),
|
||||||
|
UNSIGNED_DOUBLE(8, "UNSIGNED_DOUBLE"),
|
||||||
|
INTEGER(4, "INTEGER"),
|
||||||
|
BIGINT(8, "BIGINT"),
|
||||||
|
TINYINT(1, "TINYINT"),
|
||||||
|
SMALLINT(2, "SMALLINT"),
|
||||||
|
FLOAT(4, "FLOAT"),
|
||||||
|
DOUBLE(8, "DOUBLE"),
|
||||||
|
DECIMAL(-1, "DECIMAL"),
|
||||||
|
BOOLEAN(1, "BOOLEAN"),
|
||||||
|
UNSIGNED_TIME(8, "UNSIGNED_TIME"),
|
||||||
|
UNSIGNED_DATE(8, "UNSIGNED_DATE"),
|
||||||
|
UNSIGNED_TIMESTAMP(12, "UNSIGNED_TIMESTAMP"),
|
||||||
|
TIME(8, "TIME"),
|
||||||
|
DATE(8, "DATE"),
|
||||||
|
TIMESTAMP(12, "TIMESTAMP"),
|
||||||
|
VARCHAR(-1, "VARCHAR"),
|
||||||
|
VARBINARY(-1, "VARBINARY");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -1:长度可变
|
||||||
|
*/
|
||||||
|
private int len;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
PhType(int len, String type) {
|
||||||
|
this.len = len;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLen() {
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PhType getType(Class<?> javaType) {
|
||||||
|
if (javaType == null) return DEFAULT;
|
||||||
|
PhType phType;
|
||||||
|
if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = INTEGER;
|
||||||
|
} else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = BIGINT;
|
||||||
|
} else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = TINYINT;
|
||||||
|
} else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = SMALLINT;
|
||||||
|
} else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = FLOAT;
|
||||||
|
} else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DOUBLE;
|
||||||
|
} else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = BOOLEAN;
|
||||||
|
} else if (java.sql.Date.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (Time.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (Timestamp.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = TIMESTAMP;
|
||||||
|
} else if (Date.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (byte[].class.isAssignableFrom(javaType)) {
|
||||||
|
phType = VARBINARY;
|
||||||
|
} else if (String.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = VARCHAR;
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DECIMAL;
|
||||||
|
} else if (BigInteger.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = UNSIGNED_LONG;
|
||||||
|
} else {
|
||||||
|
phType = DEFAULT;
|
||||||
|
}
|
||||||
|
return phType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PhType getType(String type) {
|
||||||
|
if (type == null) return DEFAULT;
|
||||||
|
PhType phType;
|
||||||
|
if (type.equalsIgnoreCase(UNSIGNED_INT.type)) {
|
||||||
|
phType = UNSIGNED_INT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_LONG.type)) {
|
||||||
|
phType = UNSIGNED_LONG;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_TINYINT.type)) {
|
||||||
|
phType = UNSIGNED_TINYINT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_SMALLINT.type)) {
|
||||||
|
phType = UNSIGNED_SMALLINT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_FLOAT.type)) {
|
||||||
|
phType = UNSIGNED_FLOAT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_DOUBLE.type)) {
|
||||||
|
phType = UNSIGNED_DOUBLE;
|
||||||
|
} else if (type.equalsIgnoreCase(INTEGER.type)) {
|
||||||
|
phType = INTEGER;
|
||||||
|
} else if (type.equalsIgnoreCase(BIGINT.type)) {
|
||||||
|
phType = BIGINT;
|
||||||
|
} else if (type.equalsIgnoreCase(TINYINT.type)) {
|
||||||
|
phType = TINYINT;
|
||||||
|
} else if (type.equalsIgnoreCase(SMALLINT.type)) {
|
||||||
|
phType = SMALLINT;
|
||||||
|
} else if (type.equalsIgnoreCase(FLOAT.type)) {
|
||||||
|
phType = FLOAT;
|
||||||
|
} else if (type.equalsIgnoreCase(DOUBLE.type)) {
|
||||||
|
phType = DOUBLE;
|
||||||
|
} else if (type.equalsIgnoreCase(BOOLEAN.type)) {
|
||||||
|
phType = BOOLEAN;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_TIME.type)) {
|
||||||
|
phType = UNSIGNED_TIME;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_DATE.type)) {
|
||||||
|
phType = UNSIGNED_DATE;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_TIMESTAMP.type)) {
|
||||||
|
phType = UNSIGNED_TIMESTAMP;
|
||||||
|
} else if (type.equalsIgnoreCase(TIME.type)) {
|
||||||
|
phType = TIME;
|
||||||
|
} else if (type.equalsIgnoreCase(DATE.type)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (type.equalsIgnoreCase(TIMESTAMP.type)) {
|
||||||
|
phType = TIMESTAMP;
|
||||||
|
} else if (type.equalsIgnoreCase(VARCHAR.type)) {
|
||||||
|
phType = VARCHAR;
|
||||||
|
} else if (type.equalsIgnoreCase(VARBINARY.type)) {
|
||||||
|
phType = VARBINARY;
|
||||||
|
} else if (type.equalsIgnoreCase(DECIMAL.type)) {
|
||||||
|
phType = DECIMAL;
|
||||||
|
} else {
|
||||||
|
phType = DEFAULT;
|
||||||
|
}
|
||||||
|
return phType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,614 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
import com.google.common.math.LongMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phoenix类型转换工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:14:26
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class PhTypeUtil {
|
||||||
|
|
||||||
|
public static byte[] toBytes(Object v, PhType phType) {
|
||||||
|
if (v == null) return null;
|
||||||
|
byte[] b = null;
|
||||||
|
if (phType == PhType.DEFAULT) {
|
||||||
|
PhType phType1 = PhType.getType(v.getClass());
|
||||||
|
if (phType1 != null && phType1 != PhType.DEFAULT) {
|
||||||
|
toBytes(v, phType1);
|
||||||
|
}
|
||||||
|
} else if (phType == PhType.INTEGER) {
|
||||||
|
b = new byte[Bytes.SIZEOF_INT];
|
||||||
|
encodeInt(((Number) v).intValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_INT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_INT];
|
||||||
|
encodeUnsignedInt(((Number) v).intValue(), b, 0);
|
||||||
|
} else if (phType == PhType.BIGINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeLong(((Number) v).longValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_LONG) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeUnsignedLong(((Number) v).longValue(), b, 0);
|
||||||
|
} else if (phType == PhType.SMALLINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_SHORT];
|
||||||
|
encodeShort(((Number) v).shortValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_SMALLINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_SHORT];
|
||||||
|
encodeUnsignedShort(((Number) v).shortValue(), b, 0);
|
||||||
|
} else if (phType == PhType.TINYINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_BYTE];
|
||||||
|
encodeByte(((Number) v).byteValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TINYINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_BYTE];
|
||||||
|
encodeUnsignedByte(((Number) v).byteValue(), b, 0);
|
||||||
|
} else if (phType == PhType.FLOAT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_FLOAT];
|
||||||
|
encodeFloat(((Number) v).floatValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_FLOAT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_FLOAT];
|
||||||
|
encodeUnsignedFloat(((Number) v).floatValue(), b, 0);
|
||||||
|
} else if (phType == PhType.DOUBLE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_DOUBLE];
|
||||||
|
encodeDouble(((Number) v).doubleValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_DOUBLE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_DOUBLE];
|
||||||
|
encodeUnsignedDouble(((Number) v).doubleValue(), b, 0);
|
||||||
|
} else if (phType == PhType.BOOLEAN) {
|
||||||
|
if ((Boolean) v) {
|
||||||
|
b = new byte[] { 1 };
|
||||||
|
} else {
|
||||||
|
b = new byte[] { 0 };
|
||||||
|
}
|
||||||
|
} else if (phType == PhType.TIME || phType == PhType.DATE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeDate(v, b, 0);
|
||||||
|
} else if (phType == PhType.TIMESTAMP) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT];
|
||||||
|
encodeTimestamp(v, b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeUnsignedDate(v, b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIMESTAMP) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT];
|
||||||
|
encodeUnsignedTimestamp(v, b, 0);
|
||||||
|
} else if (phType == PhType.VARBINARY) {
|
||||||
|
b = (byte[]) v;
|
||||||
|
} else if (phType == PhType.VARCHAR) {
|
||||||
|
b = Bytes.toBytes(v.toString());
|
||||||
|
} else if (phType == PhType.DECIMAL) {
|
||||||
|
if (v instanceof BigDecimal) {
|
||||||
|
b = encodeDecimal(v);
|
||||||
|
} else if (v instanceof Number) {
|
||||||
|
b = encodeDecimal(new BigDecimal(v.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object toObject(byte[] b, PhType phType) {
|
||||||
|
if (b == null) return null;
|
||||||
|
Object v = null;
|
||||||
|
if (phType == PhType.INTEGER) {
|
||||||
|
v = decodeInt(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_INT) {
|
||||||
|
v = decodeUnsignedInt(b, 0);
|
||||||
|
} else if (phType == PhType.BIGINT) {
|
||||||
|
v = decodeLong(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_LONG) {
|
||||||
|
v = decodeUnsignedLong(b, 0);
|
||||||
|
} else if (phType == PhType.SMALLINT) {
|
||||||
|
v = decodeShort(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_SMALLINT) {
|
||||||
|
v = decodeUnsignedShort(b, 0);
|
||||||
|
} else if (phType == PhType.TINYINT) {
|
||||||
|
v = decodeByte(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TINYINT) {
|
||||||
|
v = decodeUnsignedByte(b, 0);
|
||||||
|
} else if (phType == PhType.FLOAT) {
|
||||||
|
v = decodeFloat(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_FLOAT) {
|
||||||
|
v = decodeUnsignedFloat(b, 0);
|
||||||
|
} else if (phType == PhType.DOUBLE) {
|
||||||
|
v = decodeDouble(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_DOUBLE) {
|
||||||
|
v = decodeUnsignedDouble(b, 0);
|
||||||
|
} else if (phType == PhType.BOOLEAN) {
|
||||||
|
checkForSufficientLength(b, 0, Bytes.SIZEOF_BOOLEAN);
|
||||||
|
if (b[0] == 1) {
|
||||||
|
v = true;
|
||||||
|
} else if (b[0] == 0) {
|
||||||
|
v = false;
|
||||||
|
}
|
||||||
|
} else if (phType == PhType.TIME || phType == PhType.DATE) {
|
||||||
|
v = new Date(decodeLong(b, 0));
|
||||||
|
} else if (phType == PhType.TIMESTAMP) {
|
||||||
|
long millisDeserialized = decodeLong(b, 0);
|
||||||
|
Timestamp ts = new Timestamp(millisDeserialized);
|
||||||
|
int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG);
|
||||||
|
ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized);
|
||||||
|
v = ts;
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) {
|
||||||
|
v = new Date(decodeUnsignedLong(b, 0));
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIMESTAMP) {
|
||||||
|
long millisDeserialized = decodeUnsignedLong(b, 0);
|
||||||
|
Timestamp ts = new Timestamp(millisDeserialized);
|
||||||
|
int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG);
|
||||||
|
ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized);
|
||||||
|
v = ts;
|
||||||
|
} else if (phType == PhType.VARBINARY) {
|
||||||
|
v = b;
|
||||||
|
} else if (phType == PhType.VARCHAR || phType == PhType.DEFAULT) {
|
||||||
|
v = Bytes.toString(b);
|
||||||
|
} else if (phType == PhType.DECIMAL) {
|
||||||
|
v = decodeDecimal(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeInt(byte[] bytes, int o) {
|
||||||
|
checkForSufficientLength(bytes, o, Bytes.SIZEOF_INT);
|
||||||
|
int v;
|
||||||
|
v = bytes[o] ^ 0x80; // Flip sign bit back
|
||||||
|
for (int i = 1; i < Bytes.SIZEOF_INT; i++) {
|
||||||
|
v = (v << 8) + (bytes[o + i] & 0xff);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeInt(int v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
b[o + 0] = (byte) ((v >> 24) ^ 0x80); // Flip sign bit so that INTEGER is binary comparable
|
||||||
|
b[o + 1] = (byte) (v >> 16);
|
||||||
|
b[o + 2] = (byte) (v >> 8);
|
||||||
|
b[o + 3] = (byte) v;
|
||||||
|
return Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeUnsignedInt(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
|
||||||
|
int v = Bytes.toInt(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedInt(int v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putInt(b, o, v);
|
||||||
|
return Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long decodeLong(byte[] bytes, int o) {
|
||||||
|
checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG);
|
||||||
|
long v;
|
||||||
|
byte b = bytes[o];
|
||||||
|
v = b ^ 0x80; // Flip sign bit back
|
||||||
|
for (int i = 1; i < Bytes.SIZEOF_LONG; i++) {
|
||||||
|
b = bytes[o + i];
|
||||||
|
v = (v << 8) + (b & 0xff);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeLong(long v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
b[o + 0] = (byte) ((v >> 56) ^ 0x80); // Flip sign bit so that INTEGER is binary comparable
|
||||||
|
b[o + 1] = (byte) (v >> 48);
|
||||||
|
b[o + 2] = (byte) (v >> 40);
|
||||||
|
b[o + 3] = (byte) (v >> 32);
|
||||||
|
b[o + 4] = (byte) (v >> 24);
|
||||||
|
b[o + 5] = (byte) (v >> 16);
|
||||||
|
b[o + 6] = (byte) (v >> 8);
|
||||||
|
b[o + 7] = (byte) v;
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long decodeUnsignedLong(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
long v = 0;
|
||||||
|
for (int i = o; i < o + Bytes.SIZEOF_LONG; i++) {
|
||||||
|
v <<= 8;
|
||||||
|
v ^= b[i] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedLong(long v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putLong(b, o, v);
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short decodeShort(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
int v;
|
||||||
|
v = b[o] ^ 0x80; // Flip sign bit back
|
||||||
|
for (int i = 1; i < Bytes.SIZEOF_SHORT; i++) {
|
||||||
|
v = (v << 8) + (b[o + i] & 0xff);
|
||||||
|
}
|
||||||
|
return (short) v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeShort(short v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
b[o + 0] = (byte) ((v >> 8) ^ 0x80); // Flip sign bit so that Short is binary comparable
|
||||||
|
b[o + 1] = (byte) v;
|
||||||
|
return Bytes.SIZEOF_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short decodeUnsignedShort(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
short v = Bytes.toShort(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedShort(short v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putShort(b, o, v);
|
||||||
|
return Bytes.SIZEOF_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte decodeByte(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
|
||||||
|
int v;
|
||||||
|
v = b[o] ^ 0x80; // Flip sign bit back
|
||||||
|
return (byte) v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeByte(byte v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
|
||||||
|
b[o] = (byte) (v ^ 0x80); // Flip sign bit so that Short is binary comparable
|
||||||
|
return Bytes.SIZEOF_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte decodeUnsignedByte(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
|
||||||
|
byte v = b[o];
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedByte(byte v, byte[] b, int o) {
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putByte(b, o, v);
|
||||||
|
return Bytes.SIZEOF_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float decodeFloat(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
int value;
|
||||||
|
value = Bytes.toInt(b, o);
|
||||||
|
value--;
|
||||||
|
value ^= (~value >> Integer.SIZE - 1) | Integer.MIN_VALUE;
|
||||||
|
return Float.intBitsToFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeFloat(float v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
|
||||||
|
int i = Float.floatToIntBits(v);
|
||||||
|
i = (i ^ ((i >> Integer.SIZE - 1) | Integer.MIN_VALUE)) + 1;
|
||||||
|
Bytes.putInt(b, o, i);
|
||||||
|
return Bytes.SIZEOF_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float decodeUnsignedFloat(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
|
||||||
|
float v = Bytes.toFloat(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedFloat(float v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putFloat(b, o, v);
|
||||||
|
return Bytes.SIZEOF_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double decodeDouble(byte[] bytes, int o) {
|
||||||
|
checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG);
|
||||||
|
long l;
|
||||||
|
l = Bytes.toLong(bytes, o);
|
||||||
|
l--;
|
||||||
|
l ^= (~l >> Long.SIZE - 1) | Long.MIN_VALUE;
|
||||||
|
return Double.longBitsToDouble(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeDouble(double v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
long l = Double.doubleToLongBits(v);
|
||||||
|
l = (l ^ ((l >> Long.SIZE - 1) | Long.MIN_VALUE)) + 1;
|
||||||
|
Bytes.putLong(b, o, l);
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double decodeUnsignedDouble(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE);
|
||||||
|
double v = Bytes.toDouble(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedDouble(double v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putDouble(b, o, v);
|
||||||
|
return Bytes.SIZEOF_DOUBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeDate(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Date) {
|
||||||
|
encodeLong(((Date) v).getTime(), b, 0);
|
||||||
|
} else if (v instanceof String) {
|
||||||
|
String dateStr = (String) v;
|
||||||
|
Date date;
|
||||||
|
try {
|
||||||
|
date = parseDatetime(dateStr);
|
||||||
|
if (date != null) {
|
||||||
|
encodeLong(date.getTime(), b, 0);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeTimestamp(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Timestamp) {
|
||||||
|
Timestamp ts = (Timestamp) v;
|
||||||
|
encodeLong(ts.getTime(), b, o);
|
||||||
|
Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000);
|
||||||
|
} else {
|
||||||
|
encodeDate(v, b, o);
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedDate(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Date) {
|
||||||
|
encodeUnsignedLong(((Date) v).getTime(), b, 0);
|
||||||
|
} else if (v instanceof String) {
|
||||||
|
String dateStr = (String) v;
|
||||||
|
Date date;
|
||||||
|
try {
|
||||||
|
date = parseDatetime(dateStr);
|
||||||
|
if (date != null) {
|
||||||
|
encodeUnsignedLong(date.getTime(), b, 0);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedTimestamp(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Timestamp) {
|
||||||
|
Timestamp ts = (Timestamp) v;
|
||||||
|
encodeUnsignedLong(ts.getTime(), b, o);
|
||||||
|
Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000);
|
||||||
|
} else {
|
||||||
|
encodeUnsignedDate(v, b, o);
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] encodeDecimal(Object object) {
|
||||||
|
if (object == null) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
BigDecimal v = (BigDecimal) object;
|
||||||
|
v = v.round(DEFAULT_MATH_CONTEXT).stripTrailingZeros();
|
||||||
|
int len = getLength(v);
|
||||||
|
byte[] result = new byte[Math.min(len, 21)];
|
||||||
|
decimalToBytes(v, result, 0, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal decodeDecimal(byte[] bytes, int offset, int length) {
|
||||||
|
if (length == 1 && bytes[offset] == ZERO_BYTE) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
int signum = ((bytes[offset] & 0x80) == 0) ? -1 : 1;
|
||||||
|
int scale;
|
||||||
|
int index;
|
||||||
|
int digitOffset;
|
||||||
|
long multiplier = 100L;
|
||||||
|
int begIndex = offset + 1;
|
||||||
|
if (signum == 1) {
|
||||||
|
scale = (byte) (((bytes[offset] & 0x7F) - 65) * -2);
|
||||||
|
index = offset + length;
|
||||||
|
digitOffset = POS_DIGIT_OFFSET;
|
||||||
|
} else {
|
||||||
|
scale = (byte) ((~bytes[offset] - 65 - 128) * -2);
|
||||||
|
index = offset + length - (bytes[offset + length - 1] == NEG_TERMINAL_BYTE ? 1 : 0);
|
||||||
|
digitOffset = -NEG_DIGIT_OFFSET;
|
||||||
|
}
|
||||||
|
length = index - offset;
|
||||||
|
long l = signum * bytes[--index] - digitOffset;
|
||||||
|
if (l % 10 == 0) { // trailing zero
|
||||||
|
scale--; // drop trailing zero and compensate in the scale
|
||||||
|
l /= 10;
|
||||||
|
multiplier = 10;
|
||||||
|
}
|
||||||
|
// Use long arithmetic for as long as we can
|
||||||
|
while (index > begIndex) {
|
||||||
|
if (l >= MAX_LONG_FOR_DESERIALIZE || multiplier >= Long.MAX_VALUE / 100) {
|
||||||
|
multiplier = LongMath.divide(multiplier, 100L, RoundingMode.UNNECESSARY);
|
||||||
|
break; // Exit loop early so we don't overflow our multiplier
|
||||||
|
}
|
||||||
|
int digit100 = signum * bytes[--index] - digitOffset;
|
||||||
|
l += digit100 * multiplier;
|
||||||
|
multiplier = LongMath.checkedMultiply(multiplier, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger bi;
|
||||||
|
// If still more digits, switch to BigInteger arithmetic
|
||||||
|
if (index > begIndex) {
|
||||||
|
bi = BigInteger.valueOf(l);
|
||||||
|
BigInteger biMultiplier = BigInteger.valueOf(multiplier).multiply(ONE_HUNDRED);
|
||||||
|
do {
|
||||||
|
int digit100 = signum * bytes[--index] - digitOffset;
|
||||||
|
bi = bi.add(biMultiplier.multiply(BigInteger.valueOf(digit100)));
|
||||||
|
biMultiplier = biMultiplier.multiply(ONE_HUNDRED);
|
||||||
|
} while (index > begIndex);
|
||||||
|
if (signum == -1) {
|
||||||
|
bi = bi.negate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bi = BigInteger.valueOf(l * signum);
|
||||||
|
}
|
||||||
|
// Update the scale based on the precision
|
||||||
|
scale += (length - 2) * 2;
|
||||||
|
BigDecimal v = new BigDecimal(bi, scale);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLength(BigDecimal v) {
|
||||||
|
int signum = v.signum();
|
||||||
|
if (signum == 0) { // Special case for zero
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return (signum < 0 ? 2 : 1) + (v.precision() + 1 + (v.scale() % 2 == 0 ? 0 : 1)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MAX_PRECISION = 38;
|
||||||
|
private static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(MAX_PRECISION, RoundingMode.HALF_UP);
|
||||||
|
private static final Integer MAX_BIG_DECIMAL_BYTES = 21;
|
||||||
|
private static final byte ZERO_BYTE = (byte) 0x80;
|
||||||
|
private static final byte NEG_TERMINAL_BYTE = (byte) 102;
|
||||||
|
private static final int EXP_BYTE_OFFSET = 65;
|
||||||
|
private static final int POS_DIGIT_OFFSET = 1;
|
||||||
|
private static final int NEG_DIGIT_OFFSET = 101;
|
||||||
|
private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
|
||||||
|
private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
|
||||||
|
private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100);
|
||||||
|
private static final long MAX_LONG_FOR_DESERIALIZE = Long.MAX_VALUE / 1000;
|
||||||
|
|
||||||
|
private static int decimalToBytes(BigDecimal v, byte[] result, final int offset, int length) {
|
||||||
|
int signum = v.signum();
|
||||||
|
if (signum == 0) {
|
||||||
|
result[offset] = ZERO_BYTE;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int index = offset + length;
|
||||||
|
int scale = v.scale();
|
||||||
|
int expOffset = scale % 2 * (scale < 0 ? -1 : 1);
|
||||||
|
int multiplyBy;
|
||||||
|
BigInteger divideBy;
|
||||||
|
if (expOffset == 0) {
|
||||||
|
multiplyBy = 1;
|
||||||
|
divideBy = ONE_HUNDRED;
|
||||||
|
} else {
|
||||||
|
multiplyBy = 10;
|
||||||
|
divideBy = BigInteger.TEN;
|
||||||
|
}
|
||||||
|
// Normalize the scale based on what is necessary to end up with a base 100
|
||||||
|
// decimal (i.e. 10.123e3)
|
||||||
|
int digitOffset;
|
||||||
|
BigInteger compareAgainst;
|
||||||
|
if (signum == 1) {
|
||||||
|
digitOffset = POS_DIGIT_OFFSET;
|
||||||
|
compareAgainst = MAX_LONG;
|
||||||
|
scale -= (length - 2) * 2;
|
||||||
|
result[offset] = (byte) ((-(scale + expOffset) / 2 + EXP_BYTE_OFFSET) | 0x80);
|
||||||
|
} else {
|
||||||
|
digitOffset = NEG_DIGIT_OFFSET;
|
||||||
|
compareAgainst = MIN_LONG;
|
||||||
|
// Scale adjustment shouldn't include terminal byte in length
|
||||||
|
scale -= (length - 2 - 1) * 2;
|
||||||
|
result[offset] = (byte) (~(-(scale + expOffset) / 2 + EXP_BYTE_OFFSET + 128) & 0x7F);
|
||||||
|
if (length <= MAX_BIG_DECIMAL_BYTES) {
|
||||||
|
result[--index] = NEG_TERMINAL_BYTE;
|
||||||
|
} else {
|
||||||
|
// Adjust length and offset down because we don't have enough room
|
||||||
|
length = MAX_BIG_DECIMAL_BYTES;
|
||||||
|
index = offset + length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BigInteger bi = v.unscaledValue();
|
||||||
|
// Use BigDecimal arithmetic until we can fit into a long
|
||||||
|
while (bi.compareTo(compareAgainst) * signum > 0) {
|
||||||
|
BigInteger[] dandr = bi.divideAndRemainder(divideBy);
|
||||||
|
bi = dandr[0];
|
||||||
|
int digit = dandr[1].intValue();
|
||||||
|
result[--index] = (byte) (digit * multiplyBy + digitOffset);
|
||||||
|
multiplyBy = 1;
|
||||||
|
divideBy = ONE_HUNDRED;
|
||||||
|
}
|
||||||
|
long l = bi.longValue();
|
||||||
|
do {
|
||||||
|
long divBy = 100 / multiplyBy;
|
||||||
|
long digit = l % divBy;
|
||||||
|
l /= divBy;
|
||||||
|
result[--index] = (byte) (digit * multiplyBy + digitOffset);
|
||||||
|
multiplyBy = 1;
|
||||||
|
} while (l != 0);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkForSufficientLength(byte[] b, int offset, int requiredLength) {
|
||||||
|
if (b.length < offset + requiredLength) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected length of at least " + requiredLength + " bytes, but had " + (b.length - offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date parseDatetime(String dateStr) {
|
||||||
|
Date date = null;
|
||||||
|
int len = dateStr.length();
|
||||||
|
if (len == 10 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-') {
|
||||||
|
date = new DateTime(dateStr).toDate();
|
||||||
|
} else if (len == 8 && dateStr.charAt(2) == ':' && dateStr.charAt(5) == ':') {
|
||||||
|
date = new DateTime("T" + dateStr).toDate();
|
||||||
|
} else if (len >= 19 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-' && dateStr.charAt(13) == ':'
|
||||||
|
&& dateStr.charAt(16) == ':') {
|
||||||
|
date = new DateTime(dateStr.replace(" ", "T")).toDate();
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java类型
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:11:36
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public enum Type {
|
||||||
|
DEFAULT("STRING"),
|
||||||
|
STRING("STRING"),
|
||||||
|
INTEGER("INTEGER"),
|
||||||
|
LONG("LONG"),
|
||||||
|
SHORT("SHORT"),
|
||||||
|
BOOLEAN("BOOLEAN"),
|
||||||
|
FLOAT("FLOAT"),
|
||||||
|
DOUBLE("DOUBLE"),
|
||||||
|
BIGDECIMAL("BIGDECIMAL"),
|
||||||
|
DATE("DATE"),
|
||||||
|
BYTE("BYTE"),
|
||||||
|
BYTES("BYTES");
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
Type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type getType(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
Type res;
|
||||||
|
if (type.equalsIgnoreCase("STRING")) {
|
||||||
|
res = STRING;
|
||||||
|
} else if (type.equalsIgnoreCase("INTEGER")) {
|
||||||
|
res = INTEGER;
|
||||||
|
} else if (type.equalsIgnoreCase("LONG")) {
|
||||||
|
res = LONG;
|
||||||
|
} else if (type.equalsIgnoreCase("SHORT")) {
|
||||||
|
res = SHORT;
|
||||||
|
} else if (type.equalsIgnoreCase("BOOLEAN")) {
|
||||||
|
res = BOOLEAN;
|
||||||
|
} else if (type.equalsIgnoreCase("FLOAT")) {
|
||||||
|
res = FLOAT;
|
||||||
|
} else if (type.equalsIgnoreCase("DOUBLE")) {
|
||||||
|
res = DOUBLE;
|
||||||
|
} else if (type.equalsIgnoreCase("BIGDECIMAL")) {
|
||||||
|
res = BIGDECIMAL;
|
||||||
|
} else if (type.equalsIgnoreCase("DATE")) {
|
||||||
|
res = DATE;
|
||||||
|
} else if (type.equalsIgnoreCase("BYTE")) {
|
||||||
|
res = BYTE;
|
||||||
|
} else if (type.equalsIgnoreCase("BYTES")) {
|
||||||
|
res = BYTES;
|
||||||
|
} else {
|
||||||
|
res = DEFAULT;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type getType(Class<?> javaType) {
|
||||||
|
if (javaType == null) {
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
Type type;
|
||||||
|
if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) {
|
||||||
|
type = INTEGER;
|
||||||
|
} else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) {
|
||||||
|
type = LONG;
|
||||||
|
} else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) {
|
||||||
|
type = BYTE;
|
||||||
|
} else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) {
|
||||||
|
type = SHORT;
|
||||||
|
} else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) {
|
||||||
|
type = FLOAT;
|
||||||
|
} else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) {
|
||||||
|
type = DOUBLE;
|
||||||
|
} else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) {
|
||||||
|
type = BOOLEAN;
|
||||||
|
} else if (Date.class.isAssignableFrom(javaType)) {
|
||||||
|
type = DATE;
|
||||||
|
} else if (byte[].class.isAssignableFrom(javaType)) {
|
||||||
|
type = BYTES;
|
||||||
|
} else if (String.class.isAssignableFrom(javaType)) {
|
||||||
|
type = STRING;
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(javaType)) {
|
||||||
|
type = BIGDECIMAL;
|
||||||
|
} else if (BigInteger.class.isAssignableFrom(javaType)) {
|
||||||
|
type = LONG;
|
||||||
|
} else {
|
||||||
|
type = DEFAULT;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java类型转换工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class TypeUtil {
|
||||||
|
|
||||||
|
public static byte[] toBytes(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] bytes;
|
||||||
|
if (obj instanceof String) {
|
||||||
|
bytes = Bytes.toBytes((String) obj);
|
||||||
|
} else if (obj instanceof Integer) {
|
||||||
|
bytes = Bytes.toBytes((Integer) obj);
|
||||||
|
} else if (obj instanceof Long) {
|
||||||
|
bytes = Bytes.toBytes((Long) obj);
|
||||||
|
} else if (obj instanceof Short) {
|
||||||
|
bytes = Bytes.toBytes((Short) obj);
|
||||||
|
} else if (obj instanceof Boolean) {
|
||||||
|
bytes = Bytes.toBytes((Boolean) obj);
|
||||||
|
} else if (obj instanceof Float) {
|
||||||
|
bytes = Bytes.toBytes((Float) obj);
|
||||||
|
} else if (obj instanceof Double) {
|
||||||
|
bytes = Bytes.toBytes((Double) obj);
|
||||||
|
} else if (obj instanceof BigDecimal) {
|
||||||
|
bytes = Bytes.toBytes((BigDecimal) obj);
|
||||||
|
} else if (obj instanceof BigInteger) {
|
||||||
|
bytes = Bytes.toBytes(((BigInteger) obj).longValue());
|
||||||
|
} else if (obj instanceof Date) {
|
||||||
|
bytes = Bytes.toBytes(((Date) obj).getTime());
|
||||||
|
} else if (obj instanceof Byte) {
|
||||||
|
bytes = new byte[] { (byte) obj };
|
||||||
|
} else if (obj instanceof byte[]) {
|
||||||
|
bytes = (byte[]) obj;
|
||||||
|
} else {
|
||||||
|
// 其余类型统一转换为string
|
||||||
|
bytes = Bytes.toBytes(obj.toString());
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytes(Object v, Type type) {
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] b = null;
|
||||||
|
if (type == Type.DEFAULT) {
|
||||||
|
Type type1 = Type.getType(v.getClass());
|
||||||
|
if (type1 != null && type1 != Type.DEFAULT) {
|
||||||
|
b = toBytes(v, type1);
|
||||||
|
}
|
||||||
|
} else if (type == Type.STRING) {
|
||||||
|
b = Bytes.toBytes(v.toString());
|
||||||
|
} else if (type == Type.INTEGER) {
|
||||||
|
b = Bytes.toBytes(((Number) v).intValue());
|
||||||
|
} else if (type == Type.LONG) {
|
||||||
|
b = Bytes.toBytes(((Number) v).longValue());
|
||||||
|
} else if (type == Type.SHORT) {
|
||||||
|
b = Bytes.toBytes(((Number) v).shortValue());
|
||||||
|
} else if (type == Type.BYTE) {
|
||||||
|
b = Bytes.toBytes(((Number) v).byteValue());
|
||||||
|
} else if (type == Type.FLOAT) {
|
||||||
|
b = Bytes.toBytes(((Number) v).floatValue());
|
||||||
|
} else if (type == Type.DOUBLE) {
|
||||||
|
b = Bytes.toBytes(((Number) v).doubleValue());
|
||||||
|
} else if (type == Type.BOOLEAN) {
|
||||||
|
b = Bytes.toBytes(((Boolean) v));
|
||||||
|
} else if (type == Type.DATE) {
|
||||||
|
b = Bytes.toBytes(((Date) v).getTime());
|
||||||
|
} else if (type == Type.BYTES) {
|
||||||
|
b = (byte[]) v;
|
||||||
|
} else if (type == Type.BIGDECIMAL) {
|
||||||
|
if (v instanceof BigDecimal) {
|
||||||
|
b = Bytes.toBytes((BigDecimal) v);
|
||||||
|
} else {
|
||||||
|
b = Bytes.toBytes(new BigDecimal(v.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toObject(byte[] bytes, Class<T> clazz) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object res;
|
||||||
|
if (String.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toString(bytes);
|
||||||
|
} else if (Integer.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toInt(bytes);
|
||||||
|
} else if (Long.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toLong(bytes);
|
||||||
|
} else if (Short.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toShort(bytes);
|
||||||
|
} else if (Boolean.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toBoolean(bytes);
|
||||||
|
} else if (Float.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toFloat(bytes);
|
||||||
|
} else if (Double.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toDouble(bytes);
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toBigDecimal(bytes);
|
||||||
|
} else if (BigInteger.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toLong(bytes);
|
||||||
|
} else if (java.sql.Date.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new java.sql.Date(ts);
|
||||||
|
} else if (Time.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new Time(ts);
|
||||||
|
} else if (Timestamp.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new Timestamp(ts);
|
||||||
|
} else if (Date.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new Date(ts);
|
||||||
|
} else if (Byte.class.isAssignableFrom(clazz)) {
|
||||||
|
res = bytes[0];
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("mismatch class type");
|
||||||
|
}
|
||||||
|
// noinspection unchecked
|
||||||
|
return (T) res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toObject(byte[] bytes, Type type) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object res = null;
|
||||||
|
if (type == Type.STRING || type == Type.DEFAULT) {
|
||||||
|
res = Bytes.toString(bytes);
|
||||||
|
} else if (type == Type.INTEGER) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_INT) {
|
||||||
|
res = Bytes.toInt(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.LONG) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_LONG) {
|
||||||
|
res = Bytes.toLong(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.SHORT) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_SHORT) {
|
||||||
|
res = Bytes.toShort(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.BYTE) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_BYTE) {
|
||||||
|
res = bytes[0];
|
||||||
|
}
|
||||||
|
} else if (type == Type.FLOAT) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_FLOAT) {
|
||||||
|
res = Bytes.toFloat(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.DOUBLE) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_DOUBLE) {
|
||||||
|
res = Bytes.toDouble(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.BOOLEAN) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_BOOLEAN) {
|
||||||
|
res = Bytes.toBoolean(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.DATE) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_LONG) {
|
||||||
|
res = new Date(Bytes.toLong(bytes));
|
||||||
|
}
|
||||||
|
} else if (type == Type.BYTES) {
|
||||||
|
res = bytes;
|
||||||
|
} else if (type == Type.BIGDECIMAL) {
|
||||||
|
res = Bytes.toBigDecimal(bytes);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("mismatch class type");
|
||||||
|
}
|
||||||
|
// noinspection unchecked
|
||||||
|
return (T) res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
hbase=com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# 详细映射配置
|
||||||
|
mytest_person2.yml
|
||||||
|
|
||||||
|
# 简易配置, 只用指定数据库名.表名, 详细配置全部使用默认
|
||||||
|
mytest.person
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
hbaseOrm:
|
||||||
|
mode: PHOENIX #NATIVE #STRING
|
||||||
|
database: mytest # 数据库名
|
||||||
|
table: person2 # 数据库表名
|
||||||
|
hbaseTable: MYTEST.PERSON2 # HBase表名
|
||||||
|
family: CF # 默认统一Family名称
|
||||||
|
uppercaseQualifier: true # 字段名转大写, 默认为true
|
||||||
|
commitBatch: 3000 # 批量提交的大小
|
||||||
|
#rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复
|
||||||
|
columns:
|
||||||
|
# 数据库字段:HBase对应字段
|
||||||
|
id: ROWKEY$UNSIGNED_LONG
|
||||||
|
name: NAME
|
||||||
|
email: EMAIL
|
||||||
|
type: $DECIMAL
|
||||||
|
c_time: C_TIME$UNSIGNED_TIMESTAMP
|
||||||
|
birthday: BIRTHDAY$DATE
|
||||||
|
|
||||||
|
# -- NATIVE类型
|
||||||
|
# $DEFAULT
|
||||||
|
# $STRING
|
||||||
|
# $INTEGER
|
||||||
|
# $LONG
|
||||||
|
# $SHORT
|
||||||
|
# $BOOLEAN
|
||||||
|
# $FLOAT
|
||||||
|
# $DOUBLE
|
||||||
|
# $BIGDECIMAL
|
||||||
|
# $DATE
|
||||||
|
# $BYTE
|
||||||
|
# $BYTES
|
||||||
|
|
||||||
|
# -- PHOENIX类型
|
||||||
|
# $DEFAULT 对应PHOENIX里的VARCHAR
|
||||||
|
# $UNSIGNED_INT 对应PHOENIX里的UNSIGNED_INT 4字节
|
||||||
|
# $UNSIGNED_LONG 对应PHOENIX里的UNSIGNED_LONG 8字节
|
||||||
|
# $UNSIGNED_TINYINT 对应PHOENIX里的UNSIGNED_TINYINT 1字节
|
||||||
|
# $UNSIGNED_SMALLINT 对应PHOENIX里的UNSIGNED_SMALLINT 2字节
|
||||||
|
# $UNSIGNED_FLOAT 对应PHOENIX里的UNSIGNED_FLOAT 4字节
|
||||||
|
# $UNSIGNED_DOUBLE 对应PHOENIX里的UNSIGNED_DOUBLE 8字节
|
||||||
|
# $INTEGER 对应PHOENIX里的INTEGER 4字节
|
||||||
|
# $BIGINT 对应PHOENIX里的BIGINT 8字节
|
||||||
|
# $TINYINT 对应PHOENIX里的TINYINT 1字节
|
||||||
|
# $SMALLINT 对应PHOENIX里的SMALLINT 2字节
|
||||||
|
# $FLOAT 对应PHOENIX里的FLOAT 4字节
|
||||||
|
# DOUBLE 对应PHOENIX里的DOUBLE 8字节
|
||||||
|
# $BOOLEAN 对应PHOENIX里的BOOLEAN 1字节
|
||||||
|
# $TIME 对应PHOENIX里的TIME 8字节
|
||||||
|
# $DATE 对应PHOENIX里的DATE 8字节
|
||||||
|
# $TIMESTAMP 对应PHOENIX里的TIMESTAMP 12字节
|
||||||
|
# $UNSIGNED_TIME 对应PHOENIX里的UNSIGNED_TIME 8字节
|
||||||
|
# $UNSIGNED_DATE 对应PHOENIX里的UNSIGNED_DATE 8字节
|
||||||
|
# $UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节
|
||||||
|
# $VARCHAR 对应PHOENIX里的VARCHAR 动态长度
|
||||||
|
# $VARBINARY 对应PHOENIX里的VARBINARY 动态长度
|
||||||
|
# $DECIMAL 对应PHOENIX里的DECIMAL 动态长度
|
||||||
47
client-adapter/logger/pom.xml
Normal file
47
client-adapter/logger/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?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.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.logger</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter logger example 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>
|
||||||
|
</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>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.logger;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器示例
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:45:38
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("logger") // logger参数对应CanalOuterAdapterConfiguration配置中的name
|
||||||
|
public class LoggerAdapterExample implements CanalOuterAdapter {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeOut(Message message) {
|
||||||
|
// 直接输出日志信息
|
||||||
|
MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Dml dml) {
|
||||||
|
logger.info(dml.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CanalOuterAdapterConfiguration configuration) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
logger=com.alibaba.otter.canal.client.adapter.logger.LoggerAdapterExample
|
||||||
21
client-adapter/pom.xml
Normal file
21
client-adapter/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?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</artifactId>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>canal client adapter module for otter ${project.version}</name>
|
||||||
|
<modules>
|
||||||
|
<module>common</module>
|
||||||
|
<module>logger</module>
|
||||||
|
<module>hbase</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -1,44 +1,52 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
<modelVersion>4.0.0</modelVersion>
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.kafka</artifactId>
|
<artifactId>canal.client-launcher</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>canal kafka module for otter ${project.version}</name>
|
<name>canal client launcher module for otter ${project.version}</name>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.deployer</artifactId>
|
<artifactId>client-adapter.common</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 单独引入kafka依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka-clients</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.yaml</groupId>
|
<groupId>org.yaml</groupId>
|
||||||
<artifactId>snakeyaml</artifactId>
|
<artifactId>snakeyaml</artifactId>
|
||||||
<version>1.17</version>
|
<version>1.17</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- outer adapter -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.kafka</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>kafka_2.11</artifactId>
|
<artifactId>client-adapter.logger</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>${project.version}</version>
|
||||||
<exclusions>
|
<classifier>jar-with-dependencies</classifier>
|
||||||
<exclusion>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-log4j12</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--kafka_2.11_1.1.1 exclusion掉了netty 的依赖,但CanalServerWithNetty 依赖 netty3,升级kafka至 1.1.1 需要显示加入,否则会启动失败 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.netty</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>netty</artifactId>
|
<artifactId>client-adapter.hbase</artifactId>
|
||||||
<version>3.2.2.Final</version>
|
<version>${project.version}</version>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -53,10 +61,7 @@
|
|||||||
</archive>
|
</archive>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>**/logback.xml</exclude>
|
<exclude>**/logback.xml</exclude>
|
||||||
<exclude>**/canal.properties</exclude>
|
<exclude>**/canal-client.yml</exclude>
|
||||||
<exclude>**/spring/**</exclude>
|
|
||||||
<exclude>**/example/**</exclude>
|
|
||||||
<exclude>**/kafka.yml</exclude>
|
|
||||||
</excludes>
|
</excludes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -103,7 +108,7 @@
|
|||||||
<descriptors>
|
<descriptors>
|
||||||
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
|
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
|
||||||
</descriptors>
|
</descriptors>
|
||||||
<finalName>canal</finalName>
|
<finalName>canal_client</finalName>
|
||||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -140,4 +145,5 @@
|
|||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -22,28 +22,21 @@
|
|||||||
<fileMode>0755</fileMode>
|
<fileMode>0755</fileMode>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>../deployer/src/main/conf</directory>
|
<directory>./src/main/conf</directory>
|
||||||
<outputDirectory>/conf</outputDirectory>
|
<outputDirectory>/conf</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>**/*</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>../deployer/src/main/resources</directory>
|
|
||||||
<outputDirectory>/conf</outputDirectory>
|
|
||||||
<includes>
|
|
||||||
<include>**/*</include>
|
|
||||||
</includes>
|
|
||||||
<excludes>
|
|
||||||
<exclude>logback.xml</exclude>
|
|
||||||
</excludes>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>./src/main/resources</directory>
|
<directory>./src/main/resources</directory>
|
||||||
<outputDirectory>/conf</outputDirectory>
|
<outputDirectory>/conf</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>**/*</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/**</exclude>
|
||||||
|
</excludes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target</directory>
|
<directory>target</directory>
|
||||||
@@ -22,28 +22,21 @@
|
|||||||
<fileMode>0755</fileMode>
|
<fileMode>0755</fileMode>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>../deployer/src/main/conf</directory>
|
<directory>./src/main/conf</directory>
|
||||||
<outputDirectory>/conf</outputDirectory>
|
<outputDirectory>/conf</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>**/*</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>../deployer/src/main/resources</directory>
|
|
||||||
<outputDirectory>/conf</outputDirectory>
|
|
||||||
<includes>
|
|
||||||
<include>**/*</include>
|
|
||||||
</includes>
|
|
||||||
<excludes>
|
|
||||||
<exclude>logback.xml</exclude>
|
|
||||||
</excludes>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>./src/main/resources</directory>
|
<directory>./src/main/resources</directory>
|
||||||
<outputDirectory>/conf</outputDirectory>
|
<outputDirectory>/conf</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>**/*</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/**</exclude>
|
||||||
|
</excludes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target</directory>
|
<directory>target</directory>
|
||||||
6
kafka/src/main/bin/startup.bat → client-launcher/src/main/bin/startup.bat
Executable file → Normal file
6
kafka/src/main/bin/startup.bat → client-launcher/src/main/bin/startup.bat
Executable file → Normal file
@@ -6,7 +6,7 @@ set ENV_PATH=.\
|
|||||||
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
|
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
|
||||||
|
|
||||||
set conf_dir=%ENV_PATH%\..\conf
|
set conf_dir=%ENV_PATH%\..\conf
|
||||||
set canal_conf=%conf_dir%\canal.properties
|
set canal_conf=%conf_dir%\canal-client.yml
|
||||||
set logback_configurationFile=%conf_dir%\logback.xml
|
set logback_configurationFile=%conf_dir%\logback.xml
|
||||||
|
|
||||||
set CLASSPATH=%conf_dir%
|
set CLASSPATH=%conf_dir%
|
||||||
@@ -19,7 +19,7 @@ set CANAL_OPTS= -DappName=otter-canal -Dlogback.configurationFile="%logback_conf
|
|||||||
|
|
||||||
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS%
|
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS%
|
||||||
|
|
||||||
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.kafka.CanalLauncher
|
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher
|
||||||
echo start cmd : %CMD_STR%
|
echo start cmd : %CMD_STR%
|
||||||
|
|
||||||
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.kafka.CanalLauncher
|
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher
|
||||||
@@ -10,20 +10,16 @@ case "`uname`" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
base=${bin_abs_path}/..
|
base=${bin_abs_path}/..
|
||||||
canal_conf=$base/conf/canal.properties
|
canal_conf=$base/conf/canal-client.yml
|
||||||
logback_configurationFile=$base/conf/logback.xml
|
logback_configurationFile=$base/conf/logback.xml
|
||||||
export LANG=en_US.UTF-8
|
export LANG=en_US.UTF-8
|
||||||
export BASE=$base
|
export BASE=$base
|
||||||
|
|
||||||
if [ -f $base/bin/canal.pid ] ; then
|
if [ -f $base/bin/canal_client.pid ] ; then
|
||||||
echo "found canal.pid , Please run stop.sh first ,then startup.sh" 2>&2
|
echo "found canal_client.pid , Please run stop.sh first ,then startup.sh" 2>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d $base/logs/canal ] ; then
|
|
||||||
mkdir -p $base/logs/canal
|
|
||||||
fi
|
|
||||||
|
|
||||||
## set java path
|
## set java path
|
||||||
if [ -z "$JAVA" ] ; then
|
if [ -z "$JAVA" ] ; then
|
||||||
JAVA=$(which java)
|
JAVA=$(which java)
|
||||||
@@ -78,7 +74,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
|
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
|
||||||
CANAL_OPTS="-DappName=otter-canal -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf"
|
CANAL_OPTS="-DappName=otter-canal-client -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf"
|
||||||
|
|
||||||
if [ -e $canal_conf -a -e $logback_configurationFile ]
|
if [ -e $canal_conf -a -e $logback_configurationFile ]
|
||||||
then
|
then
|
||||||
@@ -94,11 +90,11 @@ then
|
|||||||
echo LOG CONFIGURATION : $logback_configurationFile
|
echo LOG CONFIGURATION : $logback_configurationFile
|
||||||
echo canal conf : $canal_conf
|
echo canal conf : $canal_conf
|
||||||
echo CLASSPATH :$CLASSPATH
|
echo CLASSPATH :$CLASSPATH
|
||||||
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.kafka.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 &
|
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.client.ClientLauncher 1>>$base/logs/canal_client.log 2>&1 &
|
||||||
echo $! > $base/bin/canal.pid
|
echo $! > $base/bin/canal_client.pid
|
||||||
|
|
||||||
echo "cd to $current_path for continue"
|
echo "cd to $current_path for continue"
|
||||||
cd $current_path
|
cd $current_path
|
||||||
else
|
else
|
||||||
echo "canal conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!"
|
echo "canal client conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!"
|
||||||
fi
|
fi
|
||||||
@@ -37,15 +37,15 @@ get_pid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
base=`dirname $0`/..
|
base=`dirname $0`/..
|
||||||
pidfile=$base/bin/canal.pid
|
pidfile=$base/bin/canal_client.pid
|
||||||
if [ ! -f "$pidfile" ];then
|
if [ ! -f "$pidfile" ];then
|
||||||
echo "canal is not running. exists"
|
echo "canal client is not running. exists"
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pid=`cat $pidfile`
|
pid=`cat $pidfile`
|
||||||
if [ "$pid" == "" ] ; then
|
if [ "$pid" == "" ] ; then
|
||||||
pid=`get_pid "appName=otter-canal"`
|
pid=`get_pid "appName=otter-canal-client"`
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "`hostname`: stopping canal $pid ... "
|
echo -e "`hostname`: stopping canal $pid ... "
|
||||||
@@ -54,7 +54,7 @@ kill $pid
|
|||||||
LOOPS=0
|
LOOPS=0
|
||||||
while (true);
|
while (true);
|
||||||
do
|
do
|
||||||
gpid=`get_pid "appName=otter-canal" "$pid"`
|
gpid=`get_pid "appName=otter-canal-client" "$pid"`
|
||||||
if [ "$gpid" == "" ] ; then
|
if [ "$gpid" == "" ] ; then
|
||||||
echo "Oook! cost:$LOOPS"
|
echo "Oook! cost:$LOOPS"
|
||||||
`rm $pidfile`
|
`rm $pidfile`
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.alibaba.otter.canal.client;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.loader.CanalAdapterLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
|
public class ClientLauncher {
|
||||||
|
|
||||||
|
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClientLauncher.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
logger.info("## set default uncaught exception handler");
|
||||||
|
setGlobalUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
logger.info("## load canal client configurations");
|
||||||
|
String conf = System.getProperty("client.conf", "classpath:canal-client.yml");
|
||||||
|
CanalClientConfig canalClientConfig;
|
||||||
|
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
|
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
||||||
|
canalClientConfig = new Yaml().loadAs(ClientLauncher.class.getClassLoader().getResourceAsStream(conf),
|
||||||
|
CanalClientConfig.class);
|
||||||
|
} else {
|
||||||
|
canalClientConfig = new Yaml().loadAs(new FileInputStream(conf), CanalClientConfig.class);
|
||||||
|
}
|
||||||
|
logger.info("## start the canal client adapters.");
|
||||||
|
final CanalAdapterLoader adapterLoader = new CanalAdapterLoader(canalClientConfig);
|
||||||
|
adapterLoader.init();
|
||||||
|
logger.info("## the canal client adapters are running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the canal client adapters");
|
||||||
|
adapterLoader.destroy();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("## something goes wrong when stopping canal client adapters:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## canal client adapters are down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## something goes wrong when starting up the canal client adapters:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("UnCaughtException", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 适配器工作线程抽象类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
protected String canalDestination; // canal实例
|
||||||
|
protected List<List<CanalOuterAdapter>> canalOuterAdapters; // 外部适配器
|
||||||
|
protected ExecutorService groupInnerExecutorService; // 组内工作线程池
|
||||||
|
protected volatile boolean running = false; // 是否运行中
|
||||||
|
protected Thread thread = null;
|
||||||
|
protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("parse events has an error", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected void writeOut(final Message message) {
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
|
// 组间适配器并行运行
|
||||||
|
for (List<CanalOuterAdapter> outerAdapters : canalOuterAdapters) {
|
||||||
|
final List<CanalOuterAdapter> adapters = outerAdapters;
|
||||||
|
futures.add(groupInnerExecutorService.submit(new Callable<Boolean>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() {
|
||||||
|
try {
|
||||||
|
// 组内适配器穿行运行,尽量不要配置组内适配器
|
||||||
|
for (CanalOuterAdapter c : adapters) {
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
c.writeOut(message);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("{} elapsed time: {}",
|
||||||
|
c.getClass().getName(),
|
||||||
|
(System.currentTimeMillis() - begin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 等待所有适配器写入完成
|
||||||
|
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
||||||
|
for (Future<Boolean> f : futures) {
|
||||||
|
try {
|
||||||
|
if (!f.get()) {
|
||||||
|
logger.error("Outer adapter write failed");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void start();
|
||||||
|
|
||||||
|
public abstract void stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.consumer.CommitFailedException;
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka对应的client适配器工作线程
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
|
private KafkaCanalConnector connector;
|
||||||
|
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId,
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapters){
|
||||||
|
this.canalOuterAdapters = canalOuterAdapters;
|
||||||
|
this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||||
|
this.topic = topic;
|
||||||
|
this.canalDestination = topic;
|
||||||
|
connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId);
|
||||||
|
// connector.setSessionTimeout(5L, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// super.initSwitcher(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (!running) {
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
running = true;
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connector.stopRunning();
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
// if (switcher != null && !switcher.state()) {
|
||||||
|
// switcher.set(true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupInnerExecutorService.shutdown();
|
||||||
|
logger.info("topic {} connectors' worker thread dead!", this.topic);
|
||||||
|
for (List<CanalOuterAdapter> outerAdapters : canalOuterAdapters) {
|
||||||
|
for (CanalOuterAdapter adapter : outerAdapters) {
|
||||||
|
adapter.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("topic {} all connectors destroyed!", this.topic);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
;
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
final AtomicBoolean executing = new AtomicBoolean(true);
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
logger.info("=============> Start to connect topic: {} <=============", this.topic);
|
||||||
|
connector.connect();
|
||||||
|
logger.info("=============> Start to subscribe topic: {} <=============", this.topic);
|
||||||
|
connector.subscribe();
|
||||||
|
logger.info("=============> Subscribe topic: {} succeed <=============", this.topic);
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
// switcher.get(); //等待开关开启
|
||||||
|
|
||||||
|
final Message message = connector.getWithoutAck();
|
||||||
|
|
||||||
|
executing.set(true);
|
||||||
|
if (message != null) {
|
||||||
|
executor.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("topic: {} batchId: {} batchSize: {} ",
|
||||||
|
topic,
|
||||||
|
message.getId(),
|
||||||
|
message.getEntries().size());
|
||||||
|
}
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
writeOut(message);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) {
|
||||||
|
logger.error("topic: {} batchId {} elapsed time: {} ms",
|
||||||
|
topic,
|
||||||
|
message.getId(),
|
||||||
|
now - begin);
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("topic: {} batchId {} elapsed time: {} ms",
|
||||||
|
topic,
|
||||||
|
message.getId(),
|
||||||
|
now - begin);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
executing.compareAndSet(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端
|
||||||
|
long currentTS = System.currentTimeMillis();
|
||||||
|
while (executing.get()) {
|
||||||
|
// 大于1分钟未消费完ack一次keep alive
|
||||||
|
if (System.currentTimeMillis() - currentTS > 60000) {
|
||||||
|
connector.ack();
|
||||||
|
currentTS = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connector.ack();
|
||||||
|
}
|
||||||
|
} catch (CommitFailedException e) {
|
||||||
|
logger.warn(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
TimeUnit.SECONDS.sleep(1L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector.unsubscribe();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// No-op. Continue process
|
||||||
|
}
|
||||||
|
connector.disconnnect();
|
||||||
|
logger.info("=============> Disconnect topic: {} <=============", this.topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器的加载器
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalAdapterLoader {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class);
|
||||||
|
|
||||||
|
private CanalClientConfig canalClientConfig;
|
||||||
|
|
||||||
|
private Map<String, CanalAdapterWorker> canalWorkers = new HashMap<>();
|
||||||
|
|
||||||
|
private Map<String, CanalAdapterKafkaWorker> canalKafkaWorkers = new HashMap<>();
|
||||||
|
|
||||||
|
private ExtensionLoader<CanalOuterAdapter> loader;
|
||||||
|
|
||||||
|
public CanalAdapterLoader(CanalClientConfig canalClientConfig){
|
||||||
|
this.canalClientConfig = canalClientConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化canal-client、 canal-client-kafka的适配器
|
||||||
|
*/
|
||||||
|
public void init() {
|
||||||
|
// canal instances 和 kafka topics 配置不能同时为空
|
||||||
|
if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getKafkaTopics() == null) {
|
||||||
|
throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics");
|
||||||
|
}
|
||||||
|
|
||||||
|
loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class,
|
||||||
|
"" /* TODO canalClientConfig.getClassloaderPolicy() */);
|
||||||
|
|
||||||
|
String canalServerHost = this.canalClientConfig.getCanalServerHost();
|
||||||
|
SocketAddress sa = null;
|
||||||
|
if (canalServerHost != null) {
|
||||||
|
String[] ipPort = canalServerHost.split(":");
|
||||||
|
sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1]));
|
||||||
|
}
|
||||||
|
String zkHosts = this.canalClientConfig.getZookeeperHosts();
|
||||||
|
|
||||||
|
// if (zkHosts == null && sa == null) {
|
||||||
|
// throw new RuntimeException("Blank config property: canalServerHost or
|
||||||
|
// zookeeperHosts");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 初始化canal-client的适配器
|
||||||
|
if (canalClientConfig.getCanalInstances() != null) {
|
||||||
|
for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) {
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) {
|
||||||
|
List<CanalOuterAdapter> canalOutConnectors = new ArrayList<>();
|
||||||
|
for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) {
|
||||||
|
loadConnector(c, canalOutConnectors);
|
||||||
|
}
|
||||||
|
canalOuterAdapterGroups.add(canalOutConnectors);
|
||||||
|
}
|
||||||
|
CanalAdapterWorker worker;
|
||||||
|
if (zkHosts != null) {
|
||||||
|
worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups);
|
||||||
|
} else {
|
||||||
|
worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups);
|
||||||
|
}
|
||||||
|
canalWorkers.put(instance.getInstance(), worker);
|
||||||
|
worker.start();
|
||||||
|
logger.info("Start adapter for canal instance: {} succeed", instance.getInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化canal-client-kafka的适配器
|
||||||
|
if (canalClientConfig.getKafkaTopics() != null) {
|
||||||
|
for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) {
|
||||||
|
for (CanalClientConfig.Group group : kafkaTopic.getGroups()) {
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
|
|
||||||
|
List<CanalOuterAdapter> canalOuterAdapters = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) {
|
||||||
|
// for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) {
|
||||||
|
loadConnector(config, canalOuterAdapters);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||||
|
|
||||||
|
// String zkServers = canalClientConfig.getZookeeperHosts();
|
||||||
|
CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts,
|
||||||
|
canalClientConfig.getBootstrapServers(),
|
||||||
|
kafkaTopic.getTopic(),
|
||||||
|
group.getGroupId(),
|
||||||
|
canalOuterAdapterGroups);
|
||||||
|
canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker);
|
||||||
|
canalKafkaWorker.start();
|
||||||
|
logger.info("Start adapter for canal-client kafka topic: {} succeed",
|
||||||
|
kafkaTopic.getTopic() + "-" + group.getGroupId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadConnector(CanalOuterAdapterConfiguration config, List<CanalOuterAdapter> canalOutConnectors) {
|
||||||
|
try {
|
||||||
|
CanalOuterAdapter adapter = loader.getExtension(config.getName());
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
// 替换ClassLoader
|
||||||
|
Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader());
|
||||||
|
adapter.init(config);
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
canalOutConnectors.add(adapter);
|
||||||
|
logger.info("Load canal adapter: {} succeed", config.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Load canal adapter: {} failed", config.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
if (canalWorkers.size() > 0) {
|
||||||
|
ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size());
|
||||||
|
for (CanalAdapterWorker v : canalWorkers.values()) {
|
||||||
|
final CanalAdapterWorker caw = v;
|
||||||
|
stopExecutorService.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
caw.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stopExecutorService.shutdown();
|
||||||
|
}
|
||||||
|
if (canalKafkaWorkers.size() > 0) {
|
||||||
|
ExecutorService stopKafkaExecutorService = Executors.newFixedThreadPool(canalKafkaWorkers.size());
|
||||||
|
for (CanalAdapterKafkaWorker v : canalKafkaWorkers.values()) {
|
||||||
|
final CanalAdapterKafkaWorker cakw = v;
|
||||||
|
stopKafkaExecutorService.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cakw.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stopKafkaExecutorService.shutdown();
|
||||||
|
}
|
||||||
|
logger.info("All canal adapters destroyed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.impl.ClusterCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生canal-server对应的client适配器工作线程
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
|
private static final int BATCH_SIZE = 50;
|
||||||
|
private static final int SO_TIMEOUT = 0;
|
||||||
|
|
||||||
|
private CanalConnector connector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单台client适配器worker的构造方法
|
||||||
|
*
|
||||||
|
* @param canalDestination canal实例名
|
||||||
|
* @param address canal-server地址
|
||||||
|
* @param canalOuterAdapters 外部适配器组
|
||||||
|
*/
|
||||||
|
public CanalAdapterWorker(String canalDestination, SocketAddress address,
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapters){
|
||||||
|
this.canalOuterAdapters = canalOuterAdapters;
|
||||||
|
this.canalDestination = canalDestination;
|
||||||
|
groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||||
|
connector = CanalConnectors.newSingleConnector(address, canalDestination, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HA模式下client适配器worker的构造方法
|
||||||
|
*
|
||||||
|
* @param canalDestination canal实例名
|
||||||
|
* @param zookeeperHosts zookeeper地址
|
||||||
|
* @param canalOuterAdapters 外部适配器组
|
||||||
|
*/
|
||||||
|
public CanalAdapterWorker(String canalDestination, String zookeeperHosts,
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapters){
|
||||||
|
this.canalOuterAdapters = canalOuterAdapters;
|
||||||
|
this.canalDestination = canalDestination;
|
||||||
|
groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||||
|
connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", "");
|
||||||
|
((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT);
|
||||||
|
|
||||||
|
// super.initSwitcher(canalDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (!running) {
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
thread.start();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (switcher != null && !switcher.state()) {
|
||||||
|
// switcher.set(true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
connector.stopRunning();
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination);
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupInnerExecutorService.shutdown();
|
||||||
|
logger.info("destination {} adapters' worker thread dead!", canalDestination);
|
||||||
|
for (List<CanalOuterAdapter> outerAdapters : canalOuterAdapters) {
|
||||||
|
for (CanalOuterAdapter adapter : outerAdapters) {
|
||||||
|
adapter.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("destination {} all adapters destroyed!", canalDestination);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
; // waiting until running == true
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
// if (switcher != null) {
|
||||||
|
// switcher.get();
|
||||||
|
// }
|
||||||
|
logger.info("=============> Start to connect destination: {} <=============", this.canalDestination);
|
||||||
|
connector.connect();
|
||||||
|
logger.info("=============> Start to subscribe destination: {} <=============", this.canalDestination);
|
||||||
|
connector.subscribe();
|
||||||
|
logger.info("=============> Subscribe destination: {} succeed <=============", this.canalDestination);
|
||||||
|
while (running) {
|
||||||
|
// try {
|
||||||
|
// if (switcher != null) {
|
||||||
|
// switcher.get();
|
||||||
|
// }
|
||||||
|
// } catch (TimeoutException e) {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// server配置canal.instance.network.soTimeout(默认: 30s)
|
||||||
|
// 范围内未与server交互,server将关闭本次socket连接
|
||||||
|
Message message = connector.getWithoutAck(BATCH_SIZE); // 获取指定数量的数据
|
||||||
|
long batchId = message.getId();
|
||||||
|
try {
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("destination: {} batchId: {} batchSize: {} ",
|
||||||
|
this.canalDestination,
|
||||||
|
batchId,
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
writeOut(message);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("destination: {} batchId: {} elapsed time: {} ms",
|
||||||
|
this.canalDestination,
|
||||||
|
batchId,
|
||||||
|
now - begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connector.ack(batchId); // 提交确认
|
||||||
|
} catch (Exception e) {
|
||||||
|
connector.rollback(batchId); // 处理失败, 回滚数据
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("process error!", e);
|
||||||
|
} finally {
|
||||||
|
connector.disconnect();
|
||||||
|
logger.info("=============> Disconnect destination: {} <=============", this.canalDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (running) { // is reconnect
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
client-launcher/src/main/resources/canal-client.yml
Normal file
21
client-launcher/src/main/resources/canal-client.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
canalServerHost: 127.0.0.1:11111
|
||||||
|
#zookeeperHosts: slave1:2181
|
||||||
|
#bootstrapServers: slave1:6667,slave2:6667
|
||||||
|
|
||||||
|
canalInstances:
|
||||||
|
- instance: example
|
||||||
|
adapterGroups:
|
||||||
|
- outAdapters:
|
||||||
|
- name: logger
|
||||||
|
- name: hbase
|
||||||
|
hosts: slave1:2181
|
||||||
|
properties: {znodeParent: "/hbase-unsecure"}
|
||||||
|
#kafkaTopics:
|
||||||
|
#- topic: example
|
||||||
|
# groups:
|
||||||
|
# - groupId: example_g1
|
||||||
|
# outAdapters:
|
||||||
|
# - name: logger
|
||||||
|
# - name: hbase
|
||||||
|
# hosts: slave1:2181
|
||||||
|
# properties: {znodeParent: "/hbase-unsecure"}
|
||||||
47
client-launcher/src/main/resources/logback.xml
Normal file
47
client-launcher/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<configuration scan="true" scanPeriod=" 5 seconds">
|
||||||
|
<jmxConfigurator />
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
|
||||||
|
<discriminator>
|
||||||
|
<Key>destination</Key>
|
||||||
|
<DefaultValue>canal_client</DefaultValue>
|
||||||
|
</discriminator>
|
||||||
|
<sift>
|
||||||
|
<appender name="FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<File>../logs/${destination}.log</File>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- rollover daily -->
|
||||||
|
<fileNamePattern>../logs/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<!-- or whenever the file size reaches 100MB -->
|
||||||
|
<maxFileSize>512MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>
|
||||||
|
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
</sift>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.alibaba.otter.canal.client" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<!--<appender-ref ref="STDOUT"/>-->
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<!--<appender-ref ref="STDOUT"/>-->
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -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.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -16,6 +16,13 @@
|
|||||||
<artifactId>canal.protocol</artifactId>
|
<artifactId>canal.protocol</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 客户端要使用请单独引入kafka依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka-clients</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- junit -->
|
<!-- junit -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client;
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
|
||||||
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
|
||||||
import com.alibaba.otter.canal.kafka.client.running.ClientRunningData;
|
|
||||||
import com.alibaba.otter.canal.kafka.client.running.ClientRunningListener;
|
|
||||||
import com.alibaba.otter.canal.kafka.client.running.ClientRunningMonitor;
|
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
|
||||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||||
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
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 java.util.Collections;
|
import com.alibaba.otter.canal.client.kafka.running.ClientRunningData;
|
||||||
import java.util.Properties;
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
import java.util.concurrent.TimeUnit;
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal kafka 数据操作客户端
|
* canal kafka 数据操作客户端
|
||||||
@@ -29,13 +27,13 @@ public class KafkaCanalConnector {
|
|||||||
private String topic;
|
private String topic;
|
||||||
private Integer partition;
|
private Integer partition;
|
||||||
private Properties properties;
|
private Properties properties;
|
||||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
// private ClientRunningMonitor runningMonitor; // 运行控制
|
||||||
|
// private BooleanMutex mutex = new BooleanMutex(false);
|
||||||
private ZkClientx zkClientx;
|
private ZkClientx zkClientx;
|
||||||
private BooleanMutex mutex = new BooleanMutex(false);
|
|
||||||
private volatile boolean connected = false;
|
private volatile boolean connected = false;
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
|
|
||||||
public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId) {
|
public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId){
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.partition = partition;
|
this.partition = partition;
|
||||||
|
|
||||||
@@ -44,10 +42,10 @@ public class KafkaCanalConnector {
|
|||||||
properties.put("group.id", groupId);
|
properties.put("group.id", groupId);
|
||||||
properties.put("enable.auto.commit", false);
|
properties.put("enable.auto.commit", false);
|
||||||
properties.put("auto.commit.interval.ms", "1000");
|
properties.put("auto.commit.interval.ms", "1000");
|
||||||
properties.put("auto.offset.reset", "latest"); //如果没有offset则从最后的offset开始读
|
properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读
|
||||||
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
|
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
|
||||||
properties.put("session.timeout.ms", "30000"); // 默认为30秒
|
properties.put("session.timeout.ms", "30000"); // 默认为30秒
|
||||||
properties.put("max.poll.records", "1"); // 所以一次只取一条数据
|
properties.put("max.poll.records", "1"); // 一次只取一条message数据
|
||||||
properties.put("key.deserializer", StringDeserializer.class.getName());
|
properties.put("key.deserializer", StringDeserializer.class.getName());
|
||||||
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
||||||
|
|
||||||
@@ -58,19 +56,19 @@ public class KafkaCanalConnector {
|
|||||||
clientData.setGroupId(groupId);
|
clientData.setGroupId(groupId);
|
||||||
clientData.setAddress(AddressUtils.getHostIp());
|
clientData.setAddress(AddressUtils.getHostIp());
|
||||||
|
|
||||||
runningMonitor = new ClientRunningMonitor();
|
// runningMonitor = new ClientRunningMonitor();
|
||||||
runningMonitor.setTopic(topic);
|
// runningMonitor.setTopic(topic);
|
||||||
runningMonitor.setZkClient(zkClientx);
|
// runningMonitor.setZkClient(zkClientx);
|
||||||
runningMonitor.setClientData(clientData);
|
// runningMonitor.setClientData(clientData);
|
||||||
runningMonitor.setListener(new ClientRunningListener() {
|
// runningMonitor.setListener(new ClientRunningListener() {
|
||||||
public void processActiveEnter() {
|
// public void processActiveEnter() {
|
||||||
mutex.set(true);
|
// mutex.set(true);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public void processActiveExit() {
|
// public void processActiveExit() {
|
||||||
mutex.set(false);
|
// mutex.set(false);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -95,11 +93,11 @@ public class KafkaCanalConnector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runningMonitor != null) {
|
// if (runningMonitor != null) {
|
||||||
if (!runningMonitor.isStart()) {
|
// if (!runningMonitor.isStart()) {
|
||||||
runningMonitor.start();
|
// runningMonitor.start();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
connected = true;
|
connected = true;
|
||||||
|
|
||||||
@@ -115,9 +113,9 @@ public class KafkaCanalConnector {
|
|||||||
kafkaConsumer.close();
|
kafkaConsumer.close();
|
||||||
|
|
||||||
connected = false;
|
connected = false;
|
||||||
if (runningMonitor.isStart()) {
|
// if (runningMonitor.isStart()) {
|
||||||
runningMonitor.stop();
|
// runningMonitor.stop();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitClientRunning() {
|
private void waitClientRunning() {
|
||||||
@@ -128,12 +126,12 @@ public class KafkaCanalConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
mutex.get();// 阻塞等待
|
// mutex.get();// 阻塞等待
|
||||||
} else {
|
} else {
|
||||||
// 单机模式直接设置为running
|
// 单机模式直接设置为running
|
||||||
running = true;
|
running = true;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (Exception e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
throw new CanalClientException(e);
|
throw new CanalClientException(e);
|
||||||
}
|
}
|
||||||
@@ -141,7 +139,8 @@ public class KafkaCanalConnector {
|
|||||||
|
|
||||||
public boolean checkValid() {
|
public boolean checkValid() {
|
||||||
if (zkClientx != null) {
|
if (zkClientx != null) {
|
||||||
return mutex.state();
|
// return mutex.state();
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return true;// 默认都放过
|
return true;// 默认都放过
|
||||||
}
|
}
|
||||||
@@ -234,9 +233,9 @@ public class KafkaCanalConnector {
|
|||||||
public void stopRunning() {
|
public void stopRunning() {
|
||||||
if (running) {
|
if (running) {
|
||||||
running = false; // 设置为非running状态
|
running = false; // 设置为非running状态
|
||||||
if (!mutex.state()) {
|
// if (!mutex.state()) {
|
||||||
mutex.set(true); // 中断阻塞
|
// mutex.set(true); // 中断阻塞
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client;
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal kafka connectors创建工具类
|
* canal kafka connectors创建工具类
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client;
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client.running;
|
package com.alibaba.otter.canal.client.kafka.running;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* client running状态信息
|
* client running状态信息
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client.running;
|
package com.alibaba.otter.canal.client.kafka.running;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* client running状态信息
|
* client running状态信息
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client.running;
|
package com.alibaba.otter.canal.client.kafka.running;
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client.running;
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client.running;
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -7,8 +7,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.otter.canal.kafka.client.running;
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -9,8 +9,8 @@ import org.junit.Test;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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.0</version>
|
<version>1.1.1-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>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -11,6 +11,11 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>canal dbsync module for otter ${project.version}</name>
|
<name>canal dbsync module for otter ${project.version}</name>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.parse.driver</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- log -->
|
<!-- log -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.taobao.tddl.dbsync.binlog;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.TableMapLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ public final class LogContext {
|
|||||||
|
|
||||||
private LogPosition logPosition;
|
private LogPosition logPosition;
|
||||||
|
|
||||||
|
private GTIDSet gtidSet;
|
||||||
|
|
||||||
public LogContext(){
|
public LogContext(){
|
||||||
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
}
|
}
|
||||||
@@ -60,4 +63,12 @@ public final class LogContext {
|
|||||||
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
mapOfTable.clear();
|
mapOfTable.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GTIDSet getGtidSet() {
|
||||||
|
return gtidSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGtidSet(GTIDSet gtidSet) {
|
||||||
|
this.gtidSet = gtidSet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.BitSet;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent;
|
||||||
@@ -159,18 +160,20 @@ public final class LogDecoder {
|
|||||||
// remove checksum bytes
|
// remove checksum bytes
|
||||||
buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN);
|
buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||||
}
|
}
|
||||||
|
GTIDSet gtidSet = context.getGtidSet();
|
||||||
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);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.XID_EVENT: {
|
case LogEvent.XID_EVENT: {
|
||||||
XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent);
|
XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.TABLE_MAP_EVENT: {
|
case LogEvent.TABLE_MAP_EVENT: {
|
||||||
@@ -268,12 +271,14 @@ public final class LogDecoder {
|
|||||||
RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent);
|
RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.USER_VAR_EVENT: {
|
case LogEvent.USER_VAR_EVENT: {
|
||||||
UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent);
|
UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.FORMAT_DESCRIPTION_EVENT: {
|
case LogEvent.FORMAT_DESCRIPTION_EVENT: {
|
||||||
@@ -324,6 +329,7 @@ public final class LogDecoder {
|
|||||||
HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent);
|
HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.IGNORABLE_LOG_EVENT: {
|
case LogEvent.IGNORABLE_LOG_EVENT: {
|
||||||
@@ -336,6 +342,7 @@ public final class LogDecoder {
|
|||||||
RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent);
|
RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.WRITE_ROWS_EVENT: {
|
case LogEvent.WRITE_ROWS_EVENT: {
|
||||||
@@ -343,6 +350,7 @@ public final class LogDecoder {
|
|||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
event.fillTable(context);
|
event.fillTable(context);
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.UPDATE_ROWS_EVENT: {
|
case LogEvent.UPDATE_ROWS_EVENT: {
|
||||||
@@ -350,6 +358,7 @@ public final class LogDecoder {
|
|||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
event.fillTable(context);
|
event.fillTable(context);
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.DELETE_ROWS_EVENT: {
|
case LogEvent.DELETE_ROWS_EVENT: {
|
||||||
@@ -357,6 +366,7 @@ public final class LogDecoder {
|
|||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
event.fillTable(context);
|
event.fillTable(context);
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.GTID_LOG_EVENT:
|
case LogEvent.GTID_LOG_EVENT:
|
||||||
@@ -364,6 +374,11 @@ public final class LogDecoder {
|
|||||||
GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent);
|
GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
if (gtidSet != null) {
|
||||||
|
gtidSet.update(event.getGtidStr());
|
||||||
|
// update latest gtid
|
||||||
|
header.putGtidStr(gtidSet);
|
||||||
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: {
|
case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: {
|
||||||
@@ -394,6 +409,7 @@ public final class LogDecoder {
|
|||||||
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.BINLOG_CHECKPOINT_EVENT: {
|
case LogEvent.BINLOG_CHECKPOINT_EVENT: {
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
sequenceNumber = buffer.getLong64();
|
sequenceNumber = buffer.getLong64();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ignore gtid info read
|
// ignore gtid info read
|
||||||
// sid.copy_from((uchar *)ptr_buffer);
|
// sid.copy_from((uchar *)ptr_buffer);
|
||||||
// ptr_buffer+= ENCODED_SID_LENGTH;
|
// ptr_buffer+= ENCODED_SID_LENGTH;
|
||||||
@@ -82,4 +80,11 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
public Long getSequenceNumber() {
|
public Long getSequenceNumber() {
|
||||||
return sequenceNumber;
|
return sequenceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGtidStr() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(sid.toString()).append(":");
|
||||||
|
sb.append(gno);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.taobao.tddl.dbsync.binlog.event;
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
@@ -120,6 +121,8 @@ public final class LogHeader {
|
|||||||
*/
|
*/
|
||||||
protected String logFileName;
|
protected String logFileName;
|
||||||
|
|
||||||
|
protected String gtidStr;
|
||||||
|
|
||||||
/* for Start_event_v3 */
|
/* for Start_event_v3 */
|
||||||
public LogHeader(final int type){
|
public LogHeader(final int type){
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -288,4 +291,14 @@ public final class LogHeader {
|
|||||||
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGtidStr() {
|
||||||
|
return gtidStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putGtidStr(GTIDSet gtidSet) {
|
||||||
|
if (gtidSet != null) {
|
||||||
|
this.gtidStr = gtidSet.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ public class FetcherPerformanceTest {
|
|||||||
DirectLogFetcher fetcher = new DirectLogFetcher();
|
DirectLogFetcher fetcher = new DirectLogFetcher();
|
||||||
try {
|
try {
|
||||||
Class.forName("com.mysql.jdbc.Driver");
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306",
|
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
|
||||||
"root",
|
|
||||||
"hello");
|
|
||||||
Statement statement = connection.createStatement();
|
Statement statement = connection.createStatement();
|
||||||
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||||
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
|
|||||||
@@ -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.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
<exclude>**/canal.properties</exclude>
|
<exclude>**/canal.properties</exclude>
|
||||||
<exclude>**/spring/**</exclude>
|
<exclude>**/spring/**</exclude>
|
||||||
<exclude>**/example/**</exclude>
|
<exclude>**/example/**</exclude>
|
||||||
|
<exclude>**/kafka.yml</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class CanalConstants {
|
|||||||
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
|
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
|
||||||
public static final String CANAL_AUTO_SCAN_INTERVAL = ROOT + "." + "auto.scan.interval";
|
public static final String CANAL_AUTO_SCAN_INTERVAL = ROOT + "." + "auto.scan.interval";
|
||||||
public static final String CANAL_CONF_DIR = ROOT + "." + "conf.dir";
|
public static final String CANAL_CONF_DIR = ROOT + "." + "conf.dir";
|
||||||
|
public static final String CANAL_SERVER_MODE = ROOT + "." + "serverMode";
|
||||||
|
|
||||||
public static final String CANAL_DESTINATION_SPLIT = ",";
|
public static final String CANAL_DESTINATION_SPLIT = ",";
|
||||||
public static final String GLOBAL_NAME = "global";
|
public static final String GLOBAL_NAME = "global";
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ public class CanalController {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getProperty(Properties properties, String key) {
|
public String getProperty(Properties properties, String key) {
|
||||||
key = StringUtils.trim(key);
|
key = StringUtils.trim(key);
|
||||||
String value = System.getProperty(key);
|
String value = System.getProperty(key);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.CanalKafkaStarter;
|
||||||
|
import com.alibaba.otter.canal.server.CanalServerStarter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal独立版本启动的入口类
|
* canal独立版本启动的入口类
|
||||||
*
|
*
|
||||||
@@ -51,6 +54,18 @@ public class CanalLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CanalServerStarter canalServerStarter = null;
|
||||||
|
String serverMode = controller.getProperty(properties, CanalConstants.CANAL_SERVER_MODE);
|
||||||
|
if (serverMode.equalsIgnoreCase("kafka")) {
|
||||||
|
canalServerStarter = new CanalKafkaStarter();
|
||||||
|
} else if (serverMode.equalsIgnoreCase("rocketMQ")) {
|
||||||
|
// 预留rocketMQ启动
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canalServerStarter != null) {
|
||||||
|
canalServerStarter.init();
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ canal.zkServers=
|
|||||||
# flush data to zk
|
# flush data to zk
|
||||||
canal.zookeeper.flush.period = 1000
|
canal.zookeeper.flush.period = 1000
|
||||||
canal.withoutNetty = false
|
canal.withoutNetty = false
|
||||||
|
# tcp, kafka, rocketMQ
|
||||||
|
canal.serverMode = tcp
|
||||||
# flush meta cursor/parse position to file
|
# flush meta cursor/parse position to file
|
||||||
canal.file.data.dir = ${canal.conf.dir}
|
canal.file.data.dir = ${canal.conf.dir}
|
||||||
canal.file.flush.period = 1000
|
canal.file.flush.period = 1000
|
||||||
|
|||||||
15
deployer/src/main/resources/kafka.yml
Normal file
15
deployer/src/main/resources/kafka.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
servers: slave1:6667,slave2:6667,slave3:6667
|
||||||
|
retries: 0
|
||||||
|
batchSize: 16384
|
||||||
|
lingerMs: 1
|
||||||
|
bufferMemory: 33554432
|
||||||
|
|
||||||
|
# Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下)
|
||||||
|
canalBatchSize: 50
|
||||||
|
# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
|
||||||
|
canalGetTimeout: 100
|
||||||
|
|
||||||
|
canalDestinations:
|
||||||
|
- canalDestination: example
|
||||||
|
topic: example
|
||||||
|
partition:
|
||||||
@@ -938,7 +938,7 @@
|
|||||||
"bars": false,
|
"bars": false,
|
||||||
"dashLength": 10,
|
"dashLength": 10,
|
||||||
"dashes": false,
|
"dashes": false,
|
||||||
"datasource": "Prometheus",
|
"datasource": "$datasource",
|
||||||
"description": "client 请求的GET与ACK包的QPS。",
|
"description": "client 请求的GET与ACK包的QPS。",
|
||||||
"fill": 1,
|
"fill": 1,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ RUN \
|
|||||||
yum clean all && \
|
yum clean all && \
|
||||||
true
|
true
|
||||||
|
|
||||||
# 2222 sys , 8080 web , 8000 debug , 11111 canal
|
# 2222 sys , 8000 debug , 11111 canal , 11112 metrics
|
||||||
EXPOSE 2222 11111 8000 8080
|
EXPOSE 2222 11111 8000 11112
|
||||||
|
|
||||||
WORKDIR /home/admin
|
WORKDIR /home/admin
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ DATA="$BASE/data"
|
|||||||
mkdir -p $DATA
|
mkdir -p $DATA
|
||||||
CONFIG=${@:1}
|
CONFIG=${@:1}
|
||||||
#VOLUMNS="-v $DATA:/home/admin/canal-server/logs"
|
#VOLUMNS="-v $DATA:/home/admin/canal-server/logs"
|
||||||
PORTLIST="8000 8080 2222 11111"
|
PORTLIST="8000 2222 11111 11112"
|
||||||
PORTS=""
|
PORTS=""
|
||||||
for PORT in $PORTLIST ; do
|
for PORT in $PORTLIST ; do
|
||||||
#exist=`check_port $PORT`
|
#exist=`check_port $PORT`
|
||||||
|
|||||||
@@ -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.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -138,6 +138,16 @@ public class BioSocketChannel implements SocketChannel {
|
|||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
return socket.getRemoteSocketAddress();
|
return socket.getRemoteSocketAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getLocalSocketAddress() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
return socket.getLocalSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ public class NettySocketChannel implements SocketChannel {
|
|||||||
return channel != null ? channel.remoteAddress() : null;
|
return channel != null ? channel.remoteAddress() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SocketAddress getLocalSocketAddress() {
|
||||||
|
return channel != null ? channel.localAddress() : null;
|
||||||
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
channel.close();
|
channel.close();
|
||||||
|
|||||||
@@ -21,5 +21,7 @@ public interface SocketChannel {
|
|||||||
|
|
||||||
public SocketAddress getRemoteSocketAddress();
|
public SocketAddress getRemoteSocketAddress();
|
||||||
|
|
||||||
|
public SocketAddress getLocalSocketAddress();
|
||||||
|
|
||||||
public void close();
|
public void close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
<?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">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<artifactId>canal</artifactId>
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
|
||||||
<version>1.1.0</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
|
||||||
<artifactId>canal.kafka.client</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
<name>canal kafka client module for otter ${project.version}</name>
|
|
||||||
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
|
||||||
<artifactId>canal.protocol</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.kafka</groupId>
|
|
||||||
<artifactId>kafka-clients</artifactId>
|
|
||||||
<version>1.1.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- junit -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>dev</id>
|
|
||||||
<activation>
|
|
||||||
<activeByDefault>true</activeByDefault>
|
|
||||||
<property>
|
|
||||||
<name>env</name>
|
|
||||||
<value>!javadoc</value>
|
|
||||||
</property>
|
|
||||||
</activation>
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<id>javadoc</id>
|
|
||||||
<activation>
|
|
||||||
<property>
|
|
||||||
<name>env</name>
|
|
||||||
<value>javadoc</value>
|
|
||||||
</property>
|
|
||||||
</activation>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<version>2.9.1</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-javadocs</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<aggregate>true</aggregate>
|
|
||||||
<show>public</show>
|
|
||||||
<nohelp>true</nohelp>
|
|
||||||
<header>${project.artifactId}-${project.version}</header>
|
|
||||||
<footer>${project.artifactId}-${project.version}</footer>
|
|
||||||
<doctitle>${project.artifactId}-${project.version}</doctitle>
|
|
||||||
<links>
|
|
||||||
<link>https://github.com/alibaba/canal</link>
|
|
||||||
</links>
|
|
||||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-scm-publish-plugin</artifactId>
|
|
||||||
<version>1.0-beta-2</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-javadocs</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>publish-scm</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<checkoutDirectory>${project.build.directory}/scmpublish</checkoutDirectory>
|
|
||||||
<checkinComment>Publishing javadoc for ${project.artifactId}:${project.version}</checkinComment>
|
|
||||||
<content>${project.build.directory}/apidocs</content>
|
|
||||||
<skipDeletedFiles>true</skipDeletedFiles>
|
|
||||||
<pubScmUrl>scm:git:git@github.com:alibaba/canal.git</pubScmUrl>
|
|
||||||
<scmBranch>gh-pages</scmBranch>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
</project>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<configuration scan="true" scanPeriod=" 5 seconds">
|
|
||||||
|
|
||||||
<jmxConfigurator />
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
|
||||||
</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="org.apache.kafka" additivity="false">
|
|
||||||
<level value="ERROR" />
|
|
||||||
<appender-ref ref="STDOUT" />
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.kafka;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.kafka.producer.CanalKafkaStarter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* canal-kafka独立版本启动的入口类
|
|
||||||
*
|
|
||||||
* @author machengyuan 2018-6-11 下午05:30:49
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public class CanalLauncher {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
CanalServerStarter.init();
|
|
||||||
CanalKafkaStarter.init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.kafka;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.deployer.CanalController;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* canal server 启动类
|
|
||||||
*
|
|
||||||
* @author machengyuan 2018-6-11 下午05:30:49
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public class CanalServerStarter {
|
|
||||||
|
|
||||||
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class);
|
|
||||||
private volatile static boolean running = false;
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
try {
|
|
||||||
logger.info("## set default uncaught exception handler");
|
|
||||||
setGlobalUncaughtExceptionHandler();
|
|
||||||
|
|
||||||
logger.info("## load canal configurations");
|
|
||||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
|
||||||
Properties properties = new Properties();
|
|
||||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
|
||||||
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
|
||||||
properties.load(CanalLauncher.class.getClassLoader().getResourceAsStream(conf));
|
|
||||||
} else {
|
|
||||||
properties.load(new FileInputStream(conf));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("## start the canal server.");
|
|
||||||
final CanalController controller = new CanalController(properties);
|
|
||||||
controller.start();
|
|
||||||
running = true;
|
|
||||||
logger.info("## the canal server is running now ......");
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
logger.info("## stop the canal server");
|
|
||||||
running = false;
|
|
||||||
controller.stop();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.warn("##something goes wrong when stopping canal Server:", e);
|
|
||||||
} finally {
|
|
||||||
logger.info("## canal server is down.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRunning() {
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setGlobalUncaughtExceptionHandler() {
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
|
||||||
logger.error("UnCaughtException", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.kafka.producer;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
|
||||||
import org.apache.kafka.clients.producer.Producer;
|
|
||||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
|
||||||
import org.apache.kafka.common.serialization.StringSerializer;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic;
|
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* kafka producer 主操作类
|
|
||||||
*
|
|
||||||
* @author machengyuan 2018-6-11 下午05:30:49
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public class CanalKafkaProducer {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class);
|
|
||||||
|
|
||||||
private Producer<String, Message> producer;
|
|
||||||
|
|
||||||
public void init(KafkaProperties kafkaProperties) {
|
|
||||||
Properties properties = new Properties();
|
|
||||||
properties.put("bootstrap.servers", kafkaProperties.getServers());
|
|
||||||
properties.put("acks", "all");
|
|
||||||
properties.put("retries", kafkaProperties.getRetries());
|
|
||||||
properties.put("batch.size", kafkaProperties.getBatchSize());
|
|
||||||
properties.put("linger.ms", kafkaProperties.getLingerMs());
|
|
||||||
properties.put("buffer.memory", kafkaProperties.getBufferMemory());
|
|
||||||
properties.put("key.serializer", StringSerializer.class.getName());
|
|
||||||
properties.put("value.serializer", MessageSerializer.class.getName());
|
|
||||||
producer = new KafkaProducer<String, Message>(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
try {
|
|
||||||
logger.info("## stop the kafka producer");
|
|
||||||
producer.close();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.warn("##something goes wrong when stopping kafka producer:", e);
|
|
||||||
} finally {
|
|
||||||
logger.info("## kafka producer is down.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(Topic topic, Message message) throws IOException {
|
|
||||||
// set canal.instance.filter.transaction.entry = true
|
|
||||||
|
|
||||||
// boolean valid = false;
|
|
||||||
// if (message != null) {
|
|
||||||
// if (message.isRaw() && !message.getRawEntries().isEmpty()) {
|
|
||||||
// for (ByteString byteString : message.getRawEntries()) {
|
|
||||||
// CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
|
|
||||||
// if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN
|
|
||||||
// && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) {
|
|
||||||
// valid = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else if (!message.getEntries().isEmpty()){
|
|
||||||
// for (CanalEntry.Entry entry : message.getEntries()) {
|
|
||||||
// if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN
|
|
||||||
// && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) {
|
|
||||||
// valid = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (!valid) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
ProducerRecord<String, Message> record;
|
|
||||||
if (topic.getPartition() != null) {
|
|
||||||
record = new ProducerRecord<String, Message>(topic.getTopic(), topic.getPartition(), null, message);
|
|
||||||
} else {
|
|
||||||
record = new ProducerRecord<String, Message>(topic.getTopic(), message);
|
|
||||||
}
|
|
||||||
producer.send(record);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
servers: slave1:6667,slave2:6667,slave3:6667
|
|
||||||
retries: 0
|
|
||||||
batchSize: 16384
|
|
||||||
lingerMs: 1
|
|
||||||
bufferMemory: 33554432
|
|
||||||
# canal的批次大小,单位 k,量大建议改为1M
|
|
||||||
canalBatchSize: 50
|
|
||||||
filterTransactionEntry: true
|
|
||||||
|
|
||||||
canalDestinations:
|
|
||||||
- canalDestination: example
|
|
||||||
topic: example
|
|
||||||
partition:
|
|
||||||
# 一个destination可以对应多个topic
|
|
||||||
# topics:
|
|
||||||
# - topic: example
|
|
||||||
# partition:
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<configuration scan="true" scanPeriod=" 5 seconds">
|
|
||||||
<jmxConfigurator />
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
|
||||||
</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
|
|
||||||
<discriminator>
|
|
||||||
<Key>destination</Key>
|
|
||||||
<DefaultValue>canal</DefaultValue>
|
|
||||||
</discriminator>
|
|
||||||
<sift>
|
|
||||||
<appender name="FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
||||||
<File>../logs/${destination}/${destination}.log</File>
|
|
||||||
<rollingPolicy
|
|
||||||
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
||||||
<!-- rollover daily -->
|
|
||||||
<fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
|
|
||||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
|
||||||
<!-- or whenever the file size reaches 100MB -->
|
|
||||||
<maxFileSize>512MB</maxFileSize>
|
|
||||||
</timeBasedFileNamingAndTriggeringPolicy>
|
|
||||||
<maxHistory>60</maxHistory>
|
|
||||||
</rollingPolicy>
|
|
||||||
<encoder>
|
|
||||||
<pattern>
|
|
||||||
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
|
||||||
</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
</sift>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<appender name="CANAL-META" class="ch.qos.logback.classic.sift.SiftingAppender">
|
|
||||||
<discriminator>
|
|
||||||
<Key>destination</Key>
|
|
||||||
<DefaultValue>canal</DefaultValue>
|
|
||||||
</discriminator>
|
|
||||||
<sift>
|
|
||||||
<appender name="META-FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
||||||
<File>../logs/${destination}/meta.log</File>
|
|
||||||
<rollingPolicy
|
|
||||||
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
||||||
<!-- rollover daily -->
|
|
||||||
<fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/meta-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
|
|
||||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
|
||||||
<!-- or whenever the file size reaches 100MB -->
|
|
||||||
<maxFileSize>32MB</maxFileSize>
|
|
||||||
</timeBasedFileNamingAndTriggeringPolicy>
|
|
||||||
<maxHistory>60</maxHistory>
|
|
||||||
</rollingPolicy>
|
|
||||||
<encoder>
|
|
||||||
<pattern>
|
|
||||||
%d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n
|
|
||||||
</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
</sift>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="com.alibaba.otter.canal.instance" additivity="false">
|
|
||||||
<level value="INFO" />
|
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
|
||||||
</logger>
|
|
||||||
<logger name="com.alibaba.otter.canal.deployer" additivity="false">
|
|
||||||
<level value="INFO" />
|
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
|
||||||
</logger>
|
|
||||||
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
|
|
||||||
<level value="INFO" />
|
|
||||||
<appender-ref ref="CANAL-META" />
|
|
||||||
</logger>
|
|
||||||
<logger name="com.alibaba.otter.canal.kafka" additivity="false">
|
|
||||||
<level value="INFO" />
|
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<root level="WARN">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
||||||
@@ -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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.parse</artifactId>
|
<artifactId>canal.parse</artifactId>
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ public class PositionNotFoundException extends CanalParseException {
|
|||||||
|
|
||||||
private static final long serialVersionUID = -7382448928116244017L;
|
private static final long serialVersionUID = -7382448928116244017L;
|
||||||
|
|
||||||
public PositionNotFoundException(String errorCode) {
|
public PositionNotFoundException(String errorCode){
|
||||||
super(errorCode);
|
super(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PositionNotFoundException(String errorCode, Throwable cause) {
|
public PositionNotFoundException(String errorCode, Throwable cause){
|
||||||
super(errorCode, cause);
|
super(errorCode, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PositionNotFoundException(String errorCode, String errorDesc) {
|
public PositionNotFoundException(String errorCode, String errorDesc){
|
||||||
super(errorCode, errorDesc);
|
super(errorCode, errorDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PositionNotFoundException(String errorCode, String errorDesc, Throwable cause) {
|
public PositionNotFoundException(String errorCode, String errorDesc, Throwable cause){
|
||||||
super(errorCode, errorDesc, cause);
|
super(errorCode, errorDesc, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PositionNotFoundException(Throwable cause) {
|
public PositionNotFoundException(Throwable cause){
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,27 +6,27 @@ import com.alibaba.otter.canal.common.CanalException;
|
|||||||
* @author chengjin.lyf on 2018/8/8 下午1:07
|
* @author chengjin.lyf on 2018/8/8 下午1:07
|
||||||
* @since 1.0.25
|
* @since 1.0.25
|
||||||
*/
|
*/
|
||||||
public class ServerIdNotMatchException extends CanalException{
|
public class ServerIdNotMatchException extends CanalException {
|
||||||
|
|
||||||
private static final long serialVersionUID = -6124989280379293953L;
|
private static final long serialVersionUID = -6124989280379293953L;
|
||||||
|
|
||||||
public ServerIdNotMatchException(String errorCode) {
|
public ServerIdNotMatchException(String errorCode){
|
||||||
super(errorCode);
|
super(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerIdNotMatchException(String errorCode, Throwable cause) {
|
public ServerIdNotMatchException(String errorCode, Throwable cause){
|
||||||
super(errorCode, cause);
|
super(errorCode, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerIdNotMatchException(String errorCode, String errorDesc) {
|
public ServerIdNotMatchException(String errorCode, String errorDesc){
|
||||||
super(errorCode, errorDesc);
|
super(errorCode, errorDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerIdNotMatchException(String errorCode, String errorDesc, Throwable cause) {
|
public ServerIdNotMatchException(String errorCode, String errorDesc, Throwable cause){
|
||||||
super(errorCode, errorDesc, cause);
|
super(errorCode, errorDesc, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerIdNotMatchException(Throwable cause) {
|
public ServerIdNotMatchException(Throwable cause){
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
|||||||
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.exception.PositionNotFoundException;
|
import com.alibaba.otter.canal.parse.exception.PositionNotFoundException;
|
||||||
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlMultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
@@ -235,12 +237,14 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
if (parallel) {
|
if (parallel) {
|
||||||
// build stage processor
|
// build stage processor
|
||||||
multiStageCoprocessor = buildMultiStageCoprocessor();
|
multiStageCoprocessor = buildMultiStageCoprocessor();
|
||||||
multiStageCoprocessor.start();
|
|
||||||
|
|
||||||
if (isGTIDMode()) {
|
if (isGTIDMode()) {
|
||||||
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
||||||
erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), multiStageCoprocessor);
|
GTIDSet gtidSet = MysqlGTIDSet.parse(startPosition.getGtid());
|
||||||
|
((MysqlMultiStageCoprocessor) multiStageCoprocessor).setGtidSet(gtidSet);
|
||||||
|
multiStageCoprocessor.start();
|
||||||
|
erosaConnection.dump(gtidSet, multiStageCoprocessor);
|
||||||
} else {
|
} else {
|
||||||
|
multiStageCoprocessor.start();
|
||||||
if (StringUtils.isEmpty(startPosition.getJournalName())
|
if (StringUtils.isEmpty(startPosition.getJournalName())
|
||||||
&& startPosition.getTimestamp() != null) {
|
&& startPosition.getTimestamp() != null) {
|
||||||
erosaConnection.dump(startPosition.getTimestamp(), multiStageCoprocessor);
|
erosaConnection.dump(startPosition.getTimestamp(), multiStageCoprocessor);
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ package com.alibaba.otter.canal.parse.inbound;
|
|||||||
* @since 1.0.25
|
* @since 1.0.25
|
||||||
*/
|
*/
|
||||||
public interface ParserExceptionHandler {
|
public interface ParserExceptionHandler {
|
||||||
|
|
||||||
void handle(Throwable e);
|
void handle(Throwable e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
@@ -76,13 +75,6 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected boolean processTableMeta(EntryPosition position) {
|
protected boolean processTableMeta(EntryPosition position) {
|
||||||
if (isGTIDMode()) {
|
|
||||||
if (binlogParser instanceof LogEventConvert) {
|
|
||||||
// 记录gtid
|
|
||||||
((LogEventConvert) binlogParser).setGtidSet(MysqlGTIDSet.parse(position.getGtid()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tableMetaTSDB != null) {
|
if (tableMetaTSDB != null) {
|
||||||
if (position.getTimestamp() == null || position.getTimestamp() <= 0) {
|
if (position.getTimestamp() == null || position.getTimestamp() <= 0) {
|
||||||
throw new CanalParseException("use gtid and TableMeta TSDB should be config timestamp > 0");
|
throw new CanalParseException("use gtid and TableMeta TSDB should be config timestamp > 0");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import static com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetche
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -179,6 +180,8 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
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();
|
||||||
|
// fix bug: #890 将gtid传输至context中,供decode使用
|
||||||
|
context.setGtidSet(gtidSet);
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
accumulateReceivedBytes(fetcher.limit());
|
accumulateReceivedBytes(fetcher.limit());
|
||||||
LogEvent event = null;
|
LogEvent event = null;
|
||||||
@@ -252,10 +255,18 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
|
|
||||||
private void sendRegisterSlave() throws IOException {
|
private void sendRegisterSlave() throws IOException {
|
||||||
RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
|
RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
|
||||||
cmd.reportHost = authInfo.getAddress().getAddress().getHostAddress();
|
SocketAddress socketAddress = connector.getChannel().getLocalSocketAddress();
|
||||||
|
if (socketAddress == null || !(socketAddress instanceof InetSocketAddress)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress address = (InetSocketAddress) socketAddress;
|
||||||
|
String host = address.getHostString();
|
||||||
|
int port = address.getPort();
|
||||||
|
cmd.reportHost = host;
|
||||||
|
cmd.reportPort = port;
|
||||||
cmd.reportPasswd = authInfo.getPassword();
|
cmd.reportPasswd = authInfo.getPassword();
|
||||||
cmd.reportUser = authInfo.getUsername();
|
cmd.reportUser = authInfo.getUsername();
|
||||||
cmd.reportPort = authInfo.getAddress().getPort(); // 暂时先用master节点的port
|
|
||||||
cmd.serverId = this.slaveId;
|
cmd.serverId = this.slaveId;
|
||||||
byte[] cmdBody = cmd.toBytes();
|
byte[] cmdBody = cmd.toBytes();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.concurrent.locks.LockSupport;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer;
|
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer;
|
||||||
@@ -63,6 +64,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
|||||||
private String destination;
|
private String destination;
|
||||||
private volatile CanalParseException exception;
|
private volatile CanalParseException exception;
|
||||||
private AtomicLong eventsPublishBlockingTime;
|
private AtomicLong eventsPublishBlockingTime;
|
||||||
|
private GTIDSet gtidSet;
|
||||||
|
|
||||||
public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert,
|
public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert,
|
||||||
EventTransactionBuffer transactionBuffer, String destination){
|
EventTransactionBuffer transactionBuffer, String destination){
|
||||||
@@ -80,9 +82,9 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
|||||||
this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(),
|
this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(),
|
||||||
ringBufferSize,
|
ringBufferSize,
|
||||||
new BlockingWaitStrategy());
|
new BlockingWaitStrategy());
|
||||||
|
int tc = parserThreadCount > 0 ? parserThreadCount : 1;
|
||||||
this.parserExecutor = Executors.newFixedThreadPool(parserThreadCount,
|
this.parserExecutor = Executors.newFixedThreadPool(tc, new NamedThreadFactory("MultiStageCoprocessor-Parser-"
|
||||||
new NamedThreadFactory("MultiStageCoprocessor-Parser-" + destination));
|
+ destination));
|
||||||
|
|
||||||
this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-"
|
this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-"
|
||||||
+ destination));
|
+ destination));
|
||||||
@@ -227,6 +229,9 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
|||||||
public SimpleParserStage(){
|
public SimpleParserStage(){
|
||||||
decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
context = new LogContext();
|
context = new LogContext();
|
||||||
|
if (gtidSet != null) {
|
||||||
|
context.setGtidSet(gtidSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
|
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
|
||||||
@@ -447,4 +452,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
|
|||||||
this.eventsPublishBlockingTime = eventsPublishBlockingTime;
|
this.eventsPublishBlockingTime = eventsPublishBlockingTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGtidSet(GTIDSet gtidSet) {
|
||||||
|
this.gtidSet = gtidSet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
@@ -99,13 +98,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
private boolean filterRows = false;
|
private boolean filterRows = false;
|
||||||
private boolean useDruidDdlFilter = true;
|
private boolean useDruidDdlFilter = true;
|
||||||
|
|
||||||
// latest gtid
|
|
||||||
private GTIDSet gtidSet;
|
|
||||||
|
|
||||||
public LogEventConvert(GTIDSet gtidSet){
|
|
||||||
this.gtidSet = gtidSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogEventConvert(){
|
public LogEventConvert(){
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -172,13 +164,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|
|
||||||
private Entry parseGTIDLogEvent(GtidLogEvent logEvent) {
|
private Entry parseGTIDLogEvent(GtidLogEvent logEvent) {
|
||||||
LogHeader logHeader = logEvent.getHeader();
|
LogHeader logHeader = logEvent.getHeader();
|
||||||
String value = logEvent.getSid().toString() + ":" + logEvent.getGno();
|
|
||||||
Pair.Builder builder = Pair.newBuilder();
|
Pair.Builder builder = Pair.newBuilder();
|
||||||
builder.setKey("gtid");
|
builder.setKey("gtid");
|
||||||
builder.setValue(value);
|
builder.setValue(logEvent.getGtidStr());
|
||||||
if (gtidSet != null) {
|
|
||||||
gtidSet.update(value);
|
|
||||||
}
|
|
||||||
if (logEvent.getLastCommitted() != null) {
|
if (logEvent.getLastCommitted() != null) {
|
||||||
builder.setKey("lastCommitted");
|
builder.setKey("lastCommitted");
|
||||||
builder.setValue(String.valueOf(logEvent.getLastCommitted()));
|
builder.setValue(String.valueOf(logEvent.getLastCommitted()));
|
||||||
@@ -845,9 +834,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
headerBuilder.setEventLength(logHeader.getEventLen());
|
headerBuilder.setEventLength(logHeader.getEventLen());
|
||||||
// enable gtid position
|
// enable gtid position
|
||||||
if (gtidSet != null) {
|
if (StringUtils.isNotEmpty(logHeader.getGtidStr())) {
|
||||||
String gtid = gtidSet.toString();
|
headerBuilder.setGtid(logHeader.getGtidStr());
|
||||||
headerBuilder.setGtid(gtid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add rowsCount suppport
|
// add rowsCount suppport
|
||||||
@@ -977,8 +965,4 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
public void setFilterRows(boolean filterRows) {
|
public void setFilterRows(boolean filterRows) {
|
||||||
this.filterRows = filterRows;
|
this.filterRows = filterRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGtidSet(GTIDSet gtidSet) {
|
|
||||||
this.gtidSet = gtidSet;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ public class SimpleDdlParser {
|
|||||||
* http://dev.mysql.com/doc/refman/5.6/en/create-index.html
|
* http://dev.mysql.com/doc/refman/5.6/en/create-index.html
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public static final String CREATE_INDEX_PATTERN = "^\\s*CREATE\\s*(UNIQUE)?(FULLTEXT)?(SPATIAL)?\\s*INDEX\\s*(.*?)\\s*ON\\s*(.*?)$";
|
public static final String CREATE_INDEX_PATTERN = "^\\s*CREATE\\s*(UNIQUE)?(FULLTEXT)?(SPATIAL)?\\s*INDEX\\s*(.*?)\\s+ON\\s+(.*?)$";
|
||||||
public static final String DROP_INDEX_PATTERN = "^\\s*DROP\\s*INDEX\\s*(.*?)\\s*ON\\s*(.*?)$";
|
public static final String DROP_INDEX_PATTERN = "^\\s*DROP\\s*INDEX\\s*(.*?)\\s+ON\\s+(.*?)$";
|
||||||
|
|
||||||
public static DdlResult parse(String queryString, String schmeaName) {
|
public static DdlResult parse(String queryString, String schmeaName) {
|
||||||
queryString = removeComment(queryString); // 去除/* */的sql注释内容
|
queryString = removeComment(queryString); // 去除/* */的sql注释内容
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ public class MysqlBinlogDumpPerformanceTest {
|
|||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
final MysqlEventParser controller = new MysqlEventParser();
|
final MysqlEventParser controller = new MysqlEventParser();
|
||||||
final EntryPosition startPosition = new EntryPosition("mysql-bin.001699", 120L, 100L);
|
final EntryPosition startPosition = new EntryPosition("mysql-bin.000007", 89796293L, 100L);
|
||||||
controller.setConnectionCharset(Charset.forName("UTF-8"));
|
controller.setConnectionCharset(Charset.forName("UTF-8"));
|
||||||
controller.setSlaveId(3344L);
|
controller.setSlaveId(3344L);
|
||||||
controller.setDetectingEnable(false);
|
controller.setDetectingEnable(false);
|
||||||
controller.setFilterQueryDml(true);
|
controller.setFilterQueryDml(true);
|
||||||
controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3328), "root", "hello"));
|
controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("100.81.154.142", 3306), "canal", "canal"));
|
||||||
controller.setMasterPosition(startPosition);
|
controller.setMasterPosition(startPosition);
|
||||||
controller.setEnableTsdb(false);
|
controller.setEnableTsdb(false);
|
||||||
controller.setDestination("example");
|
controller.setDestination("example");
|
||||||
|
|||||||
@@ -225,5 +225,19 @@ public class SimpleDdlParserTest {
|
|||||||
Assert.assertNotNull(result);
|
Assert.assertNotNull(result);
|
||||||
Assert.assertEquals("retl", result.getSchemaName());
|
Assert.assertEquals("retl", result.getSchemaName());
|
||||||
Assert.assertEquals("retl_mark", result.getTableName());
|
Assert.assertEquals("retl_mark", result.getTableName());
|
||||||
|
|
||||||
|
// test index name contains 'on' -- version
|
||||||
|
queryString = "create index schema_new_index_version_s_idx on q_contract_account (contract_id,main_contract_id)";
|
||||||
|
result = SimpleDdlParser.parse(queryString, "retl");
|
||||||
|
Assert.assertNotNull(result);
|
||||||
|
Assert.assertEquals("retl", result.getSchemaName());
|
||||||
|
Assert.assertEquals("q_contract_account", result.getTableName());
|
||||||
|
|
||||||
|
queryString = "drop index schema_new_index_version_s_idx on q_contract_account";
|
||||||
|
result = SimpleDdlParser.parse(queryString, "retl");
|
||||||
|
Assert.assertNotNull(result);
|
||||||
|
Assert.assertEquals("retl", result.getSchemaName());
|
||||||
|
Assert.assertEquals("q_contract_account", result.getTableName());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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.0</version>
|
<version>1.1.1-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>
|
||||||
@@ -117,9 +117,9 @@
|
|||||||
<module>client</module>
|
<module>client</module>
|
||||||
<module>deployer</module>
|
<module>deployer</module>
|
||||||
<module>example</module>
|
<module>example</module>
|
||||||
<module>kafka</module>
|
|
||||||
<module>kafka-client</module>
|
|
||||||
<module>prometheus</module>
|
<module>prometheus</module>
|
||||||
|
<module>client-adapter</module>
|
||||||
|
<module>client-launcher</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -332,7 +332,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.7.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>${java_source_version}</source>
|
<source>${java_source_version}</source>
|
||||||
<target>${java_target_version}</target>
|
<target>${java_target_version}</target>
|
||||||
|
|||||||
@@ -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.0</version>
|
<version>1.1.1-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.0</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<name>canal prometheus module for otter ${project.version}</name>
|
<name>canal prometheus module for otter ${project.version}</name>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ public class EntryCollector extends Collector implements InstanceRegistry {
|
|||||||
for (EntryMetricsHolder emh : instances.values()) {
|
for (EntryMetricsHolder emh : instances.values()) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long latest = emh.latestExecTime.get();
|
long latest = emh.latestExecTime.get();
|
||||||
if (now >= latest) {
|
// execTime > now,delay显示为0
|
||||||
delay.addMetric(emh.destLabelValues, (now - latest));
|
long d = (now >= latest) ? (now - latest) : 0;
|
||||||
}
|
delay.addMetric(emh.destLabelValues, d);
|
||||||
transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue());
|
transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue());
|
||||||
}
|
}
|
||||||
mfs.add(delay);
|
mfs.add(delay);
|
||||||
|
|||||||
@@ -99,15 +99,13 @@ public class StoreCollector extends Collector implements InstanceRegistry {
|
|||||||
long get = Math.min(smh.getExecTime.get(), pet);
|
long get = Math.min(smh.getExecTime.get(), pet);
|
||||||
long aet = Math.min(smh.ackExecTime.get(), get);
|
long aet = Math.min(smh.ackExecTime.get(), get);
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (now >= pet) {
|
// execTime > now,delay显示为0
|
||||||
putDelay.addMetric(smh.destLabelValues, (now - pet));
|
long pd = (now >= pet) ? (now - pet) : 0;
|
||||||
}
|
putDelay.addMetric(smh.destLabelValues, pd);
|
||||||
if (now >= get) {
|
long gd = (now >= get) ? (now - get) : 0;
|
||||||
getDelay.addMetric(smh.destLabelValues, (now - get));
|
getDelay.addMetric(smh.destLabelValues, gd);
|
||||||
}
|
long ad = (now >= aet) ? (now - aet) : 0;
|
||||||
if (now >= aet) {
|
ackDelay.addMetric(smh.destLabelValues, ad);
|
||||||
ackDelay.addMetric(smh.destLabelValues, (now - aet));
|
|
||||||
}
|
|
||||||
putRows.addMetric(smh.destLabelValues, smh.putTableRows.doubleValue());
|
putRows.addMetric(smh.destLabelValues, smh.putTableRows.doubleValue());
|
||||||
getRows.addMetric(smh.destLabelValues, smh.getTableRows.doubleValue());
|
getRows.addMetric(smh.destLabelValues, smh.getTableRows.doubleValue());
|
||||||
ackRows.addMetric(smh.destLabelValues, smh.ackTableRows.doubleValue());
|
ackRows.addMetric(smh.destLabelValues, smh.ackTableRows.doubleValue());
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user