mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Add RocketMQ canal connector
This commit is contained in:
@@ -17,34 +17,34 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* 外部适配器的加载器
|
||||
* RocktMQ外部适配器的加载器
|
||||
*
|
||||
* @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 static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class);
|
||||
|
||||
private CanalClientConfig canalClientConfig;
|
||||
private CanalClientConfig canalClientConfig;
|
||||
|
||||
private Map<String, CanalAdapterWorker> canalWorkers = new HashMap<>();
|
||||
private Map<String, CanalAdapterWorker> canalWorkers = new HashMap<>();
|
||||
|
||||
private Map<String, CanalAdapterKafkaWorker> canalKafkaWorkers = new HashMap<>();
|
||||
private Map<String, CanalAdapterRocketMQWorker> canalMQWorker = new HashMap<>();
|
||||
|
||||
private ExtensionLoader<CanalOuterAdapter> loader;
|
||||
private ExtensionLoader<CanalOuterAdapter> loader;
|
||||
|
||||
public CanalAdapterLoader(CanalClientConfig canalClientConfig){
|
||||
public CanalAdapterLoader(CanalClientConfig canalClientConfig) {
|
||||
this.canalClientConfig = canalClientConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化canal-client、 canal-client-kafka的适配器
|
||||
* 初始化canal-client、 canal-client-rocketmq的适配器
|
||||
*/
|
||||
public void init() {
|
||||
// canal instances 和 kafka topics 配置不能同时为空
|
||||
if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getKafkaTopics() == null) {
|
||||
throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics");
|
||||
// canal instances 和 rocketmq topics 配置不能同时为空
|
||||
if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getMqTopics() == null) {
|
||||
throw new RuntimeException("Blank config property: canalInstances or canalMQTopics");
|
||||
}
|
||||
|
||||
loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class,
|
||||
@@ -87,10 +87,10 @@ public class CanalAdapterLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化canal-client-kafka的适配器
|
||||
if (canalClientConfig.getKafkaTopics() != null) {
|
||||
for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) {
|
||||
for (CanalClientConfig.Group group : kafkaTopic.getGroups()) {
|
||||
// 初始化canal-client-rocketmq的适配器
|
||||
if (canalClientConfig.getMqTopics() != null) {
|
||||
for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) {
|
||||
for (CanalClientConfig.Group group : topic.getGroups()) {
|
||||
List<List<CanalOuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||
|
||||
List<CanalOuterAdapter> canalOuterAdapters = new ArrayList<>();
|
||||
@@ -102,16 +102,15 @@ public class CanalAdapterLoader {
|
||||
}
|
||||
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||
|
||||
// String zkServers = canalClientConfig.getZookeeperHosts();
|
||||
CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts,
|
||||
CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(
|
||||
canalClientConfig.getBootstrapServers(),
|
||||
kafkaTopic.getTopic(),
|
||||
topic.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());
|
||||
canalMQWorker.put(topic.getTopic() + "-" + group.getGroupId(), rocketMQWorker);
|
||||
rocketMQWorker.start();
|
||||
logger.info("Start adapter for canal-client rocketmq topic: {} succeed",
|
||||
topic.getTopic() + "-" + group.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package com.alibaba.otter.canal.client.adapter.loader;
|
||||
|
||||
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||
import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector;
|
||||
import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider;
|
||||
import com.alibaba.otter.canal.protocol.Message;
|
||||
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;
|
||||
|
||||
/**
|
||||
* kafka对应的client适配器工作线程
|
||||
*
|
||||
* @author machengyuan 2018-8-19 下午11:30:49
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
|
||||
|
||||
private RocketMQCanalConnector connector;
|
||||
|
||||
private String topic;
|
||||
|
||||
public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId,
|
||||
List<List<CanalOuterAdapter>> canalOuterAdapters) {
|
||||
this.canalOuterAdapters = canalOuterAdapters;
|
||||
this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||
this.topic = topic;
|
||||
this.canalDestination = topic;
|
||||
connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId);
|
||||
}
|
||||
|
||||
@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(1);
|
||||
|
||||
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);
|
||||
}
|
||||
connector.ack(message.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
} finally {
|
||||
executing.compareAndSet(true, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.debug("Message is null");
|
||||
}
|
||||
} 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.stopRunning();
|
||||
logger.info("=============> Disconnect topic: {} <=============", this.topic);
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,20 @@ 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:
|
||||
#canalInstances:
|
||||
#- instance: example
|
||||
# adapterGroups:
|
||||
# - outAdapters:
|
||||
# - name: logger
|
||||
# - name: hbase
|
||||
# hosts: slave1:2181
|
||||
# properties: {znodeParent: "/hbase-unsecure"}
|
||||
mqTopics:
|
||||
- topic: example
|
||||
groups:
|
||||
- groupId: example_g1
|
||||
outAdapters:
|
||||
- name: logger
|
||||
# - name: hbase
|
||||
# hosts: slave1:2181
|
||||
# properties: {znodeParent: "/hbase-unsecure"}
|
||||
|
||||
Reference in New Issue
Block a user