diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/ExecutorTemplate.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/ExecutorTemplate.java
new file mode 100644
index 00000000..dfe68069
--- /dev/null
+++ b/common/src/main/java/com/alibaba/otter/canal/common/utils/ExecutorTemplate.java
@@ -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中好多地方都写多线程,比较多的都是重复的逻辑代码,抽象一下做个模板把
+ *
+ *
+ * 示例代码:
+ * ExecutorTemplate template = new ExecutorTemplate(executor);
+ * ...
+ * try {
+ * for ( ....) {
+ * template.submit(new Runnable() {})
+ * }
+ *
+ * List> result = template.waitForResult();
+ * // do result
+ * } finally {
+ * template.clear();
+ * }
+ *
+ * 注意:该模板工程,不支持多业务并发调用,会出现数据混乱
+ *
+ */
+public class ExecutorTemplate {
+
+ private volatile ThreadPoolExecutor executor = null;
+ private volatile List futures = null;
+
+ public ExecutorTemplate(ThreadPoolExecutor executor){
+ this.futures = Collections.synchronizedList(new ArrayList());
+ this.executor = executor;
+ }
+
+ public void submit(Runnable task) {
+ Future future = executor.submit(task, null);
+ futures.add(future);
+ check(future);
+ }
+
+ public void submit(Callable 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();
+ }
+
+}
diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalStarter.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalStarter.java
index 38e86221..f5c3e14f 100644
--- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalStarter.java
+++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalStarter.java
@@ -10,8 +10,8 @@ import com.alibaba.otter.canal.admin.netty.CanalAdminWithNetty;
import com.alibaba.otter.canal.common.MQProperties;
import com.alibaba.otter.canal.deployer.admin.CanalAdminController;
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.rocketmq.CanalRocketMQProducer;
import com.alibaba.otter.canal.server.CanalMQStarter;
import com.alibaba.otter.canal.spi.CanalMQProducer;
@@ -69,11 +69,15 @@ public class CanalStarter {
canalMQProducer = new CanalRabbitMQProducer();
}
+ MQProperties mqProperties = null;
if (canalMQProducer != null) {
+ mqProperties = buildMQProperties(properties);
// disable netty
System.setProperty(CanalConstants.CANAL_WITHOUT_NETTY, "true");
- // 设置为raw避免ByteString->Entry的二次解析
- System.setProperty("canal.instance.memory.rawEntry", "false");
+ if (mqProperties.getFlatMessage()) {
+ // 设置为raw避免ByteString->Entry的二次解析
+ System.setProperty("canal.instance.memory.rawEntry", "false");
+ }
}
logger.info("## start the canal server.");
@@ -99,7 +103,6 @@ public class CanalStarter {
if (canalMQProducer != null) {
canalMQStarter = new CanalMQStarter(canalMQProducer);
- MQProperties mqProperties = buildMQProperties(properties);
String destinations = CanalController.getProperty(properties, CanalConstants.CANAL_DESTINATIONS);
canalMQStarter.start(mqProperties, destinations);
controller.setCanalMQStarter(canalMQStarter);
diff --git a/server/src/main/java/com/alibaba/otter/canal/common/AbstractMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/common/AbstractMQProducer.java
new file mode 100644
index 00000000..a14b72c8
--- /dev/null
+++ b/server/src/main/java/com/alibaba/otter/canal/common/AbstractMQProducer.java
@@ -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(parallelThreadSize * 2),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ }
+
+ @Override
+ public void stop() {
+ executor.shutdownNow();
+ executor = null;
+ }
+
+}
diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQMessageUtils.java b/server/src/main/java/com/alibaba/otter/canal/common/MQMessageUtils.java
index 0073c186..7045a9d1 100644
--- a/server/src/main/java/com/alibaba/otter/canal/common/MQMessageUtils.java
+++ b/server/src/main/java/com/alibaba/otter/canal/common/MQMessageUtils.java
@@ -1,15 +1,18 @@
package com.alibaba.otter.canal.common;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ThreadPoolExecutor;
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.protocol.CanalEntry;
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 2019年9月29日 下午12:36:26
+ * @since 5.0.0
+ */
public class MQMessageUtils {
@SuppressWarnings("deprecation")
@@ -160,6 +167,67 @@ public class MQMessageUtils {
return messages;
}
+ /**
+ * 多线程构造message的rowChanged对象,比如为partition/flastMessage转化等处理
+ * 因为protobuf对象的序列化和反序列化是cpu密集型,串行执行会有代价
+ */
+ public static EntryRowData[] buildMessageData(Message message, ThreadPoolExecutor executor) {
+ ExecutorTemplate template = new ExecutorTemplate(executor);
+ if (message.isRaw()) {
+ List 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 分区
*
@@ -168,40 +236,20 @@ public class MQMessageUtils {
* @return 分区message数组
*/
@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) {
partitionsNum = 1;
}
Message[] partitionMessages = new Message[partitionsNum];
List[] partitionEntries = new List[partitionsNum];
for (int i = 0; i < partitionsNum; i++) {
- partitionEntries[i] = new ArrayList<>();
+ // 注意一下并发
+ partitionEntries[i] = Collections.synchronizedList(Lists.newArrayList());
}
- List entries;
- if (message.isRaw()) {
- List rawEntries = message.getRawEntries();
- 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);
- }
+ for (EntryRowData data : datas) {
+ CanalEntry.Entry entry = data.entry;
+ CanalEntry.RowChange rowChange = data.rowChange;
if (rowChange.getIsDdl()) {
partitionEntries[0].add(entry);
@@ -220,6 +268,10 @@ public class MQMessageUtils {
// tableHash not need split entry message
partitionEntries[pkHash].add(entry);
} else {
+ // build new entry
+ Entry.Builder builder = Entry.newBuilder(entry);
+ RowChange.Builder rowChangeBuilder = RowChange.newBuilder(rowChange);
+
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
int hashCode = database.hashCode();
CanalEntry.EventType eventType = rowChange.getEventType();
@@ -247,9 +299,7 @@ public class MQMessageUtils {
int pkHash = Math.abs(hashCode) % partitionsNum;
pkHash = Math.abs(pkHash);
- // build new entry
- Entry.Builder builder = Entry.newBuilder(entry);
- RowChange.Builder rowChangeBuilder = RowChange.newBuilder(rowChange);
+ // clear rowDatas
rowChangeBuilder.clearRowDatas();
rowChangeBuilder.addRowDatas(rowData);
builder.clearStoreValue();
@@ -267,7 +317,7 @@ public class MQMessageUtils {
for (int i = 0; i < partitionsNum; i++) {
List entriesTmp = partitionEntries[i];
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
* @return FlatMessage列表
*/
- public static List messageConverter(Message message) {
- try {
- if (message == null) {
- return null;
- }
+ public static List messageConverter(EntryRowData[] datas, long id) {
+ List flatMessages = new ArrayList<>();
+ for (EntryRowData entryRowData : datas) {
+ 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 flatMessages = new ArrayList<>();
- List entrys = null;
- if (message.isRaw()) {
- List rawEntries = message.getRawEntries();
- entrys = new ArrayList(rawEntries.size());
- for (ByteString byteString : rawEntries) {
- CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
- entrys.add(entry);
- }
- } else {
- entrys = message.getEntries();
- }
+ if (!rowChange.getIsDdl()) {
+ Map sqlType = new LinkedHashMap<>();
+ Map mysqlType = new LinkedHashMap<>();
+ List