fixed issue #2258 , optimizer kafka send perf

This commit is contained in:
agapple
2019-09-30 14:24:59 +08:00
parent c634f98fca
commit 0fb717a97f
10 changed files with 600 additions and 362 deletions

View File

@@ -0,0 +1,100 @@
package com.alibaba.otter.canal.common.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 多线程执行器模板代码,otter中好多地方都写多线程,比较多的都是重复的逻辑代码,抽象一下做个模板把
*
* <pre>
* 示例代码:
* ExecutorTemplate template = new ExecutorTemplate(executor);
* ...
* try {
* for ( ....) {
* template.submit(new Runnable() {})
* }
*
* List<?> result = template.waitForResult();
* // do result
* } finally {
* template.clear();
* }
*
* 注意:该模板工程,不支持多业务并发调用,会出现数据混乱
* </pre>
*/
public class ExecutorTemplate {
private volatile ThreadPoolExecutor executor = null;
private volatile List<Future> futures = null;
public ExecutorTemplate(ThreadPoolExecutor executor){
this.futures = Collections.synchronizedList(new ArrayList<Future>());
this.executor = executor;
}
public void submit(Runnable task) {
Future future = executor.submit(task, null);
futures.add(future);
check(future);
}
public void submit(Callable<Exception> task) {
Future future = executor.submit(task);
futures.add(future);
check(future);
}
private void check(Future future) {
if (future.isDone()) {
// 立即判断一次,因为使用了CallerRun可能当场跑出结果,针对有异常时快速响应,而不是等跑完所有的才抛异常
try {
future.get();
} catch (Throwable e) {
// 取消完之后立马退出
cacelAllFutures();
throw new RuntimeException(e);
}
}
}
public synchronized List<?> waitForResult() {
List result = new ArrayList();
RuntimeException exception = null;
for (Future future : futures) {
try {
result.add(future.get());
} catch (Throwable e) {
exception = new RuntimeException(e);
// 如何一个future出现了异常,就退出
break;
}
}
if (exception != null) {
cacelAllFutures();
throw exception;
} else {
return result;
}
}
public void cacelAllFutures() {
for (Future future : futures) {
if (!future.isDone() && !future.isCancelled()) {
future.cancel(true);
}
}
}
public void clear() {
futures.clear();
}
}

View File

@@ -10,8 +10,8 @@ import com.alibaba.otter.canal.admin.netty.CanalAdminWithNetty;
import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.common.MQProperties;
import com.alibaba.otter.canal.deployer.admin.CanalAdminController; import com.alibaba.otter.canal.deployer.admin.CanalAdminController;
import com.alibaba.otter.canal.kafka.CanalKafkaProducer; import com.alibaba.otter.canal.kafka.CanalKafkaProducer;
import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer;
import com.alibaba.otter.canal.rabbitmq.CanalRabbitMQProducer; import com.alibaba.otter.canal.rabbitmq.CanalRabbitMQProducer;
import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer;
import com.alibaba.otter.canal.server.CanalMQStarter; import com.alibaba.otter.canal.server.CanalMQStarter;
import com.alibaba.otter.canal.spi.CanalMQProducer; import com.alibaba.otter.canal.spi.CanalMQProducer;
@@ -69,11 +69,15 @@ public class CanalStarter {
canalMQProducer = new CanalRabbitMQProducer(); canalMQProducer = new CanalRabbitMQProducer();
} }
MQProperties mqProperties = null;
if (canalMQProducer != null) { if (canalMQProducer != null) {
mqProperties = buildMQProperties(properties);
// disable netty // disable netty
System.setProperty(CanalConstants.CANAL_WITHOUT_NETTY, "true"); System.setProperty(CanalConstants.CANAL_WITHOUT_NETTY, "true");
// 设置为raw避免ByteString->Entry的二次解析 if (mqProperties.getFlatMessage()) {
System.setProperty("canal.instance.memory.rawEntry", "false"); // 设置为raw避免ByteString->Entry的二次解析
System.setProperty("canal.instance.memory.rawEntry", "false");
}
} }
logger.info("## start the canal server."); logger.info("## start the canal server.");
@@ -99,7 +103,6 @@ public class CanalStarter {
if (canalMQProducer != null) { if (canalMQProducer != null) {
canalMQStarter = new CanalMQStarter(canalMQProducer); canalMQStarter = new CanalMQStarter(canalMQProducer);
MQProperties mqProperties = buildMQProperties(properties);
String destinations = CanalController.getProperty(properties, CanalConstants.CANAL_DESTINATIONS); String destinations = CanalController.getProperty(properties, CanalConstants.CANAL_DESTINATIONS);
canalMQStarter.start(mqProperties, destinations); canalMQStarter.start(mqProperties, destinations);
controller.setCanalMQStarter(canalMQStarter); controller.setCanalMQStarter(canalMQStarter);

View File

@@ -0,0 +1,35 @@
package com.alibaba.otter.canal.common;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.alibaba.otter.canal.spi.CanalMQProducer;
/**
* @author agapple 2019年9月29日 上午11:17:11
* @since 1.1.5
*/
public abstract class AbstractMQProducer implements CanalMQProducer {
protected ThreadPoolExecutor executor;
@Override
public void init(MQProperties mqProperties) {
int parallelThreadSize = mqProperties.getParallelThreadSize();
executor = new ThreadPoolExecutor(parallelThreadSize,
parallelThreadSize,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(parallelThreadSize * 2),
new ThreadPoolExecutor.CallerRunsPolicy());
}
@Override
public void stop() {
executor.shutdownNow();
executor = null;
}
}

View File

@@ -1,15 +1,18 @@
package com.alibaba.otter.canal.common; package com.alibaba.otter.canal.common;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ThreadPoolExecutor;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry; import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
@@ -28,6 +31,10 @@ import com.google.protobuf.InvalidProtocolBufferException;
* *
* @author agapple 2018年12月11日 下午1:28:32 * @author agapple 2018年12月11日 下午1:28:32
*/ */
/**
* @author agapple 2019年9月29日 下午12:36:26
* @since 5.0.0
*/
public class MQMessageUtils { public class MQMessageUtils {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@@ -160,6 +167,67 @@ public class MQMessageUtils {
return messages; return messages;
} }
/**
* 多线程构造message的rowChanged对象,比如为partition/flastMessage转化等处理 </br>
* 因为protobuf对象的序列化和反序列化是cpu密集型,串行执行会有代价
*/
public static EntryRowData[] buildMessageData(Message message, ThreadPoolExecutor executor) {
ExecutorTemplate template = new ExecutorTemplate(executor);
if (message.isRaw()) {
List<ByteString> rawEntries = message.getRawEntries();
final EntryRowData[] datas = new EntryRowData[rawEntries.size()];
int i = 0;
for (ByteString byteString : rawEntries) {
final int index = i;
template.submit(new Runnable() {
@Override
public void run() {
try {
Entry entry = Entry.parseFrom(byteString);
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
datas[index] = new EntryRowData();
datas[index].entry = entry;
datas[index].rowChange = rowChange;
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
});
i++;
}
template.waitForResult();
return datas;
} else {
final EntryRowData[] datas = new EntryRowData[message.getEntries().size()];
int i = 0;
for (Entry entry : message.getEntries()) {
final int index = i;
template.submit(new Runnable() {
@Override
public void run() {
try {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
datas[index] = new EntryRowData();
datas[index].entry = entry;
datas[index].rowChange = rowChange;
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
});
i++;
}
template.waitForResult();
return datas;
}
}
/** /**
* 将 message 分区 * 将 message 分区
* *
@@ -168,40 +236,20 @@ public class MQMessageUtils {
* @return 分区message数组 * @return 分区message数组
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Message[] messagePartition(Message message, Integer partitionsNum, String pkHashConfigs) { public static Message[] messagePartition(EntryRowData[] datas, long id, Integer partitionsNum, String pkHashConfigs) {
if (partitionsNum == null) { if (partitionsNum == null) {
partitionsNum = 1; partitionsNum = 1;
} }
Message[] partitionMessages = new Message[partitionsNum]; Message[] partitionMessages = new Message[partitionsNum];
List<Entry>[] partitionEntries = new List[partitionsNum]; List<Entry>[] partitionEntries = new List[partitionsNum];
for (int i = 0; i < partitionsNum; i++) { for (int i = 0; i < partitionsNum; i++) {
partitionEntries[i] = new ArrayList<>(); // 注意一下并发
partitionEntries[i] = Collections.synchronizedList(Lists.newArrayList());
} }
List<CanalEntry.Entry> entries; for (EntryRowData data : datas) {
if (message.isRaw()) { CanalEntry.Entry entry = data.entry;
List<ByteString> rawEntries = message.getRawEntries(); CanalEntry.RowChange rowChange = data.rowChange;
entries = new ArrayList<>(rawEntries.size());
for (ByteString byteString : rawEntries) {
Entry entry;
try {
entry = Entry.parseFrom(byteString);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
entries.add(entry);
}
} else {
entries = message.getEntries();
}
for (Entry entry : entries) {
CanalEntry.RowChange rowChange;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
if (rowChange.getIsDdl()) { if (rowChange.getIsDdl()) {
partitionEntries[0].add(entry); partitionEntries[0].add(entry);
@@ -220,6 +268,10 @@ public class MQMessageUtils {
// tableHash not need split entry message // tableHash not need split entry message
partitionEntries[pkHash].add(entry); partitionEntries[pkHash].add(entry);
} else { } else {
// build new entry
Entry.Builder builder = Entry.newBuilder(entry);
RowChange.Builder rowChangeBuilder = RowChange.newBuilder(rowChange);
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
int hashCode = database.hashCode(); int hashCode = database.hashCode();
CanalEntry.EventType eventType = rowChange.getEventType(); CanalEntry.EventType eventType = rowChange.getEventType();
@@ -247,9 +299,7 @@ public class MQMessageUtils {
int pkHash = Math.abs(hashCode) % partitionsNum; int pkHash = Math.abs(hashCode) % partitionsNum;
pkHash = Math.abs(pkHash); pkHash = Math.abs(pkHash);
// build new entry // clear rowDatas
Entry.Builder builder = Entry.newBuilder(entry);
RowChange.Builder rowChangeBuilder = RowChange.newBuilder(rowChange);
rowChangeBuilder.clearRowDatas(); rowChangeBuilder.clearRowDatas();
rowChangeBuilder.addRowDatas(rowData); rowChangeBuilder.addRowDatas(rowData);
builder.clearStoreValue(); builder.clearStoreValue();
@@ -267,7 +317,7 @@ public class MQMessageUtils {
for (int i = 0; i < partitionsNum; i++) { for (int i = 0; i < partitionsNum; i++) {
List<Entry> entriesTmp = partitionEntries[i]; List<Entry> entriesTmp = partitionEntries[i];
if (!entriesTmp.isEmpty()) { if (!entriesTmp.isEmpty()) {
partitionMessages[i] = new Message(message.getId(), entriesTmp); partitionMessages[i] = new Message(id, entriesTmp);
} }
} }
@@ -280,131 +330,100 @@ public class MQMessageUtils {
* @param message 原生message * @param message 原生message
* @return FlatMessage列表 * @return FlatMessage列表
*/ */
public static List<FlatMessage> messageConverter(Message message) { public static List<FlatMessage> messageConverter(EntryRowData[] datas, long id) {
try { List<FlatMessage> flatMessages = new ArrayList<>();
if (message == null) { for (EntryRowData entryRowData : datas) {
return null; CanalEntry.Entry entry = entryRowData.entry;
} CanalEntry.RowChange rowChange = entryRowData.rowChange;
// build flatMessage
CanalEntry.EventType eventType = rowChange.getEventType();
FlatMessage flatMessage = new FlatMessage(id);
flatMessages.add(flatMessage);
flatMessage.setDatabase(entry.getHeader().getSchemaName());
flatMessage.setTable(entry.getHeader().getTableName());
flatMessage.setIsDdl(rowChange.getIsDdl());
flatMessage.setType(eventType.toString());
flatMessage.setEs(entry.getHeader().getExecuteTime());
flatMessage.setTs(System.currentTimeMillis());
flatMessage.setSql(rowChange.getSql());
List<FlatMessage> flatMessages = new ArrayList<>(); if (!rowChange.getIsDdl()) {
List<CanalEntry.Entry> entrys = null; Map<String, Integer> sqlType = new LinkedHashMap<>();
if (message.isRaw()) { Map<String, String> mysqlType = new LinkedHashMap<>();
List<ByteString> rawEntries = message.getRawEntries(); List<Map<String, String>> data = new ArrayList<>();
entrys = new ArrayList<CanalEntry.Entry>(rawEntries.size()); List<Map<String, String>> old = new ArrayList<>();
for (ByteString byteString : rawEntries) {
CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
entrys.add(entry);
}
} else {
entrys = message.getEntries();
}
for (CanalEntry.Entry entry : entrys) { Set<String> updateSet = new HashSet<>();
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN boolean hasInitPkNames = false;
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
continue; if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
} && eventType != CanalEntry.EventType.DELETE) {
continue;
}
CanalEntry.RowChange rowChange; Map<String, String> row = new LinkedHashMap<>();
try { List<CanalEntry.Column> columns;
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(); if (eventType == CanalEntry.EventType.DELETE) {
columns = rowData.getBeforeColumnsList();
} else {
columns = rowData.getAfterColumnsList();
}
FlatMessage flatMessage = new FlatMessage(message.getId()); for (CanalEntry.Column column : columns) {
flatMessages.add(flatMessage); if (!hasInitPkNames && column.getIsKey()) {
flatMessage.setDatabase(entry.getHeader().getSchemaName()); flatMessage.addPkName(column.getName());
flatMessage.setTable(entry.getHeader().getTableName());
flatMessage.setIsDdl(rowChange.getIsDdl());
flatMessage.setType(eventType.toString());
flatMessage.setEs(entry.getHeader().getExecuteTime());
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<>();
boolean hasInitPkNames = false;
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
&& eventType != CanalEntry.EventType.DELETE) {
continue;
} }
sqlType.put(column.getName(), column.getSqlType());
Map<String, String> row = new LinkedHashMap<>(); mysqlType.put(column.getName(), column.getMysqlType());
List<CanalEntry.Column> columns; if (column.getIsNull()) {
row.put(column.getName(), null);
if (eventType == CanalEntry.EventType.DELETE) {
columns = rowData.getBeforeColumnsList();
} else { } else {
columns = rowData.getAfterColumnsList(); row.put(column.getName(), column.getValue());
} }
// 获取update为true的字段
for (CanalEntry.Column column : columns) { if (column.getUpdated()) {
if (!hasInitPkNames && column.getIsKey()) { updateSet.add(column.getName());
flatMessage.addPkName(column.getName());
}
sqlType.put(column.getName(), column.getSqlType());
mysqlType.put(column.getName(), column.getMysqlType());
if (column.getIsNull()) {
row.put(column.getName(), null);
} else {
row.put(column.getName(), column.getValue());
}
// 获取update为true的字段
if (column.getUpdated()) {
updateSet.add(column.getName());
}
} }
}
hasInitPkNames = true; hasInitPkNames = true;
if (!row.isEmpty()) { if (!row.isEmpty()) {
data.add(row); data.add(row);
} }
if (eventType == CanalEntry.EventType.UPDATE) { if (eventType == CanalEntry.EventType.UPDATE) {
Map<String, String> rowOld = new LinkedHashMap<>(); Map<String, String> rowOld = new LinkedHashMap<>();
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
if (updateSet.contains(column.getName())) { if (updateSet.contains(column.getName())) {
if (column.getIsNull()) { if (column.getIsNull()) {
rowOld.put(column.getName(), null); rowOld.put(column.getName(), null);
} else { } else {
rowOld.put(column.getName(), column.getValue()); rowOld.put(column.getName(), column.getValue());
}
} }
} }
// update操作将记录修改前的值 }
if (!rowOld.isEmpty()) { // update操作将记录修改前的值
old.add(rowOld); if (!rowOld.isEmpty()) {
} old.add(rowOld);
} }
} }
if (!sqlType.isEmpty()) { }
flatMessage.setSqlType(sqlType); if (!sqlType.isEmpty()) {
} flatMessage.setSqlType(sqlType);
if (!mysqlType.isEmpty()) { }
flatMessage.setMysqlType(mysqlType); if (!mysqlType.isEmpty()) {
} flatMessage.setMysqlType(mysqlType);
if (!data.isEmpty()) { }
flatMessage.setData(data); if (!data.isEmpty()) {
} flatMessage.setData(data);
if (!old.isEmpty()) { }
flatMessage.setOld(old); if (!old.isEmpty()) {
} flatMessage.setOld(old);
} }
} }
return flatMessages;
} catch (Exception e) {
throw new RuntimeException(e);
} }
return flatMessages;
} }
/** /**
@@ -616,4 +635,10 @@ public class MQMessageUtils {
public AviaterRegexFilter schemaRegexFilter; public AviaterRegexFilter schemaRegexFilter;
public AviaterRegexFilter tableRegexFilter; public AviaterRegexFilter tableRegexFilter;
} }
public static class EntryRowData {
public Entry entry;
public RowChange rowChange;
}
} }

View File

@@ -30,14 +30,24 @@ public class MQProperties {
private String accessChannel = null; private String accessChannel = null;
private String customizedTraceTopic = null; private String customizedTraceTopic = null;
private String namespace = ""; private String namespace = "";
private boolean kerberosEnable = false; // kafka集群是否启动Kerberos认证 // kafka集群是否启动Kerberos认证
private String kerberosKrb5FilePath = ""; // 启动Kerberos认证时配置为krb5.conf文件的路径 private boolean kerberosEnable = false;
private String kerberosJaasFilePath = ""; // 启动Kerberos认证时配置为jaas.conf文件的路径 // 启动Kerberos认证时配置为krb5.conf文件的路径
private String username = ""; // rabbitmq 账号 private String kerberosKrb5FilePath = "";
private String password = ""; // rabbitmq 密码 // 启动Kerberos认证时配置为jaas.conf文件的路径
private String vhost = ""; // rabbitmq 密码 private String kerberosJaasFilePath = "";
private long aliyunUID = 0; // aliyun 用户ID rabbitmq 阿里云需要使用 // rabbitmq 账号
private String exchange = ""; // rabbitmq 交换机 private String username = "";
// rabbitmq 密码
private String password = "";
// rabbitmq 密码
private String vhost = "";
// aliyun 用户ID rabbitmq 阿里云需要使用
private long aliyunUID = 0;
// rabbitmq 交换机
private String exchange = "";
// 消息发送的并行度
private int parallelThreadSize = 8;
public static class CanalDestination { public static class CanalDestination {
@@ -321,17 +331,28 @@ public class MQProperties {
this.exchange = exchange; this.exchange = exchange;
} }
public int getParallelThreadSize() {
return parallelThreadSize;
}
public void setParallelThreadSize(int parallelThreadSize) {
this.parallelThreadSize = parallelThreadSize;
}
@Override @Override
public String toString() { public String toString() {
return "MQProperties{" + "servers='" + servers + '\'' + ", retries=" + retries + ", batchSize=" + batchSize return "MQProperties [servers=" + servers + ", retries=" + retries + ", batchSize=" + batchSize + ", lingerMs="
+ ", lingerMs=" + lingerMs + ", maxRequestSize=" + maxRequestSize + ", bufferMemory=" + bufferMemory + lingerMs + ", maxRequestSize=" + maxRequestSize + ", bufferMemory=" + bufferMemory
+ ", filterTransactionEntry=" + filterTransactionEntry + ", producerGroup='" + producerGroup + '\'' + ", filterTransactionEntry=" + filterTransactionEntry + ", producerGroup=" + producerGroup
+ ", canalBatchSize=" + canalBatchSize + ", canalGetTimeout=" + canalGetTimeout + ", flatMessage=" + ", canalBatchSize=" + canalBatchSize + ", canalGetTimeout=" + canalGetTimeout + ", flatMessage="
+ flatMessage + ", compressionType='" + compressionType + '\'' + ", acks='" + acks + '\'' + flatMessage + ", compressionType=" + compressionType + ", acks=" + acks + ", aliyunAccessKey="
+ ", aliyunAccessKey='" + aliyunAccessKey + '\'' + ", aliyunSecretKey='" + aliyunSecretKey + '\'' + aliyunAccessKey + ", aliyunSecretKey=" + aliyunSecretKey + ", properties=" + properties
+ ", properties=" + properties + ", enableMessageTrace=" + enableMessageTrace + ", accessChannel='" + ", enableMessageTrace=" + enableMessageTrace + ", accessChannel=" + accessChannel
+ accessChannel + '\'' + ", customizedTraceTopic='" + customizedTraceTopic + '\'' + ", namespace='" + ", customizedTraceTopic=" + customizedTraceTopic + ", namespace=" + namespace + ", kerberosEnable="
+ namespace + '\'' + ", kerberosEnable='" + kerberosEnable + '\'' + ", kerberosKrb5FilePath='" + kerberosEnable + ", kerberosKrb5FilePath=" + kerberosKrb5FilePath + ", kerberosJaasFilePath="
+ kerberosKrb5FilePath + '\'' + ", kerberosJaasFilePath='" + kerberosJaasFilePath + '\'' + '}'; + kerberosJaasFilePath + ", username=" + username + ", password=" + password + ", vhost=" + vhost
+ ", aliyunUID=" + aliyunUID + ", exchange=" + exchange + ", parallelThreadSize=" + parallelThreadSize
+ "]";
} }
} }

View File

@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@@ -18,8 +19,12 @@ import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.common.AbstractMQProducer;
import com.alibaba.otter.canal.common.CanalMessageSerializer;
import com.alibaba.otter.canal.common.MQMessageUtils; import com.alibaba.otter.canal.common.MQMessageUtils;
import com.alibaba.otter.canal.common.MQMessageUtils.EntryRowData;
import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.common.MQProperties;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.spi.CanalMQProducer; import com.alibaba.otter.canal.spi.CanalMQProducer;
@@ -30,16 +35,17 @@ import com.alibaba.otter.canal.spi.CanalMQProducer;
* @author machengyuan 2018-6-11 下午05:30:49 * @author machengyuan 2018-6-11 下午05:30:49
* @version 1.0.0 * @version 1.0.0
*/ */
public class CanalKafkaProducer implements CanalMQProducer { public class CanalKafkaProducer extends AbstractMQProducer implements CanalMQProducer {
private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class);
private Producer<String, Message> producer; private Producer<String, byte[]> producer;
private Producer<String, String> producer2; // 用于扁平message的数据投递 private MQProperties kafkaProperties;
private MQProperties kafkaProperties;
@Override @Override
public void init(MQProperties kafkaProperties) { public void init(MQProperties kafkaProperties) {
super.init(kafkaProperties);
this.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());
@@ -73,13 +79,8 @@ public class CanalKafkaProducer implements CanalMQProducer {
} }
} }
if (!kafkaProperties.getFlatMessage()) { properties.put("value.serializer", KafkaMessageSerializer.class.getName());
properties.put("value.serializer", MessageSerializer.class.getName()); producer = new KafkaProducer<String, byte[]>(properties);
producer = new KafkaProducer<String, Message>(properties);
} else {
properties.put("value.serializer", StringSerializer.class.getName());
producer2 = new KafkaProducer<String, String>(properties);
}
} }
@Override @Override
@@ -89,133 +90,149 @@ public class CanalKafkaProducer implements CanalMQProducer {
if (producer != null) { if (producer != null) {
producer.close(); 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 {
logger.info("## kafka producer is down."); logger.info("## kafka producer is down.");
} }
super.stop();
} }
@Override @Override
public void send(MQProperties.CanalDestination canalDestination, Message message, Callback callback) { public void send(MQProperties.CanalDestination canalDestination, Message message, Callback callback) {
ExecutorTemplate template = new ExecutorTemplate(executor);
boolean flat = kafkaProperties.getFlatMessage();
try { try {
List result = null;
if (!StringUtils.isEmpty(canalDestination.getDynamicTopic())) { if (!StringUtils.isEmpty(canalDestination.getDynamicTopic())) {
// 动态topic // 动态topic路由计算,只是基于schema/table,不涉及proto数据反序列化
Map<String, Message> messageMap = MQMessageUtils.messageTopics(message, Map<String, Message> messageMap = MQMessageUtils.messageTopics(message,
canalDestination.getTopic(), canalDestination.getTopic(),
canalDestination.getDynamicTopic()); canalDestination.getDynamicTopic());
// 针对不同的topic,引入多线程提升效率
for (Map.Entry<String, Message> entry : messageMap.entrySet()) { for (Map.Entry<String, Message> entry : messageMap.entrySet()) {
String topicName = entry.getKey(); //.replace('.', '_'); final String topicName = entry.getKey().replace('.', '_');
Message messageSub = entry.getValue(); final Message messageSub = entry.getValue();
if (logger.isDebugEnabled()) { template.submit(new Callable() {
logger.debug("## Send message to kafka topic: " + topicName);
} @Override
send(canalDestination, topicName, messageSub); public List<Future> call() throws Exception {
try {
return send(canalDestination, topicName, messageSub, flat);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} }
result = template.waitForResult();
} else { } else {
send(canalDestination, canalDestination.getTopic(), message); result = new ArrayList();
List<Future> futures = send(canalDestination, canalDestination.getTopic(), message, flat);
result.add(futures);
} }
// 一个批次的所有topic和分区的队列,都采用异步的模式进行多线程批量发送
// 最后在集结点进行flush等待,确保所有数据都写出成功
// 注意:kafka的异步模式如果要保证顺序性,需要设置max.in.flight.requests.per.connection=1,确保在网络异常重试时有排他性
producer.flush();
// flush操作也有可能是发送失败,这里需要异步关注一下发送结果,针对有异常的直接出发rollback
for (Object obj : result) {
List<Future> futures = (List<Future>) obj;
for (Future future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
callback.commit(); callback.commit();
} catch (Throwable e) { } catch (Throwable e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
callback.rollback(); callback.rollback();
} finally {
template.clear();
} }
} }
private void send(MQProperties.CanalDestination canalDestination, String topicName, Message message) private List<Future> send(MQProperties.CanalDestination canalDestination, String topicName, Message message,
throws Exception { boolean flat) throws Exception {
if (!kafkaProperties.getFlatMessage()) { List<ProducerRecord<String, byte[]>> records = new ArrayList<ProducerRecord<String, byte[]>>();
List<ProducerRecord> records = new ArrayList<ProducerRecord>(); if (!flat) {
if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) { if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) {
Message[] messages = MQMessageUtils.messagePartition(message, // 并发构造
EntryRowData[] datas = MQMessageUtils.buildMessageData(message, executor);
// 串行分区
Message[] messages = MQMessageUtils.messagePartition(datas,
message.getId(),
canalDestination.getPartitionsNum(), canalDestination.getPartitionsNum(),
canalDestination.getPartitionHash()); canalDestination.getPartitionHash());
int length = messages.length; int length = messages.length;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
Message messagePartition = messages[i]; Message messagePartition = messages[i];
if (messagePartition != null) { if (messagePartition != null) {
records.add(new ProducerRecord<String, Message>(topicName, i, null, messagePartition)); records.add(new ProducerRecord<String, byte[]>(topicName,
i,
null,
CanalMessageSerializer.serializer(messagePartition,
kafkaProperties.isFilterTransactionEntry())));
} }
} }
} else { } else {
final int partition = canalDestination.getPartition() != null ? canalDestination.getPartition() : 0; final int partition = canalDestination.getPartition() != null ? canalDestination.getPartition() : 0;
records.add(new ProducerRecord<String, Message>(topicName, partition, null, message)); records.add(new ProducerRecord<String, byte[]>(topicName,
partition,
null,
CanalMessageSerializer.serializer(message, kafkaProperties.isFilterTransactionEntry())));
} }
produce(topicName, records, false);
} else { } else {
// 发送扁平数据json // 发送扁平数据json
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(message); // 并发构造
List<ProducerRecord> records = new ArrayList<ProducerRecord>(); EntryRowData[] datas = MQMessageUtils.buildMessageData(message, executor);
if (flatMessages != null) { // 串行分区
for (FlatMessage flatMessage : flatMessages) { List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, message.getId());
if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) { for (FlatMessage flatMessage : flatMessages) {
FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage, if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) {
canalDestination.getPartitionsNum(), FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage,
canalDestination.getPartitionHash()); canalDestination.getPartitionsNum(),
int length = partitionFlatMessage.length; canalDestination.getPartitionHash());
for (int i = 0; i < length; i++) { int length = partitionFlatMessage.length;
FlatMessage flatMessagePart = partitionFlatMessage[i]; for (int i = 0; i < length; i++) {
if (flatMessagePart != null) { FlatMessage flatMessagePart = partitionFlatMessage[i];
records.add(new ProducerRecord<String, String>(topicName, if (flatMessagePart != null) {
i, records.add(new ProducerRecord<String, byte[]>(topicName,
null, i,
JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue))); null,
} JSON.toJSONBytes(flatMessagePart, SerializerFeature.WriteMapNullValue)));
} }
} else {
final int partition = canalDestination.getPartition() != null ? canalDestination.getPartition() : 0;
records.add(new ProducerRecord<String, String>(topicName,
partition,
null,
JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)));
} }
} else {
// 每条记录需要flush final int partition = canalDestination.getPartition() != null ? canalDestination.getPartition() : 0;
produce(topicName, records, true); records.add(new ProducerRecord<String, byte[]>(topicName,
records.clear(); partition,
null,
JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue)));
} }
} }
} }
return produce(topicName, records, flat);
} }
private void produce(String topicName, List<ProducerRecord> records, boolean flatMessage) { private List<Future> produce(String topicName, List<ProducerRecord<String, byte[]>> records, boolean flatMessage) {
Producer producerTmp = null;
if (flatMessage) {
producerTmp = producer2;
} else {
producerTmp = producer;
}
List<Future> futures = new ArrayList<Future>(); List<Future> futures = new ArrayList<Future>();
try { // 异步发送,因为在partition hash的时候已经按照每个分区合并了消息,走到这一步不需要考虑单个分区内的顺序问题
// 异步发送,因为在partition hash的时候已经按照每个分区合并了消息,走到这一步不需要考虑单个分区内的顺序问题 for (ProducerRecord record : records) {
for (ProducerRecord record : records) { futures.add(producer.send(record));
futures.add(producerTmp.send(record));
}
} finally {
if (logger.isDebugEnabled()) {
for (ProducerRecord record : records) {
logger.debug("Send message to kafka topic: [{}], packet: {}", topicName, record.toString());
}
}
// 批量刷出
producerTmp.flush();
// flush操作也有可能是发送失败,这里需要异步关注一下发送结果,针对有异常的直接出发rollback
for (Future future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
} }
return futures;
} }
} }

View File

@@ -0,0 +1,28 @@
package com.alibaba.otter.canal.kafka;
import java.util.Map;
import org.apache.kafka.common.serialization.Serializer;
/**
* Kafka Message类的序列化
*
* @author machengyuan 2018-6-11 下午05:30:49
* @version 1.0.0
*/
public class KafkaMessageSerializer implements Serializer<byte[]> {
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
@Override
public byte[] serialize(String topic, byte[] data) {
return data;
}
@Override
public void close() {
// nothing to do
}
}

View File

@@ -1,39 +0,0 @@
package com.alibaba.otter.canal.kafka;
import java.util.Map;
import org.apache.commons.lang.BooleanUtils;
import org.apache.kafka.common.serialization.Serializer;
import com.alibaba.otter.canal.common.CanalMessageSerializer;
import com.alibaba.otter.canal.protocol.Message;
/**
* Kafka Message类的序列化
*
* @author machengyuan 2018-6-11 下午05:30:49
* @version 1.0.0
*/
public class MessageSerializer implements Serializer<Message> {
private boolean filterTransactionEntry = false;
public MessageSerializer(){
this.filterTransactionEntry = BooleanUtils.toBoolean(System.getProperty("canal.instance.filter.transaction.entry",
"false"));
}
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
@Override
public byte[] serialize(String topic, Message data) {
return CanalMessageSerializer.serializer(data, filterTransactionEntry);
}
@Override
public void close() {
// nothing to do
}
}

View File

@@ -1,9 +1,20 @@
package com.alibaba.otter.canal.rabbitmq; package com.alibaba.otter.canal.rabbitmq;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.common.AbstractMQProducer;
import com.alibaba.otter.canal.common.CanalMessageSerializer; import com.alibaba.otter.canal.common.CanalMessageSerializer;
import com.alibaba.otter.canal.common.MQMessageUtils; import com.alibaba.otter.canal.common.MQMessageUtils;
import com.alibaba.otter.canal.common.MQMessageUtils.EntryRowData;
import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.common.MQProperties;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
@@ -13,16 +24,7 @@ import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection; import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.ConnectionFactory;
import org.apache.commons.lang.StringUtils; public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQProducer {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class CanalRabbitMQProducer implements CanalMQProducer {
private static final Logger logger = LoggerFactory.getLogger(CanalRabbitMQProducer.class); private static final Logger logger = LoggerFactory.getLogger(CanalRabbitMQProducer.class);
private MQProperties mqProperties; private MQProperties mqProperties;
@@ -31,6 +33,7 @@ public class CanalRabbitMQProducer implements CanalMQProducer {
@Override @Override
public void init(MQProperties mqProperties) { public void init(MQProperties mqProperties) {
super.init(mqProperties);
this.mqProperties = mqProperties; this.mqProperties = mqProperties;
ConnectionFactory factory = new ConnectionFactory(); ConnectionFactory factory = new ConnectionFactory();
factory.setHost(mqProperties.getServers()); factory.setHost(mqProperties.getServers());
@@ -54,13 +57,14 @@ public class CanalRabbitMQProducer implements CanalMQProducer {
} }
@Override @Override
public void send(MQProperties.CanalDestination canalDestination, Message message, public void send(MQProperties.CanalDestination canalDestination, Message message, Callback callback)
Callback callback) throws IOException { throws IOException {
try { try {
if (!StringUtils.isEmpty(canalDestination.getDynamicTopic())) { if (!StringUtils.isEmpty(canalDestination.getDynamicTopic())) {
// 动态topic // 动态topic
Map<String, com.alibaba.otter.canal.protocol.Message> messageMap = MQMessageUtils Map<String, com.alibaba.otter.canal.protocol.Message> messageMap = MQMessageUtils.messageTopics(message,
.messageTopics(message, canalDestination.getTopic(), canalDestination.getDynamicTopic()); canalDestination.getTopic(),
canalDestination.getDynamicTopic());
for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) { for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) {
String topicName = entry.getKey().replace('.', '_'); String topicName = entry.getKey().replace('.', '_');
@@ -76,8 +80,8 @@ public class CanalRabbitMQProducer implements CanalMQProducer {
} }
} }
private void send(MQProperties.CanalDestination canalDestination, String topicName, private void send(MQProperties.CanalDestination canalDestination, String topicName, Message messageSub)
Message messageSub) throws Exception { throws Exception {
if (!mqProperties.getFlatMessage()) { if (!mqProperties.getFlatMessage()) {
byte[] message = CanalMessageSerializer.serializer(messageSub, mqProperties.isFilterTransactionEntry()); byte[] message = CanalMessageSerializer.serializer(messageSub, mqProperties.isFilterTransactionEntry());
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@@ -85,10 +89,13 @@ public class CanalRabbitMQProducer implements CanalMQProducer {
} }
sendMessage(topicName, message); sendMessage(topicName, message);
} else { } else {
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(messageSub); // 并发构造
EntryRowData[] datas = MQMessageUtils.buildMessageData(messageSub, executor);
// 串行分区
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
if (flatMessages != null) { if (flatMessages != null) {
for (FlatMessage flatMessage : flatMessages) { for (FlatMessage flatMessage : flatMessages) {
byte[] message = JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue).getBytes(); byte[] message = JSON.toJSONBytes(flatMessage, SerializerFeature.WriteMapNullValue);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("send message:{} to destination:{}", logger.debug("send message:{} to destination:{}",
message, message,
@@ -115,5 +122,7 @@ public class CanalRabbitMQProducer implements CanalMQProducer {
} catch (IOException | TimeoutException ex) { } catch (IOException | TimeoutException ex) {
throw new CanalServerException("Stop RabbitMQ producer error", ex); throw new CanalServerException("Stop RabbitMQ producer error", ex);
} }
super.stop();
} }
} }

View File

@@ -7,7 +7,6 @@ import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.AccessChannel;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.MessageQueueSelector;
@@ -15,20 +14,22 @@ import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.common.AbstractMQProducer;
import com.alibaba.otter.canal.common.CanalMessageSerializer; import com.alibaba.otter.canal.common.CanalMessageSerializer;
import com.alibaba.otter.canal.common.MQMessageUtils; import com.alibaba.otter.canal.common.MQMessageUtils;
import com.alibaba.otter.canal.common.MQMessageUtils.EntryRowData;
import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.common.MQProperties;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.server.exception.CanalServerException;
import com.alibaba.otter.canal.spi.CanalMQProducer; import com.alibaba.otter.canal.spi.CanalMQProducer;
public class CanalRocketMQProducer implements CanalMQProducer { public class CanalRocketMQProducer extends AbstractMQProducer implements CanalMQProducer {
private static final Logger logger = LoggerFactory.getLogger(CanalRocketMQProducer.class); private static final Logger logger = LoggerFactory.getLogger(CanalRocketMQProducer.class);
private DefaultMQProducer defaultMQProducer; private DefaultMQProducer defaultMQProducer;
@@ -37,6 +38,7 @@ public class CanalRocketMQProducer implements CanalMQProducer {
@Override @Override
public void init(MQProperties rocketMQProperties) { public void init(MQProperties rocketMQProperties) {
super.init(rocketMQProperties);
this.mqProperties = rocketMQProperties; this.mqProperties = rocketMQProperties;
RPCHook rpcHook = null; RPCHook rpcHook = null;
if (rocketMQProperties.getAliyunAccessKey().length() > 0 if (rocketMQProperties.getAliyunAccessKey().length() > 0
@@ -71,6 +73,7 @@ public class CanalRocketMQProducer implements CanalMQProducer {
@Override @Override
public void send(final MQProperties.CanalDestination destination, com.alibaba.otter.canal.protocol.Message data, public void send(final MQProperties.CanalDestination destination, com.alibaba.otter.canal.protocol.Message data,
Callback callback) { Callback callback) {
ExecutorTemplate template = new ExecutorTemplate(executor);
try { try {
if (!StringUtils.isEmpty(destination.getDynamicTopic())) { if (!StringUtils.isEmpty(destination.getDynamicTopic())) {
// 动态topic // 动态topic
@@ -81,66 +84,94 @@ public class CanalRocketMQProducer implements CanalMQProducer {
for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) { for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) {
String topicName = entry.getKey().replace('.', '_'); String topicName = entry.getKey().replace('.', '_');
com.alibaba.otter.canal.protocol.Message messageSub = entry.getValue(); com.alibaba.otter.canal.protocol.Message messageSub = entry.getValue();
send(destination, topicName, messageSub); template.submit(new Runnable() {
@Override
public void run() {
try {
send(destination, topicName, messageSub);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} }
template.waitForResult();
} else { } else {
send(destination, destination.getTopic(), data); send(destination, destination.getTopic(), data);
} }
callback.commit(); callback.commit();
} catch (Throwable e) { } catch (Throwable e) {
logger.error(e.getMessage(), e);
callback.rollback(); callback.rollback();
} finally {
template.clear();
} }
} }
public void send(final MQProperties.CanalDestination destination, String topicName, public void send(final MQProperties.CanalDestination destination, String topicName,
com.alibaba.otter.canal.protocol.Message data) throws Exception { com.alibaba.otter.canal.protocol.Message message) throws Exception {
if (!mqProperties.getFlatMessage()) { if (!mqProperties.getFlatMessage()) {
try { if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) {
if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) { // 并发构造
com.alibaba.otter.canal.protocol.Message[] messages = MQMessageUtils.messagePartition(data, EntryRowData[] datas = MQMessageUtils.buildMessageData(message, executor);
destination.getPartitionsNum(), // 串行分区
destination.getPartitionHash()); com.alibaba.otter.canal.protocol.Message[] messages = MQMessageUtils.messagePartition(datas,
int length = messages.length; message.getId(),
for (int i = 0; i < length; i++) { destination.getPartitionsNum(),
com.alibaba.otter.canal.protocol.Message dataPartition = messages[i]; destination.getPartitionHash());
if (dataPartition != null) { int length = messages.length;
if (logger.isDebugEnabled()) {
logger.debug("flatMessagePart: {}, partition: {}", ExecutorTemplate template = new ExecutorTemplate(executor);
JSON.toJSONString(dataPartition, SerializerFeature.WriteMapNullValue), for (int i = 0; i < length; i++) {
i); com.alibaba.otter.canal.protocol.Message dataPartition = messages[i];
if (dataPartition != null) {
final int index = i;
template.submit(new Runnable() {
@Override
public void run() {
try {
if (logger.isDebugEnabled()) {
logger.debug("flatMessagePart: {}, partition: {}",
JSON.toJSONString(dataPartition, SerializerFeature.WriteMapNullValue),
index);
}
Message data = new Message(topicName,
CanalMessageSerializer.serializer(dataPartition,
mqProperties.isFilterTransactionEntry()));
sendMessage(data, index);
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
final int index = i; });
try {
Message message = new Message(topicName,
CanalMessageSerializer.serializer(dataPartition,
mqProperties.isFilterTransactionEntry()));
sendMessage(message, index);
} catch (Exception e) {
logger.error("send flat message to hashed partition error", e);
throw e;
}
}
} }
} else {
final int partition = destination.getPartition() != null ? destination.getPartition() : 0;
Message message = new Message(topicName, CanalMessageSerializer.serializer(data,
mqProperties.isFilterTransactionEntry()));
if (logger.isDebugEnabled()) {
logger.debug("send message:{} to destination:{}, partition: {}",
message,
destination.getCanalDestination(),
partition);
}
sendMessage(message, partition);
} }
} catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { // 等所有分片发送完毕
logger.error("Send message error!", e); template.waitForResult();
throw e; } else {
final int partition = destination.getPartition() != null ? destination.getPartition() : 0;
Message data = new Message(topicName, CanalMessageSerializer.serializer(message,
mqProperties.isFilterTransactionEntry()));
if (logger.isDebugEnabled()) {
logger.debug("send message:{} to destination:{}, partition: {}",
message,
destination.getCanalDestination(),
partition);
}
sendMessage(data, partition);
} }
} else { } else {
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(data); // 并发构造
EntryRowData[] datas = MQMessageUtils.buildMessageData(message, executor);
// 串行分区
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, message.getId());
if (flatMessages != null) { if (flatMessages != null) {
for (FlatMessage flatMessage : flatMessages) { for (FlatMessage flatMessage : flatMessages) {
ExecutorTemplate template = new ExecutorTemplate(executor);
if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) { if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) {
FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage, FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage,
destination.getPartitionsNum(), destination.getPartitionsNum(),
@@ -155,16 +186,24 @@ public class CanalRocketMQProducer implements CanalMQProducer {
i); i);
} }
final int index = i; final int index = i;
try { template.submit(new Runnable() {
Message message = new Message(topicName, JSON.toJSONString(flatMessagePart,
SerializerFeature.WriteMapNullValue).getBytes()); @Override
sendMessage(message, index); public void run() {
} catch (Exception e) { try {
logger.error("send flat message to hashed partition error", e); Message data = new Message(topicName, JSON.toJSONBytes(flatMessagePart,
throw e; SerializerFeature.WriteMapNullValue));
} sendMessage(data, index);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} }
} }
// 批量等所有分区的结果
template.waitForResult();
} else { } else {
try { try {
final int partition = destination.getPartition() != null ? destination.getPartition() : 0; final int partition = destination.getPartition() != null ? destination.getPartition() : 0;
@@ -174,12 +213,11 @@ public class CanalRocketMQProducer implements CanalMQProducer {
topicName, topicName,
partition); partition);
} }
Message message = new Message(topicName, JSON.toJSONString(flatMessage, Message data = new Message(topicName, JSON.toJSONBytes(flatMessage,
SerializerFeature.WriteMapNullValue).getBytes()); SerializerFeature.WriteMapNullValue));
sendMessage(message, partition); sendMessage(data, partition);
} catch (Exception e) { } catch (Exception e) {
logger.error("send flat message to fixed partition error", e); throw new RuntimeException(e);
throw e;
} }
} }
} }
@@ -212,5 +250,6 @@ public class CanalRocketMQProducer implements CanalMQProducer {
public void stop() { public void stop() {
logger.info("## Stop RocketMQ producer##"); logger.info("## Stop RocketMQ producer##");
this.defaultMQProducer.shutdown(); this.defaultMQProducer.shutdown();
super.stop();
} }
} }