Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a3a1d955 | ||
|
|
a857b87388 | ||
|
|
b57ac0bcdf | ||
|
|
ea6391d1c3 | ||
|
|
a2eb128698 | ||
|
|
760f2dda80 | ||
|
|
bf9dc0073d | ||
|
|
d4797a8fb9 | ||
|
|
060bd8e31d | ||
|
|
88de747ed5 | ||
|
|
725d36aa98 | ||
|
|
d24e10fb3e | ||
|
|
fdae646e26 | ||
|
|
40c3e65341 | ||
|
|
b5a1898cec | ||
|
|
680faea2bb | ||
|
|
1cd77bf551 | ||
|
|
9786faba9a | ||
|
|
325fe89aed | ||
|
|
ffeec8fc86 | ||
|
|
7662c9241e | ||
|
|
d01195ac82 | ||
|
|
8b186d1330 | ||
|
|
b7431eb7d0 | ||
|
|
c7fc149e7e | ||
|
|
368554166c | ||
|
|
c4ee350717 | ||
|
|
ae0f43d1c7 |
@@ -1,33 +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">
|
||||
<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>
|
||||
@@ -1,34 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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 + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,563 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
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 "";
|
||||
}
|
||||
@@ -1,101 +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">
|
||||
<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>
|
||||
@@ -1,115 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,418 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,614 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
hbase=com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter
|
||||
@@ -1,5 +0,0 @@
|
||||
# 详细映射配置
|
||||
mytest_person2.yml
|
||||
|
||||
# 简易配置, 只用指定数据库名.表名, 详细配置全部使用默认
|
||||
mytest.person
|
||||
@@ -1,56 +0,0 @@
|
||||
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 动态长度
|
||||
@@ -1,47 +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">
|
||||
<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>
|
||||
@@ -1,45 +0,0 @@
|
||||
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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
logger=com.alibaba.otter.canal.client.adapter.logger.LoggerAdapterExample
|
||||
@@ -1,21 +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">
|
||||
<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,65 +0,0 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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"}
|
||||
@@ -1,47 +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_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>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -16,13 +16,6 @@
|
||||
<artifactId>canal.protocol</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- 客户端要使用请单独引入kafka依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.common</artifactId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -11,11 +11,6 @@
|
||||
<packaging>jar</packaging>
|
||||
<name>canal dbsync module for otter ${project.version}</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal.parse.driver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- log -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
|
||||
@@ -168,9 +168,7 @@ public final class FileLogFetcher extends LogFetcher {
|
||||
* @see com.taobao.tddl.dbsync.binlog.LogFetcher#close()
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (fin != null) {
|
||||
fin.close();
|
||||
}
|
||||
if (fin != null) fin.close();
|
||||
|
||||
fin = null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.taobao.tddl.dbsync.binlog;
|
||||
import java.util.HashMap;
|
||||
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.TableMapLogEvent;
|
||||
|
||||
@@ -21,8 +20,6 @@ public final class LogContext {
|
||||
|
||||
private LogPosition logPosition;
|
||||
|
||||
private GTIDSet gtidSet;
|
||||
|
||||
public LogContext(){
|
||||
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||
}
|
||||
@@ -63,12 +60,4 @@ public final class LogContext {
|
||||
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||
mapOfTable.clear();
|
||||
}
|
||||
|
||||
public GTIDSet getGtidSet() {
|
||||
return gtidSet;
|
||||
}
|
||||
|
||||
public void setGtidSet(GTIDSet gtidSet) {
|
||||
this.gtidSet = gtidSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.util.BitSet;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||
import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent;
|
||||
@@ -47,7 +46,7 @@ import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent;
|
||||
|
||||
/**
|
||||
* Implements a binary-log decoder.
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* LogDecoder decoder = new LogDecoder();
|
||||
* decoder.handle(...);
|
||||
@@ -62,7 +61,7 @@ import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent;
|
||||
* while (event != null);
|
||||
* // no more events in buffer.
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
|
||||
* @version 1.0
|
||||
*/
|
||||
@@ -89,7 +88,7 @@ public final class LogDecoder {
|
||||
|
||||
/**
|
||||
* Decoding an event from binary-log buffer.
|
||||
*
|
||||
*
|
||||
* @return <code>UknownLogEvent</code> if event type is unknown or skipped,
|
||||
* <code>null</code> if buffer is not including a full event.
|
||||
*/
|
||||
@@ -141,7 +140,7 @@ public final class LogDecoder {
|
||||
|
||||
/**
|
||||
* Deserialize an event from buffer.
|
||||
*
|
||||
*
|
||||
* @return <code>UknownLogEvent</code> if event type is unknown or skipped.
|
||||
*/
|
||||
public static LogEvent decode(LogBuffer buffer, LogHeader header, LogContext context) throws IOException {
|
||||
@@ -160,20 +159,18 @@ public final class LogDecoder {
|
||||
// remove checksum bytes
|
||||
buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||
}
|
||||
GTIDSet gtidSet = context.getGtidSet();
|
||||
|
||||
switch (header.getType()) {
|
||||
case LogEvent.QUERY_EVENT: {
|
||||
QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.XID_EVENT: {
|
||||
XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.TABLE_MAP_EVENT: {
|
||||
@@ -271,14 +268,12 @@ public final class LogDecoder {
|
||||
RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.USER_VAR_EVENT: {
|
||||
UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.FORMAT_DESCRIPTION_EVENT: {
|
||||
@@ -329,7 +324,6 @@ public final class LogDecoder {
|
||||
HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.IGNORABLE_LOG_EVENT: {
|
||||
@@ -342,7 +336,6 @@ public final class LogDecoder {
|
||||
RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.WRITE_ROWS_EVENT: {
|
||||
@@ -350,7 +343,6 @@ public final class LogDecoder {
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
event.fillTable(context);
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.UPDATE_ROWS_EVENT: {
|
||||
@@ -358,7 +350,6 @@ public final class LogDecoder {
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
event.fillTable(context);
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.DELETE_ROWS_EVENT: {
|
||||
@@ -366,7 +357,6 @@ public final class LogDecoder {
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
event.fillTable(context);
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.GTID_LOG_EVENT:
|
||||
@@ -374,11 +364,6 @@ public final class LogDecoder {
|
||||
GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
if (gtidSet != null) {
|
||||
gtidSet.update(event.getGtidStr());
|
||||
// update latest gtid
|
||||
header.putGtidStr(gtidSet);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: {
|
||||
@@ -409,7 +394,6 @@ public final class LogDecoder {
|
||||
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
logPosition.position = header.getLogPos();
|
||||
header.putGtidStr(context.getGtidSet());
|
||||
return event;
|
||||
}
|
||||
case LogEvent.BINLOG_CHECKPOINT_EVENT: {
|
||||
|
||||
@@ -14,10 +14,10 @@ import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
public class GtidLogEvent extends LogEvent {
|
||||
|
||||
// / Length of the commit_flag in event encoding
|
||||
public static final int ENCODED_FLAG_LENGTH = 1;
|
||||
public static final int ENCODED_FLAG_LENGTH = 1;
|
||||
// / Length of SID in event encoding
|
||||
public static final int ENCODED_SID_LENGTH = 16;
|
||||
public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
|
||||
public static final int ENCODED_SID_LENGTH = 16;
|
||||
public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
|
||||
|
||||
private boolean commitFlag;
|
||||
private UUID sid;
|
||||
@@ -50,6 +50,8 @@ public class GtidLogEvent extends LogEvent {
|
||||
sequenceNumber = buffer.getLong64();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ignore gtid info read
|
||||
// sid.copy_from((uchar *)ptr_buffer);
|
||||
// ptr_buffer+= ENCODED_SID_LENGTH;
|
||||
@@ -80,11 +82,4 @@ public class GtidLogEvent extends LogEvent {
|
||||
public Long getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
public String getGtidStr() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(sid.toString()).append(":");
|
||||
sb.append(gno);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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.LogEvent;
|
||||
|
||||
@@ -121,8 +120,6 @@ public final class LogHeader {
|
||||
*/
|
||||
protected String logFileName;
|
||||
|
||||
protected String gtidStr;
|
||||
|
||||
/* for Start_event_v3 */
|
||||
public LogHeader(final int type){
|
||||
this.type = type;
|
||||
@@ -291,14 +288,4 @@ public final class LogHeader {
|
||||
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,7 +15,9 @@ public class FetcherPerformanceTest {
|
||||
DirectLogFetcher fetcher = new DirectLogFetcher();
|
||||
try {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
|
||||
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306",
|
||||
"root",
|
||||
"hello");
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -40,7 +40,6 @@
|
||||
<exclude>**/canal.properties</exclude>
|
||||
<exclude>**/spring/**</exclude>
|
||||
<exclude>**/example/**</exclude>
|
||||
<exclude>**/kafka.yml</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -15,7 +15,6 @@ public class CanalConstants {
|
||||
public static final String CANAL_ID = ROOT + "." + "id";
|
||||
public static final String CANAL_IP = ROOT + "." + "ip";
|
||||
public static final String CANAL_PORT = ROOT + "." + "port";
|
||||
public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port";
|
||||
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
|
||||
public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty";
|
||||
|
||||
@@ -23,7 +22,6 @@ public class CanalConstants {
|
||||
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_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 GLOBAL_NAME = "global";
|
||||
|
||||
@@ -98,14 +98,6 @@ public class CanalController {
|
||||
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
||||
embededCanalServer = CanalServerWithEmbedded.instance();
|
||||
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
||||
try {
|
||||
int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT));
|
||||
embededCanalServer.setMetricsPort(metricsPort);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.info("No valid metrics server port found, use default 11112.");
|
||||
embededCanalServer.setMetricsPort(11112);
|
||||
}
|
||||
|
||||
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
|
||||
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
|
||||
canalServer = CanalServerWithNetty.instance();
|
||||
@@ -387,7 +379,7 @@ public class CanalController {
|
||||
return config;
|
||||
}
|
||||
|
||||
public String getProperty(Properties properties, String key) {
|
||||
private String getProperty(Properties properties, String key) {
|
||||
key = StringUtils.trim(key);
|
||||
String value = System.getProperty(key);
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.kafka.CanalKafkaStarter;
|
||||
import com.alibaba.otter.canal.server.CanalServerStarter;
|
||||
|
||||
/**
|
||||
* canal独立版本启动的入口类
|
||||
*
|
||||
@@ -54,18 +51,6 @@ 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) {
|
||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||
System.exit(0);
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
canal.id= 1
|
||||
canal.ip=
|
||||
canal.port=11111
|
||||
canal.metrics.pull.port=11112
|
||||
canal.zkServers=
|
||||
# flush data to zk
|
||||
canal.zookeeper.flush.period = 1000
|
||||
canal.withoutNetty = false
|
||||
# tcp, kafka, rocketMQ
|
||||
canal.serverMode = tcp
|
||||
# flush meta cursor/parse position to file
|
||||
canal.file.data.dir = ${canal.conf.dir}
|
||||
canal.file.flush.period = 1000
|
||||
@@ -69,10 +66,6 @@ canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MO
|
||||
canal.instance.tsdb.dbUsername=canal
|
||||
canal.instance.tsdb.dbPassword=canal
|
||||
|
||||
# rds oss binlog account
|
||||
canal.instance.rds.accesskey =
|
||||
canal.instance.rds.secretkey =
|
||||
|
||||
#################################################
|
||||
######### destinations #############
|
||||
#################################################
|
||||
@@ -89,6 +82,7 @@ canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
|
||||
canal.instance.global.mode = spring
|
||||
canal.instance.global.lazy = false
|
||||
#canal.instance.global.manager.address = 127.0.0.1:1099
|
||||
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
||||
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||
|
||||
@@ -12,11 +12,6 @@ canal.instance.master.position=
|
||||
canal.instance.master.timestamp=
|
||||
canal.instance.master.gtid=
|
||||
|
||||
# rds oss binlog
|
||||
canal.instance.rds.accesskey=
|
||||
canal.instance.rds.secretkey=
|
||||
canal.instance.rds.instanceId=
|
||||
|
||||
# table meta tsdb info
|
||||
canal.instance.tsdb.enable=true
|
||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#################################################
|
||||
# rds openapi binlog
|
||||
canal.instance.rds.open.url=https://rds.aliyuncs.com/
|
||||
canal.instance.rds.open.accesskey=
|
||||
canal.instance.rds.open.secretkey=
|
||||
canal.instance.rds.instanceId=
|
||||
canal.instance.rds.startTime=
|
||||
canal.instance.rds.endTime=
|
||||
|
||||
# local binlog dir
|
||||
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
|
||||
# position info
|
||||
canal.instance.master.address=127.0.0.1:3306
|
||||
|
||||
# table meta tsdb info
|
||||
canal.instance.tsdb.enable=true
|
||||
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
||||
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||
#canal.instance.tsdb.dbUsername=canal
|
||||
#canal.instance.tsdb.dbPassword=canal
|
||||
|
||||
# username/password
|
||||
canal.instance.dbUsername=canal
|
||||
canal.instance.dbPassword=canal
|
||||
canal.instance.defaultDatabaseName=test
|
||||
canal.instance.connectionCharset=UTF-8
|
||||
# table regex
|
||||
canal.instance.filter.regex=test\\..*
|
||||
# table black regex
|
||||
canal.instance.filter.black.regex=
|
||||
#################################################
|
||||
@@ -79,7 +79,7 @@
|
||||
</logger>
|
||||
|
||||
<root level="WARN">
|
||||
<!-- <appender-ref ref="STDOUT"/> -->
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="baseEventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy" abstract="true">
|
||||
<property name="accesskey" value="${canal.instance.rds.accesskey:}" />
|
||||
<property name="secretkey" value="${canal.instance.rds.secretkey:}" />
|
||||
<property name="instanceId" value="${canal.instance.rds.instanceId:}" />
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -10,7 +10,26 @@
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<import resource="classpath:spring/base-instance.xml" />
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
@@ -65,7 +84,7 @@
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" parent="baseEventParser" >
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
|
||||
@@ -10,7 +10,26 @@
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<import resource="classpath:spring/base-instance.xml" />
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
@@ -51,7 +70,7 @@
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" parent="baseEventParser">
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
|
||||
@@ -10,7 +10,26 @@
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<import resource="classpath:spring/base-instance.xml" />
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
@@ -57,7 +76,7 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser1" parent="baseEventParser">
|
||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
@@ -155,7 +174,7 @@
|
||||
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser2" parent="baseEventParser">
|
||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="eventParser">
|
||||
<ref local="eventParser" />
|
||||
</property>
|
||||
<property name="eventSink">
|
||||
<ref local="eventSink" />
|
||||
</property>
|
||||
<property name="eventStore">
|
||||
<ref local="eventStore" />
|
||||
</property>
|
||||
<property name="metaManager">
|
||||
<ref local="metaManager" />
|
||||
</property>
|
||||
<property name="alarmHandler">
|
||||
<ref local="alarmHandler" />
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- 报警处理类 -->
|
||||
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
|
||||
|
||||
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
|
||||
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
|
||||
<property name="period" value="${canal.file.flush.period:1000}" />
|
||||
</bean>
|
||||
|
||||
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
|
||||
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
|
||||
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
|
||||
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
|
||||
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
|
||||
</bean>
|
||||
|
||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||
<property name="eventStore" ref="eventStore" />
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="alarmHandler" ref="alarmHandler" />
|
||||
|
||||
<!-- 解析过滤处理 -->
|
||||
<property name="eventFilter">
|
||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<property name="eventBlackFilter">
|
||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
|
||||
<constructor-arg index="1" value="false" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
|
||||
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
|
||||
|
||||
<!-- 解析编码 -->
|
||||
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
||||
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<!-- 解析数据库信息 -->
|
||||
<property name="masterInfo">
|
||||
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
|
||||
<property name="address" value="${canal.instance.master.address}" />
|
||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
|
||||
<property name="directory" value="${canal.instance.parser.directory:}"/>
|
||||
|
||||
<!-- rds相关 -->
|
||||
<property name="url" value="${canal.instance.rds.open.url:}"/>
|
||||
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
|
||||
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
|
||||
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
|
||||
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
|
||||
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
|
||||
<!-- parallel parser -->
|
||||
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -10,7 +10,26 @@
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<import resource="classpath:spring/base-instance.xml" />
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
@@ -48,7 +67,7 @@
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" parent="baseEventParser">
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
FROM canal/osbase:v1
|
||||
FROM centos:centos6.7
|
||||
|
||||
MAINTAINER agapple (jianghang115@gmail.com)
|
||||
|
||||
# install system
|
||||
RUN \
|
||||
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||
echo 'root:Hello1234' | chpasswd && \
|
||||
groupadd -r admin && useradd -g admin admin && \
|
||||
yum install -y man && \
|
||||
yum install -y dstat && \
|
||||
yum install -y unzip && \
|
||||
yum install -y nc && \
|
||||
yum install -y openssh-server && \
|
||||
yum install -y tar && \
|
||||
yum install -y which && \
|
||||
yum install -y wget && \
|
||||
yum install -y perl && \
|
||||
yum install -y file && \
|
||||
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
|
||||
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
|
||||
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
|
||||
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
|
||||
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
|
||||
yum install -y cronie && \
|
||||
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
|
||||
yum clean all && \
|
||||
true
|
||||
|
||||
# install canal
|
||||
COPY image/ /tmp/docker/
|
||||
COPY canal.deployer-*.tar.gz /home/admin/
|
||||
COPY jdk-8-linux-x64.rpm /tmp/
|
||||
|
||||
RUN \
|
||||
cp -R /tmp/docker/alidata /alidata && \
|
||||
@@ -13,6 +39,14 @@ RUN \
|
||||
cp -R /tmp/docker/admin/* /home/admin/ && \
|
||||
/bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \
|
||||
|
||||
touch /var/lib/rpm/* && \
|
||||
yum -y install /tmp/jdk-8-linux-x64.rpm && \
|
||||
/bin/rm -f /tmp/jdk-8-linux-x64.rpm && \
|
||||
|
||||
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
|
||||
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
|
||||
/bin/mv /home/admin/bin/clean_log /etc/cron.d && \
|
||||
|
||||
mkdir -p /home/admin/canal-server && \
|
||||
tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \
|
||||
/bin/rm -f /home/admin/canal.deployer-*.tar.gz && \
|
||||
@@ -24,10 +58,10 @@ RUN \
|
||||
yum clean all && \
|
||||
true
|
||||
|
||||
# 2222 sys , 8000 debug , 11111 canal , 11112 metrics
|
||||
EXPOSE 2222 11111 8000 11112
|
||||
# 2222 sys , 8080 web , 8000 debug , 11111 canal
|
||||
EXPOSE 2222 11111 8000 8080
|
||||
|
||||
WORKDIR /home/admin
|
||||
|
||||
ENTRYPOINT [ "/alidata/bin/main.sh" ]
|
||||
CMD [ "/home/admin/app.sh" ]
|
||||
CMD [ "/home/admin/app.sh" ]
|
||||
@@ -1,41 +0,0 @@
|
||||
FROM centos:centos6.10
|
||||
|
||||
MAINTAINER agapple (jianghang115@gmail.com)
|
||||
|
||||
env DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.rpm"
|
||||
# install system
|
||||
RUN \
|
||||
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||
echo 'root:Hello1234' | chpasswd && \
|
||||
groupadd -r admin && useradd -g admin admin && \
|
||||
yum install -y man && \
|
||||
yum install -y dstat && \
|
||||
yum install -y unzip && \
|
||||
yum install -y nc && \
|
||||
yum install -y openssh-server && \
|
||||
yum install -y tar && \
|
||||
yum install -y which && \
|
||||
yum install -y wget && \
|
||||
yum install -y perl && \
|
||||
yum install -y file && \
|
||||
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
|
||||
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
|
||||
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
|
||||
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
|
||||
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
|
||||
yum install -y cronie && \
|
||||
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
|
||||
true
|
||||
|
||||
RUN \
|
||||
touch /var/lib/rpm/* && \
|
||||
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O /tmp/jdk-8-linux-x64.rpm && \
|
||||
yum -y install /tmp/jdk-8-linux-x64.rpm && \
|
||||
/bin/rm -f /tmp/jdk-8-linux-x64.rpm && \
|
||||
|
||||
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
|
||||
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
|
||||
yum clean all && \
|
||||
true
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -14,16 +14,17 @@ case "`uname`" in
|
||||
esac
|
||||
BASE=${bin_abs_path}
|
||||
|
||||
if [ "$1" == "base" ] ; then
|
||||
docker build --no-cache -t canal/osbase $BASE/base
|
||||
if [ ! -f $BASE/jdk*.rpm ] ; then
|
||||
DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz"
|
||||
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm
|
||||
fi
|
||||
|
||||
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
||||
|
||||
if [ "$1" == "kafka" ] ; then
|
||||
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
|
||||
docker build --no-cache -t canal/canal-server $BASE/
|
||||
else
|
||||
rm -rf $BASE/canal.*.tar.gz ;
|
||||
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
||||
if [ "$1" == "kafka" ] ; then
|
||||
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
|
||||
docker build --no-cache -t canal/canal-server $BASE/
|
||||
else
|
||||
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
||||
docker build --no-cache -t canal/canal-server $BASE/
|
||||
fi
|
||||
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
||||
docker build --no-cache -t canal/canal-server $BASE/
|
||||
fi
|
||||
|
||||
@@ -86,9 +86,7 @@ function start_canal() {
|
||||
echo "multi destination:$destination is not support"
|
||||
exit 1;
|
||||
else
|
||||
if [ "$destination" != "" ] && [ "$destination" != "example" ] ; then
|
||||
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination
|
||||
fi
|
||||
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination
|
||||
fi
|
||||
su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1'
|
||||
sleep 5
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
#!/bin/sh
|
||||
metrics_port=`perl -le 'print $ENV{"canal.metrics.pull.port"}'`
|
||||
if [ "$metrics_port" == "" ]; then
|
||||
metrics_port="11112"
|
||||
fi
|
||||
|
||||
CHECK_URL="http://127.0.0.1:$metrics_port/metrics"
|
||||
CHECK_POINT="canal"
|
||||
CHECK_URL="http://127.0.0.1:8080/metrics"
|
||||
CHECK_POINT="success"
|
||||
CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT`
|
||||
if [ $CHECK_COUNT -eq 0 ]; then
|
||||
echo "[FAILED]"
|
||||
|
||||
@@ -10,7 +10,7 @@ function usage() {
|
||||
echo " -e canal.instance.connectionCharset=UTF-8 \\"
|
||||
echo " -e canal.instance.tsdb.enable=true \\"
|
||||
echo " -e canal.instance.gtidon=false \\"
|
||||
echo " -e canal.instance.filter.regex=.*\\\\\\..* "
|
||||
echo " -e canal.instance.filter.regex=.*\\..* "
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ DATA="$BASE/data"
|
||||
mkdir -p $DATA
|
||||
CONFIG=${@:1}
|
||||
#VOLUMNS="-v $DATA:/home/admin/canal-server/logs"
|
||||
PORTLIST="8000 2222 11111 11112"
|
||||
PORTLIST="8000 8080 2222 11111"
|
||||
PORTS=""
|
||||
for PORT in $PORTLIST ; do
|
||||
#exist=`check_port $PORT`
|
||||
@@ -89,4 +89,4 @@ MEMORY="-m 4096m"
|
||||
LOCALHOST=`getMyIp`
|
||||
cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server"
|
||||
echo $cmd
|
||||
eval $cmd
|
||||
eval $cmd
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -138,16 +138,6 @@ public class BioSocketChannel implements SocketChannel {
|
||||
if (socket != null) {
|
||||
return socket.getRemoteSocketAddress();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SocketAddress getLocalSocketAddress() {
|
||||
Socket socket = this.socket;
|
||||
if (socket != null) {
|
||||
return socket.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -216,10 +216,6 @@ public class NettySocketChannel implements SocketChannel {
|
||||
return channel != null ? channel.remoteAddress() : null;
|
||||
}
|
||||
|
||||
public SocketAddress getLocalSocketAddress() {
|
||||
return channel != null ? channel.localAddress() : null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
|
||||
@@ -21,7 +21,5 @@ public interface SocketChannel {
|
||||
|
||||
public SocketAddress getRemoteSocketAddress();
|
||||
|
||||
public SocketAddress getLocalSocketAddress();
|
||||
|
||||
public void close();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -265,6 +265,7 @@ public class AbstractCanalClientTest {
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
|
||||
builder.append(" type=" + column.getMysqlType());
|
||||
if (column.getUpdated()) {
|
||||
builder.append(" update=" + column.getUpdated());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.instance.core</artifactId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.meta.FileMixedMetaManager;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -39,7 +38,12 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||
import com.alibaba.otter.canal.parse.index.*;
|
||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.index.MetaLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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.0.26-SNAPSHOT</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>0.9.0.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 +1,21 @@
|
||||
package com.alibaba.otter.canal.client.kafka;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
package com.alibaba.otter.canal.kafka.client;
|
||||
|
||||
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.KafkaConsumer;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
|
||||
import com.alibaba.otter.canal.client.kafka.running.ClientRunningData;
|
||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||
import com.alibaba.otter.canal.protocol.Message;
|
||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* canal kafka 数据操作客户端
|
||||
@@ -24,16 +26,16 @@ import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
public class KafkaCanalConnector {
|
||||
|
||||
private KafkaConsumer<String, Message> kafkaConsumer;
|
||||
private String topic;
|
||||
private Integer partition;
|
||||
private Properties properties;
|
||||
// private ClientRunningMonitor runningMonitor; // 运行控制
|
||||
// private BooleanMutex mutex = new BooleanMutex(false);
|
||||
private ZkClientx zkClientx;
|
||||
private volatile boolean connected = false;
|
||||
private volatile boolean running = false;
|
||||
private String topic;
|
||||
private Integer partition;
|
||||
private Properties properties;
|
||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||
private ZkClientx zkClientx;
|
||||
private BooleanMutex mutex = new BooleanMutex(false);
|
||||
private volatile boolean connected = 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.partition = partition;
|
||||
|
||||
@@ -42,10 +44,10 @@ public class KafkaCanalConnector {
|
||||
properties.put("group.id", groupId);
|
||||
properties.put("enable.auto.commit", false);
|
||||
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("session.timeout.ms", "30000"); // 默认为30秒
|
||||
properties.put("max.poll.records", "1"); // 一次只取一条message数据
|
||||
properties.put("max.poll.records", "1"); // 所以一次只取一条数据
|
||||
properties.put("key.deserializer", StringDeserializer.class.getName());
|
||||
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
||||
|
||||
@@ -56,19 +58,19 @@ public class KafkaCanalConnector {
|
||||
clientData.setGroupId(groupId);
|
||||
clientData.setAddress(AddressUtils.getHostIp());
|
||||
|
||||
// runningMonitor = new ClientRunningMonitor();
|
||||
// runningMonitor.setTopic(topic);
|
||||
// runningMonitor.setZkClient(zkClientx);
|
||||
// runningMonitor.setClientData(clientData);
|
||||
// runningMonitor.setListener(new ClientRunningListener() {
|
||||
// public void processActiveEnter() {
|
||||
// mutex.set(true);
|
||||
// }
|
||||
//
|
||||
// public void processActiveExit() {
|
||||
// mutex.set(false);
|
||||
// }
|
||||
// });
|
||||
runningMonitor = new ClientRunningMonitor();
|
||||
runningMonitor.setTopic(topic);
|
||||
runningMonitor.setZkClient(zkClientx);
|
||||
runningMonitor.setClientData(clientData);
|
||||
runningMonitor.setListener(new ClientRunningListener() {
|
||||
public void processActiveEnter() {
|
||||
mutex.set(true);
|
||||
}
|
||||
|
||||
public void processActiveExit() {
|
||||
mutex.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,11 +95,11 @@ public class KafkaCanalConnector {
|
||||
return;
|
||||
}
|
||||
|
||||
// if (runningMonitor != null) {
|
||||
// if (!runningMonitor.isStart()) {
|
||||
// runningMonitor.start();
|
||||
// }
|
||||
// }
|
||||
if (runningMonitor != null) {
|
||||
if (!runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
|
||||
connected = true;
|
||||
|
||||
@@ -113,9 +115,9 @@ public class KafkaCanalConnector {
|
||||
kafkaConsumer.close();
|
||||
|
||||
connected = false;
|
||||
// if (runningMonitor.isStart()) {
|
||||
// runningMonitor.stop();
|
||||
// }
|
||||
if (runningMonitor.isStart()) {
|
||||
runningMonitor.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void waitClientRunning() {
|
||||
@@ -126,12 +128,12 @@ public class KafkaCanalConnector {
|
||||
}
|
||||
|
||||
running = true;
|
||||
// mutex.get();// 阻塞等待
|
||||
mutex.get();// 阻塞等待
|
||||
} else {
|
||||
// 单机模式直接设置为running
|
||||
running = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new CanalClientException(e);
|
||||
}
|
||||
@@ -139,8 +141,7 @@ public class KafkaCanalConnector {
|
||||
|
||||
public boolean checkValid() {
|
||||
if (zkClientx != null) {
|
||||
// return mutex.state();
|
||||
return true;
|
||||
return mutex.state();
|
||||
} else {
|
||||
return true;// 默认都放过
|
||||
}
|
||||
@@ -233,9 +234,9 @@ public class KafkaCanalConnector {
|
||||
public void stopRunning() {
|
||||
if (running) {
|
||||
running = false; // 设置为非running状态
|
||||
// if (!mutex.state()) {
|
||||
// mutex.set(true); // 中断阻塞
|
||||
// }
|
||||
if (!mutex.state()) {
|
||||
mutex.set(true); // 中断阻塞
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.otter.canal.client.kafka;
|
||||
package com.alibaba.otter.canal.kafka.client;
|
||||
|
||||
/**
|
||||
* canal kafka connectors创建工具类
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.otter.canal.client.kafka;
|
||||
package com.alibaba.otter.canal.kafka.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.otter.canal.client.kafka.running;
|
||||
package com.alibaba.otter.canal.kafka.client.running;
|
||||
|
||||
/**
|
||||
* client running状态信息
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.otter.canal.client.kafka.running;
|
||||
package com.alibaba.otter.canal.kafka.client.running;
|
||||
|
||||
/**
|
||||
* client running状态信息
|
||||