增加扁平Message用于卡夫卡数据投递

This commit is contained in:
mcy
2018-09-14 11:22:47 +08:00
parent a6798c61df
commit e8e0032404
19 changed files with 686 additions and 247 deletions

View File

@@ -1,7 +1,9 @@
package com.alibaba.otter.canal.client.adapter; package com.alibaba.otter.canal.client.adapter;
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; 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.SPI; import com.alibaba.otter.canal.client.adapter.support.SPI;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
/** /**
@@ -23,9 +25,11 @@ public interface CanalOuterAdapter {
/** /**
* 往适配器中写入数据 * 往适配器中写入数据
* *
* @param message message数据包 * @param dml 数据包
*/ */
void writeOut(Message message); void writeOut(Dml dml);
// void writeOut(FlatMessage flatMessage);
/** /**
* 外部适配器销毁接口 * 外部适配器销毁接口

View File

@@ -20,6 +20,8 @@ public class CanalClientConfig {
private String bootstrapServers; private String bootstrapServers;
private Boolean flatMessage = true;
private List<KafkaTopic> kafkaTopics; private List<KafkaTopic> kafkaTopics;
private List<CanalInstance> canalInstances; private List<CanalInstance> canalInstances;
@@ -56,6 +58,14 @@ public class CanalClientConfig {
this.bootstrapServers = bootstrapServers; this.bootstrapServers = bootstrapServers;
} }
public Boolean getFlatMessage() {
return flatMessage;
}
public void setFlatMessage(Boolean flatMessage) {
this.flatMessage = flatMessage;
}
public List<KafkaTopic> getKafkaTopics() { public List<KafkaTopic> getKafkaTopics() {
return kafkaTopics; return kafkaTopics;
} }

View File

@@ -21,7 +21,7 @@ public class JdbcTypeUtil {
private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class); private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class);
public static Object typeConvert(String tableName, String columnName, String value, int sqlType, String mysqlType) { public static Object typeConvert(String columnName, String value, int sqlType, String mysqlType) {
if (value == null || value.equals("")) { if (value == null || value.equals("")) {
return null; return null;
} }
@@ -96,7 +96,7 @@ public class JdbcTypeUtil {
} }
return res; return res;
} catch (Exception e) { } catch (Exception e) {
logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType); logger.error("table: {} column: {}, failed convert type {} to {}", columnName, value, sqlType);
return value; return value;
} }
} }

View File

@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.support;
import java.util.*; import java.util.*;
import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
/** /**
@@ -63,8 +64,7 @@ public class MessageUtil {
for (CanalEntry.Column column : columns) { for (CanalEntry.Column column : columns) {
row.put(column.getName(), row.put(column.getName(),
JdbcTypeUtil.typeConvert(dml.getTable(), JdbcTypeUtil.typeConvert(column.getName(),
column.getName(),
column.getValue(), column.getValue(),
column.getSqlType(), column.getSqlType(),
column.getMysqlType())); column.getMysqlType()));
@@ -82,8 +82,7 @@ public class MessageUtil {
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
if (updateSet.contains(column.getName())) { if (updateSet.contains(column.getName())) {
rowOld.put(column.getName(), rowOld.put(column.getName(),
JdbcTypeUtil.typeConvert(dml.getTable(), JdbcTypeUtil.typeConvert(column.getName(),
column.getName(),
column.getValue(), column.getValue(),
column.getSqlType(), column.getSqlType(),
column.getMysqlType())); column.getMysqlType()));
@@ -106,6 +105,57 @@ public class MessageUtil {
} }
} }
public static Dml flatMessage2Dml(FlatMessage flatMessage) {
if (flatMessage == null) {
return null;
}
Dml dml = new Dml();
dml.setDatabase(flatMessage.getDatabase());
dml.setTable(flatMessage.getTable());
dml.setType(flatMessage.getType());
dml.setTs(flatMessage.getTs());
dml.setSql(flatMessage.getSql());
if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) {
throw new RuntimeException("SqlType or mysqlType is null");
}
List<Map<String, String>> data = flatMessage.getData();
if (data != null) {
dml.setData(changeRows(data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
}
List<Map<String, String>> old = flatMessage.getOld();
if (old != null) {
dml.setOld(changeRows(old, flatMessage.getSqlType(), flatMessage.getMysqlType()));
}
return dml;
}
private static List<Map<String, Object>> changeRows(List<Map<String, String>> rows, Map<String, Integer> sqlTypes,
Map<String, String> mysqlTypes) {
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, String> row : rows) {
Map<String, Object> resultRow = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : row.entrySet()) {
String columnName = entry.getKey();
String columnValue = entry.getValue();
Integer sqlType = sqlTypes.get(columnName);
if (sqlType == null) {
continue;
}
String mysqlType = mysqlTypes.get(columnName);
if (mysqlType == null) {
continue;
}
Object finalValue = JdbcTypeUtil.typeConvert(columnName, columnValue, sqlType, mysqlType);
resultRow.put(columnName, finalValue);
}
result.add(resultRow);
}
return result;
}
public interface Consumer<T> { public interface Consumer<T> {
void accept(T t); void accept(T t);

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.alibaba.otter.canal.protocol.FlatMessage;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -86,20 +87,14 @@ public class HbaseAdapter implements CanalOuterAdapter {
} }
@Override @Override
public void writeOut(Message message) { public void writeOut(Dml dml) {
MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() { if (dml == null) {
return;
@Override }
public void accept(Dml dml) { String database = dml.getDatabase();
if (dml == null) { String table = dml.getTable();
return; MappingConfig config = mappingConfigCache.get(database + "-" + table);
} hbaseSyncService.sync(config, dml);
String database = dml.getDatabase();
String table = dml.getTable();
MappingConfig config = mappingConfigCache.get(database + "-" + table);
hbaseSyncService.sync(config, dml);
}
});
} }
@Override @Override

View File

@@ -6,9 +6,7 @@ import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; 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.CanalOuterAdapterConfiguration;
import com.alibaba.otter.canal.client.adapter.support.Dml; 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.client.adapter.support.SPI;
import com.alibaba.otter.canal.protocol.Message;
/** /**
* 外部适配器示例 * 外部适配器示例
@@ -22,20 +20,13 @@ public class LoggerAdapterExample implements CanalOuterAdapter {
private Logger logger = LoggerFactory.getLogger(this.getClass()); private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override @Override
public void writeOut(Message message) { public void init(CanalOuterAdapterConfiguration configuration) {
// 直接输出日志信息
MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() {
@Override
public void accept(Dml dml) {
logger.info(dml.toString());
}
});
} }
@Override @Override
public void init(CanalOuterAdapterConfiguration configuration) { public void writeOut(Dml dml) {
logger.info(dml.toString());
} }
@Override @Override

View File

@@ -7,10 +7,13 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
/** /**
@@ -47,9 +50,16 @@ public abstract class AbstractCanalAdapterWorker {
public Boolean call() { public Boolean call() {
try { try {
// 组内适配器穿行运行,尽量不要配置组内适配器 // 组内适配器穿行运行,尽量不要配置组内适配器
for (CanalOuterAdapter c : adapters) { for (final CanalOuterAdapter c : adapters) {
long begin = System.currentTimeMillis(); long begin = System.currentTimeMillis();
c.writeOut(message); MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() {
@Override
public void accept(Dml dml) {
c.writeOut(dml);
}
});
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("{} elapsed time: {}", logger.debug("{} elapsed time: {}",
c.getClass().getName(), c.getClass().getName(),
@@ -71,7 +81,49 @@ public abstract class AbstractCanalAdapterWorker {
logger.error("Outer adapter write failed"); logger.error("Outer adapter write failed");
} }
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
logger.error(e.getMessage(), e); // ignore
}
}
}
}
protected void writeOut(final FlatMessage flatMessage) {
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();
Dml dml = MessageUtil.flatMessage2Dml(flatMessage);
c.writeOut(dml);
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) {
// ignore
} }
} }
} }

View File

@@ -12,6 +12,7 @@ import org.apache.kafka.common.errors.WakeupException;
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
/** /**
@@ -26,14 +27,17 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
private String topic; private String topic;
public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId, private boolean flatMessage;
List<List<CanalOuterAdapter>> canalOuterAdapters){
public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId,
List<List<CanalOuterAdapter>> canalOuterAdapters, boolean flatMessage){
this.canalOuterAdapters = canalOuterAdapters; this.canalOuterAdapters = canalOuterAdapters;
this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
this.topic = topic; this.topic = topic;
this.canalDestination = topic; this.canalDestination = topic;
connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId); this.flatMessage = flatMessage;
connector.setSessionTimeout(5L, TimeUnit.MINUTES); connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage);
// connector.setSessionTimeout(1L, TimeUnit.MINUTES);
// super.initSwitcher(topic); // super.initSwitcher(topic);
} }
@@ -104,56 +108,47 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
try { try {
// switcher.get(); //等待开关开启 // switcher.get(); //等待开关开启
final Message message = connector.getWithoutAck(); List<?> messages;
if (!flatMessage) {
messages = connector.getWithoutAck();
} else {
messages = connector.getFlatMessageWithoutAck(100L, TimeUnit.MILLISECONDS);
}
if (messages != null) {
for (final Object message : messages) {
executing.set(true);
if (message != null) {
executor.submit(new Runnable() {
executing.set(true); @Override
if (message != null) { public void run() {
executor.submit(new Runnable() { try {
if (message instanceof FlatMessage) {
writeOut((FlatMessage) message);
} else {
writeOut((Message) message);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
executing.compareAndSet(true, false);
}
}
});
@Override // 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端
public void run() { long currentTS = System.currentTimeMillis();
try { while (executing.get()) {
if (logger.isDebugEnabled()) { // 大于10秒未消费完ack一次keep alive
logger.debug("topic: {} batchId: {} batchSize: {} ", if (System.currentTimeMillis() - currentTS > 10000) {
topic, connector.ack();
message.getId(), currentTS = System.currentTimeMillis();
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();
} }
connector.ack();
} catch (CommitFailedException e) { } catch (CommitFailedException e) {
logger.warn(e.getMessage()); logger.warn(e.getMessage());
} catch (Exception e) { } catch (Exception e) {
@@ -161,19 +156,23 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
TimeUnit.SECONDS.sleep(1L); TimeUnit.SECONDS.sleep(1L);
} }
} }
} catch (Exception e) { } catch (
Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} }
} }
executor.shutdown(); executor.shutdown();
try { try
{
connector.unsubscribe(); connector.unsubscribe();
} catch (WakeupException e) { } catch (WakeupException e) {
// No-op. Continue process // No-op. Continue process
} }
connector.disconnnect(); connector.disconnect();
logger.info("=============> Disconnect topic: {} <=============", this.topic); logger.info("=============> Disconnect topic: {} <=============", this.topic);
} }
} }

View File

@@ -59,6 +59,8 @@ public class CanalAdapterLoader {
} }
String zkHosts = this.canalClientConfig.getZookeeperHosts(); String zkHosts = this.canalClientConfig.getZookeeperHosts();
boolean flatMessage = this.canalClientConfig.getFlatMessage();
// if (zkHosts == null && sa == null) { // if (zkHosts == null && sa == null) {
// throw new RuntimeException("Blank config property: canalServerHost or // throw new RuntimeException("Blank config property: canalServerHost or
// zookeeperHosts"); // zookeeperHosts");
@@ -104,11 +106,12 @@ public class CanalAdapterLoader {
canalOuterAdapterGroups.add(canalOuterAdapters); canalOuterAdapterGroups.add(canalOuterAdapters);
// String zkServers = canalClientConfig.getZookeeperHosts(); // String zkServers = canalClientConfig.getZookeeperHosts();
CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts, CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(
canalClientConfig.getBootstrapServers(), canalClientConfig.getBootstrapServers(),
kafkaTopic.getTopic(), kafkaTopic.getTopic(),
group.getGroupId(), group.getGroupId(),
canalOuterAdapterGroups); canalOuterAdapterGroups,
flatMessage);
canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker); canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker);
canalKafkaWorker.start(); canalKafkaWorker.start();
logger.info("Start adapter for canal-client kafka topic: {} succeed", logger.info("Start adapter for canal-client kafka topic: {} succeed",

View File

@@ -1,21 +1,22 @@
canalServerHost: 127.0.0.1:11111 #canalServerHost: 127.0.0.1:11111
#zookeeperHosts: slave1:2181 #zookeeperHosts: slave1:2181
#bootstrapServers: slave1:6667,slave2:6667 bootstrapServers: slave1:6667,slave2:6667
flatMessage: true
canalInstances: #canalInstances:
- instance: example #- instance: example
adapterGroups: # adapterGroups:
- outAdapters: # - outAdapters:
- name: logger
- name: hbase
hosts: slave1:2181
properties: {znodeParent: "/hbase-unsecure"}
#kafkaTopics:
#- topic: example
# groups:
# - groupId: example_g1
# outAdapters:
# - name: logger # - name: logger
# - name: hbase # - name: hbase
# hosts: slave1:2181 # hosts: slave1:2181
# properties: {znodeParent: "/hbase-unsecure"} # properties: {znodeParent: "/hbase-unsecure"}
kafkaTopics:
- topic: example
groups:
- groupId: egroup
outAdapters:
- name: logger
# - name: hbase
# hosts: slave1:2181
# properties: {znodeParent: "/hbase-unsecure"}

View File

@@ -1,19 +1,20 @@
package com.alibaba.otter.canal.client.kafka; package com.alibaba.otter.canal.client.kafka;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringDeserializer;
import com.alibaba.otter.canal.client.kafka.running.ClientRunningData; import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.common.utils.AddressUtils; import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
/** /**
* canal kafka 数据操作客户端 * canal kafka 数据操作客户端
@@ -24,18 +25,19 @@ import com.alibaba.otter.canal.protocol.exception.CanalClientException;
public class KafkaCanalConnector { public class KafkaCanalConnector {
private KafkaConsumer<String, Message> kafkaConsumer; private KafkaConsumer<String, Message> kafkaConsumer;
private KafkaConsumer<String, String> kafkaConsumer2; // 用于扁平message的数据消费
private String topic; private String topic;
private Integer partition; private Integer partition;
private Properties properties; private Properties properties;
// private ClientRunningMonitor runningMonitor; // 运行控制
// private BooleanMutex mutex = new BooleanMutex(false);
private ZkClientx zkClientx;
private volatile boolean connected = false; private volatile boolean connected = false;
private volatile boolean running = false; private volatile boolean running = false;
private boolean flatMessage;
public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId){ public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId,
boolean flatMessage){
this.topic = topic; this.topic = topic;
this.partition = partition; this.partition = partition;
this.flatMessage = flatMessage;
properties = new Properties(); properties = new Properties();
properties.put("bootstrap.servers", servers); properties.put("bootstrap.servers", servers);
@@ -45,32 +47,13 @@ public class KafkaCanalConnector {
properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读 properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置 properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
properties.put("session.timeout.ms", "30000"); // 默认为30秒 properties.put("session.timeout.ms", "30000"); // 默认为30秒
properties.put("max.poll.records", "1"); // 一次只取一条message数据 properties.put("max.poll.records", "100");
properties.put("key.deserializer", StringDeserializer.class.getName()); properties.put("key.deserializer", StringDeserializer.class.getName());
properties.put("value.deserializer", MessageDeserializer.class.getName()); if (!flatMessage) {
properties.put("value.deserializer", MessageDeserializer.class.getName());
if (zkServers != null) { } else {
zkClientx = new ZkClientx(zkServers); properties.put("value.deserializer", StringDeserializer.class.getName());
ClientRunningData clientData = new ClientRunningData();
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);
// }
// });
} }
} }
/** /**
@@ -93,57 +76,36 @@ public class KafkaCanalConnector {
return; return;
} }
// if (runningMonitor != null) {
// if (!runningMonitor.isStart()) {
// runningMonitor.start();
// }
// }
connected = true; connected = true;
if (kafkaConsumer == null) { if (kafkaConsumer == null && !flatMessage) {
kafkaConsumer = new KafkaConsumer<String, Message>(properties); kafkaConsumer = new KafkaConsumer<String, Message>(properties);
} }
if (kafkaConsumer2 == null && flatMessage) {
kafkaConsumer2 = new KafkaConsumer<String, String>(properties);
}
} }
/** /**
* 关闭链接 * 关闭链接
*/ */
public void disconnnect() { public void disconnect() {
kafkaConsumer.close(); if (kafkaConsumer != null) {
kafkaConsumer.close();
}
if (kafkaConsumer2 != null) {
kafkaConsumer2.close();
}
connected = false; connected = false;
// if (runningMonitor.isStart()) {
// runningMonitor.stop();
// }
} }
private void waitClientRunning() { private void waitClientRunning() {
try { running = true;
if (zkClientx != null) {
if (!connected) {// 未调用connect
throw new CanalClientException("should connect first");
}
running = true;
// mutex.get();// 阻塞等待
} else {
// 单机模式直接设置为running
running = true;
}
} catch (Exception e) {
Thread.currentThread().interrupt();
throw new CanalClientException(e);
}
} }
public boolean checkValid() { public boolean checkValid() {
if (zkClientx != null) { return true;// 默认都放过
// return mutex.state();
return true;
} else {
return true;// 默认都放过
}
} }
/** /**
@@ -156,10 +118,20 @@ public class KafkaCanalConnector {
} }
if (partition == null) { if (partition == null) {
kafkaConsumer.subscribe(Collections.singletonList(topic)); if (kafkaConsumer != null) {
kafkaConsumer.subscribe(Collections.singletonList(topic));
}
if (kafkaConsumer2 != null) {
kafkaConsumer2.subscribe(Collections.singletonList(topic));
}
} else { } else {
TopicPartition topicPartition = new TopicPartition(topic, partition); TopicPartition topicPartition = new TopicPartition(topic, partition);
kafkaConsumer.assign(Collections.singletonList(topicPartition)); if (kafkaConsumer != null) {
kafkaConsumer.assign(Collections.singletonList(topicPartition));
}
if (kafkaConsumer2 != null) {
kafkaConsumer2.assign(Collections.singletonList(topicPartition));
}
} }
} }
@@ -172,7 +144,12 @@ public class KafkaCanalConnector {
return; return;
} }
kafkaConsumer.unsubscribe(); if (kafkaConsumer != null) {
kafkaConsumer.unsubscribe();
}
if (kafkaConsumer2 != null) {
kafkaConsumer2.unsubscribe();
}
} }
/** /**
@@ -180,22 +157,22 @@ public class KafkaCanalConnector {
* *
* @return * @return
*/ */
public Message get() { public List<Message> get() {
return get(100L, TimeUnit.MILLISECONDS); return get(100L, TimeUnit.MILLISECONDS);
} }
public Message get(Long timeout, TimeUnit unit) { public List<Message> get(Long timeout, TimeUnit unit) {
waitClientRunning(); waitClientRunning();
if (!running) { if (!running) {
return null; return null;
} }
Message message = getWithoutAck(timeout, unit); List<Message> messages = getWithoutAck(timeout, unit);
this.ack(); this.ack();
return message; return messages;
} }
public Message getWithoutAck() { public List<Message> getWithoutAck() {
return getWithoutAck(100L, TimeUnit.MILLISECONDS); return getWithoutAck(100L, TimeUnit.MILLISECONDS);
} }
@@ -204,7 +181,7 @@ public class KafkaCanalConnector {
* *
* @return * @return
*/ */
public Message getWithoutAck(Long timeout, TimeUnit unit) { public List<Message> getWithoutAck(Long timeout, TimeUnit unit) {
waitClientRunning(); waitClientRunning();
if (!running) { if (!running) {
return null; return null;
@@ -213,7 +190,33 @@ public class KafkaCanalConnector {
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据 ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据
if (!records.isEmpty()) { if (!records.isEmpty()) {
return records.iterator().next().value(); // return records.iterator().next().value();
List<Message> messages = new ArrayList<>();
for (ConsumerRecord<String, Message> record : records) {
messages.add(record.value());
}
return messages;
}
return null;
}
public List<FlatMessage> getFlatMessageWithoutAck(Long timeout, TimeUnit unit) {
waitClientRunning();
if (!running) {
return null;
}
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
if (!records.isEmpty()) {
List<FlatMessage> flatMessages = new ArrayList<>();
for (ConsumerRecord<String, String> record : records) {
String flatMessageJson = record.value();
FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
flatMessages.add(flatMessage);
}
return flatMessages;
} }
return null; return null;
} }
@@ -227,7 +230,12 @@ public class KafkaCanalConnector {
return; return;
} }
kafkaConsumer.commitSync(); if (kafkaConsumer != null) {
kafkaConsumer.commitSync();
}
if (kafkaConsumer2 != null) {
kafkaConsumer2.commitAsync();
}
} }
public void stopRunning() { public void stopRunning() {

View File

@@ -17,8 +17,9 @@ public class KafkaCanalConnectors {
* @param groupId * @param groupId
* @return * @return
*/ */
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition, String groupId) { public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition,
return new KafkaCanalConnector(null, servers, topic, partition, groupId); String groupId) {
return new KafkaCanalConnector(servers, topic, partition, groupId, false);
} }
/** /**
@@ -30,33 +31,21 @@ public class KafkaCanalConnectors {
* @return * @return
*/ */
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, String groupId) { public static KafkaCanalConnector newKafkaConnector(String servers, String topic, String groupId) {
return new KafkaCanalConnector(null, servers, topic, null, groupId); return new KafkaCanalConnector(servers, topic, null, groupId, false);
} }
/** /**
* 创建kafka客户端链接 * 创建kafka客户端链接
* *
* @param zkServers
* @param servers * @param servers
* @param topic * @param topic
* @param partition * @param partition
* @param groupId * @param groupId
* @param flatMessage
* @return * @return
*/ */
public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, Integer partition, String groupId) { public static KafkaCanalConnector newKafkaConnector( String servers, String topic,
return new KafkaCanalConnector(zkServers, servers, topic, partition, groupId); Integer partition, String groupId,boolean flatMessage) {
} return new KafkaCanalConnector(servers, topic, partition, groupId, flatMessage);
/**
* 创建kafka客户端链接
*
* @param zkServers
* @param servers
* @param topic
* @param groupId
* @return
*/
public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, String groupId) {
return new KafkaCanalConnector(zkServers, servers, topic, null, groupId);
} }
} }

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.running.kafka; package com.alibaba.otter.canal.client.running.kafka;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.kafka.common.errors.WakeupException; import org.apache.kafka.common.errors.WakeupException;
@@ -19,32 +20,33 @@ import com.alibaba.otter.canal.protocol.Message;
*/ */
public class CanalKafkaClientExample { public class CanalKafkaClientExample {
protected final static Logger logger = LoggerFactory.getLogger(CanalKafkaClientExample.class); protected final static Logger logger = LoggerFactory.getLogger(CanalKafkaClientExample.class);
private KafkaCanalConnector connector; private KafkaCanalConnector connector;
private static volatile boolean running = false; private static volatile boolean running = false;
private Thread thread = null; private Thread thread = null;
private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) { public void uncaughtException(Thread t, Throwable e) {
logger.error("parse events has an error", e); logger.error("parse events has an error", e);
} }
}; };
public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId) { public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId){
connector = KafkaCanalConnectors.newKafkaConnector(zkServers, servers, topic, partition, groupId); connector = KafkaCanalConnectors.newKafkaConnector(servers, topic, partition, groupId, false);
} }
public static void main(String[] args) { public static void main(String[] args) {
try { try {
final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(AbstractKafkaTest.zkServers, final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(
AbstractKafkaTest.servers, AbstractKafkaTest.zkServers,
AbstractKafkaTest.topic, AbstractKafkaTest.servers,
AbstractKafkaTest.partition, AbstractKafkaTest.topic,
AbstractKafkaTest.groupId); AbstractKafkaTest.partition,
AbstractKafkaTest.groupId);
logger.info("## start the kafka consumer: {}-{}", AbstractKafkaTest.topic, AbstractKafkaTest.groupId); logger.info("## start the kafka consumer: {}-{}", AbstractKafkaTest.topic, AbstractKafkaTest.groupId);
kafkaCanalClientExample.start(); kafkaCanalClientExample.start();
logger.info("## the canal kafka consumer is running now ......"); logger.info("## the canal kafka consumer is running now ......");
@@ -107,21 +109,24 @@ public class CanalKafkaClientExample {
connector.subscribe(); connector.subscribe();
while (running) { while (running) {
try { try {
Message message = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message List<Message> messages = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message
if (message == null) {
if (messages == null) {
continue; continue;
} }
long batchId = message.getId(); for (Message message : messages) {
int size = message.getEntries().size(); long batchId = message.getId();
if (batchId == -1 || size == 0) { int size = message.getEntries().size();
// try { if (batchId == -1 || size == 0) {
// Thread.sleep(1000); // try {
// } catch (InterruptedException e) { // Thread.sleep(1000);
// } // } catch (InterruptedException e) {
} else { // }
// printSummary(message, batchId, size); } else {
// printEntry(message.getEntries()); // printSummary(message, batchId, size);
logger.info(message.toString()); // printEntry(message.getEntries());
logger.info(message.toString());
}
} }
connector.ack(); // 提交确认 connector.ack(); // 提交确认
@@ -139,6 +144,6 @@ public class CanalKafkaClientExample {
} catch (WakeupException e) { } catch (WakeupException e) {
// No-op. Continue process // No-op. Continue process
} }
connector.disconnnect(); connector.disconnect();
} }
} }

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.running.kafka; package com.alibaba.otter.canal.client.running.kafka;
import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -39,9 +40,9 @@ public class KafkaClientRunningTest extends AbstractKafkaTest {
connector.subscribe(); connector.subscribe();
while (running) { while (running) {
try { try {
Message message = connector.getWithoutAck(3L, TimeUnit.SECONDS); List<Message> messages = connector.getWithoutAck(3L, TimeUnit.SECONDS);
if (message != null) { if (messages != null) {
System.out.println(message); System.out.println(messages);
} }
connector.ack(); connector.ack();
} catch (WakeupException e) { } catch (WakeupException e) {
@@ -49,7 +50,7 @@ public class KafkaClientRunningTest extends AbstractKafkaTest {
} }
} }
connector.unsubscribe(); connector.unsubscribe();
connector.disconnnect(); connector.disconnect();
} }
}); });

View File

@@ -8,8 +8,11 @@ bufferMemory: 33554432
canalBatchSize: 50 canalBatchSize: 50
# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时 # Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
canalGetTimeout: 100 canalGetTimeout: 100
flatMessage: true
canalDestinations: canalDestinations:
- canalDestination: example - canalDestination: example
topic: example topic: example
partition: partition:

View File

@@ -0,0 +1,286 @@
package com.alibaba.otter.canal.protocol;
import java.io.Serializable;
import java.util.*;
import com.google.common.collect.Table;
import com.google.protobuf.ByteString;
/**
* @author machengyuan 2018-9-13 下午10:31:14
* @version 1.0.0
*/
public class FlatMessage implements Serializable {
private static final long serialVersionUID = -3386650678735860050L;
private long id;
private String database;
private String table;
private Boolean isDdl;
private String type;
private Long ts;
private String sql;
private Map<String, Integer> sqlType;
private Map<String, String> mysqlType;
private List<Map<String, String>> data;
private List<Map<String, String>> old;
public FlatMessage() {
}
public FlatMessage(long id){
this.id = id;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
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 Boolean getIsDdl() {
return isDdl;
}
public void setIsDdl(Boolean isDdl) {
this.isDdl = isDdl;
}
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 Map<String, Integer> getSqlType() {
return sqlType;
}
public void setSqlType(Map<String, Integer> sqlType) {
this.sqlType = sqlType;
}
public Map<String, String> getMysqlType() {
return mysqlType;
}
public void setMysqlType(Map<String, String> mysqlType) {
this.mysqlType = mysqlType;
}
public List<Map<String, String>> getData() {
return data;
}
public void setData(List<Map<String, String>> data) {
this.data = data;
}
public List<Map<String, String>> getOld() {
return old;
}
public void setOld(List<Map<String, String>> old) {
this.old = old;
}
public static List<FlatMessage> messageConverter(Message message) {
try {
if (message == null) {
return null;
}
List<FlatMessage> flatMessages = new ArrayList<>();
List<ByteString> rawEntries = message.getRawEntries();
for (ByteString byteString : rawEntries) {
CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
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();
FlatMessage flatMessage = new FlatMessage(message.getId());
flatMessages.add(flatMessage);
flatMessage.setDatabase(entry.getHeader().getSchemaName());
flatMessage.setTable(entry.getHeader().getTableName());
flatMessage.setIsDdl(rowChange.getIsDdl());
flatMessage.setType(eventType.toString());
flatMessage.setTs(System.currentTimeMillis());
flatMessage.setSql(rowChange.getSql());
if (!rowChange.getIsDdl()) {
Map<String, Integer> sqlType = new LinkedHashMap<>();
Map<String, String> mysqlType = new LinkedHashMap<>();
List<Map<String, String>> data = new ArrayList<>();
List<Map<String, String>> old = new ArrayList<>();
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, String> row = new LinkedHashMap<>();
List<CanalEntry.Column> columns;
if (eventType == CanalEntry.EventType.DELETE) {
columns = rowData.getBeforeColumnsList();
} else {
columns = rowData.getAfterColumnsList();
}
for (CanalEntry.Column column : columns) {
sqlType.put(column.getName(), column.getSqlType());
mysqlType.put(column.getName(), column.getMysqlType());
row.put(column.getName(), column.getValue());
// 获取update为true的字段
if (column.getUpdated()) {
updateSet.add(column.getName());
}
}
if (!row.isEmpty()) {
data.add(row);
}
if (eventType == CanalEntry.EventType.UPDATE) {
Map<String, String> rowOld = new LinkedHashMap<>();
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
if (updateSet.contains(column.getName())) {
rowOld.put(column.getName(), column.getValue());
}
}
// update操作将记录修改前的值
if (!rowOld.isEmpty()) {
old.add(rowOld);
}
}
}
if (!sqlType.isEmpty()) {
flatMessage.setSqlType(sqlType);
}
if (!mysqlType.isEmpty()) {
flatMessage.setMysqlType(mysqlType);
}
if (!data.isEmpty()) {
flatMessage.setData(data);
}
if (!old.isEmpty()) {
flatMessage.setOld(old);
}
}
}
return flatMessages;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum,
Table<String, String, String> pkHashConfig) {
FlatMessage[] partitionMessages = new FlatMessage[partitionsNum];
String pk = pkHashConfig.get(flatMessage.getDatabase(), flatMessage.getTable());
if (pk == null || flatMessage.getIsDdl()) {
partitionMessages[0] = flatMessage;
} else {
if (flatMessage.getData() != null) {
int idx = 0;
for (Map<String, String> row : flatMessage.getData()) {
String value = row.get(pk);
if (value == null) {
value = "";
}
int hash = value.hashCode();
int pkHash = Math.abs(hash) % partitionsNum;
FlatMessage flatMessageTmp = partitionMessages[pkHash];
if (flatMessageTmp == null) {
flatMessageTmp = new FlatMessage(flatMessage.getId());
partitionMessages[pkHash] = flatMessageTmp;
flatMessageTmp.setDatabase(flatMessage.getDatabase());
flatMessageTmp.setTable(flatMessage.getTable());
flatMessageTmp.setIsDdl(flatMessage.getIsDdl());
flatMessageTmp.setType(flatMessage.getType());
flatMessageTmp.setSql(flatMessage.getSql());
flatMessageTmp.setSqlType(flatMessage.getSqlType());
flatMessageTmp.setMysqlType(flatMessage.getMysqlType());
}
List<Map<String, String>> data = flatMessageTmp.getData();
if (data == null) {
data = new ArrayList<>();
flatMessageTmp.setData(data);
}
data.add(row);
if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) {
List<Map<String, String>> old = flatMessageTmp.getOld();
if (old == null) {
old = new ArrayList<>();
flatMessageTmp.setOld(old);
}
old.add(flatMessage.getOld().get(idx));
}
idx++;
}
}
}
return partitionMessages;
}
@Override
public String toString() {
return "FlatMessage{" + "id=" + id + ", database='" + database + '\'' + ", table='" + table + '\'' + ", isDdl="
+ isDdl + ", type='" + type + '\'' + ", ts=" + ts + ", sql='" + sql + '\'' + ", sqlType=" + sqlType
+ ", mysqlType=" + mysqlType + ", data=" + data + ", old=" + old + '}';
}
}

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.kafka; package com.alibaba.otter.canal.kafka;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@@ -11,6 +12,8 @@ import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
/** /**
@@ -25,7 +28,12 @@ public class CanalKafkaProducer {
private Producer<String, Message> producer; private Producer<String, Message> producer;
private Producer<String, String> producer2; // 用于扁平message的数据投递
private KafkaProperties kafkaProperties;
public void init(KafkaProperties kafkaProperties) { public void init(KafkaProperties kafkaProperties) {
this.kafkaProperties = kafkaProperties;
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("bootstrap.servers", kafkaProperties.getServers()); properties.put("bootstrap.servers", kafkaProperties.getServers());
properties.put("acks", "all"); properties.put("acks", "all");
@@ -34,15 +42,26 @@ public class CanalKafkaProducer {
properties.put("linger.ms", kafkaProperties.getLingerMs()); properties.put("linger.ms", kafkaProperties.getLingerMs());
properties.put("buffer.memory", kafkaProperties.getBufferMemory()); properties.put("buffer.memory", kafkaProperties.getBufferMemory());
properties.put("key.serializer", StringSerializer.class.getName()); properties.put("key.serializer", StringSerializer.class.getName());
properties.put("value.serializer", MessageSerializer.class.getName()); if (!kafkaProperties.getFlatMessage()) {
producer = new KafkaProducer<String, Message>(properties); properties.put("value.serializer", MessageSerializer.class.getName());
producer = new KafkaProducer<String, Message>(properties);
} else {
properties.put("value.serializer", StringSerializer.class.getName());
producer2 = new KafkaProducer<String, String>(properties);
}
// producer.initTransactions(); // producer.initTransactions();
} }
public void stop() { public void stop() {
try { try {
logger.info("## stop the kafka producer"); logger.info("## stop the kafka producer");
producer.close(); if (producer != null) {
producer.close();
}
if (producer2 != null) {
producer2.close();
}
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("##something goes wrong when stopping kafka producer:", e); logger.warn("##something goes wrong when stopping kafka producer:", e);
} finally { } finally {
@@ -53,14 +72,27 @@ public class CanalKafkaProducer {
public void send(KafkaProperties.Topic topic, Message message, Callback callback) { public void send(KafkaProperties.Topic topic, Message message, Callback callback) {
try { try {
// producer.beginTransaction(); // producer.beginTransaction();
ProducerRecord<String, Message> record; if (!kafkaProperties.getFlatMessage()) {
if (topic.getPartition() != null) { ProducerRecord<String, Message> record;
record = new ProducerRecord<String, Message>(topic.getTopic(), topic.getPartition(), null, message); if (topic.getPartition() != null) {
record = new ProducerRecord<String, Message>(topic.getTopic(), topic.getPartition(), null, message);
} else {
record = new ProducerRecord<String, Message>(topic.getTopic(), message);
}
Future<RecordMetadata> future = producer.send(record);
future.get();
} else { } else {
record = new ProducerRecord<String, Message>(topic.getTopic(), message); // 发送扁平数据json
List<FlatMessage> flatMessages = FlatMessage.messageConverter(message);
if (flatMessages != null) {
for (FlatMessage flatMessage : flatMessages) {
ProducerRecord<String, String> record = new ProducerRecord<String, String>(topic.getTopic(),
JSON.toJSONString(flatMessage));
producer2.send(record);
}
}
} }
Future<RecordMetadata> future = producer.send(record);
future.get();
// producer.commitTransaction(); // producer.commitTransaction();
callback.commit(); callback.commit();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {

View File

@@ -21,6 +21,7 @@ public class KafkaProperties {
private boolean filterTransactionEntry = true; private boolean filterTransactionEntry = true;
private int canalBatchSize = 50; private int canalBatchSize = 50;
private Long canalGetTimeout; private Long canalGetTimeout;
private boolean flatMessage = true;
private List<CanalDestination> canalDestinations = new ArrayList<CanalDestination>(); private List<CanalDestination> canalDestinations = new ArrayList<CanalDestination>();
@@ -160,6 +161,14 @@ public class KafkaProperties {
this.canalGetTimeout = canalGetTimeout; this.canalGetTimeout = canalGetTimeout;
} }
public boolean getFlatMessage() {
return flatMessage;
}
public void setFlatMessage(boolean flatMessage) {
this.flatMessage = flatMessage;
}
public List<CanalDestination> getCanalDestinations() { public List<CanalDestination> getCanalDestinations() {
return canalDestinations; return canalDestinations;
} }

View File

@@ -311,9 +311,10 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C
} }
if (CollectionUtils.isEmpty(events.getEvents())) { if (CollectionUtils.isEmpty(events.getEvents())) {
logger.debug("getWithoutAck successfully, clientId:{} batchSize:{} but result is null", // logger.debug("getWithoutAck successfully, clientId:{} batchSize:{} but result
clientIdentity.getClientId(), // is null",
batchSize); // clientIdentity.getClientId(),
// batchSize);
return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能 return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能
} else { } else {
// 记录到流式信息 // 记录到流式信息