From 03ee9c7ecf8120b5fe8d47ed2c0b3b64dc376e01 Mon Sep 17 00:00:00 2001 From: machey Date: Mon, 10 Dec 2018 00:09:49 +0800 Subject: [PATCH 01/21] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E4=B8=BB=E9=94=AEHash?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=AD=A3=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8Cpk=E9=80=9A=E9=85=8D=E7=AC=A6?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E8=87=AA=E5=8A=A8=E8=8E=B7=E5=8F=96=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/instance/core/CanalMQConfig.java | 34 +--- .../otter/canal/protocol/FlatMessage.java | 168 ++++++++++++------ .../otter/canal/common/MQProperties.java | 15 +- .../otter/canal/server/CanalMQStarter.java | 2 +- 4 files changed, 123 insertions(+), 96 deletions(-) diff --git a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java index c275f150..53c50742 100644 --- a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java +++ b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java @@ -1,16 +1,11 @@ package com.alibaba.otter.canal.instance.core; -import java.util.LinkedHashMap; -import java.util.Map; - public class CanalMQConfig { - private String topic; - private Integer partition; - private Integer partitionsNum; - private String partitionHash; - - private volatile Map partitionHashProperties; + private String topic; + private Integer partition; + private Integer partitionsNum; + private String partitionHash; public String getTopic() { return topic; @@ -44,25 +39,4 @@ public class CanalMQConfig { this.partitionHash = partitionHash; } - public Map getPartitionHashProperties() { - if (partitionHashProperties == null) { - synchronized (CanalMQConfig.class) { - if (partitionHashProperties == null) { - if (partitionHash != null) { - partitionHashProperties = new LinkedHashMap<>(); - String[] items = partitionHash.split(","); - for (String item : items) { - int i = item.indexOf(":"); - if (i > -1) { - String dbTable = item.substring(0, i).trim(); - String pk = item.substring(i + 1).trim(); - partitionHashProperties.put(dbTable, pk); - } - } - } - } - } - } - return partitionHashProperties; - } } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 94c539cb..568d689a 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -1,12 +1,10 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; import com.google.protobuf.ByteString; @@ -33,8 +31,7 @@ public class FlatMessage implements Serializable { private List> data; private List> old; - public FlatMessage(){ - } + private transient Message message; // 所属message public FlatMessage(long id){ this.id = id; @@ -136,6 +133,14 @@ public class FlatMessage implements Serializable { this.es = es; } + public Message getMessage() { + return message; + } + + public void setMessage(Message message) { + this.message = message; + } + /** * 将Message转换为FlatMessage * @@ -187,6 +192,7 @@ public class FlatMessage implements Serializable { flatMessage.setEs(entry.getHeader().getExecuteTime()); flatMessage.setTs(System.currentTimeMillis()); flatMessage.setSql(rowChange.getSql()); + flatMessage.setMessage(message); if (!rowChange.getIsDdl()) { Map sqlType = new LinkedHashMap<>(); @@ -269,70 +275,116 @@ public class FlatMessage implements Serializable { * * @param flatMessage flatMessage * @param partitionsNum 分区数量 - * @param pkHashConfig hash映射 + * @param pkHashConfigs hash映射 * @return 拆分后的flatMessage数组 */ - public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum, - Map pkHashConfig) { + public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum, String pkHashConfigs) { if (partitionsNum == null) { partitionsNum = 1; } FlatMessage[] partitionMessages = new FlatMessage[partitionsNum]; - String pk = pkHashConfig.get(flatMessage.getDatabase() + "." + flatMessage.getTable()); - if (pk == null || flatMessage.getIsDdl()) { + + if (flatMessage.getIsDdl()) { partitionMessages[0] = flatMessage; } else { if (flatMessage.getData() != null) { - int idx = 0; - for (Map row : flatMessage.getData()) { - Map o = null; - if (flatMessage.getOld() != null) { - o = flatMessage.getOld().get(idx); - } - String value; - // 如果old中有pk值说明主键有修改, 以旧的主键值hash为准 - if (o != null && o.containsKey(pk)) { - value = o.get(pk); - } else { - value = row.get(pk); - } - if (value == null) { - value = ""; - } - int hash = value.hashCode(); - int pkHash = Math.abs(hash) % partitionsNum; - // math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序 - pkHash = Math.abs(pkHash); + String database = flatMessage.getDatabase(); + String table = flatMessage.getTable(); - 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()); - flatMessageTmp.setEs(flatMessage.getEs()); - flatMessageTmp.setTs(flatMessage.getTs()); + String pk = null; + boolean isMatch = false; + + String[] pkHashConfigArray = StringUtils.split(pkHashConfigs, ","); + for (String pkHashConfig : pkHashConfigArray) { + int i = pkHashConfig.lastIndexOf("."); + if (!pkHashConfig.endsWith(".$pk$")) { + // 如果指定了主键 + pk = pkHashConfig.substring(i + 1); } - List> data = flatMessageTmp.getData(); - if (data == null) { - data = new ArrayList<>(); - flatMessageTmp.setData(data); + pkHashConfig = pkHashConfig.substring(0, i); + isMatch = Pattern.matches(pkHashConfig, database + "." + table); + if (isMatch) { + break; } - data.add(row); - if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) { - List> old = flatMessageTmp.getOld(); - if (old == null) { - old = new ArrayList<>(); - flatMessageTmp.setOld(old); + } + + if (!isMatch) { + // 如果都没有匹配,发送到第一个分区 + partitionMessages[0] = flatMessage; + } else { + if (pk == null) { + // 如果未指定主键(通配符主键),从原生message中取主键字段 + CanalEntry.Entry entry = flatMessage.getMessage().getEntries().get(0); + CanalEntry.RowChange rowChange; + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getIsKey()) { + pk = column.getName(); + break; + } + } + } + if (pk == null || !flatMessage.getData().get(0).containsKey(pk)) { + // 如果都没有匹配的主键,发送到第一个分区 + partitionMessages[0] = flatMessage; + } else { + int idx = 0; + for (Map row : flatMessage.getData()) { + Map o = null; + if (flatMessage.getOld() != null) { + o = flatMessage.getOld().get(idx); + } + String value; + // 如果old中有pk值说明主键有修改, 以旧的主键值hash为准 + if (o != null && o.containsKey(pk)) { + value = o.get(pk); + } else { + value = row.get(pk); + } + if (value == null) { + value = ""; + } + int hash = value.hashCode(); + int pkHash = Math.abs(hash) % partitionsNum; + // math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序 + pkHash = Math.abs(pkHash); + + 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()); + flatMessageTmp.setEs(flatMessage.getEs()); + flatMessageTmp.setTs(flatMessage.getTs()); + } + List> data = flatMessageTmp.getData(); + if (data == null) { + data = new ArrayList<>(); + flatMessageTmp.setData(data); + } + data.add(row); + if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) { + List> old = flatMessageTmp.getOld(); + if (old == null) { + old = new ArrayList<>(); + flatMessageTmp.setOld(old); + } + old.add(flatMessage.getOld().get(idx)); + } + idx++; } - old.add(flatMessage.getOld().get(idx)); } - idx++; } } } diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index a337ccc6..7e82f9c6 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -28,11 +28,11 @@ public class MQProperties { public static class CanalDestination { - private String canalDestination; - private String topic; - private Integer partition; - private Integer partitionsNum; - private Map partitionHash; + private String canalDestination; + private String topic; + private Integer partition; + private Integer partitionsNum; + private String partitionHash; public String getCanalDestination() { return canalDestination; @@ -66,11 +66,11 @@ public class MQProperties { this.partitionsNum = partitionsNum; } - public Map getPartitionHash() { + public String getPartitionHash() { return partitionHash; } - public void setPartitionHash(Map partitionHash) { + public void setPartitionHash(String partitionHash) { this.partitionHash = partitionHash; } } @@ -186,6 +186,7 @@ public class MQProperties { public void setAliyunSecretKey(String aliyunSecretKey) { this.aliyunSecretKey = aliyunSecretKey; } + public int getMaxRequestSize() { return maxRequestSize; } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index a283e23f..248e5af4 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -138,7 +138,7 @@ public class CanalMQStarter { canalDestination.setTopic(mqConfig.getTopic()); canalDestination.setPartition(mqConfig.getPartition()); canalDestination.setPartitionsNum(mqConfig.getPartitionsNum()); - canalDestination.setPartitionHash(mqConfig.getPartitionHashProperties()); + canalDestination.setPartitionHash(mqConfig.getPartitionHash()); canalServer.subscribe(clientIdentity); logger.info("## the MQ producer: {} is running now ......", destination); From 547cbf82c83eb4987e9568569788818fe1e7a806 Mon Sep 17 00:00:00 2001 From: machey Date: Mon, 10 Dec 2018 01:03:18 +0800 Subject: [PATCH 02/21] =?UTF-8?q?=E5=AF=B9=E5=8E=9F=E7=94=9Fmessage?= =?UTF-8?q?=E5=8F=91=E9=80=81mq=E7=9A=84=E5=88=86=E5=8C=BA=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/protocol/FlatMessage.java | 42 +++--- .../alibaba/otter/canal/protocol/Message.java | 113 +++++++++++++++++ .../otter/canal/kafka/CanalKafkaProducer.java | 30 +++-- .../canal/rocketmq/CanalRocketMQProducer.java | 120 ++++++++++++------ 4 files changed, 240 insertions(+), 65 deletions(-) diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 568d689a..05d8f795 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -14,24 +14,24 @@ import com.google.protobuf.ByteString; */ public class FlatMessage implements Serializable { - private static final long serialVersionUID = -3386650678735860050L; + private static final long serialVersionUID = -3386650678735860050L; - private long id; - private String database; - private String table; - private Boolean isDdl; - private String type; + private long id; + private String database; + private String table; + private Boolean isDdl; + private String type; // binlog executeTime - private Long es; + private Long es; // dml build timeStamp - private Long ts; - private String sql; - private Map sqlType; - private Map mysqlType; - private List> data; - private List> old; + private Long ts; + private String sql; + private Map sqlType; + private Map mysqlType; + private List> data; + private List> old; - private transient Message message; // 所属message + private transient CanalEntry.Entry entry; // 所属entry public FlatMessage(long id){ this.id = id; @@ -133,12 +133,12 @@ public class FlatMessage implements Serializable { this.es = es; } - public Message getMessage() { - return message; + public CanalEntry.Entry getEntry() { + return entry; } - public void setMessage(Message message) { - this.message = message; + public void setEntry(CanalEntry.Entry entry) { + this.entry = entry; } /** @@ -192,7 +192,7 @@ public class FlatMessage implements Serializable { flatMessage.setEs(entry.getHeader().getExecuteTime()); flatMessage.setTs(System.currentTimeMillis()); flatMessage.setSql(rowChange.getSql()); - flatMessage.setMessage(message); + flatMessage.setEntry(entry); if (!rowChange.getIsDdl()) { Map sqlType = new LinkedHashMap<>(); @@ -287,7 +287,7 @@ public class FlatMessage implements Serializable { if (flatMessage.getIsDdl()) { partitionMessages[0] = flatMessage; } else { - if (flatMessage.getData() != null) { + if (flatMessage.getData() != null && !flatMessage.getData().isEmpty()) { String database = flatMessage.getDatabase(); String table = flatMessage.getTable(); @@ -314,7 +314,7 @@ public class FlatMessage implements Serializable { } else { if (pk == null) { // 如果未指定主键(通配符主键),从原生message中取主键字段 - CanalEntry.Entry entry = flatMessage.getMessage().getEntries().get(0); + CanalEntry.Entry entry = flatMessage.getEntry(); CanalEntry.RowChange rowChange; try { rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index bf79ee45..d8957d92 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -3,7 +3,9 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import com.alibaba.otter.canal.common.utils.CanalToStringStyle; @@ -89,4 +91,115 @@ public class Message implements Serializable { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } + /** + * 将 message 分区 + * + * @param partitionsNum 分区数 + * @param pkHashConfigs 分区库表主键正则表达式 + * @return 分区message数组 + */ + @SuppressWarnings("unchecked") + public Message[] messagePartition(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<>(); + } + for (Entry entry : this.getEntries()) { + CanalEntry.RowChange rowChange; + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + if (rowChange.getIsDdl()) { + partitionEntries[0].add(entry); + } else { + if (rowChange.getRowDatasList() != null && !rowChange.getRowDatasList().isEmpty()) { + String database = entry.getHeader().getSchemaName(); + String table = entry.getHeader().getTableName(); + + String pk = null; + boolean isMatch = false; + + String[] pkHashConfigArray = StringUtils.split(pkHashConfigs, ","); + for (String pkHashConfig : pkHashConfigArray) { + int i = pkHashConfig.lastIndexOf("."); + if (!pkHashConfig.endsWith(".$pk$")) { + // 如果指定了主键 + pk = pkHashConfig.substring(i + 1); + } + pkHashConfig = pkHashConfig.substring(0, i); + isMatch = Pattern.matches(pkHashConfig, database + "." + table); + if (isMatch) { + break; + } + } + + if (!isMatch) { + // 如果都没有匹配,发送到第一个分区 + partitionEntries[0].add(entry); + } else { + if (pk == null) { + // 如果未指定主键(通配符主键),取主键字段 + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getIsKey()) { + pk = column.getName(); + break; + } + } + } + } + if (pk == null) { + // 如果都没有匹配的主键,发送到第一个分区 + partitionEntries[0].add(entry); + } else { + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + boolean hasOldPk = false; + // 如果before中有pk值说明主键有修改, 以旧的主键值hash为准 + for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { + if (column.getName().equalsIgnoreCase(pk)) { + int hash = column.getValue().hashCode(); + int pkHash = Math.abs(hash) % partitionsNum; + pkHash = Math.abs(pkHash); + partitionEntries[pkHash].add(entry); + hasOldPk = true; + break; + } + } + if (!hasOldPk) { + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getName().equalsIgnoreCase(pk)) { + int hash = column.getValue().hashCode(); + int pkHash = Math.abs(hash) % partitionsNum; + pkHash = Math.abs(pkHash); + partitionEntries[pkHash].add(entry); + break; + } + } + } + } + } + } + } + } + + for (int i = 0; i < partitionsNum; i++) { + List entries = partitionEntries[i]; + if (!entries.isEmpty()) { + partitionMessages[i] = new Message(this.id, entries); + } + } + + return partitionMessages; + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 230e9607..0dd93ea5 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -80,22 +80,36 @@ public class CanalKafkaProducer implements CanalMQProducer { // producer.beginTransaction(); if (!kafkaProperties.getFlatMessage()) { try { - ProducerRecord record; + ProducerRecord record = null; if (canalDestination.getPartition() != null) { - record = new ProducerRecord(canalDestination.getTopic(), + record = new ProducerRecord<>(canalDestination.getTopic(), canalDestination.getPartition(), null, message); } else { - record = new ProducerRecord(canalDestination.getTopic(), 0, null, message); + if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) { + Message[] messages = message.messagePartition(canalDestination.getPartitionsNum(), + canalDestination.getPartitionHash()); + int length = messages.length; + for (int i = 0; i < length; i++) { + Message messagePartition = messages[i]; + if (messagePartition != null) { + record = new ProducerRecord<>(canalDestination.getTopic(), i, null, messagePartition); + } + } + } else { + record = new ProducerRecord<>(canalDestination.getTopic(), 0, null, message); + } } - producer.send(record).get(); + if (record != null) { + producer.send(record).get(); - if (logger.isDebugEnabled()) { - logger.debug("Send message to kafka topic: [{}], packet: {}", - canalDestination.getTopic(), - message.toString()); + if (logger.isDebugEnabled()) { + logger.debug("Send message to kafka topic: [{}], packet: {}", + canalDestination.getTopic(), + message.toString()); + } } } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index 74e3920b..56648d4e 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -1,14 +1,7 @@ package com.alibaba.otter.canal.rocketmq; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.otter.canal.common.CanalMessageSerializer; -import com.alibaba.otter.canal.common.MQProperties; -import com.alibaba.otter.canal.protocol.FlatMessage; -import com.alibaba.otter.canal.server.exception.CanalServerException; -import com.alibaba.otter.canal.spi.CanalMQProducer; -import com.aliyun.openservices.apache.api.impl.authority.SessionCredentials; -import com.aliyun.openservices.apache.api.impl.rocketmq.ClientRPCHook; +import java.util.List; + import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; @@ -20,7 +13,15 @@ import org.apache.rocketmq.remoting.exception.RemotingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.otter.canal.common.CanalMessageSerializer; +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.protocol.FlatMessage; +import com.alibaba.otter.canal.server.exception.CanalServerException; +import com.alibaba.otter.canal.spi.CanalMQProducer; +import com.aliyun.openservices.apache.api.impl.authority.SessionCredentials; +import com.aliyun.openservices.apache.api.impl.rocketmq.ClientRPCHook; public class CanalRocketMQProducer implements CanalMQProducer { @@ -34,8 +35,8 @@ public class CanalRocketMQProducer implements CanalMQProducer { public void init(MQProperties rocketMQProperties) { this.mqProperties = rocketMQProperties; RPCHook rpcHook = null; - if(rocketMQProperties.getAliyunAccessKey().length() > 0 - && rocketMQProperties.getAliyunSecretKey().length() > 0){ + if (rocketMQProperties.getAliyunAccessKey().length() > 0 + && rocketMQProperties.getAliyunSecretKey().length() > 0) { SessionCredentials sessionCredentials = new SessionCredentials(); sessionCredentials.setAccessKey(rocketMQProperties.getAliyunAccessKey()); sessionCredentials.setSecretKey(rocketMQProperties.getAliyunSecretKey()); @@ -59,23 +60,65 @@ public class CanalRocketMQProducer implements CanalMQProducer { Callback callback) { if (!mqProperties.getFlatMessage()) { try { - Message message = new Message(destination.getTopic(), CanalMessageSerializer.serializer(data, - mqProperties.isFilterTransactionEntry())); - logger.debug("send message:{} to destination:{}, partition: {}", - message, - destination.getCanalDestination(), - destination.getPartition()); - this.defaultMQProducer.send(message, new MessageQueueSelector() { - - @Override - public MessageQueue select(List mqs, Message msg, Object arg) { - int partition = 0; - if (destination.getPartition() != null) { - partition = destination.getPartition(); - } - return mqs.get(partition); + if (destination.getPartition() != null) { + Message message = new Message(destination.getTopic(), + CanalMessageSerializer.serializer(data, mqProperties.isFilterTransactionEntry())); + if (logger.isDebugEnabled()) { + logger.debug("send message:{} to destination:{}, partition: {}", + message, + destination.getCanalDestination(), + destination.getPartition()); } - }, null); + this.defaultMQProducer.send(message, new MessageQueueSelector() { + + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + int partition = 0; + if (destination.getPartition() != null) { + partition = destination.getPartition(); + } + return mqs.get(partition); + } + }, null); + } else { + if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) { + com.alibaba.otter.canal.protocol.Message[] messages = data + .messagePartition(destination.getPartitionsNum(), destination.getPartitionHash()); + int length = messages.length; + for (int i = 0; i < length; i++) { + com.alibaba.otter.canal.protocol.Message dataPartition = messages[i]; + if (dataPartition != null) { + if (logger.isDebugEnabled()) { + logger.debug("flatMessagePart: {}, partition: {}", + JSON.toJSONString(dataPartition, SerializerFeature.WriteMapNullValue), + i); + } + final int index = i; + try { + Message message = new Message(destination.getTopic(), + CanalMessageSerializer.serializer(dataPartition, + mqProperties.isFilterTransactionEntry())); + this.defaultMQProducer.send(message, new MessageQueueSelector() { + + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + if (index > mqs.size()) { + throw new CanalServerException("partition number is error,config num:" + + destination.getPartitionsNum() + + ", mq num: " + mqs.size()); + } + return mqs.get(index); + } + }, null); + } catch (Exception e) { + logger.error("send flat message to hashed partition error", e); + callback.rollback(); + return; + } + } + } + } + } } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { logger.error("Send message error!", e); callback.rollback(); @@ -87,10 +130,12 @@ public class CanalRocketMQProducer implements CanalMQProducer { for (FlatMessage flatMessage : flatMessages) { if (destination.getPartition() != null) { try { - logger.info("send flat message: {} to topic: {} fixed partition: {}", - JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue), - destination.getTopic(), - destination.getPartition()); + if (logger.isDebugEnabled()) { + logger.debug("send message: {} to topic: {} fixed partition: {}", + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue), + destination.getTopic(), + destination.getPartition()); + } Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @@ -114,13 +159,16 @@ public class CanalRocketMQProducer implements CanalMQProducer { for (int i = 0; i < length; i++) { FlatMessage flatMessagePart = partitionFlatMessage[i]; if (flatMessagePart != null) { - logger.debug("flatMessagePart: {}, partition: {}", - JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue), - i); + if (logger.isDebugEnabled()) { + logger.debug("flatMessagePart: {}, partition: {}", + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue), + i); + } final int index = i; try { Message message = new Message(destination.getTopic(), - JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue).getBytes()); + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue) + .getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override From 5650b56bedc4eeb919a1f1389b5d67adf57be6c3 Mon Sep 17 00:00:00 2001 From: machey Date: Mon, 10 Dec 2018 01:10:04 +0800 Subject: [PATCH 03/21] =?UTF-8?q?=E6=89=93=E5=8D=B0=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/launcher/loader/AbstractCanalAdapterWorker.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index db87bf01..ec650231 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -70,6 +70,7 @@ public abstract class AbstractCanalAdapterWorker { }); return true; } catch (Exception e) { + logger.error(e.getMessage(), e); return false; } })); @@ -108,6 +109,7 @@ public abstract class AbstractCanalAdapterWorker { }); return true; } catch (Exception e) { + logger.error(e.getMessage(), e); return false; } })); From 24d898d87a0f9b7636627842ba5d7ea0d30fc49c Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 10 Dec 2018 09:25:40 +0800 Subject: [PATCH 04/21] =?UTF-8?q?rdb=20=E5=85=A8schema=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loader/AbstractCanalAdapterWorker.java | 4 +- .../src/main/resources/application.yml | 22 +++-- client-adapter/rdb/pom.xml | 7 +- .../canal/client/adapter/rdb/RdbAdapter.java | 41 ++++++--- .../adapter/rdb/config/MappingConfig.java | 61 +++++++------ .../adapter/rdb/service/RdbEtlService.java | 6 +- .../rdb/service/RdbMirrorDbSyncService.java | 66 ++++++++++++++ .../adapter/rdb/service/RdbSyncService.java | 12 +-- .../client/adapter/rdb/support/SyncUtil.java | 10 +++ .../src/main/resources/rdb/mytest_user.yml | 16 ++-- .../canal/client/adapter/rdb/test/DBTest.java | 2 +- .../adapter/rdb/test/SqlParserTest.java | 86 +++++++++++++++++++ 12 files changed, 258 insertions(+), 75 deletions(-) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java create mode 100644 client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index db87bf01..fdf2a91e 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -70,6 +70,7 @@ public abstract class AbstractCanalAdapterWorker { }); return true; } catch (Exception e) { + logger.error(e.getMessage(), e); return false; } })); @@ -108,6 +109,7 @@ public abstract class AbstractCanalAdapterWorker { }); return true; } catch (Exception e) { + logger.error(e.getMessage(), e); return false; } })); @@ -178,7 +180,7 @@ public abstract class AbstractCanalAdapterWorker { /** * 分批同步 - * + * * @param dmls * @param adapter */ diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 098bc1f3..4a5cac3b 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: canal.conf: canalServerHost: 127.0.0.1:11111 # zookeeperHosts: slave1:2181 -# mqServers: slave1:6667 #or rocketmq +# mqServers: 127.0.0.1:9092 #or rocketmq # flatMessage: true batchSize: 500 syncBatchSize: 1000 @@ -34,7 +34,7 @@ canal.conf: groups: - groupId: g1 outerAdapters: - - name: logger +# - name: logger # - name: rdb # key: oracle1 # properties: @@ -42,15 +42,13 @@ canal.conf: # jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 -# - name: rdb -# key: postgres1 -# properties: -# jdbc.driverClassName: org.postgresql.Driver -# jdbc.url: jdbc:postgresql://localhost:5432/postgres -# jdbc.username: postgres -# jdbc.password: 121212 -# threads: 1 -# commitSize: 3000 + - name: rdb + key: mysql1 + properties: + jdbc.driverClassName: com.mysql.jdbc.Driver + jdbc.url: jdbc:mysql://192.168.100.36/mytest?useUnicode=true + jdbc.username: root + jdbc.password: Ambari-123 # - name: hbase # properties: # hbase.zookeeper.quorum: 127.0.0.1 @@ -59,4 +57,4 @@ canal.conf: # - name: es # hosts: 127.0.0.1:9300 # properties: -# cluster.name: elasticsearch \ No newline at end of file +# cluster.name: elasticsearch diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml index 4ac00ffd..881b9399 100644 --- a/client-adapter/rdb/pom.xml +++ b/client-adapter/rdb/pom.xml @@ -24,6 +24,11 @@ 1.19 provided + + com.alibaba.fastsql + fastsql + 2.0.0_preview_644 + mysql @@ -100,4 +105,4 @@ - \ No newline at end of file + diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index ae38b124..cc2fd32e 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -2,10 +2,10 @@ package com.alibaba.otter.canal.client.adapter.rdb; import java.sql.Connection; import java.sql.SQLException; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -21,22 +21,26 @@ import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; +import com.alibaba.otter.canal.client.adapter.rdb.service.RdbMirrorDbSyncService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; +import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.support.*; @SPI("rdb") public class RdbAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); + private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); - private Map rdbMapping = new HashMap<>(); // 文件名对应配置 - private Map> mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 + private Map rdbMapping = new ConcurrentHashMap<>(); // 文件名对应配置 + private Map> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置 + private Map mirrorDbConfigCache = new ConcurrentHashMap<>(); // 镜像库配置 private DruidDataSource dataSource; private RdbSyncService rdbSyncService; + private RdbMirrorDbSyncService rdbMirrorDbSyncService; - private ExecutorService executor = Executors.newFixedThreadPool(1); + private ExecutorService executor = Executors.newFixedThreadPool(1); private RdbConfigMonitor rdbConfigMonitor; @@ -62,12 +66,18 @@ public class RdbAdapter implements OuterAdapter { for (Map.Entry entry : rdbMapping.entrySet()) { String configName = entry.getKey(); MappingConfig mappingConfig = entry.getValue(); - Map configMap = mappingConfigCache - .computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." - + mappingConfig.getDbMapping().getDatabase() + "." - + mappingConfig.getDbMapping().getTable(), - k1 -> new HashMap<>()); - configMap.put(configName, mappingConfig); + if (!mappingConfig.getDbMapping().isMirrorDb()) { + Map configMap = mappingConfigCache.computeIfAbsent( + StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + mappingConfig.getDbMapping() + .getDatabase() + "." + mappingConfig.getDbMapping().getTable(), + k1 -> new ConcurrentHashMap<>()); + configMap.put(configName, mappingConfig); + } else { + // mirrorDB + mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getDbMapping().getDatabase(), + mappingConfig); + } } Map properties = configuration.getProperties(); @@ -96,6 +106,10 @@ public class RdbAdapter implements OuterAdapter { dataSource, threads != null ? Integer.valueOf(threads) : null); + rdbMirrorDbSyncService = new RdbMirrorDbSyncService(mirrorDbConfigCache, + dataSource, + threads != null ? Integer.valueOf(threads) : null); + rdbConfigMonitor = new RdbConfigMonitor(); rdbConfigMonitor.init(configuration.getKey(), this); } @@ -103,6 +117,7 @@ public class RdbAdapter implements OuterAdapter { @Override public void sync(List dmls) { rdbSyncService.sync(dmls); + rdbMirrorDbSyncService.sync(dmls); } @Override @@ -157,7 +172,7 @@ public class RdbAdapter implements OuterAdapter { public Map count(String task) { MappingConfig config = rdbMapping.get(task); MappingConfig.DbMapping dbMapping = config.getDbMapping(); - String sql = "SELECT COUNT(1) AS cnt FROM " + dbMapping.getTargetTable(); + String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.dbTable(dbMapping); Connection conn = null; Map res = new LinkedHashMap<>(); try { @@ -183,7 +198,7 @@ public class RdbAdapter implements OuterAdapter { } } } - res.put("targetTable", dbMapping.getTargetTable()); + res.put("targetTable", SyncUtil.dbTable(dbMapping)); return res; } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index 1d32147a..11594cde 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -1,8 +1,6 @@ package com.alibaba.otter.canal.client.adapter.rdb.config; -import java.util.LinkedHashSet; import java.util.Map; -import java.util.Set; /** * RDB表映射配置 @@ -66,30 +64,37 @@ public class MappingConfig { if (dbMapping.database == null || dbMapping.database.isEmpty()) { throw new NullPointerException("dbMapping.database"); } - if (dbMapping.table == null || dbMapping.table.isEmpty()) { + if (!dbMapping.isMirrorDb() && (dbMapping.table == null || dbMapping.table.isEmpty())) { throw new NullPointerException("dbMapping.table"); } - if (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty()) { + if (!dbMapping.isMirrorDb() && (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) { throw new NullPointerException("dbMapping.targetTable"); } } public static class DbMapping { - private String database; // 数据库名或schema名 - private String table; // 表面名 - private Map targetPk; // 目标表主键字段 - private boolean mapAll = false; // 映射所有字段 - private String targetTable; // 目标表名 - private Map targetColumns; // 目标表字段映射 + private Boolean mirrorDb = false; // 是否镜像库 + private String database; // 数据库名或schema名 + private String table; // 表名 + private Map targetPk; // 目标表主键字段 + private Boolean mapAll = false; // 映射所有字段 + private String targetDb; // 目标库名 + private String targetTable; // 目标表名 + private Map targetColumns; // 目标表字段映射 - private String etlCondition; // etl条件sql + private String etlCondition; // etl条件sql - private Set families = new LinkedHashSet<>(); // column family列表 private int readBatch = 5000; - private int commitBatch = 5000; // etl等批量提交大小 + private int commitBatch = 5000; // etl等批量提交大小 - // private volatile Map allColumns; // mapAll为true,自动设置改字段 + public boolean isMirrorDb() { + return mirrorDb == null ? false : mirrorDb; + } + + public void setMirrorDb(Boolean mirrorDb) { + this.mirrorDb = mirrorDb; + } public String getDatabase() { return database; @@ -116,13 +121,21 @@ public class MappingConfig { } public boolean isMapAll() { - return mapAll; + return mapAll == null ? false : mapAll; } - public void setMapAll(boolean mapAll) { + public void setMapAll(Boolean mapAll) { this.mapAll = mapAll; } + public String getTargetDb() { + return targetDb; + } + + public void setTargetDb(String targetDb) { + this.targetDb = targetDb; + } + public String getTargetTable() { return targetTable; } @@ -147,14 +160,6 @@ public class MappingConfig { this.etlCondition = etlCondition; } - public Set getFamilies() { - return families; - } - - public void setFamilies(Set families) { - this.families = families; - } - public int getReadBatch() { return readBatch; } @@ -170,13 +175,5 @@ public class MappingConfig { public void setCommitBatch(int commitBatch) { this.commitBatch = commitBatch; } - - // public Map getAllColumns() { - // return allColumns; - // } - // - // public void setAllColumns(Map allColumns) { - // this.allColumns = allColumns; - // } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java index a42e5f3c..22f1bc03 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java @@ -109,7 +109,7 @@ public class RdbEtlService { logger.info( dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); - etlResult.setResultMessage("导入目标表 " + dbMapping.getTargetTable() + " 数据:" + successCount.get() + " 条"); + etlResult.setResultMessage("导入目标表 " + SyncUtil.dbTable(dbMapping) + " 数据:" + successCount.get() + " 条"); } catch (Exception e) { logger.error(e.getMessage(), e); errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage()); @@ -187,7 +187,7 @@ public class RdbEtlService { // } StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + insertSql.append("INSERT INTO ").append(SyncUtil.dbTable(dbMapping)).append(" ("); columnsMap .forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); @@ -209,7 +209,7 @@ public class RdbEtlService { // 删除数据 Map values = new LinkedHashMap<>(); StringBuilder deleteSql = new StringBuilder( - "DELETE FROM " + dbMapping.getTargetTable() + " WHERE "); + "DELETE FROM " + SyncUtil.dbTable(dbMapping) + " WHERE "); appendCondition(dbMapping, deleteSql, values, rs); try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) { int k = 1; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java new file mode 100644 index 00000000..7b3f9bdc --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java @@ -0,0 +1,66 @@ +package com.alibaba.otter.canal.client.adapter.rdb.service; + +import java.io.StringWriter; +import java.sql.Connection; +import java.sql.Statement; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import com.alibaba.fastsql.sql.ast.SQLName; +import com.alibaba.fastsql.sql.ast.SQLStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; +import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor; +import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +import com.alibaba.fastsql.sql.parser.SQLStatementParser; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.support.Dml; + +public class RdbMirrorDbSyncService { + + private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class); + + private Map mirrorDbConfigCache; // 镜像库配置 + private DataSource dataSource; + + public RdbMirrorDbSyncService(Map mirrorDbConfigCache, DataSource dataSource, + Integer threads){ + this.mirrorDbConfigCache = mirrorDbConfigCache; + this.dataSource = dataSource; + } + + public void sync(List dmls) { + for (Dml dml : dmls) { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + MappingConfig configMap = mirrorDbConfigCache.get(destination + "." + database); + if (configMap == null) { + continue; + } + if (dml.getSql() != null) { + // DDL + executeDdl(database, dml.getSql()); + } else { + // DML + // TODO + } + } + } + + private void executeDdl(String database, String sql) { + try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) { + statement.execute(sql); + if (logger.isTraceEnabled()) { + logger.trace("Execute DDL sql: {} for database: {}", sql, database); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index ef80bb2f..38469ac4 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -91,7 +91,9 @@ public class RdbSyncService { dmlsPartition[hash].add(syncItem); }); } else { - int hash = Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % threads); + int hash = 0; + // Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % + // threads); List singleDmls = SingleDml.dml2SingleDmls(dml); singleDmls.forEach(singleDml -> { SyncItem syncItem = new SyncItem(config, singleDml); @@ -164,7 +166,7 @@ public class RdbSyncService { Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + insertSql.append("INSERT INTO ").append(SyncUtil.dbTable(dbMapping)).append(" ("); columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); int len = insertSql.length(); @@ -228,7 +230,7 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutor.getConn(), config); StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + updateSql.append("UPDATE ").append(SyncUtil.dbTable(dbMapping)).append(" SET "); List> values = new ArrayList<>(); for (String srcColumnName : old.keySet()) { List targetColumnNames = new ArrayList<>(); @@ -280,7 +282,7 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutor.getConn(), config); StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + sql.append("DELETE FROM ").append(SyncUtil.dbTable(dbMapping)).append(" WHERE "); List> values = new ArrayList<>(); // 拼接主键 @@ -313,7 +315,7 @@ public class RdbSyncService { if (columnType == null) { columnType = new LinkedHashMap<>(); final Map columnTypeTmp = columnType; - String sql = "SELECT * FROM " + dbMapping.getTargetTable() + " WHERE 1=2"; + String sql = "SELECT * FROM " + SyncUtil.dbTable(dbMapping) + " WHERE 1=2"; Util.sqlRS(conn, sql, rs -> { try { ResultSetMetaData rsd = rs.getMetaData(); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java index 6be7ef3f..33cf5344 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; @@ -253,4 +254,13 @@ public class SyncUtil { pstmt.setObject(i, value, type); } } + + public static String dbTable(MappingConfig.DbMapping dbMapping) { + String result = ""; + if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) { + result += dbMapping.getTargetDb() + "."; + } + result += dbMapping.getTargetTable(); + return result; + } } diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml index 9b40dd75..68fb859e 100644 --- a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -1,17 +1,19 @@ dataSourceKey: defaultDS destination: example -outerAdapterKey: oracle1 +outerAdapterKey: mysql1 concurrent: true dbMapping: + mirrorDb: true database: mytest - table: user - targetTable: mytest.tb_user - targetPk: - id: id - mapAll: true +# table: user + targetDb: mytest2 +# targetTable: mytest.tb_user +# targetPk: +# id: id +# mapAll: true # targetColumns: # id: # name: # role_id: # c_time: -# test1: \ No newline at end of file +# test1: diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java index 5057765f..c983d7d8 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java @@ -42,7 +42,7 @@ public class DBTest { .prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)"); java.util.Date now = new java.util.Date(); - for (int i = 1; i <= 100000; i++) { + for (int i = 1; i <= 10000; i++) { pstmt.clearParameters(); pstmt.setLong(1, (long) i); pstmt.setString(2, "test_" + i); diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java new file mode 100644 index 00000000..59d0bcff --- /dev/null +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java @@ -0,0 +1,86 @@ +wpackage com.alibaba.otter.canal.client.adapter.rdb.test; + +import com.alibaba.fastsql.sql.ast.SQLName; +import com.alibaba.fastsql.sql.ast.SQLStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; +import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlCreateTableParser; +import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor; +import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +import com.alibaba.fastsql.sql.parser.SQLStatementParser; + +import java.io.StringWriter; + +public class SqlParserTest { + + public static class TableNameVisitor extends MySqlOutputVisitor { + + public TableNameVisitor(Appendable appender){ + super(appender); + } + + @Override + public boolean visit(SQLExprTableSource x) { + SQLName table = (SQLName) x.getExpr(); + String tableName = table.getSimpleName(); + + // 改写tableName + print0("new_" + tableName.toUpperCase()); + + return true; + } + + } + + public static void main(String[] args) { + // String sql = "select * from `mytest`.`t` where id=1 and name=ming group by + // uid limit 1,200 order by ctime"; + + String sql = "CREATE TABLE `mytest`.`user` (\n" + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" + + " `name` varchar(30) NOT NULL,\n" + " `c_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" + + " `role_id` bigint(20) DEFAULT NULL,\n" + " `test1` text,\n" + " `test2` blob,\n" + + " `key` varchar(30) DEFAULT NULL,\n" + " PRIMARY KEY (`id`)\n" + + ") ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;"; + + // // 新建 MySQL Parser + // SQLStatementParser parser = new MySqlStatementParser(sql); + // + // // 使用Parser解析生成AST,这里SQLStatement就是AST + // SQLStatement sqlStatement = parser.parseStatement(); + // + // MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); + // sqlStatement.accept(visitor); + // + // System.out.println("getTables:" + visitor.getTables()); + // System.out.println("getParameters:" + visitor.getParameters()); + // System.out.println("getOrderByColumns:" + visitor.getOrderByColumns()); + // System.out.println("getGroupByColumns:" + visitor.getGroupByColumns()); + // System.out.println("---------------------------------------------------------------------------"); + // + // // 使用select访问者进行select的关键信息打印 + // // SelectPrintVisitor selectPrintVisitor = new SelectPrintVisitor(); + // // sqlStatement.accept(selectPrintVisitor); + // + // System.out.println("---------------------------------------------------------------------------"); + // // 最终sql输出 + // StringWriter out = new StringWriter(); + // TableNameVisitor outputVisitor = new TableNameVisitor(out); + // sqlStatement.accept(outputVisitor); + // System.out.println(out.toString()); + + MySqlCreateTableParser parser1 = new MySqlCreateTableParser(sql); + SQLCreateTableStatement createTableStatement = parser1.parseCreateTable(); +// MySqlSchemaStatVisitor visitor1 = new MySqlSchemaStatVisitor(); +// createTableStatement.accept(visitor1); + // visitor1.getTables().forEach((k, v) -> { + // System.out.println(k.); + // System.out.println(v); + // }); + // 最终sql输出 + StringWriter out = new StringWriter(); + TableNameVisitor outputVisitor = new TableNameVisitor(out); + createTableStatement.accept(outputVisitor); + System.out.println(out.toString()); + } +} From 99a1123e010eea0b087c11421932c820c34a6681 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 10 Dec 2018 09:52:48 +0800 Subject: [PATCH 05/21] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/example/instance.properties | 6 ++-- .../alibaba/otter/canal/protocol/Message.java | 29 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index 3a9ac883..8d884abd 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -1,5 +1,5 @@ ################################################# -## mysql serverId , v1.0.26+ will autoGen +## mysql serverId , v1.0.26+ will autoGen # canal.instance.mysql.slaveId=0 # enable gtid use true/false @@ -48,5 +48,5 @@ canal.mq.topic=example canal.mq.partition=0 # hash partition config #canal.mq.partitionsNum=3 -#canal.mq.partitionHash=mytest.person:id,mytest.role:id -################################################# \ No newline at end of file +#canal.mq.partitionHash=.*\\..*.$pk$ +################################################# diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index d8957d92..a01a8006 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; +import com.google.protobuf.InvalidProtocolBufferException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; @@ -93,7 +94,7 @@ public class Message implements Serializable { /** * 将 message 分区 - * + * * @param partitionsNum 分区数 * @param pkHashConfigs 分区库表主键正则表达式 * @return 分区message数组 @@ -108,7 +109,25 @@ public class Message implements Serializable { for (int i = 0; i < partitionsNum; i++) { partitionEntries[i] = new ArrayList<>(); } - for (Entry entry : this.getEntries()) { + + List entries; + if (this.isRaw()) { + List rawEntries = this.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 = this.getEntries(); + } + + for (Entry entry : entries) { CanalEntry.RowChange rowChange; try { rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); @@ -194,9 +213,9 @@ public class Message implements Serializable { } for (int i = 0; i < partitionsNum; i++) { - List entries = partitionEntries[i]; - if (!entries.isEmpty()) { - partitionMessages[i] = new Message(this.id, entries); + List entriesTmp = partitionEntries[i]; + if (!entriesTmp.isEmpty()) { + partitionMessages[i] = new Message(this.id, entriesTmp); } } From 706ee88914c738073b0ae34180f10ce90fbb845b Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 10 Dec 2018 21:28:09 +0800 Subject: [PATCH 06/21] =?UTF-8?q?pattern=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20pk=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- protocol/pom.xml | 8 ++ .../otter/canal/protocol/FlatMessage.java | 78 ++++++----- .../alibaba/otter/canal/protocol/Message.java | 54 +++++--- .../protocol/aviater/AviaterRegexFilter.java | 124 ++++++++++++++++++ .../canal/protocol/aviater/PatternUtils.java | 38 ++++++ .../canal/protocol/aviater/RegexFunction.java | 31 +++++ 6 files changed, 283 insertions(+), 50 deletions(-) create mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java create mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java create mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java diff --git a/protocol/pom.xml b/protocol/pom.xml index 1b776b7e..0f7904d6 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -26,5 +26,13 @@ commons-lang commons-lang + + oro + oro + + + com.googlecode.aviator + aviator + diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 05d8f795..263b0d6e 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -2,8 +2,10 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; import java.util.*; -import java.util.regex.Pattern; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import org.apache.commons.lang.StringUtils; import com.google.protobuf.ByteString; @@ -14,24 +16,27 @@ import com.google.protobuf.ByteString; */ public class FlatMessage implements Serializable { - private static final long serialVersionUID = -3386650678735860050L; + private static final long serialVersionUID = -3386650678735860050L; - private long id; - private String database; - private String table; - private Boolean isDdl; - private String type; + private static ConcurrentMap schemaTabPk = new ConcurrentHashMap<>(); + private static ConcurrentHashMap regexFilters = new ConcurrentHashMap<>(); + + private long id; + private String database; + private String table; + private Boolean isDdl; + private String type; // binlog executeTime - private Long es; + private Long es; // dml build timeStamp - private Long ts; - private String sql; - private Map sqlType; - private Map mysqlType; - private List> data; - private List> old; + private Long ts; + private String sql; + private Map sqlType; + private Map mysqlType; + private List> data; + private List> old; - private transient CanalEntry.Entry entry; // 所属entry + private transient CanalEntry.Entry entry; // 所属entry public FlatMessage(long id){ this.id = id; @@ -143,7 +148,7 @@ public class FlatMessage implements Serializable { /** * 将Message转换为FlatMessage - * + * * @param message 原生message * @return FlatMessage列表 */ @@ -272,7 +277,7 @@ public class FlatMessage implements Serializable { /** * 将FlatMessage按指定的字段值hash拆分 - * + * * @param flatMessage flatMessage * @param partitionsNum 分区数量 * @param pkHashConfigs hash映射 @@ -302,7 +307,14 @@ public class FlatMessage implements Serializable { pk = pkHashConfig.substring(i + 1); } pkHashConfig = pkHashConfig.substring(0, i); - isMatch = Pattern.matches(pkHashConfig, database + "." + table); + + AviaterRegexFilter aviaterRegexFilter = regexFilters.get(pkHashConfig); + if (aviaterRegexFilter == null) { + aviaterRegexFilter = new AviaterRegexFilter(pkHashConfig); + regexFilters.putIfAbsent(pkHashConfig, aviaterRegexFilter); + } + + isMatch = aviaterRegexFilter.filter(database + "." + table); if (isMatch) { break; } @@ -313,19 +325,23 @@ public class FlatMessage implements Serializable { partitionMessages[0] = flatMessage; } else { if (pk == null) { - // 如果未指定主键(通配符主键),从原生message中取主键字段 - CanalEntry.Entry entry = flatMessage.getEntry(); - CanalEntry.RowChange rowChange; - try { - rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } - CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getIsKey()) { - pk = column.getName(); - break; + pk = schemaTabPk.get(database + "." + table); + if (pk == null) { + // 如果未指定主键(通配符主键),从原生message中取主键字段 + CanalEntry.Entry entry = flatMessage.getEntry(); + CanalEntry.RowChange rowChange; + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getIsKey()) { + pk = column.getName(); + schemaTabPk.putIfAbsent(database + "." + table, pk); + break; + } } } } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index a01a8006..00837fec 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -3,15 +3,17 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; -import com.google.protobuf.InvalidProtocolBufferException; +import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import com.alibaba.otter.canal.common.utils.CanalToStringStyle; import com.alibaba.otter.canal.protocol.CanalEntry.Entry; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; /** * @author zebin.xuzb @ 2012-6-19 @@ -19,14 +21,17 @@ import com.google.protobuf.ByteString; */ public class Message implements Serializable { - private static final long serialVersionUID = 1234034768477580009L; + private static final long serialVersionUID = 1234034768477580009L; - private long id; - private List entries = new ArrayList(); + private static ConcurrentMap schemaTabPk = new ConcurrentHashMap<>(); + private static ConcurrentHashMap regexFilters = new ConcurrentHashMap<>(); + + private long id; + private List entries = new ArrayList(); // row data for performance, see: // https://github.com/alibaba/canal/issues/726 - private boolean raw = true; - private List rawEntries = new ArrayList(); + private boolean raw = true; + private List rawEntries = new ArrayList(); public Message(long id, List entries){ this.id = id; @@ -152,7 +157,14 @@ public class Message implements Serializable { pk = pkHashConfig.substring(i + 1); } pkHashConfig = pkHashConfig.substring(0, i); - isMatch = Pattern.matches(pkHashConfig, database + "." + table); + + AviaterRegexFilter aviaterRegexFilter = regexFilters.get(pkHashConfig); + if (aviaterRegexFilter == null) { + aviaterRegexFilter = new AviaterRegexFilter(pkHashConfig); + regexFilters.putIfAbsent(pkHashConfig, aviaterRegexFilter); + } + + isMatch = aviaterRegexFilter.filter(database + "." + table); if (isMatch) { break; } @@ -163,17 +175,21 @@ public class Message implements Serializable { partitionEntries[0].add(entry); } else { if (pk == null) { - // 如果未指定主键(通配符主键),取主键字段 - try { - rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } - CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getIsKey()) { - pk = column.getName(); - break; + pk = schemaTabPk.get(database + "." + table); + if (pk == null) { + // 如果未指定主键(通配符主键),取主键字段 + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getIsKey()) { + pk = column.getName(); + schemaTabPk.putIfAbsent(database + "." + table, pk); + break; + } } } } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java new file mode 100644 index 00000000..db7750ba --- /dev/null +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java @@ -0,0 +1,124 @@ +package com.alibaba.otter.canal.protocol.aviater; + +import java.util.*; + +import org.apache.commons.lang.StringUtils; + +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.Expression; + +/** + * 基于aviater进行tableName正则匹配的过滤算法 + * + * @author jianghang 2012-7-20 下午06:01:34 + */ +public class AviaterRegexFilter { + + private static final String SPLIT = ","; + private static final String PATTERN_SPLIT = "|"; + private static final String FILTER_EXPRESSION = "regex(pattern,target)"; + private static final RegexFunction regexFunction = new RegexFunction(); + private final Expression exp = AviatorEvaluator.compile(FILTER_EXPRESSION, true); + static { + AviatorEvaluator.addFunction(regexFunction); + } + + private static final Comparator COMPARATOR = new StringComparator(); + + final private String pattern; + final private boolean defaultEmptyValue; + + public AviaterRegexFilter(String pattern){ + this(pattern, true); + } + + public AviaterRegexFilter(String pattern, boolean defaultEmptyValue){ + this.defaultEmptyValue = defaultEmptyValue; + List list = null; + if (StringUtils.isEmpty(pattern)) { + list = new ArrayList(); + } else { + String[] ss = StringUtils.split(pattern, SPLIT); + list = Arrays.asList(ss); + } + + // 对pattern按照从长到短的排序 + // 因为 foo|foot 匹配 foot 会出错,原因是 foot 匹配了 foo 之后,会返回 foo,但是 foo 的长度和 foot + // 的长度不一样 + Collections.sort(list, COMPARATOR); + // 对pattern进行头尾完全匹配 + list = completionPattern(list); + this.pattern = StringUtils.join(list, PATTERN_SPLIT); + } + + public boolean filter(String filtered) { + if (StringUtils.isEmpty(pattern)) { + return defaultEmptyValue; + } + + if (StringUtils.isEmpty(filtered)) { + return defaultEmptyValue; + } + + Map env = new HashMap(); + env.put("pattern", pattern); + env.put("target", filtered.toLowerCase()); + return (Boolean) exp.execute(env); + } + + /** + * 修复正则表达式匹配的问题,因为使用了 oro 的 matches,会出现: + * + *
+     * foo|foot 匹配 foot 出错,原因是 foot 匹配了 foo 之后,会返回 foo,但是 foo 的长度和 foot 的长度不一样
+     * 
+ * + * 因此此类对正则表达式进行了从长到短的排序 + * + * @author zebin.xuzb 2012-10-22 下午2:02:26 + * @version 1.0.0 + */ + private static class StringComparator implements Comparator { + + @Override + public int compare(String str1, String str2) { + if (str1.length() > str2.length()) { + return -1; + } else if (str1.length() < str2.length()) { + return 1; + } else { + return 0; + } + } + } + + /** + * 修复正则表达式匹配的问题,即使按照长度递减排序,还是会出现以下问题: + * + *
+     * foooo|f.*t 匹配 fooooot 出错,原因是 fooooot 匹配了 foooo 之后,会将 fooo 和数据进行匹配,但是 foooo 的长度和 fooooot 的长度不一样
+     * 
+ * + * 因此此类对正则表达式进行头尾完全匹配 + * + * @author simon + * @version 1.0.0 + */ + + private List completionPattern(List patterns) { + List result = new ArrayList(); + for (String pattern : patterns) { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("^"); + stringBuffer.append(pattern); + stringBuffer.append("$"); + result.add(stringBuffer.toString()); + } + return result; + } + + @Override + public String toString() { + return pattern; + } +} diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java new file mode 100644 index 00000000..e04f0c4b --- /dev/null +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java @@ -0,0 +1,38 @@ +package com.alibaba.otter.canal.protocol.aviater; + +import java.util.Map; + +import org.apache.oro.text.regex.MalformedPatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternCompiler; +import org.apache.oro.text.regex.Perl5Compiler; + +import com.google.common.base.Function; +import com.google.common.collect.MapMaker; +import com.google.common.collect.MigrateMap; + +public class PatternUtils { + @SuppressWarnings("deprecation") + private static Map patterns = MigrateMap.makeComputingMap(new MapMaker().softValues(), + new Function() { + + public Pattern apply(String pattern) { + try { + PatternCompiler pc = new Perl5Compiler(); + return pc.compile(pattern, + Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.READ_ONLY_MASK + | Perl5Compiler.SINGLELINE_MASK); + } catch (MalformedPatternException e) { + throw new RuntimeException(e); + } + } + }); + + public static Pattern getPattern(String pattern) { + return patterns.get(pattern); + } + + public static void clear() { + patterns.clear(); + } +} diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java new file mode 100644 index 00000000..be084c25 --- /dev/null +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java @@ -0,0 +1,31 @@ +package com.alibaba.otter.canal.protocol.aviater; + +import java.util.Map; + +import org.apache.oro.text.regex.Perl5Matcher; + +import com.googlecode.aviator.runtime.function.AbstractFunction; +import com.googlecode.aviator.runtime.function.FunctionUtils; +import com.googlecode.aviator.runtime.type.AviatorBoolean; +import com.googlecode.aviator.runtime.type.AviatorObject; + +/** + * 提供aviator regex的代码扩展 + * + * @author jianghang 2012-7-23 上午10:29:23 + */ +public class RegexFunction extends AbstractFunction { + + public AviatorObject call(Map env, AviatorObject arg1, AviatorObject arg2) { + String pattern = FunctionUtils.getStringValue(arg1, env); + String text = FunctionUtils.getStringValue(arg2, env); + Perl5Matcher matcher = new Perl5Matcher(); + boolean isMatch = matcher.matches(text, PatternUtils.getPattern(pattern)); + return AviatorBoolean.valueOf(isMatch); + } + + public String getName() { + return "regex"; + } + +} From eac3f5ce5fa2152136e525e1909c91d7373381f8 Mon Sep 17 00:00:00 2001 From: machey Date: Mon, 10 Dec 2018 23:54:07 +0800 Subject: [PATCH 07/21] =?UTF-8?q?=E4=BF=AE=E6=94=B9convert=E6=97=B6?= =?UTF-8?q?=E8=AE=BE=E7=BD=AEpk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/protocol/FlatMessage.java | 35 ++++----- .../alibaba/otter/canal/protocol/Message.java | 77 +++++++++---------- 2 files changed, 49 insertions(+), 63 deletions(-) diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 263b0d6e..53924d4a 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -5,9 +5,9 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import org.apache.commons.lang.StringUtils; +import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import com.google.protobuf.ByteString; /** @@ -18,12 +18,12 @@ public class FlatMessage implements Serializable { private static final long serialVersionUID = -3386650678735860050L; - private static ConcurrentMap schemaTabPk = new ConcurrentHashMap<>(); private static ConcurrentHashMap regexFilters = new ConcurrentHashMap<>(); private long id; private String database; private String table; + private String pk; private Boolean isDdl; private String type; // binlog executeTime @@ -66,6 +66,14 @@ public class FlatMessage implements Serializable { this.table = table; } + public String getPk() { + return pk; + } + + public void setPk(String pk) { + this.pk = pk; + } + public Boolean getIsDdl() { return isDdl; } @@ -222,6 +230,9 @@ public class FlatMessage implements Serializable { } for (CanalEntry.Column column : columns) { + if (flatMessage.getPk() == null && column.getIsKey()) { + flatMessage.setPk(column.getName()); + } sqlType.put(column.getName(), column.getSqlType()); mysqlType.put(column.getName(), column.getMysqlType()); if (column.getIsNull()) { @@ -325,25 +336,7 @@ public class FlatMessage implements Serializable { partitionMessages[0] = flatMessage; } else { if (pk == null) { - pk = schemaTabPk.get(database + "." + table); - if (pk == null) { - // 如果未指定主键(通配符主键),从原生message中取主键字段 - CanalEntry.Entry entry = flatMessage.getEntry(); - CanalEntry.RowChange rowChange; - try { - rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } - CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getIsKey()) { - pk = column.getName(); - schemaTabPk.putIfAbsent(database + "." + table, pk); - break; - } - } - } + pk = flatMessage.getPk(); } if (pk == null || !flatMessage.getData().get(0).containsKey(pk)) { // 如果都没有匹配的主键,发送到第一个分区 diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index 00837fec..6fc42a41 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -4,14 +4,13 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import com.alibaba.otter.canal.common.utils.CanalToStringStyle; import com.alibaba.otter.canal.protocol.CanalEntry.Entry; +import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; @@ -23,7 +22,6 @@ public class Message implements Serializable { private static final long serialVersionUID = 1234034768477580009L; - private static ConcurrentMap schemaTabPk = new ConcurrentHashMap<>(); private static ConcurrentHashMap regexFilters = new ConcurrentHashMap<>(); private long id; @@ -173,55 +171,50 @@ public class Message implements Serializable { if (!isMatch) { // 如果都没有匹配,发送到第一个分区 partitionEntries[0].add(entry); - } else { - if (pk == null) { - pk = schemaTabPk.get(database + "." + table); - if (pk == null) { - // 如果未指定主键(通配符主键),取主键字段 - try { - rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); + } + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + boolean hasOldPk = false; + // 如果before中有pk值说明主键有修改, 以旧的主键值hash为准 + for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { + CanalEntry.Column pkColumn = null; + if (pk != null) { + if (column.getName().equalsIgnoreCase(pk)) { + pkColumn = column; } - CanalEntry.RowData rowData = rowChange.getRowDatasList().get(0); - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getIsKey()) { - pk = column.getName(); - schemaTabPk.putIfAbsent(database + "." + table, pk); - break; - } + } else { + if (column.getIsKey()) { + pkColumn = column; } } + if (pkColumn != null) { + int hash = pkColumn.getValue().hashCode(); + int pkHash = Math.abs(hash) % partitionsNum; + pkHash = Math.abs(pkHash); + partitionEntries[pkHash].add(entry); + hasOldPk = true; + break; + } } - } - if (pk == null) { - // 如果都没有匹配的主键,发送到第一个分区 - partitionEntries[0].add(entry); - } else { - for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { - boolean hasOldPk = false; - // 如果before中有pk值说明主键有修改, 以旧的主键值hash为准 - for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { - if (column.getName().equalsIgnoreCase(pk)) { - int hash = column.getValue().hashCode(); + if (!hasOldPk) { + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + CanalEntry.Column pkColumn = null; + if (pk != null) { + if (column.getName().equalsIgnoreCase(pk)) { + pkColumn = column; + } + } else { + if (column.getIsKey()) { + pkColumn = column; + } + } + if (pkColumn != null) { + int hash = pkColumn.getValue().hashCode(); int pkHash = Math.abs(hash) % partitionsNum; pkHash = Math.abs(pkHash); partitionEntries[pkHash].add(entry); - hasOldPk = true; break; } } - if (!hasOldPk) { - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getName().equalsIgnoreCase(pk)) { - int hash = column.getValue().hashCode(); - int pkHash = Math.abs(hash) % partitionsNum; - pkHash = Math.abs(pkHash); - partitionEntries[pkHash].add(entry); - break; - } - } - } } } } From aeecf87490eee5dc297dfea7d3b5f25c660d8a80 Mon Sep 17 00:00:00 2001 From: machey Date: Tue, 11 Dec 2018 00:09:12 +0800 Subject: [PATCH 08/21] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=AF=B9before?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=96=AD=EF=BC=8C=E5=88=86=E5=8C=BA=E5=8F=91?= =?UTF-8?q?=E9=80=81=E7=9A=84=E8=A1=A8pk=E4=B8=8D=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/protocol/FlatMessage.java | 12 +--- .../alibaba/otter/canal/protocol/Message.java | 58 +++++++------------ 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 53924d4a..f46c26a4 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -344,17 +344,7 @@ public class FlatMessage implements Serializable { } else { int idx = 0; for (Map row : flatMessage.getData()) { - Map o = null; - if (flatMessage.getOld() != null) { - o = flatMessage.getOld().get(idx); - } - String value; - // 如果old中有pk值说明主键有修改, 以旧的主键值hash为准 - if (o != null && o.containsKey(pk)) { - value = o.get(pk); - } else { - value = row.get(pk); - } + String value = row.get(pk); if (value == null) { value = ""; } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index 6fc42a41..1013bfe6 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -173,48 +173,30 @@ public class Message implements Serializable { partitionEntries[0].add(entry); } for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { - boolean hasOldPk = false; - // 如果before中有pk值说明主键有修改, 以旧的主键值hash为准 - for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { - CanalEntry.Column pkColumn = null; - if (pk != null) { - if (column.getName().equalsIgnoreCase(pk)) { - pkColumn = column; - } - } else { - if (column.getIsKey()) { - pkColumn = column; - } - } - if (pkColumn != null) { - int hash = pkColumn.getValue().hashCode(); - int pkHash = Math.abs(hash) % partitionsNum; - pkHash = Math.abs(pkHash); - partitionEntries[pkHash].add(entry); - hasOldPk = true; - break; - } - } - if (!hasOldPk) { + String pkValue = null; + if (pk != null) { for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - CanalEntry.Column pkColumn = null; - if (pk != null) { - if (column.getName().equalsIgnoreCase(pk)) { - pkColumn = column; - } - } else { - if (column.getIsKey()) { - pkColumn = column; - } - } - if (pkColumn != null) { - int hash = pkColumn.getValue().hashCode(); - int pkHash = Math.abs(hash) % partitionsNum; - pkHash = Math.abs(pkHash); - partitionEntries[pkHash].add(entry); + if (column.getName().equalsIgnoreCase(pk)) { + pkValue = column.getValue(); break; } } + } else { + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getIsKey()) { + pkValue = column.getValue(); + break; + } + } + } + if (pkValue != null) { + int hash = pkValue.hashCode(); + int pkHash = Math.abs(hash) % partitionsNum; + pkHash = Math.abs(pkHash); + partitionEntries[pkHash].add(entry); + } else { + // 没有找到主键 + partitionEntries[0].add(entry); } } } From 277a273ebc71ada14372698fcdbd83ff120d45bd Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 11 Dec 2018 13:40:32 +0800 Subject: [PATCH 09/21] fixed pull #1266 , optimizer code --- .../resources/example/instance.properties | 2 +- .../otter/canal/filter/PatternUtils.java | 9 +- .../filter/aviater/AviaterRegexFilter.java | 1 + .../canal/filter/aviater/RegexFunction.java | 2 +- .../otter/canal/protocol/FlatMessage.java | 302 ++----------- .../alibaba/otter/canal/protocol/Message.java | 134 +----- .../protocol/aviater/AviaterRegexFilter.java | 124 ------ .../canal/protocol/aviater/PatternUtils.java | 38 -- .../canal/protocol/aviater/RegexFunction.java | 31 -- .../otter/canal/common/MQMessageUtils.java | 405 ++++++++++++++++++ .../otter/canal/common/MQProperties.java | 1 - .../otter/canal/kafka/CanalKafkaProducer.java | 17 +- .../canal/rocketmq/CanalRocketMQProducer.java | 27 +- 13 files changed, 464 insertions(+), 629 deletions(-) delete mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java delete mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java delete mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/common/MQMessageUtils.java diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index 8d884abd..2a1d8aac 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -48,5 +48,5 @@ canal.mq.topic=example canal.mq.partition=0 # hash partition config #canal.mq.partitionsNum=3 -#canal.mq.partitionHash=.*\\..*.$pk$ +#canal.mq.partitionHash=test.table#id^name,.*\\..* ################################################# diff --git a/filter/src/main/java/com/alibaba/otter/canal/filter/PatternUtils.java b/filter/src/main/java/com/alibaba/otter/canal/filter/PatternUtils.java index d7f27503..52b192d0 100644 --- a/filter/src/main/java/com/alibaba/otter/canal/filter/PatternUtils.java +++ b/filter/src/main/java/com/alibaba/otter/canal/filter/PatternUtils.java @@ -7,17 +7,10 @@ import org.apache.oro.text.regex.Pattern; import org.apache.oro.text.regex.PatternCompiler; import org.apache.oro.text.regex.Perl5Compiler; -import com.alibaba.otter.canal.filter.exception.CanalFilterException; import com.google.common.base.Function; import com.google.common.collect.MapMaker; import com.google.common.collect.MigrateMap; -/** - * 提供{@linkplain Pattern}的lazy get处理 - * - * @author jianghang 2013-1-22 下午09:36:44 - * @version 1.0.0 - */ public class PatternUtils { @SuppressWarnings("deprecation") @@ -32,7 +25,7 @@ public class PatternUtils { | Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); } catch (MalformedPatternException e) { - throw new CanalFilterException(e); + throw new RuntimeException(e); } } }); diff --git a/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/AviaterRegexFilter.java b/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/AviaterRegexFilter.java index 026fe006..4eb0dffa 100644 --- a/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/AviaterRegexFilter.java +++ b/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/AviaterRegexFilter.java @@ -129,4 +129,5 @@ public class AviaterRegexFilter implements CanalEventFilter { public String toString() { return pattern; } + } diff --git a/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/RegexFunction.java b/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/RegexFunction.java index ec389416..72f2136d 100644 --- a/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/RegexFunction.java +++ b/filter/src/main/java/com/alibaba/otter/canal/filter/aviater/RegexFunction.java @@ -12,7 +12,7 @@ import com.googlecode.aviator.runtime.type.AviatorObject; /** * 提供aviator regex的代码扩展 - * + * * @author jianghang 2012-7-23 上午10:29:23 */ public class RegexFunction extends AbstractFunction { diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index f46c26a4..50e1cb3f 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -1,14 +1,10 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.List; +import java.util.Map; -import org.apache.commons.lang.StringUtils; - -import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; -import com.google.protobuf.ByteString; +import com.google.common.collect.Lists; /** * @author machengyuan 2018-9-13 下午10:31:14 @@ -16,27 +12,22 @@ import com.google.protobuf.ByteString; */ public class FlatMessage implements Serializable { - private static final long serialVersionUID = -3386650678735860050L; - - private static ConcurrentHashMap regexFilters = new ConcurrentHashMap<>(); - - private long id; - private String database; - private String table; - private String pk; - private Boolean isDdl; - private String type; + private static final long serialVersionUID = -3386650678735860050L; + private long id; + private String database; + private String table; + private List pkNames; + private Boolean isDdl; + private String type; // binlog executeTime - private Long es; + private Long es; // dml build timeStamp - private Long ts; - private String sql; - private Map sqlType; - private Map mysqlType; - private List> data; - private List> old; - - private transient CanalEntry.Entry entry; // 所属entry + private Long ts; + private String sql; + private Map sqlType; + private Map mysqlType; + private List> data; + private List> old; public FlatMessage(long id){ this.id = id; @@ -66,12 +57,19 @@ public class FlatMessage implements Serializable { this.table = table; } - public String getPk() { - return pk; + public List getPkNames() { + return pkNames; } - public void setPk(String pk) { - this.pk = pk; + public void addPkName(String pkName) { + if (this.pkNames == null) { + this.pkNames = Lists.newArrayList(); + } + this.pkNames.add(pkName); + } + + public void setPkNames(List pkNames) { + this.pkNames = pkNames; } public Boolean getIsDdl() { @@ -146,250 +144,6 @@ public class FlatMessage implements Serializable { this.es = es; } - public CanalEntry.Entry getEntry() { - return entry; - } - - public void setEntry(CanalEntry.Entry entry) { - this.entry = entry; - } - - /** - * 将Message转换为FlatMessage - * - * @param message 原生message - * @return FlatMessage列表 - */ - public static List messageConverter(Message message) { - try { - if (message == null) { - return null; - } - - 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(); - } - - for (CanalEntry.Entry entry : entrys) { - 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.setEs(entry.getHeader().getExecuteTime()); - flatMessage.setTs(System.currentTimeMillis()); - flatMessage.setSql(rowChange.getSql()); - flatMessage.setEntry(entry); - - if (!rowChange.getIsDdl()) { - Map sqlType = new LinkedHashMap<>(); - Map mysqlType = new LinkedHashMap<>(); - List> data = new ArrayList<>(); - List> old = new ArrayList<>(); - - Set updateSet = new HashSet<>(); - for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { - if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE - && eventType != CanalEntry.EventType.DELETE) { - continue; - } - - Map row = new LinkedHashMap<>(); - List columns; - - if (eventType == CanalEntry.EventType.DELETE) { - columns = rowData.getBeforeColumnsList(); - } else { - columns = rowData.getAfterColumnsList(); - } - - for (CanalEntry.Column column : columns) { - if (flatMessage.getPk() == null && column.getIsKey()) { - flatMessage.setPk(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()); - } - } - if (!row.isEmpty()) { - data.add(row); - } - - if (eventType == CanalEntry.EventType.UPDATE) { - Map rowOld = new LinkedHashMap<>(); - for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { - if (updateSet.contains(column.getName())) { - if (column.getIsNull()) { - rowOld.put(column.getName(), null); - } else { - 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); - } - } - - /** - * 将FlatMessage按指定的字段值hash拆分 - * - * @param flatMessage flatMessage - * @param partitionsNum 分区数量 - * @param pkHashConfigs hash映射 - * @return 拆分后的flatMessage数组 - */ - public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum, String pkHashConfigs) { - if (partitionsNum == null) { - partitionsNum = 1; - } - FlatMessage[] partitionMessages = new FlatMessage[partitionsNum]; - - if (flatMessage.getIsDdl()) { - partitionMessages[0] = flatMessage; - } else { - if (flatMessage.getData() != null && !flatMessage.getData().isEmpty()) { - String database = flatMessage.getDatabase(); - String table = flatMessage.getTable(); - - String pk = null; - boolean isMatch = false; - - String[] pkHashConfigArray = StringUtils.split(pkHashConfigs, ","); - for (String pkHashConfig : pkHashConfigArray) { - int i = pkHashConfig.lastIndexOf("."); - if (!pkHashConfig.endsWith(".$pk$")) { - // 如果指定了主键 - pk = pkHashConfig.substring(i + 1); - } - pkHashConfig = pkHashConfig.substring(0, i); - - AviaterRegexFilter aviaterRegexFilter = regexFilters.get(pkHashConfig); - if (aviaterRegexFilter == null) { - aviaterRegexFilter = new AviaterRegexFilter(pkHashConfig); - regexFilters.putIfAbsent(pkHashConfig, aviaterRegexFilter); - } - - isMatch = aviaterRegexFilter.filter(database + "." + table); - if (isMatch) { - break; - } - } - - if (!isMatch) { - // 如果都没有匹配,发送到第一个分区 - partitionMessages[0] = flatMessage; - } else { - if (pk == null) { - pk = flatMessage.getPk(); - } - if (pk == null || !flatMessage.getData().get(0).containsKey(pk)) { - // 如果都没有匹配的主键,发送到第一个分区 - partitionMessages[0] = flatMessage; - } else { - int idx = 0; - for (Map row : flatMessage.getData()) { - String value = row.get(pk); - if (value == null) { - value = ""; - } - int hash = value.hashCode(); - int pkHash = Math.abs(hash) % partitionsNum; - // math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序 - pkHash = Math.abs(pkHash); - - 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()); - flatMessageTmp.setEs(flatMessage.getEs()); - flatMessageTmp.setTs(flatMessage.getTs()); - } - List> data = flatMessageTmp.getData(); - if (data == null) { - data = new ArrayList<>(); - flatMessageTmp.setData(data); - } - data.add(row); - if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) { - List> 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=" diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index 1013bfe6..3fa691ad 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -3,16 +3,12 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import com.alibaba.otter.canal.common.utils.CanalToStringStyle; import com.alibaba.otter.canal.protocol.CanalEntry.Entry; -import com.alibaba.otter.canal.protocol.aviater.AviaterRegexFilter; import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; /** * @author zebin.xuzb @ 2012-6-19 @@ -20,16 +16,13 @@ import com.google.protobuf.InvalidProtocolBufferException; */ public class Message implements Serializable { - private static final long serialVersionUID = 1234034768477580009L; - - private static ConcurrentHashMap regexFilters = new ConcurrentHashMap<>(); - - private long id; - private List entries = new ArrayList(); + private static final long serialVersionUID = 1234034768477580009L; + private long id; + private List entries = new ArrayList(); // row data for performance, see: // https://github.com/alibaba/canal/issues/726 - private boolean raw = true; - private List rawEntries = new ArrayList(); + private boolean raw = true; + private List rawEntries = new ArrayList(); public Message(long id, List entries){ this.id = id; @@ -95,121 +88,4 @@ public class Message implements Serializable { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } - /** - * 将 message 分区 - * - * @param partitionsNum 分区数 - * @param pkHashConfigs 分区库表主键正则表达式 - * @return 分区message数组 - */ - @SuppressWarnings("unchecked") - public Message[] messagePartition(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<>(); - } - - List entries; - if (this.isRaw()) { - List rawEntries = this.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 = this.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()) { - partitionEntries[0].add(entry); - } else { - if (rowChange.getRowDatasList() != null && !rowChange.getRowDatasList().isEmpty()) { - String database = entry.getHeader().getSchemaName(); - String table = entry.getHeader().getTableName(); - - String pk = null; - boolean isMatch = false; - - String[] pkHashConfigArray = StringUtils.split(pkHashConfigs, ","); - for (String pkHashConfig : pkHashConfigArray) { - int i = pkHashConfig.lastIndexOf("."); - if (!pkHashConfig.endsWith(".$pk$")) { - // 如果指定了主键 - pk = pkHashConfig.substring(i + 1); - } - pkHashConfig = pkHashConfig.substring(0, i); - - AviaterRegexFilter aviaterRegexFilter = regexFilters.get(pkHashConfig); - if (aviaterRegexFilter == null) { - aviaterRegexFilter = new AviaterRegexFilter(pkHashConfig); - regexFilters.putIfAbsent(pkHashConfig, aviaterRegexFilter); - } - - isMatch = aviaterRegexFilter.filter(database + "." + table); - if (isMatch) { - break; - } - } - - if (!isMatch) { - // 如果都没有匹配,发送到第一个分区 - partitionEntries[0].add(entry); - } - for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { - String pkValue = null; - if (pk != null) { - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getName().equalsIgnoreCase(pk)) { - pkValue = column.getValue(); - break; - } - } - } else { - for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (column.getIsKey()) { - pkValue = column.getValue(); - break; - } - } - } - if (pkValue != null) { - int hash = pkValue.hashCode(); - int pkHash = Math.abs(hash) % partitionsNum; - pkHash = Math.abs(pkHash); - partitionEntries[pkHash].add(entry); - } else { - // 没有找到主键 - partitionEntries[0].add(entry); - } - } - } - } - } - - for (int i = 0; i < partitionsNum; i++) { - List entriesTmp = partitionEntries[i]; - if (!entriesTmp.isEmpty()) { - partitionMessages[i] = new Message(this.id, entriesTmp); - } - } - - return partitionMessages; - } } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java deleted file mode 100644 index db7750ba..00000000 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/AviaterRegexFilter.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.alibaba.otter.canal.protocol.aviater; - -import java.util.*; - -import org.apache.commons.lang.StringUtils; - -import com.googlecode.aviator.AviatorEvaluator; -import com.googlecode.aviator.Expression; - -/** - * 基于aviater进行tableName正则匹配的过滤算法 - * - * @author jianghang 2012-7-20 下午06:01:34 - */ -public class AviaterRegexFilter { - - private static final String SPLIT = ","; - private static final String PATTERN_SPLIT = "|"; - private static final String FILTER_EXPRESSION = "regex(pattern,target)"; - private static final RegexFunction regexFunction = new RegexFunction(); - private final Expression exp = AviatorEvaluator.compile(FILTER_EXPRESSION, true); - static { - AviatorEvaluator.addFunction(regexFunction); - } - - private static final Comparator COMPARATOR = new StringComparator(); - - final private String pattern; - final private boolean defaultEmptyValue; - - public AviaterRegexFilter(String pattern){ - this(pattern, true); - } - - public AviaterRegexFilter(String pattern, boolean defaultEmptyValue){ - this.defaultEmptyValue = defaultEmptyValue; - List list = null; - if (StringUtils.isEmpty(pattern)) { - list = new ArrayList(); - } else { - String[] ss = StringUtils.split(pattern, SPLIT); - list = Arrays.asList(ss); - } - - // 对pattern按照从长到短的排序 - // 因为 foo|foot 匹配 foot 会出错,原因是 foot 匹配了 foo 之后,会返回 foo,但是 foo 的长度和 foot - // 的长度不一样 - Collections.sort(list, COMPARATOR); - // 对pattern进行头尾完全匹配 - list = completionPattern(list); - this.pattern = StringUtils.join(list, PATTERN_SPLIT); - } - - public boolean filter(String filtered) { - if (StringUtils.isEmpty(pattern)) { - return defaultEmptyValue; - } - - if (StringUtils.isEmpty(filtered)) { - return defaultEmptyValue; - } - - Map env = new HashMap(); - env.put("pattern", pattern); - env.put("target", filtered.toLowerCase()); - return (Boolean) exp.execute(env); - } - - /** - * 修复正则表达式匹配的问题,因为使用了 oro 的 matches,会出现: - * - *
-     * foo|foot 匹配 foot 出错,原因是 foot 匹配了 foo 之后,会返回 foo,但是 foo 的长度和 foot 的长度不一样
-     * 
- * - * 因此此类对正则表达式进行了从长到短的排序 - * - * @author zebin.xuzb 2012-10-22 下午2:02:26 - * @version 1.0.0 - */ - private static class StringComparator implements Comparator { - - @Override - public int compare(String str1, String str2) { - if (str1.length() > str2.length()) { - return -1; - } else if (str1.length() < str2.length()) { - return 1; - } else { - return 0; - } - } - } - - /** - * 修复正则表达式匹配的问题,即使按照长度递减排序,还是会出现以下问题: - * - *
-     * foooo|f.*t 匹配 fooooot 出错,原因是 fooooot 匹配了 foooo 之后,会将 fooo 和数据进行匹配,但是 foooo 的长度和 fooooot 的长度不一样
-     * 
- * - * 因此此类对正则表达式进行头尾完全匹配 - * - * @author simon - * @version 1.0.0 - */ - - private List completionPattern(List patterns) { - List result = new ArrayList(); - for (String pattern : patterns) { - StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append("^"); - stringBuffer.append(pattern); - stringBuffer.append("$"); - result.add(stringBuffer.toString()); - } - return result; - } - - @Override - public String toString() { - return pattern; - } -} diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java deleted file mode 100644 index e04f0c4b..00000000 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/PatternUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.alibaba.otter.canal.protocol.aviater; - -import java.util.Map; - -import org.apache.oro.text.regex.MalformedPatternException; -import org.apache.oro.text.regex.Pattern; -import org.apache.oro.text.regex.PatternCompiler; -import org.apache.oro.text.regex.Perl5Compiler; - -import com.google.common.base.Function; -import com.google.common.collect.MapMaker; -import com.google.common.collect.MigrateMap; - -public class PatternUtils { - @SuppressWarnings("deprecation") - private static Map patterns = MigrateMap.makeComputingMap(new MapMaker().softValues(), - new Function() { - - public Pattern apply(String pattern) { - try { - PatternCompiler pc = new Perl5Compiler(); - return pc.compile(pattern, - Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.READ_ONLY_MASK - | Perl5Compiler.SINGLELINE_MASK); - } catch (MalformedPatternException e) { - throw new RuntimeException(e); - } - } - }); - - public static Pattern getPattern(String pattern) { - return patterns.get(pattern); - } - - public static void clear() { - patterns.clear(); - } -} diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java deleted file mode 100644 index be084c25..00000000 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/aviater/RegexFunction.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.alibaba.otter.canal.protocol.aviater; - -import java.util.Map; - -import org.apache.oro.text.regex.Perl5Matcher; - -import com.googlecode.aviator.runtime.function.AbstractFunction; -import com.googlecode.aviator.runtime.function.FunctionUtils; -import com.googlecode.aviator.runtime.type.AviatorBoolean; -import com.googlecode.aviator.runtime.type.AviatorObject; - -/** - * 提供aviator regex的代码扩展 - * - * @author jianghang 2012-7-23 上午10:29:23 - */ -public class RegexFunction extends AbstractFunction { - - public AviatorObject call(Map env, AviatorObject arg1, AviatorObject arg2) { - String pattern = FunctionUtils.getStringValue(arg1, env); - String text = FunctionUtils.getStringValue(arg2, env); - Perl5Matcher matcher = new Perl5Matcher(); - boolean isMatch = matcher.matches(text, PatternUtils.getPattern(pattern)); - return AviatorBoolean.valueOf(isMatch); - } - - public String getName() { - return "regex"; - } - -} 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 new file mode 100644 index 00000000..c99df2f4 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQMessageUtils.java @@ -0,0 +1,405 @@ +package com.alibaba.otter.canal.common; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.CanalEntry.Entry; +import com.alibaba.otter.canal.protocol.FlatMessage; +import com.alibaba.otter.canal.protocol.Message; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; +import com.google.common.collect.MigrateMap; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +/** + * process MQ Message utils + * + * @author agapple 2018年12月11日 下午1:28:32 + */ +public class MQMessageUtils { + + @SuppressWarnings("deprecation") + private static Map> partitionDatas = MigrateMap.makeComputingMap(new MapMaker().softValues(), + new Function>() { + + public List apply(String pkHashConfigs) { + List datas = Lists.newArrayList(); + String[] pkHashConfigArray = StringUtils.split(pkHashConfigs, + ","); + // schema.table#id^name + for (String pkHashConfig : pkHashConfigArray) { + PartitionData data = new PartitionData(); + int i = pkHashConfig.lastIndexOf("#"); + if (i > 0) { + data.pkNames = Lists.newArrayList(StringUtils.split(pkHashConfig.substring(i + 1), + '^')); + pkHashConfig = pkHashConfig.substring(0, + i); + } + if (!isWildCard(pkHashConfig)) { + data.simpleName = pkHashConfig; + } else { + data.regexFilter = new AviaterRegexFilter(pkHashConfig); + } + datas.add(data); + } + + return datas; + } + }); + + /** + * 将 message 分区 + * + * @param partitionsNum 分区数 + * @param pkHashConfigs 分区库表主键正则表达式 + * @return 分区message数组 + */ + @SuppressWarnings("unchecked") + public static Message[] messagePartition(Message message, 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<>(); + } + + 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); + } + + if (rowChange.getIsDdl()) { + partitionEntries[0].add(entry); + } else { + if (rowChange.getRowDatasList() != null && !rowChange.getRowDatasList().isEmpty()) { + String database = entry.getHeader().getSchemaName(); + String table = entry.getHeader().getTableName(); + List pkNames = getParitionHashColumns(database + "." + table, pkHashConfigs); + + if (pkNames == null) { + // 如果都没有匹配,发送到第一个分区 + partitionEntries[0].add(entry); + } else { + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + int hashCode = table.hashCode(); + if (pkNames.isEmpty()) { + // isEmpty use default pkNames + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (column.getIsKey()) { + hashCode = hashCode ^ column.getValue().hashCode(); + } + } + } else { + for (CanalEntry.Column column : rowData.getAfterColumnsList()) { + if (checkPkNamesHasContain(pkNames, column.getName())) { + hashCode = hashCode ^ column.getValue().hashCode(); + } + } + } + + int pkHash = Math.abs(hashCode) % partitionsNum; + pkHash = Math.abs(pkHash); + partitionEntries[pkHash].add(entry); + } + } + } + } + } + + for (int i = 0; i < partitionsNum; i++) { + List entriesTmp = partitionEntries[i]; + if (!entriesTmp.isEmpty()) { + partitionMessages[i] = new Message(message.getId(), entriesTmp); + } + } + + return partitionMessages; + } + + /** + * 将Message转换为FlatMessage + * + * @param message 原生message + * @return FlatMessage列表 + */ + public static List messageConverter(Message message) { + try { + if (message == null) { + return null; + } + + 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(); + } + + for (CanalEntry.Entry entry : entrys) { + 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.setEs(entry.getHeader().getExecuteTime()); + flatMessage.setTs(System.currentTimeMillis()); + flatMessage.setSql(rowChange.getSql()); + + if (!rowChange.getIsDdl()) { + Map sqlType = new LinkedHashMap<>(); + Map mysqlType = new LinkedHashMap<>(); + List> data = new ArrayList<>(); + List> old = new ArrayList<>(); + + Set updateSet = new HashSet<>(); + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE + && eventType != CanalEntry.EventType.DELETE) { + continue; + } + + Map row = new LinkedHashMap<>(); + List columns; + + if (eventType == CanalEntry.EventType.DELETE) { + columns = rowData.getBeforeColumnsList(); + } else { + columns = rowData.getAfterColumnsList(); + } + + for (CanalEntry.Column column : columns) { + if (column.getIsKey()) { + 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()); + } + } + if (!row.isEmpty()) { + data.add(row); + } + + if (eventType == CanalEntry.EventType.UPDATE) { + Map rowOld = new LinkedHashMap<>(); + for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { + if (updateSet.contains(column.getName())) { + if (column.getIsNull()) { + rowOld.put(column.getName(), null); + } else { + 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); + } + } + + /** + * 将FlatMessage按指定的字段值hash拆分 + * + * @param flatMessage flatMessage + * @param partitionsNum 分区数量 + * @param pkHashConfigs hash映射 + * @return 拆分后的flatMessage数组 + */ + public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum, String pkHashConfigs) { + if (partitionsNum == null) { + partitionsNum = 1; + } + FlatMessage[] partitionMessages = new FlatMessage[partitionsNum]; + + if (flatMessage.getIsDdl()) { + partitionMessages[0] = flatMessage; + } else { + if (flatMessage.getData() != null && !flatMessage.getData().isEmpty()) { + String database = flatMessage.getDatabase(); + String table = flatMessage.getTable(); + List pkNames = getParitionHashColumns(database + "." + table, pkHashConfigs); + if (pkNames == null) { + // 如果都没有匹配,发送到第一个分区 + partitionMessages[0] = flatMessage; + } else { + if (pkNames.isEmpty()) { + pkNames = flatMessage.getPkNames(); + } + + int hashCode = table.hashCode(); + int idx = 0; + for (Map row : flatMessage.getData()) { + for (String pkName : pkNames) { + String value = row.get(pkName); + if (value == null) { + value = ""; + } + hashCode = hashCode ^ value.hashCode(); + } + + int pkHash = Math.abs(hashCode) % partitionsNum; + // math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序 + pkHash = Math.abs(pkHash); + + 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()); + flatMessageTmp.setEs(flatMessage.getEs()); + flatMessageTmp.setTs(flatMessage.getTs()); + } + List> data = flatMessageTmp.getData(); + if (data == null) { + data = new ArrayList<>(); + flatMessageTmp.setData(data); + } + data.add(row); + if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) { + List> old = flatMessageTmp.getOld(); + if (old == null) { + old = new ArrayList<>(); + flatMessageTmp.setOld(old); + } + old.add(flatMessage.getOld().get(idx)); + } + idx++; + } + } + } + } + return partitionMessages; + } + + /** + * match return List , not match return null + */ + public static List getParitionHashColumns(String name, String pkHashConfigs) { + List datas = partitionDatas.get(pkHashConfigs); + for (PartitionData data : datas) { + if (data.simpleName != null) { + if (data.simpleName.equalsIgnoreCase(name)) { + return data.pkNames; + } + } else { + if (data.regexFilter.filter(name)) { + return data.pkNames; + } + } + } + + return null; + } + + public static boolean checkPkNamesHasContain(List pkNames, String name) { + for (String pkName : pkNames) { + if (pkName.equalsIgnoreCase(name)) { + return true; + } + } + + return false; + } + + private static boolean isWildCard(String value) { + // not contaiins '.' ? + return StringUtils.containsAny(value, new char[] { '*', '?', '+', '|', '(', ')', '{', '}', '[', ']', '\\', '$', + '^' }); + } + + public static class PartitionData { + + public String simpleName; + public AviaterRegexFilter regexFilter; + public List pkNames = Lists.newArrayList(); + } + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 7e82f9c6..6f6122ec 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -1,6 +1,5 @@ package com.alibaba.otter.canal.common; -import java.util.Map; /** * kafka 配置项 diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 0dd93ea5..91d42f24 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.otter.canal.common.MQMessageUtils; import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; @@ -88,7 +89,8 @@ public class CanalKafkaProducer implements CanalMQProducer { message); } else { if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) { - Message[] messages = message.messagePartition(canalDestination.getPartitionsNum(), + Message[] messages = MQMessageUtils.messagePartition(message, + canalDestination.getPartitionsNum(), canalDestination.getPartitionHash()); int length = messages.length; for (int i = 0; i < length; i++) { @@ -119,13 +121,12 @@ public class CanalKafkaProducer implements CanalMQProducer { } } else { // 发送扁平数据json - List flatMessages = FlatMessage.messageConverter(message); + List flatMessages = MQMessageUtils.messageConverter(message); if (flatMessages != null) { for (FlatMessage flatMessage : flatMessages) { if (canalDestination.getPartition() != null) { try { - ProducerRecord record = new ProducerRecord( - canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), canalDestination.getPartition(), null, JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); @@ -139,7 +140,7 @@ public class CanalKafkaProducer implements CanalMQProducer { } else { if (canalDestination.getPartitionHash() != null && !canalDestination.getPartitionHash().isEmpty()) { - FlatMessage[] partitionFlatMessage = FlatMessage.messagePartition(flatMessage, + FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage, canalDestination.getPartitionsNum(), canalDestination.getPartitionHash()); int length = partitionFlatMessage.length; @@ -147,8 +148,7 @@ public class CanalKafkaProducer implements CanalMQProducer { FlatMessage flatMessagePart = partitionFlatMessage[i]; if (flatMessagePart != null) { try { - ProducerRecord record = new ProducerRecord( - canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), i, null, JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue)); @@ -163,8 +163,7 @@ public class CanalKafkaProducer implements CanalMQProducer { } } else { try { - ProducerRecord record = new ProducerRecord( - canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), 0, null, JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index 56648d4e..647fdfcf 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.common.CanalMessageSerializer; +import com.alibaba.otter.canal.common.MQMessageUtils; import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.server.exception.CanalServerException; @@ -61,8 +62,8 @@ public class CanalRocketMQProducer implements CanalMQProducer { if (!mqProperties.getFlatMessage()) { try { if (destination.getPartition() != null) { - Message message = new Message(destination.getTopic(), - CanalMessageSerializer.serializer(data, mqProperties.isFilterTransactionEntry())); + Message message = new Message(destination.getTopic(), CanalMessageSerializer.serializer(data, + mqProperties.isFilterTransactionEntry())); if (logger.isDebugEnabled()) { logger.debug("send message:{} to destination:{}, partition: {}", message, @@ -82,8 +83,9 @@ public class CanalRocketMQProducer implements CanalMQProducer { }, null); } else { if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) { - com.alibaba.otter.canal.protocol.Message[] messages = data - .messagePartition(destination.getPartitionsNum(), destination.getPartitionHash()); + com.alibaba.otter.canal.protocol.Message[] messages = MQMessageUtils.messagePartition(data, + destination.getPartitionsNum(), + destination.getPartitionHash()); int length = messages.length; for (int i = 0; i < length; i++) { com.alibaba.otter.canal.protocol.Message dataPartition = messages[i]; @@ -125,7 +127,7 @@ public class CanalRocketMQProducer implements CanalMQProducer { return; } } else { - List flatMessages = FlatMessage.messageConverter(data); + List flatMessages = MQMessageUtils.messageConverter(data); if (flatMessages != null) { for (FlatMessage flatMessage : flatMessages) { if (destination.getPartition() != null) { @@ -136,8 +138,8 @@ public class CanalRocketMQProducer implements CanalMQProducer { destination.getTopic(), destination.getPartition()); } - Message message = new Message(destination.getTopic(), - JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue).getBytes()); + Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessage, + SerializerFeature.WriteMapNullValue).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override @@ -152,7 +154,7 @@ public class CanalRocketMQProducer implements CanalMQProducer { } } else { if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) { - FlatMessage[] partitionFlatMessage = FlatMessage.messagePartition(flatMessage, + FlatMessage[] partitionFlatMessage = MQMessageUtils.messagePartition(flatMessage, destination.getPartitionsNum(), destination.getPartitionHash()); int length = partitionFlatMessage.length; @@ -172,17 +174,16 @@ public class CanalRocketMQProducer implements CanalMQProducer { this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override - public MessageQueue select(List mqs, Message msg, - Object arg) { + public MessageQueue select(List mqs, Message msg, Object arg) { if (index > mqs.size()) { - throw new CanalServerException( - "partition number is error,config num:" + throw new CanalServerException("partition number is error,config num:" + destination.getPartitionsNum() + ", mq num: " + mqs.size()); } return mqs.get(index); } - }, null); + }, + null); } catch (Exception e) { logger.error("send flat message to hashed partition error", e); callback.rollback(); From 48de904ee169e1f9343026aedb82bdfd95cead15 Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 11 Dec 2018 14:43:13 +0800 Subject: [PATCH 10/21] =?UTF-8?q?=E5=8E=BB=E6=8E=89=20#1266=20=E4=B8=AD?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9A=84=20protocol=20=E7=9A=84=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- protocol/pom.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/protocol/pom.xml b/protocol/pom.xml index 0f7904d6..1b776b7e 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -26,13 +26,5 @@ commons-lang commons-lang - - oro - oro - - - com.googlecode.aviator - aviator - From 0197482618bae84bb60c9f7302ed57fca8872951 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 11 Dec 2018 20:17:08 +0800 Subject: [PATCH 11/21] fixed partitionHash format --- deployer/src/main/resources/example/instance.properties | 2 +- .../java/com/alibaba/otter/canal/common/MQMessageUtils.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index 2a1d8aac..4e09a08d 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -48,5 +48,5 @@ canal.mq.topic=example canal.mq.partition=0 # hash partition config #canal.mq.partitionsNum=3 -#canal.mq.partitionHash=test.table#id^name,.*\\..* +#canal.mq.partitionHash=test.table:id^name,.*\\..* ################################################# 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 c99df2f4..e6080eba 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 @@ -36,10 +36,10 @@ public class MQMessageUtils { List datas = Lists.newArrayList(); String[] pkHashConfigArray = StringUtils.split(pkHashConfigs, ","); - // schema.table#id^name + // schema.table:id^name for (String pkHashConfig : pkHashConfigArray) { PartitionData data = new PartitionData(); - int i = pkHashConfig.lastIndexOf("#"); + int i = pkHashConfig.lastIndexOf(":"); if (i > 0) { data.pkNames = Lists.newArrayList(StringUtils.split(pkHashConfig.substring(i + 1), '^')); From 35e0e882c10f2e49831639a2ce753b8d75ccb597 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 11 Dec 2018 20:41:21 +0800 Subject: [PATCH 12/21] fixed hash mode --- .../otter/canal/common/MQMessageUtils.java | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) 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 e6080eba..b9dbfe52 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 @@ -41,11 +41,20 @@ public class MQMessageUtils { PartitionData data = new PartitionData(); int i = pkHashConfig.lastIndexOf(":"); if (i > 0) { - data.pkNames = Lists.newArrayList(StringUtils.split(pkHashConfig.substring(i + 1), - '^')); + String pkStr = pkHashConfig.substring(i + 1); + if (pkStr.equalsIgnoreCase("$pk$")) { + data.hashMode.autoPkHash = true; + } else { + data.hashMode.pkNames = Lists.newArrayList(StringUtils.split(pkStr, + '^')); + } + pkHashConfig = pkHashConfig.substring(0, i); + } else { + data.hashMode.tableHash = true; } + if (!isWildCard(pkHashConfig)) { data.simpleName = pkHashConfig; } else { @@ -107,24 +116,23 @@ public class MQMessageUtils { if (rowChange.getRowDatasList() != null && !rowChange.getRowDatasList().isEmpty()) { String database = entry.getHeader().getSchemaName(); String table = entry.getHeader().getTableName(); - List pkNames = getParitionHashColumns(database + "." + table, pkHashConfigs); - - if (pkNames == null) { + HashMode hashMode = getParitionHashColumns(database + "." + table, pkHashConfigs); + if (hashMode == null) { // 如果都没有匹配,发送到第一个分区 partitionEntries[0].add(entry); } else { for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { int hashCode = table.hashCode(); - if (pkNames.isEmpty()) { + if (hashMode.autoPkHash) { // isEmpty use default pkNames for (CanalEntry.Column column : rowData.getAfterColumnsList()) { if (column.getIsKey()) { hashCode = hashCode ^ column.getValue().hashCode(); } } - } else { + } else if (!hashMode.tableHash) { for (CanalEntry.Column column : rowData.getAfterColumnsList()) { - if (checkPkNamesHasContain(pkNames, column.getName())) { + if (checkPkNamesHasContain(hashMode.pkNames, column.getName())) { hashCode = hashCode ^ column.getValue().hashCode(); } } @@ -299,24 +307,27 @@ public class MQMessageUtils { if (flatMessage.getData() != null && !flatMessage.getData().isEmpty()) { String database = flatMessage.getDatabase(); String table = flatMessage.getTable(); - List pkNames = getParitionHashColumns(database + "." + table, pkHashConfigs); - if (pkNames == null) { + HashMode hashMode = getParitionHashColumns(database + "." + table, pkHashConfigs); + if (hashMode == null) { // 如果都没有匹配,发送到第一个分区 partitionMessages[0] = flatMessage; } else { - if (pkNames.isEmpty()) { + List pkNames = hashMode.pkNames; + if (hashMode.autoPkHash) { pkNames = flatMessage.getPkNames(); } int hashCode = table.hashCode(); int idx = 0; for (Map row : flatMessage.getData()) { - for (String pkName : pkNames) { - String value = row.get(pkName); - if (value == null) { - value = ""; + if (!hashMode.tableHash) { + for (String pkName : pkNames) { + String value = row.get(pkName); + if (value == null) { + value = ""; + } + hashCode = hashCode ^ value.hashCode(); } - hashCode = hashCode ^ value.hashCode(); } int pkHash = Math.abs(hashCode) % partitionsNum; @@ -362,16 +373,20 @@ public class MQMessageUtils { /** * match return List , not match return null */ - public static List getParitionHashColumns(String name, String pkHashConfigs) { + public static HashMode getParitionHashColumns(String name, String pkHashConfigs) { + if (StringUtils.isEmpty(pkHashConfigs)) { + return null; + } + List datas = partitionDatas.get(pkHashConfigs); for (PartitionData data : datas) { if (data.simpleName != null) { if (data.simpleName.equalsIgnoreCase(name)) { - return data.pkNames; + return data.hashMode; } } else { if (data.regexFilter.filter(name)) { - return data.pkNames; + return data.hashMode; } } } @@ -399,7 +414,14 @@ public class MQMessageUtils { public String simpleName; public AviaterRegexFilter regexFilter; - public List pkNames = Lists.newArrayList(); + public HashMode hashMode = new HashMode(); + } + + public static class HashMode { + + public boolean autoPkHash = false; + public boolean tableHash = false; + public List pkNames = Lists.newArrayList(); } } From ba5865683fcd1ce2730cdcad88824d703292918f Mon Sep 17 00:00:00 2001 From: XiaoFu <248628578@qq.com> Date: Wed, 12 Dec 2018 10:03:49 +0800 Subject: [PATCH 13/21] Update AddressUtils.java --- .../alibaba/otter/canal/common/utils/AddressUtils.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/AddressUtils.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/AddressUtils.java index 14d1831a..e380cf3b 100644 --- a/common/src/main/java/com/alibaba/otter/canal/common/utils/AddressUtils.java +++ b/common/src/main/java/com/alibaba/otter/canal/common/utils/AddressUtils.java @@ -54,15 +54,6 @@ public class AddressUtils { public static InetAddress getHostAddress() { InetAddress localAddress = null; - try { - localAddress = InetAddress.getLocalHost(); - if (isValidHostAddress(localAddress)) { - return localAddress; - } - } catch (Throwable e) { - logger.warn("Failed to retriving local host ip address, try scan network card ip address. cause: " - + e.getMessage()); - } try { Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); if (interfaces != null) { From 5c4b1f6e6312978802c5f638c2e1ba32388e49ff Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 12 Dec 2018 10:11:06 +0800 Subject: [PATCH 14/21] modify --- .../canal/client/adapter/support/Dml.java | 9 + .../client/adapter/support/MessageUtil.java | 17 +- client-adapter/rdb/pom.xml | 5 - .../canal/client/adapter/rdb/RdbAdapter.java | 4 +- .../adapter/rdb/config/MappingConfig.java | 21 ++- .../adapter/rdb/service/RdbEtlService.java | 6 +- .../rdb/service/RdbMirrorDbSyncService.java | 42 +++-- .../adapter/rdb/service/RdbSyncService.java | 8 +- .../client/adapter/rdb/support/SyncUtil.java | 2 +- .../src/main/resources/rdb/mytest_user.yml | 1 - .../adapter/rdb/test/SqlParserTest.java | 172 +++++++++--------- 11 files changed, 158 insertions(+), 129 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java index 1d5357b4..cc19813f 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java @@ -17,6 +17,7 @@ public class Dml implements Serializable { private String destination; // 对应canal的实例或者MQ的topic private String database; // 数据库或schema private String table; // 表名 + private List pkNames; private String type; // 类型: INSERT UPDATE DELETE // binlog executeTime private Long es; // 执行耗时 @@ -50,6 +51,14 @@ public class Dml implements Serializable { this.table = table; } + public List getPkNames() { + return pkNames; + } + + public void setPkNames(List pkNames) { + this.pkNames = pkNames; + } + public String getType() { return type; } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index e40c9204..87fc2fb9 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -1,11 +1,6 @@ package com.alibaba.otter.canal.client.adapter.support; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -55,6 +50,8 @@ public class MessageUtil { if (!rowChange.getIsDdl()) { Set updateSet = new HashSet<>(); + dml.setPkNames(new ArrayList<>()); + int i = 0; for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE && eventType != CanalEntry.EventType.DELETE) { @@ -71,6 +68,11 @@ public class MessageUtil { } for (CanalEntry.Column column : columns) { + if (i == 0) { + if (column.getIsKey()) { + dml.getPkNames().add(column.getName()); + } + } row.put(column.getName(), JdbcTypeUtil.typeConvert(column.getName(), column.getValue(), @@ -101,6 +103,8 @@ public class MessageUtil { old.add(rowOld); } } + + i++; } if (!data.isEmpty()) { dml.setData(data); @@ -134,6 +138,7 @@ public class MessageUtil { dml.setDestination(destination); dml.setDatabase(flatMessage.getDatabase()); dml.setTable(flatMessage.getTable()); + dml.setPkNames(flatMessage.getPkNames()); dml.setType(flatMessage.getType()); dml.setTs(flatMessage.getTs()); dml.setEs(flatMessage.getEs()); diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml index 881b9399..211950e9 100644 --- a/client-adapter/rdb/pom.xml +++ b/client-adapter/rdb/pom.xml @@ -24,11 +24,6 @@ 1.19 provided - - com.alibaba.fastsql - fastsql - 2.0.0_preview_644 - mysql diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index cc2fd32e..afadb147 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -172,7 +172,7 @@ public class RdbAdapter implements OuterAdapter { public Map count(String task) { MappingConfig config = rdbMapping.get(task); MappingConfig.DbMapping dbMapping = config.getDbMapping(); - String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.dbTable(dbMapping); + String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping); Connection conn = null; Map res = new LinkedHashMap<>(); try { @@ -198,7 +198,7 @@ public class RdbAdapter implements OuterAdapter { } } } - res.put("targetTable", SyncUtil.dbTable(dbMapping)); + res.put("targetTable", SyncUtil.getDbTableName(dbMapping)); return res; } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index 11594cde..88c0fc84 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.client.adapter.rdb.config; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -74,19 +75,19 @@ public class MappingConfig { public static class DbMapping { - private Boolean mirrorDb = false; // 是否镜像库 - private String database; // 数据库名或schema名 - private String table; // 表名 - private Map targetPk; // 目标表主键字段 - private Boolean mapAll = false; // 映射所有字段 - private String targetDb; // 目标库名 - private String targetTable; // 目标表名 - private Map targetColumns; // 目标表字段映射 + private Boolean mirrorDb = false; // 是否镜像库 + private String database; // 数据库名或schema名 + private String table; // 表名 + private Map targetPk = new LinkedHashMap<>(); // 目标表主键字段 + private Boolean mapAll = false; // 映射所有字段 + private String targetDb; // 目标库名 + private String targetTable; // 目标表名 + private Map targetColumns; // 目标表字段映射 - private String etlCondition; // etl条件sql + private String etlCondition; // etl条件sql private int readBatch = 5000; - private int commitBatch = 5000; // etl等批量提交大小 + private int commitBatch = 5000; // etl等批量提交大小 public boolean isMirrorDb() { return mirrorDb == null ? false : mirrorDb; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java index 22f1bc03..28db80af 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java @@ -109,7 +109,7 @@ public class RdbEtlService { logger.info( dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); - etlResult.setResultMessage("导入目标表 " + SyncUtil.dbTable(dbMapping) + " 数据:" + successCount.get() + " 条"); + etlResult.setResultMessage("导入目标表 " + SyncUtil.getDbTableName(dbMapping) + " 数据:" + successCount.get() + " 条"); } catch (Exception e) { logger.error(e.getMessage(), e); errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage()); @@ -187,7 +187,7 @@ public class RdbEtlService { // } StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(SyncUtil.dbTable(dbMapping)).append(" ("); + insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" ("); columnsMap .forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); @@ -209,7 +209,7 @@ public class RdbEtlService { // 删除数据 Map values = new LinkedHashMap<>(); StringBuilder deleteSql = new StringBuilder( - "DELETE FROM " + SyncUtil.dbTable(dbMapping) + " WHERE "); + "DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE "); appendCondition(dbMapping, deleteSql, values, rs); try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) { int k = 1; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java index 7b3f9bdc..f7957c37 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java @@ -1,20 +1,14 @@ package com.alibaba.otter.canal.client.adapter.rdb.service; -import java.io.StringWriter; import java.sql.Connection; import java.sql.Statement; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; -import com.alibaba.fastsql.sql.ast.SQLName; -import com.alibaba.fastsql.sql.ast.SQLStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; -import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser; -import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor; -import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; -import com.alibaba.fastsql.sql.parser.SQLStatementParser; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,10 +18,13 @@ import com.alibaba.otter.canal.client.adapter.support.Dml; public class RdbMirrorDbSyncService { - private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class); + private static final Logger logger = LoggerFactory + .getLogger(RdbMirrorDbSyncService.class); - private Map mirrorDbConfigCache; // 镜像库配置 - private DataSource dataSource; + private Map mirrorDbConfigCache; // 镜像库配置 + private DataSource dataSource; + + private static Map tableDbConfigCache = new ConcurrentHashMap<>(); public RdbMirrorDbSyncService(Map mirrorDbConfigCache, DataSource dataSource, Integer threads){ @@ -49,6 +46,29 @@ public class RdbMirrorDbSyncService { } else { // DML // TODO + MappingConfig mappingConfig = tableDbConfigCache + .get(destination + "." + database + "." + dml.getTable()); + if (mappingConfig == null) { + // 构造一个配置 + mappingConfig = new MappingConfig(); + mappingConfig.setDataSourceKey(configMap.getDataSourceKey()); + mappingConfig.setDestination(configMap.getDestination()); + mappingConfig.setOuterAdapterKey(configMap.getOuterAdapterKey()); + mappingConfig.setConcurrent(configMap.getConcurrent()); + MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping(); + mappingConfig.setDbMapping(dbMapping); + dbMapping.setDatabase(dml.getDatabase()); + dbMapping.setTable(dml.getTable()); + dbMapping.setTargetDb(dml.getDatabase()); + dbMapping.setTargetTable(dml.getTable()); + dbMapping.setMapAll(true); + List pkNames = dml.getPkNames(); + Map pkMapping = new LinkedHashMap<>(); + pkNames.forEach(pkName -> pkMapping.put(pkName, pkName)); + dbMapping.setTargetPk(pkMapping); + + tableDbConfigCache.put(destination + "." + database + "." + dml.getTable(), mappingConfig); + } } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 38469ac4..918699f4 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -166,7 +166,7 @@ public class RdbSyncService { Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(SyncUtil.dbTable(dbMapping)).append(" ("); + insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" ("); columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); int len = insertSql.length(); @@ -230,7 +230,7 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutor.getConn(), config); StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(SyncUtil.dbTable(dbMapping)).append(" SET "); + updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping)).append(" SET "); List> values = new ArrayList<>(); for (String srcColumnName : old.keySet()) { List targetColumnNames = new ArrayList<>(); @@ -282,7 +282,7 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutor.getConn(), config); StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(SyncUtil.dbTable(dbMapping)).append(" WHERE "); + sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE "); List> values = new ArrayList<>(); // 拼接主键 @@ -315,7 +315,7 @@ public class RdbSyncService { if (columnType == null) { columnType = new LinkedHashMap<>(); final Map columnTypeTmp = columnType; - String sql = "SELECT * FROM " + SyncUtil.dbTable(dbMapping) + " WHERE 1=2"; + String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE 1=2"; Util.sqlRS(conn, sql, rs -> { try { ResultSetMetaData rsd = rs.getMetaData(); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java index 33cf5344..e8362190 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java @@ -255,7 +255,7 @@ public class SyncUtil { } } - public static String dbTable(MappingConfig.DbMapping dbMapping) { + public static String getDbTableName(MappingConfig.DbMapping dbMapping) { String result = ""; if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) { result += dbMapping.getTargetDb() + "."; diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml index 68fb859e..a8e97f6d 100644 --- a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -6,7 +6,6 @@ dbMapping: mirrorDb: true database: mytest # table: user - targetDb: mytest2 # targetTable: mytest.tb_user # targetPk: # id: id diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java index 59d0bcff..698f5059 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/SqlParserTest.java @@ -1,86 +1,86 @@ -wpackage com.alibaba.otter.canal.client.adapter.rdb.test; - -import com.alibaba.fastsql.sql.ast.SQLName; -import com.alibaba.fastsql.sql.ast.SQLStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; -import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlCreateTableParser; -import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser; -import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor; -import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; -import com.alibaba.fastsql.sql.parser.SQLStatementParser; - -import java.io.StringWriter; - -public class SqlParserTest { - - public static class TableNameVisitor extends MySqlOutputVisitor { - - public TableNameVisitor(Appendable appender){ - super(appender); - } - - @Override - public boolean visit(SQLExprTableSource x) { - SQLName table = (SQLName) x.getExpr(); - String tableName = table.getSimpleName(); - - // 改写tableName - print0("new_" + tableName.toUpperCase()); - - return true; - } - - } - - public static void main(String[] args) { - // String sql = "select * from `mytest`.`t` where id=1 and name=ming group by - // uid limit 1,200 order by ctime"; - - String sql = "CREATE TABLE `mytest`.`user` (\n" + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" - + " `name` varchar(30) NOT NULL,\n" + " `c_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" - + " `role_id` bigint(20) DEFAULT NULL,\n" + " `test1` text,\n" + " `test2` blob,\n" - + " `key` varchar(30) DEFAULT NULL,\n" + " PRIMARY KEY (`id`)\n" - + ") ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;"; - - // // 新建 MySQL Parser - // SQLStatementParser parser = new MySqlStatementParser(sql); - // - // // 使用Parser解析生成AST,这里SQLStatement就是AST - // SQLStatement sqlStatement = parser.parseStatement(); - // - // MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); - // sqlStatement.accept(visitor); - // - // System.out.println("getTables:" + visitor.getTables()); - // System.out.println("getParameters:" + visitor.getParameters()); - // System.out.println("getOrderByColumns:" + visitor.getOrderByColumns()); - // System.out.println("getGroupByColumns:" + visitor.getGroupByColumns()); - // System.out.println("---------------------------------------------------------------------------"); - // - // // 使用select访问者进行select的关键信息打印 - // // SelectPrintVisitor selectPrintVisitor = new SelectPrintVisitor(); - // // sqlStatement.accept(selectPrintVisitor); - // - // System.out.println("---------------------------------------------------------------------------"); - // // 最终sql输出 - // StringWriter out = new StringWriter(); - // TableNameVisitor outputVisitor = new TableNameVisitor(out); - // sqlStatement.accept(outputVisitor); - // System.out.println(out.toString()); - - MySqlCreateTableParser parser1 = new MySqlCreateTableParser(sql); - SQLCreateTableStatement createTableStatement = parser1.parseCreateTable(); -// MySqlSchemaStatVisitor visitor1 = new MySqlSchemaStatVisitor(); -// createTableStatement.accept(visitor1); - // visitor1.getTables().forEach((k, v) -> { - // System.out.println(k.); - // System.out.println(v); - // }); - // 最终sql输出 - StringWriter out = new StringWriter(); - TableNameVisitor outputVisitor = new TableNameVisitor(out); - createTableStatement.accept(outputVisitor); - System.out.println(out.toString()); - } -} +//wpackage com.alibaba.otter.canal.client.adapter.rdb.test; +// +//import com.alibaba.fastsql.sql.ast.SQLName; +//import com.alibaba.fastsql.sql.ast.SQLStatement; +//import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement; +//import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; +//import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlCreateTableParser; +//import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser; +//import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor; +//import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +//import com.alibaba.fastsql.sql.parser.SQLStatementParser; +// +//import java.io.StringWriter; +// +//public class SqlParserTest { +// +// public static class TableNameVisitor extends MySqlOutputVisitor { +// +// public TableNameVisitor(Appendable appender){ +// super(appender); +// } +// +// @Override +// public boolean visit(SQLExprTableSource x) { +// SQLName table = (SQLName) x.getExpr(); +// String tableName = table.getSimpleName(); +// +// // 改写tableName +// print0("new_" + tableName.toUpperCase()); +// +// return true; +// } +// +// } +// +// public static void main(String[] args) { +// // String sql = "select * from `mytest`.`t` where id=1 and name=ming group by +// // uid limit 1,200 order by ctime"; +// +// String sql = "CREATE TABLE `mytest`.`user` (\n" + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" +// + " `name` varchar(30) NOT NULL,\n" + " `c_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" +// + " `role_id` bigint(20) DEFAULT NULL,\n" + " `test1` text,\n" + " `test2` blob,\n" +// + " `key` varchar(30) DEFAULT NULL,\n" + " PRIMARY KEY (`id`)\n" +// + ") ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;"; +// +// // // 新建 MySQL Parser +// // SQLStatementParser parser = new MySqlStatementParser(sql); +// // +// // // 使用Parser解析生成AST,这里SQLStatement就是AST +// // SQLStatement sqlStatement = parser.parseStatement(); +// // +// // MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); +// // sqlStatement.accept(visitor); +// // +// // System.out.println("getTables:" + visitor.getTables()); +// // System.out.println("getParameters:" + visitor.getParameters()); +// // System.out.println("getOrderByColumns:" + visitor.getOrderByColumns()); +// // System.out.println("getGroupByColumns:" + visitor.getGroupByColumns()); +// // System.out.println("---------------------------------------------------------------------------"); +// // +// // // 使用select访问者进行select的关键信息打印 +// // // SelectPrintVisitor selectPrintVisitor = new SelectPrintVisitor(); +// // // sqlStatement.accept(selectPrintVisitor); +// // +// // System.out.println("---------------------------------------------------------------------------"); +// // // 最终sql输出 +// // StringWriter out = new StringWriter(); +// // TableNameVisitor outputVisitor = new TableNameVisitor(out); +// // sqlStatement.accept(outputVisitor); +// // System.out.println(out.toString()); +// +// MySqlCreateTableParser parser1 = new MySqlCreateTableParser(sql); +// SQLCreateTableStatement createTableStatement = parser1.parseCreateTable(); +//// MySqlSchemaStatVisitor visitor1 = new MySqlSchemaStatVisitor(); +//// createTableStatement.accept(visitor1); +// // visitor1.getTables().forEach((k, v) -> { +// // System.out.println(k.); +// // System.out.println(v); +// // }); +// // 最终sql输出 +// StringWriter out = new StringWriter(); +// TableNameVisitor outputVisitor = new TableNameVisitor(out); +// createTableStatement.accept(outputVisitor); +// System.out.println(out.toString()); +// } +//} From 0523b68c26c5ef2b715966cb6d28d18a73d61a9f Mon Sep 17 00:00:00 2001 From: agapple Date: Wed, 12 Dec 2018 10:48:22 +0800 Subject: [PATCH 15/21] fixed parse error --- .../canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java index dcd490f8..0a0f6314 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java @@ -58,7 +58,7 @@ public class MemoryTableMeta implements TableMetaTSDB { public boolean init(String destination) { return true; } - + @Override public void destory() { tableMetas.clear(); @@ -74,7 +74,11 @@ public class MemoryTableMeta implements TableMetaTSDB { try { // druid暂时flush privileges语法解析有问题 if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush") - && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant")) { + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant") + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "create user") + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "drop user") + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "create database") + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "drop database")) { repository.console(ddl); } } catch (Throwable e) { From d2e3e1983140182dbe38aef629613cc7ba57d618 Mon Sep 17 00:00:00 2001 From: agapple Date: Thu, 13 Dec 2018 12:52:59 +0800 Subject: [PATCH 16/21] fixed issue #1282 , NPE --- .../alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 7ab61ba5..39ec6c62 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -508,6 +508,7 @@ public class MysqlConnection implements ErosaConnection { rs = query("select @@global.binlog_checksum"); } catch (Throwable e) { // ignore + return; } List columnValues = rs.getFieldValues(); From fbf249580f595e973f1548193de66c4f43af304d Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 14 Dec 2018 11:30:41 +0800 Subject: [PATCH 17/21] mirror db --- .../loader/AbstractCanalAdapterWorker.java | 4 +- .../canal/client/adapter/rdb/RdbAdapter.java | 74 +++++-- .../adapter/rdb/config/MappingConfig.java | 10 + .../rdb/service/RdbMirrorDbSyncService.java | 155 ++++++++++----- .../adapter/rdb/service/RdbSyncService.java | 185 ++++++++++++------ .../client/adapter/rdb/support/SyncUtil.java | 4 + 6 files changed, 309 insertions(+), 123 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index fdf2a91e..363d1410 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -200,7 +200,9 @@ public abstract class AbstractCanalAdapterWorker { len = 0; } } - adapter.sync(dmlsBatch); + if (!dmlsBatch.isEmpty()) { + adapter.sync(dmlsBatch); + } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index afadb147..dc1ebd0e 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -5,9 +5,7 @@ import java.sql.SQLException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; import javax.sql.DataSource; @@ -26,6 +24,12 @@ import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.support.*; +/** + * RDB适配器实现类 + * + * @author rewerma 2018-11-7 下午06:45:49 + * @version 1.0.0 + */ @SPI("rdb") public class RdbAdapter implements OuterAdapter { @@ -40,8 +44,6 @@ public class RdbAdapter implements OuterAdapter { private RdbSyncService rdbSyncService; private RdbMirrorDbSyncService rdbMirrorDbSyncService; - private ExecutorService executor = Executors.newFixedThreadPool(1); - private RdbConfigMonitor rdbConfigMonitor; public Map getRdbMapping() { @@ -52,6 +54,11 @@ public class RdbAdapter implements OuterAdapter { return mappingConfigCache; } + /** + * 初始化方法 + * + * @param configuration 外部适配器配置信息 + */ @Override public void init(OuterAdapterConfig configuration) { Map rdbMappingTmp = ConfigLoader.load(); @@ -80,6 +87,7 @@ public class RdbAdapter implements OuterAdapter { } } + // 初始化连接池 Map properties = configuration.getProperties(); dataSource = new DruidDataSource(); dataSource.setDriverClassName(properties.get("jdbc.driverClassName")); @@ -88,7 +96,7 @@ public class RdbAdapter implements OuterAdapter { dataSource.setPassword(properties.get("jdbc.password")); dataSource.setInitialSize(1); dataSource.setMinIdle(1); - dataSource.setMaxActive(20); + dataSource.setMaxActive(10); dataSource.setMaxWait(60000); dataSource.setTimeBetweenEvictionRunsMillis(60000); dataSource.setMinEvictableIdleTimeMillis(300000); @@ -102,24 +110,49 @@ public class RdbAdapter implements OuterAdapter { String threads = properties.get("threads"); // String commitSize = properties.get("commitSize"); - rdbSyncService = new RdbSyncService(mappingConfigCache, - dataSource, - threads != null ? Integer.valueOf(threads) : null); + rdbSyncService = new RdbSyncService(dataSource, threads != null ? Integer.valueOf(threads) : null); rdbMirrorDbSyncService = new RdbMirrorDbSyncService(mirrorDbConfigCache, dataSource, - threads != null ? Integer.valueOf(threads) : null); + threads != null ? Integer.valueOf(threads) : null, + rdbSyncService.getColumnsTypeCache()); rdbConfigMonitor = new RdbConfigMonitor(); rdbConfigMonitor.init(configuration.getKey(), this); } + /** + * 同步方法 + * + * @param dmls 数据包 + */ @Override public void sync(List dmls) { - rdbSyncService.sync(dmls); - rdbMirrorDbSyncService.sync(dmls); + ExecutorService executorService = Executors.newFixedThreadPool(2); + + Future future1 = executorService.submit(() -> { + rdbSyncService.sync(mappingConfigCache, dmls); + return true; + }); + Future future2 = executorService.submit(() -> { + rdbMirrorDbSyncService.sync(dmls); + return true; + }); + try { + future1.get(); + future2.get(); + } catch (ExecutionException | InterruptedException e) { + // ignore + } } + /** + * ETL方法 + * + * @param task 任务名, 对应配置名 + * @param params etl筛选条件 + * @return ETL结果 + */ @Override public EtlResult etl(String task, List params) { EtlResult etlResult = new EtlResult(); @@ -168,6 +201,12 @@ public class RdbAdapter implements OuterAdapter { return etlResult; } + /** + * 获取总数方法 + * + * @param task 任务名, 对应配置名 + * @return 总数 + */ @Override public Map count(String task) { MappingConfig config = rdbMapping.get(task); @@ -203,6 +242,12 @@ public class RdbAdapter implements OuterAdapter { return res; } + /** + * 获取对应canal instance name 或 mq topic + * + * @param task 任务名, 对应配置名 + * @return destination + */ @Override public String getDestination(String task) { MappingConfig config = rdbMapping.get(task); @@ -212,6 +257,9 @@ public class RdbAdapter implements OuterAdapter { return null; } + /** + * 销毁方法 + */ @Override public void destroy() { if (rdbConfigMonitor != null) { @@ -222,8 +270,6 @@ public class RdbAdapter implements OuterAdapter { rdbSyncService.close(); } - executor.shutdown(); - if (dataSource != null) { dataSource.close(); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index 88c0fc84..aa6a7d1b 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -89,6 +89,8 @@ public class MappingConfig { private int readBatch = 5000; private int commitBatch = 5000; // etl等批量提交大小 + private Map allMapColumns; + public boolean isMirrorDb() { return mirrorDb == null ? false : mirrorDb; } @@ -176,5 +178,13 @@ public class MappingConfig { public void setCommitBatch(int commitBatch) { this.commitBatch = commitBatch; } + + public Map getAllMapColumns() { + return allMapColumns; + } + + public void setAllMapColumns(Map allMapColumns) { + this.allMapColumns = allMapColumns; + } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java index f7957c37..99ee875f 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.service; import java.sql.Connection; import java.sql.Statement; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -14,70 +15,136 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml; import com.alibaba.otter.canal.client.adapter.support.Dml; +/** + * RDB镜像库同步操作业务 + * + * @author rewerma 2018-12-12 下午011:23 + * @version 1.0.0 + */ public class RdbMirrorDbSyncService { - private static final Logger logger = LoggerFactory - .getLogger(RdbMirrorDbSyncService.class); + private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class); - private Map mirrorDbConfigCache; // 镜像库配置 - private DataSource dataSource; + private Map mirrorDbConfigCache; // 镜像库配置 + private DataSource dataSource; + private RdbSyncService rdbSyncService; // rdbSyncService代理 - private static Map tableDbConfigCache = new ConcurrentHashMap<>(); + private final Map tableDbConfigCache = new ConcurrentHashMap<>(); // 自动生成的库表配置缓存 public RdbMirrorDbSyncService(Map mirrorDbConfigCache, DataSource dataSource, - Integer threads){ + Integer threads, Map> columnsTypeCache){ this.mirrorDbConfigCache = mirrorDbConfigCache; this.dataSource = dataSource; + this.rdbSyncService = new RdbSyncService(dataSource, threads, columnsTypeCache); } + /** + * 批量同步方法 + * + * @param dmls 批量 DML + */ public void sync(List dmls) { - for (Dml dml : dmls) { - String destination = StringUtils.trimToEmpty(dml.getDestination()); - String database = dml.getDatabase(); - MappingConfig configMap = mirrorDbConfigCache.get(destination + "." + database); - if (configMap == null) { - continue; - } - if (dml.getSql() != null) { - // DDL - executeDdl(database, dml.getSql()); - } else { - // DML - // TODO - MappingConfig mappingConfig = tableDbConfigCache - .get(destination + "." + database + "." + dml.getTable()); - if (mappingConfig == null) { - // 构造一个配置 - mappingConfig = new MappingConfig(); - mappingConfig.setDataSourceKey(configMap.getDataSourceKey()); - mappingConfig.setDestination(configMap.getDestination()); - mappingConfig.setOuterAdapterKey(configMap.getOuterAdapterKey()); - mappingConfig.setConcurrent(configMap.getConcurrent()); - MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping(); - mappingConfig.setDbMapping(dbMapping); - dbMapping.setDatabase(dml.getDatabase()); - dbMapping.setTable(dml.getTable()); - dbMapping.setTargetDb(dml.getDatabase()); - dbMapping.setTargetTable(dml.getTable()); - dbMapping.setMapAll(true); - List pkNames = dml.getPkNames(); - Map pkMapping = new LinkedHashMap<>(); - pkNames.forEach(pkName -> pkMapping.put(pkName, pkName)); - dbMapping.setTargetPk(pkMapping); - - tableDbConfigCache.put(destination + "." + database + "." + dml.getTable(), mappingConfig); + try { + List dmlList = new ArrayList<>(); + for (Dml dml : dmls) { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + MappingConfig configMap = mirrorDbConfigCache.get(destination + "." + database); + if (configMap == null) { + continue; + } + if (dml.getSql() != null) { + // DDL + executeDdl(dml); + rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable()); + tableDbConfigCache.remove(destination + "." + database + "." + dml.getTable()); // 删除对应库表配置 + } else { + // DML + initMappingConfig(destination + "." + database + "." + dml.getTable(), configMap, dml); + dmlList.add(dml); } } + if (!dmlList.isEmpty()) { + rdbSyncService.sync(dmlList, dml -> { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + MappingConfig config = tableDbConfigCache.get(destination + "." + database + "." + table); + + if (config == null) { + return false; + } + + if (config.getConcurrent()) { + List singleDmls = SingleDml.dml2SingleDmls(dml); + singleDmls.forEach(singleDml -> { + int hash = rdbSyncService.pkHash(config.getDbMapping(), singleDml.getData()); + RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml); + rdbSyncService.getDmlsPartition()[hash].add(syncItem); + }); + } else { + int hash = 0; + List singleDmls = SingleDml.dml2SingleDmls(dml); + singleDmls.forEach(singleDml -> { + RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml); + rdbSyncService.getDmlsPartition()[hash].add(syncItem); + }); + } + return true; + }); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); } } - private void executeDdl(String database, String sql) { + /** + * 初始化表配置 + * + * @param key 配置key: destination.database.table + * @param baseConfigMap db sync config + * @param dml DML + */ + private void initMappingConfig(String key, MappingConfig baseConfigMap, Dml dml) { + MappingConfig mappingConfig = tableDbConfigCache.get(key); + if (mappingConfig == null) { + // 构造一个配置 + mappingConfig = new MappingConfig(); + mappingConfig.setDataSourceKey(baseConfigMap.getDataSourceKey()); + mappingConfig.setDestination(baseConfigMap.getDestination()); + mappingConfig.setOuterAdapterKey(baseConfigMap.getOuterAdapterKey()); + mappingConfig.setConcurrent(baseConfigMap.getConcurrent()); + MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping(); + mappingConfig.setDbMapping(dbMapping); + dbMapping.setDatabase(dml.getDatabase()); + dbMapping.setTable(dml.getTable()); + dbMapping.setTargetDb(dml.getDatabase()); + dbMapping.setTargetTable(dml.getTable()); + dbMapping.setMapAll(true); + List pkNames = dml.getPkNames(); + Map pkMapping = new LinkedHashMap<>(); + pkNames.forEach(pkName -> pkMapping.put(pkName, pkName)); + dbMapping.setTargetPk(pkMapping); + + tableDbConfigCache.put(key, mappingConfig); + } + } + + /** + * DDL 操作 + * + * @param ddl DDL + */ + private void executeDdl(Dml ddl) { try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) { - statement.execute(sql); + statement.execute(ddl.getSql()); + // 移除对应配置 + tableDbConfigCache.remove(ddl.getDatabase() + "." + ddl.getDatabase() + "." + ddl.getTable()); if (logger.isTraceEnabled()) { - logger.trace("Execute DDL sql: {} for database: {}", sql, database); + logger.trace("Execute DDL sql: {} for database: {}", ddl.getSql(), ddl.getDatabase()); } } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 918699f4..bd4ef77b 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.function.Function; import javax.sql.DataSource; @@ -36,26 +37,37 @@ import com.alibaba.otter.canal.client.adapter.support.Util; */ public class RdbSyncService { - private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class); + private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class); - private final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); + // 源库表字段类型缓存: instance.schema.table -> + private Map> columnsTypeCache; - private Map> mappingConfigCache; // 库名-表名对应配置 + private int threads = 3; - private int threads = 3; + private List[] dmlsPartition; + private BatchExecutor[] batchExecutors; + private ExecutorService[] executorThreads; - private List[] dmlsPartition; - private BatchExecutor[] batchExecutors; - private ExecutorService[] executorThreads; + public List[] getDmlsPartition() { + return dmlsPartition; + } + + public Map> getColumnsTypeCache() { + return columnsTypeCache; + } @SuppressWarnings("unchecked") - public RdbSyncService(Map> mappingConfigCache, DataSource dataSource, - Integer threads){ + public RdbSyncService(DataSource dataSource, Integer threads){ + this(dataSource, threads, new ConcurrentHashMap<>()); + } + + @SuppressWarnings("unchecked") + public RdbSyncService(DataSource dataSource, Integer threads, Map> columnsTypeCache){ + this.columnsTypeCache = columnsTypeCache; try { if (threads != null) { this.threads = threads; } - this.mappingConfigCache = mappingConfigCache; this.dmlsPartition = new List[this.threads]; this.batchExecutors = new BatchExecutor[this.threads]; this.executorThreads = new ExecutorService[this.threads]; @@ -69,66 +81,111 @@ public class RdbSyncService { } } - public void sync(List dmls) { + /** + * 批量同步回调 + * + * @param dmls 批量 DML + * @param function 回调方法 + */ + public void sync(List dmls, Function function) { try { + boolean toExecute = false; for (Dml dml : dmls) { - String destination = StringUtils.trimToEmpty(dml.getDestination()); - String database = dml.getDatabase(); - String table = dml.getTable(); - Map configMap = mappingConfigCache - .get(destination + "." + database + "." + table); - - if (configMap == null) { - continue; + if (!toExecute) { + toExecute = function.apply(dml); + } else { + function.apply(dml); + } + } + if (toExecute) { + List futures = new ArrayList<>(); + for (int i = 0; i < threads; i++) { + int j = i; + futures.add(executorThreads[i].submit(() -> { + dmlsPartition[j] + .forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml)); + batchExecutors[j].commit(); + return true; + })); } - for (MappingConfig config : configMap.values()) { - if (config.getConcurrent()) { - List singleDmls = SingleDml.dml2SingleDmls(dml); - singleDmls.forEach(singleDml -> { - int hash = pkHash(config.getDbMapping(), singleDml.getData()); - SyncItem syncItem = new SyncItem(config, singleDml); - dmlsPartition[hash].add(syncItem); - }); - } else { - int hash = 0; - // Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % - // threads); - List singleDmls = SingleDml.dml2SingleDmls(dml); - singleDmls.forEach(singleDml -> { - SyncItem syncItem = new SyncItem(config, singleDml); - dmlsPartition[hash].add(syncItem); - }); + futures.forEach(future -> { + try { + future.get(); + } catch (Exception e) { + logger.error(e.getMessage(), e); } - } - } - List futures = new ArrayList<>(); - for (int i = 0; i < threads; i++) { - int j = i; - futures.add(executorThreads[i].submit(() -> { - dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml)); - batchExecutors[j].commit(); - return true; - })); - } + }); - futures.forEach(future -> { - try { - future.get(); - } catch (Exception e) { - logger.error(e.getMessage(), e); + for (int i = 0; i < threads; i++) { + dmlsPartition[i].clear(); } - }); - - for (int i = 0; i < threads; i++) { - dmlsPartition[i].clear(); } } catch (Exception e) { logger.error(e.getMessage(), e); } } - private void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) { + /** + * 批量同步 + * + * @param mappingConfig 配置集合 + * @param dmls 批量 DML + */ + public void sync(Map> mappingConfig, List dmls) { + try { + sync(dmls, dml -> { + if (dml.getSql() != null) { + // DDL + columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable()); + return false; + } else { + // DML + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + Map configMap = mappingConfig + .get(destination + "." + database + "." + table); + + if (configMap == null) { + return false; + } + + boolean executed = false; + for (MappingConfig config : configMap.values()) { + if (config.getConcurrent()) { + List singleDmls = SingleDml.dml2SingleDmls(dml); + singleDmls.forEach(singleDml -> { + int hash = pkHash(config.getDbMapping(), singleDml.getData()); + SyncItem syncItem = new SyncItem(config, singleDml); + dmlsPartition[hash].add(syncItem); + }); + } else { + int hash = 0; + List singleDmls = SingleDml.dml2SingleDmls(dml); + singleDmls.forEach(singleDml -> { + SyncItem syncItem = new SyncItem(config, singleDml); + dmlsPartition[hash].add(syncItem); + }); + } + executed = true; + } + return executed; + } + }); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + /** + * 单条 dml 同步 + * + * @param batchExecutor 批量事务执行器 + * @param config 对应配置对象 + * @param dml DML + */ + public void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) { try { if (config != null) { String type = dml.getType(); @@ -308,10 +365,10 @@ public class RdbSyncService { private Map getTargetColumnType(Connection conn, MappingConfig config) { DbMapping dbMapping = config.getDbMapping(); String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable(); - Map columnType = COLUMNS_TYPE_CACHE.get(cacheKey); + Map columnType = columnsTypeCache.get(cacheKey); if (columnType == null) { synchronized (RdbSyncService.class) { - columnType = COLUMNS_TYPE_CACHE.get(cacheKey); + columnType = columnsTypeCache.get(cacheKey); if (columnType == null) { columnType = new LinkedHashMap<>(); final Map columnTypeTmp = columnType; @@ -323,7 +380,7 @@ public class RdbSyncService { for (int i = 1; i <= columnCount; i++) { columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i)); } - COLUMNS_TYPE_CACHE.put(cacheKey, columnTypeTmp); + columnsTypeCache.put(cacheKey, columnTypeTmp); } catch (SQLException e) { logger.error(e.getMessage(), e); } @@ -364,12 +421,12 @@ public class RdbSyncService { sql.delete(len - 4, len); } - private class SyncItem { + public static class SyncItem { private MappingConfig config; private SingleDml singleDml; - private SyncItem(MappingConfig config, SingleDml singleDml){ + public SyncItem(MappingConfig config, SingleDml singleDml){ this.config = config; this.singleDml = singleDml; } @@ -378,11 +435,11 @@ public class RdbSyncService { /** * 取主键hash */ - private int pkHash(DbMapping dbMapping, Map d) { + public int pkHash(DbMapping dbMapping, Map d) { return pkHash(dbMapping, d, null); } - private int pkHash(DbMapping dbMapping, Map d, Map o) { + public int pkHash(DbMapping dbMapping, Map d, Map o) { int hash = 0; // 取主键 for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java index e8362190..bf21a300 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java @@ -23,6 +23,9 @@ public class SyncUtil { public static Map getColumnsMap(MappingConfig.DbMapping dbMapping, Collection columns) { Map columnsMap; if (dbMapping.isMapAll()) { + if (dbMapping.getAllMapColumns() != null) { + return dbMapping.getAllMapColumns(); + } columnsMap = new LinkedHashMap<>(); for (String srcColumn : columns) { boolean flag = true; @@ -39,6 +42,7 @@ public class SyncUtil { columnsMap.put(srcColumn, srcColumn); } } + dbMapping.setAllMapColumns(columnsMap); } else { columnsMap = dbMapping.getTargetColumns(); } From 3acdffb29708fe4e9a393c7032ad0db840cdafa3 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 14 Dec 2018 14:58:15 +0800 Subject: [PATCH 18/21] =?UTF-8?q?mirror=20schema=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/es/config/ESSyncConfigLoader.java | 6 ++- .../hbase/config/MappingConfigLoader.java | 8 +++- .../src/main/resources/application.yml | 25 ++++++---- .../canal/client/adapter/rdb/RdbAdapter.java | 12 +++-- .../adapter/rdb/config/ConfigLoader.java | 8 +++- .../adapter/rdb/config/MappingConfig.java | 12 ++--- .../adapter/rdb/config/MirrorDbConfig.java | 44 +++++++++++++++++ .../adapter/rdb/monitor/RdbConfigMonitor.java | 28 ++++++++--- .../rdb/service/RdbMirrorDbSyncService.java | 47 ++++++++++--------- .../adapter/rdb/service/RdbSyncService.java | 2 +- .../client/adapter/rdb/support/SyncUtil.java | 2 +- .../src/main/resources/rdb/mytest_user.yml | 23 ++++++--- 12 files changed, 158 insertions(+), 59 deletions(-) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MirrorDbConfig.java diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java index e83a96e6..fcf94105 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.es.config; import java.util.LinkedHashMap; import java.util.Map; +import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; @@ -19,6 +20,7 @@ public class ESSyncConfigLoader { private static Logger logger = LoggerFactory.getLogger(ESSyncConfigLoader.class); + @SuppressWarnings("unchecked") public static synchronized Map load() { logger.info("## Start loading es mapping config ... "); @@ -26,7 +28,9 @@ public class ESSyncConfigLoader { Map configContentMap = MappingConfigsLoader.loadConfigs("es"); configContentMap.forEach((fileName, content) -> { - ESSyncConfig config = new Yaml().loadAs(content, ESSyncConfig.class); + Map configMap = new Yaml().loadAs(content, Map.class); // yml自带的对象反射不是很稳定 + JSONObject configJson = new JSONObject(configMap); + ESSyncConfig config = configJson.toJavaObject(ESSyncConfig.class); try { config.validate(); } catch (Exception e) { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 1f8c05c1..3cf16a71 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.hbase.config; import java.util.LinkedHashMap; import java.util.Map; +import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; @@ -21,9 +22,10 @@ public class MappingConfigLoader { /** * 加载HBase表映射配置 - * + * * @return 配置名/配置文件名--对象 */ + @SuppressWarnings("unchecked") public static Map load() { logger.info("## Start loading hbase mapping config ... "); @@ -31,7 +33,9 @@ public class MappingConfigLoader { Map configContentMap = MappingConfigsLoader.loadConfigs("hbase"); configContentMap.forEach((fileName, content) -> { - MappingConfig config = new Yaml().loadAs(content, MappingConfig.class); + Map configMap = new Yaml().loadAs(content, Map.class); // yml自带的对象反射不是很稳定 + JSONObject configJson = new JSONObject(configMap); + MappingConfig config = configJson.toJavaObject(MappingConfig.class); try { config.validate(); } catch (Exception e) { diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 4a5cac3b..6478db45 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -34,7 +34,14 @@ canal.conf: groups: - groupId: g1 outerAdapters: -# - name: logger + - name: logger +# - name: rdb +# key: mysql1 +# properties: +# jdbc.driverClassName: com.mysql.jdbc.Driver +# jdbc.url: jdbc:mysql://192.168.0.36/mytest?useUnicode=true +# jdbc.username: root +# jdbc.password: 121212 # - name: rdb # key: oracle1 # properties: @@ -42,13 +49,15 @@ canal.conf: # jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 - - name: rdb - key: mysql1 - properties: - jdbc.driverClassName: com.mysql.jdbc.Driver - jdbc.url: jdbc:mysql://192.168.100.36/mytest?useUnicode=true - jdbc.username: root - jdbc.password: Ambari-123 +# - name: rdb +# key: postgres1 +# properties: +# jdbc.driverClassName: org.postgresql.Driver +# jdbc.url: jdbc:postgresql://localhost:5432/postgres +# jdbc.username: postgres +# jdbc.password: 121212 +# threads: 1 +# commitSize: 3000 # - name: hbase # properties: # hbase.zookeeper.quorum: 127.0.0.1 diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index dc1ebd0e..3c4f8387 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -17,6 +17,7 @@ import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig; import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbMirrorDbSyncService; @@ -37,7 +38,7 @@ public class RdbAdapter implements OuterAdapter { private Map rdbMapping = new ConcurrentHashMap<>(); // 文件名对应配置 private Map> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置 - private Map mirrorDbConfigCache = new ConcurrentHashMap<>(); // 镜像库配置 + private Map mirrorDbConfigCache = new ConcurrentHashMap<>(); // 镜像库配置 private DruidDataSource dataSource; @@ -54,6 +55,10 @@ public class RdbAdapter implements OuterAdapter { return mappingConfigCache; } + public Map getMirrorDbConfigCache() { + return mirrorDbConfigCache; + } + /** * 初始化方法 * @@ -73,7 +78,7 @@ public class RdbAdapter implements OuterAdapter { for (Map.Entry entry : rdbMapping.entrySet()) { String configName = entry.getKey(); MappingConfig mappingConfig = entry.getValue(); - if (!mappingConfig.getDbMapping().isMirrorDb()) { + if (!mappingConfig.getDbMapping().getMirrorDb()) { Map configMap = mappingConfigCache.computeIfAbsent( StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + mappingConfig.getDbMapping() .getDatabase() + "." + mappingConfig.getDbMapping().getTable(), @@ -81,9 +86,10 @@ public class RdbAdapter implements OuterAdapter { configMap.put(configName, mappingConfig); } else { // mirrorDB + mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + mappingConfig.getDbMapping().getDatabase(), - mappingConfig); + MirrorDbConfig.create(configName, mappingConfig)); } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java index c2fe3339..a55644f3 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.config; import java.util.LinkedHashMap; import java.util.Map; +import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; @@ -21,9 +22,10 @@ public class ConfigLoader { /** * 加载HBase表映射配置 - * + * * @return 配置名/配置文件名--对象 */ + @SuppressWarnings("unchecked") public static Map load() { logger.info("## Start loading rdb mapping config ... "); @@ -31,7 +33,9 @@ public class ConfigLoader { Map configContentMap = MappingConfigsLoader.loadConfigs("rdb"); configContentMap.forEach((fileName, content) -> { - MappingConfig config = new Yaml().loadAs(content, MappingConfig.class); + Map configMap = new Yaml().loadAs(content, Map.class); // yml自带的对象反射不是很稳定 + JSONObject configJson = new JSONObject(configMap); + MappingConfig config = configJson.toJavaObject(MappingConfig.class); try { config.validate(); } catch (Exception e) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index aa6a7d1b..ce1b9f15 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -65,10 +65,10 @@ public class MappingConfig { if (dbMapping.database == null || dbMapping.database.isEmpty()) { throw new NullPointerException("dbMapping.database"); } - if (!dbMapping.isMirrorDb() && (dbMapping.table == null || dbMapping.table.isEmpty())) { + if (!dbMapping.getMirrorDb() && (dbMapping.table == null || dbMapping.table.isEmpty())) { throw new NullPointerException("dbMapping.table"); } - if (!dbMapping.isMirrorDb() && (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) { + if (!dbMapping.getMirrorDb() && (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) { throw new NullPointerException("dbMapping.targetTable"); } } @@ -91,8 +91,8 @@ public class MappingConfig { private Map allMapColumns; - public boolean isMirrorDb() { - return mirrorDb == null ? false : mirrorDb; + public Boolean getMirrorDb() { + return mirrorDb; } public void setMirrorDb(Boolean mirrorDb) { @@ -123,8 +123,8 @@ public class MappingConfig { this.targetPk = targetPk; } - public boolean isMapAll() { - return mapAll == null ? false : mapAll; + public Boolean getMapAll() { + return mapAll; } public void setMapAll(Boolean mapAll) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MirrorDbConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MirrorDbConfig.java new file mode 100644 index 00000000..d84f66e7 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MirrorDbConfig.java @@ -0,0 +1,44 @@ +package com.alibaba.otter.canal.client.adapter.rdb.config; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class MirrorDbConfig { + + private String fileName; + private MappingConfig mappingConfig; + Map tableConfig = new ConcurrentHashMap<>(); + + public static MirrorDbConfig create(String fileName, MappingConfig mappingConfig) { + return new MirrorDbConfig(fileName, mappingConfig); + } + + public MirrorDbConfig(String fileName, MappingConfig mappingConfig){ + this.fileName = fileName; + this.mappingConfig = mappingConfig; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public MappingConfig getMappingConfig() { + return mappingConfig; + } + + public void setMappingConfig(MappingConfig mappingConfig) { + this.mappingConfig = mappingConfig; + } + + public Map getTableConfig() { + return tableConfig; + } + + public void setTableConfig(Map tableConfig) { + this.tableConfig = tableConfig; + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java index d74cd884..271f1393 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java @@ -4,6 +4,7 @@ import java.io.File; import java.util.HashMap; import java.util.Map; +import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationMonitor; @@ -20,7 +21,7 @@ import com.alibaba.otter.canal.client.adapter.support.Util; public class RdbConfigMonitor { - private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class); + private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class); private static final String adapterName = "rdb"; @@ -83,7 +84,8 @@ public class RdbConfigMonitor { try { if (rdbAdapter.getRdbMapping().containsKey(file.getName())) { // 加载配置文件 - String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName()); + String configContent = MappingConfigsLoader + .loadConfig(adapterName + File.separator + file.getName()); MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); config.validate(); if ((key == null && config.getOuterAdapterKey() == null) @@ -118,13 +120,19 @@ public class RdbConfigMonitor { } } - private void addConfigToCache(File file, MappingConfig config) { - rdbAdapter.getRdbMapping().put(file.getName(), config); + private void addConfigToCache(File file, MappingConfig mappingConfig) { + rdbAdapter.getRdbMapping().put(file.getName(), mappingConfig); Map configMap = rdbAdapter.getMappingConfigCache() - .computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "." - + config.getDbMapping().getDatabase() + "." + config.getDbMapping().getTable(), + .computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getDbMapping().getDatabase() + "." + + mappingConfig.getDbMapping().getTable(), k1 -> new HashMap<>()); - configMap.put(file.getName(), config); + configMap.put(file.getName(), mappingConfig); + + Map mirrorDbConfigCache = rdbAdapter.getMirrorDbConfigCache(); + mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getDbMapping().getDatabase(), + MirrorDbConfig.create(file.getName(), mappingConfig)); } private void deleteConfigFromCache(File file) { @@ -136,6 +144,12 @@ public class RdbConfigMonitor { } } + rdbAdapter.getMirrorDbConfigCache().forEach((key, mirrorDbConfig) -> { + if (mirrorDbConfig.getFileName().equals(file.getName())) { + rdbAdapter.getMirrorDbConfigCache().remove(key); + } + }); + } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java index 99ee875f..4a18ce99 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; @@ -14,7 +13,10 @@ import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig; import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml; import com.alibaba.otter.canal.client.adapter.support.Dml; @@ -26,15 +28,13 @@ import com.alibaba.otter.canal.client.adapter.support.Dml; */ public class RdbMirrorDbSyncService { - private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class); + private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class); - private Map mirrorDbConfigCache; // 镜像库配置 - private DataSource dataSource; - private RdbSyncService rdbSyncService; // rdbSyncService代理 + private Map mirrorDbConfigCache; // 镜像库配置 + private DataSource dataSource; + private RdbSyncService rdbSyncService; // rdbSyncService代理 - private final Map tableDbConfigCache = new ConcurrentHashMap<>(); // 自动生成的库表配置缓存 - - public RdbMirrorDbSyncService(Map mirrorDbConfigCache, DataSource dataSource, + public RdbMirrorDbSyncService(Map mirrorDbConfigCache, DataSource dataSource, Integer threads, Map> columnsTypeCache){ this.mirrorDbConfigCache = mirrorDbConfigCache; this.dataSource = dataSource; @@ -52,27 +52,32 @@ public class RdbMirrorDbSyncService { for (Dml dml : dmls) { String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); - MappingConfig configMap = mirrorDbConfigCache.get(destination + "." + database); - if (configMap == null) { + MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(destination + "." + database); + if (mirrorDbConfig == null) { continue; } - if (dml.getSql() != null) { + if (StringUtils.isNotEmpty(dml.getSql())) { // DDL - executeDdl(dml); + if (logger.isDebugEnabled()) { + logger.debug("DDL: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); + } + executeDdl(mirrorDbConfig, dml); rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable()); - tableDbConfigCache.remove(destination + "." + database + "." + dml.getTable()); // 删除对应库表配置 + mirrorDbConfig.getTableConfig().remove(dml.getTable()); // 删除对应库表配置 } else { // DML - initMappingConfig(destination + "." + database + "." + dml.getTable(), configMap, dml); + initMappingConfig(dml.getTable(), mirrorDbConfig.getMappingConfig(), mirrorDbConfig, dml); dmlList.add(dml); } } if (!dmlList.isEmpty()) { rdbSyncService.sync(dmlList, dml -> { + MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache + .get(dml.getDestination() + "." + dml.getDatabase()); String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); String table = dml.getTable(); - MappingConfig config = tableDbConfigCache.get(destination + "." + database + "." + table); + MappingConfig config = mirrorDbConfig.getTableConfig().get(table); if (config == null) { return false; @@ -108,10 +113,10 @@ public class RdbMirrorDbSyncService { * @param baseConfigMap db sync config * @param dml DML */ - private void initMappingConfig(String key, MappingConfig baseConfigMap, Dml dml) { - MappingConfig mappingConfig = tableDbConfigCache.get(key); + private void initMappingConfig(String key, MappingConfig baseConfigMap, MirrorDbConfig mirrorDbConfig, Dml dml) { + MappingConfig mappingConfig = mirrorDbConfig.getTableConfig().get(key); if (mappingConfig == null) { - // 构造一个配置 + // 构造表配置 mappingConfig = new MappingConfig(); mappingConfig.setDataSourceKey(baseConfigMap.getDataSourceKey()); mappingConfig.setDestination(baseConfigMap.getDestination()); @@ -129,7 +134,7 @@ public class RdbMirrorDbSyncService { pkNames.forEach(pkName -> pkMapping.put(pkName, pkName)); dbMapping.setTargetPk(pkMapping); - tableDbConfigCache.put(key, mappingConfig); + mirrorDbConfig.getTableConfig().put(key, mappingConfig); } } @@ -138,11 +143,11 @@ public class RdbMirrorDbSyncService { * * @param ddl DDL */ - private void executeDdl(Dml ddl) { + private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) { try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) { statement.execute(ddl.getSql()); // 移除对应配置 - tableDbConfigCache.remove(ddl.getDatabase() + "." + ddl.getDatabase() + "." + ddl.getTable()); + mirrorDbConfig.getTableConfig().remove(ddl.getTable()); if (logger.isTraceEnabled()) { logger.trace("Execute DDL sql: {} for database: {}", ddl.getSql(), ddl.getDatabase()); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index bd4ef77b..93797093 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -135,7 +135,7 @@ public class RdbSyncService { public void sync(Map> mappingConfig, List dmls) { try { sync(dmls, dml -> { - if (dml.getSql() != null) { + if (StringUtils.isNotEmpty(dml.getSql())) { // DDL columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable()); return false; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java index bf21a300..6e9b81a6 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java @@ -22,7 +22,7 @@ public class SyncUtil { public static Map getColumnsMap(MappingConfig.DbMapping dbMapping, Collection columns) { Map columnsMap; - if (dbMapping.isMapAll()) { + if (dbMapping.getMapAll()) { if (dbMapping.getAllMapColumns() != null) { return dbMapping.getAllMapColumns(); } diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml index a8e97f6d..a838e1c8 100644 --- a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -1,18 +1,27 @@ dataSourceKey: defaultDS destination: example -outerAdapterKey: mysql1 +outerAdapterKey: oracle1 concurrent: true dbMapping: - mirrorDb: true database: mytest -# table: user -# targetTable: mytest.tb_user -# targetPk: -# id: id -# mapAll: true + table: user + targetTable: mytest.tb_user + targetPk: + id: id + mapAll: true # targetColumns: # id: # name: # role_id: # c_time: # test1: + + +# Mirror schema synchronize config +#dataSourceKey: defaultDS +#destination: example +#outerAdapterKey: mysql1 +#concurrent: true +#dbMapping: +# mirrorDb: true +# database: mytest From 833cc55e2639048b5ecd0f7eeb12511d4fdd9cca Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 17 Dec 2018 09:47:09 +0800 Subject: [PATCH 19/21] =?UTF-8?q?FlatMessage=E6=B2=A1=E6=9C=89=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=9E=84=E9=80=A0=E6=96=B9=E6=B3=95json=E5=8F=8D?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96=E7=9A=84=E9=97=AE=E9=A2=98=20Dml?= =?UTF-8?q?=E7=B1=BB=E5=A2=9E=E5=8A=A0isDdl=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/otter/canal/client/adapter/support/Dml.java | 9 +++++++++ .../otter/canal/client/adapter/support/MessageUtil.java | 1 + .../adapter/rdb/service/RdbMirrorDbSyncService.java | 2 +- .../canal/client/adapter/rdb/service/RdbSyncService.java | 2 +- .../com/alibaba/otter/canal/protocol/FlatMessage.java | 3 +++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java index cc19813f..38fd7a3b 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java @@ -18,6 +18,7 @@ public class Dml implements Serializable { private String database; // 数据库或schema private String table; // 表名 private List pkNames; + private Boolean isDdl; private String type; // 类型: INSERT UPDATE DELETE // binlog executeTime private Long es; // 执行耗时 @@ -59,6 +60,14 @@ public class Dml implements Serializable { this.pkNames = pkNames; } + public Boolean getIsDdl() { + return isDdl; + } + + public void setIsDdl(Boolean isDdl) { + this.isDdl = isDdl; + } + public String getType() { return type; } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index 87fc2fb9..60e18c72 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -139,6 +139,7 @@ public class MessageUtil { dml.setDatabase(flatMessage.getDatabase()); dml.setTable(flatMessage.getTable()); dml.setPkNames(flatMessage.getPkNames()); + dml.setIsDdl(flatMessage.getIsDdl()); dml.setType(flatMessage.getType()); dml.setTs(flatMessage.getTs()); dml.setEs(flatMessage.getEs()); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java index 4a18ce99..cd3c6567 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbMirrorDbSyncService.java @@ -56,7 +56,7 @@ public class RdbMirrorDbSyncService { if (mirrorDbConfig == null) { continue; } - if (StringUtils.isNotEmpty(dml.getSql())) { + if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) { // DDL if (logger.isDebugEnabled()) { logger.debug("DDL: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 93797093..dd3a901a 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -135,7 +135,7 @@ public class RdbSyncService { public void sync(Map> mappingConfig, List dmls) { try { sync(dmls, dml -> { - if (StringUtils.isNotEmpty(dml.getSql())) { + if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) { // DDL columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable()); return false; diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 50e1cb3f..2b958b1c 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -29,6 +29,9 @@ public class FlatMessage implements Serializable { private List> data; private List> old; + public FlatMessage() { + } + public FlatMessage(long id){ this.id = id; } From 51d94efc9ce17532046874f264531d009430f974 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 17 Dec 2018 10:13:04 +0800 Subject: [PATCH 20/21] =?UTF-8?q?dml=E7=B1=BB=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/client/adapter/support/MessageUtil.java | 6 +++--- .../alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index 60e18c72..1ddbae0f 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -144,9 +144,9 @@ public class MessageUtil { dml.setTs(flatMessage.getTs()); dml.setEs(flatMessage.getEs()); dml.setSql(flatMessage.getSql()); - if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) { - throw new RuntimeException("SqlType or mysqlType is null"); - } + // if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) { + // throw new RuntimeException("SqlType or mysqlType is null"); + // } List> data = flatMessage.getData(); if (data != null) { dml.setData(changeRows(data, flatMessage.getSqlType(), flatMessage.getMysqlType())); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 3c4f8387..79f10955 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -75,6 +75,11 @@ public class RdbAdapter implements OuterAdapter { rdbMapping.put(key, mappingConfig); } }); + + if (rdbMapping.isEmpty()) { + throw new RuntimeException("No rdb adapter found for config key: " + configuration.getKey()); + } + for (Map.Entry entry : rdbMapping.entrySet()) { String configName = entry.getKey(); MappingConfig mappingConfig = entry.getValue(); @@ -89,7 +94,7 @@ public class RdbAdapter implements OuterAdapter { mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + mappingConfig.getDbMapping().getDatabase(), - MirrorDbConfig.create(configName, mappingConfig)); + MirrorDbConfig.create(configName, mappingConfig)); } } From a7526dbf4a6a2daccc393be69570e36af4fbd672 Mon Sep 17 00:00:00 2001 From: gsfeng <1242530633@qq.com> Date: Mon, 17 Dec 2018 10:18:06 +0800 Subject: [PATCH 21/21] Update README.md FAQ link error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6af316ae..8941c8fb 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ See the wiki page for : wiki文
  • TableMetaTSDB
  • ReleaseNotes
  • Download
  • -
  • FAQ
  • +
  • FAQ
  • 多语言业务