Compare commits
124
Commits
canal-1.0.22
...
v1.0.25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20942533e6 | ||
|
|
6b2d80a145 | ||
|
|
01ef15223c | ||
|
|
da3290c381 | ||
|
|
a9284b1b39 | ||
|
|
3409472eb9 | ||
|
|
5348994b4a | ||
|
|
a3b9f6f1eb | ||
|
|
2dc019709b | ||
|
|
c56f58deae | ||
|
|
8d6f19cc33 | ||
|
|
1463463f1e | ||
|
|
77342663e3 | ||
|
|
e01e48ff65 | ||
|
|
4f00f5ea8d | ||
|
|
19707f92d4 | ||
|
|
3930ea0f9b | ||
|
|
676cf47987 | ||
|
|
788c1d9b84 | ||
|
|
84a9aeb46c | ||
|
|
e8da1c884b | ||
|
|
041cbf04c7 | ||
|
|
a1cf292baa | ||
|
|
67436b2bc4 | ||
|
|
ca350a79bd | ||
|
|
214e404e1b | ||
|
|
34bcfbc3cb | ||
|
|
402245dbbf | ||
|
|
2cebfa04dd | ||
|
|
62e8ff6ec8 | ||
|
|
b265bf4669 | ||
|
|
989a69e7be | ||
|
|
780b4f715b | ||
|
|
8e88dc87f7 | ||
|
|
867d6607d4 | ||
|
|
22e3d2f3f1 | ||
|
|
ecdd619da7 | ||
|
|
9ba93bae5f | ||
|
|
0b2f37a272 | ||
|
|
0bd4a1146c | ||
|
|
3dba400624 | ||
|
|
523130cf8b | ||
|
|
525a00ea17 | ||
|
|
b6090da0e5 | ||
|
|
9ea88c7863 | ||
|
|
48c108806a | ||
|
|
8600e9825b | ||
|
|
29815c41b0 | ||
|
|
717e2cb7f3 | ||
|
|
3e5b3e1467 | ||
|
|
a46ac3b814 | ||
|
|
f6c2eb8963 | ||
|
|
07b979f242 | ||
|
|
6b20959a2d | ||
|
|
fdd40224d5 | ||
|
|
a6604978ed | ||
|
|
4d624cf184 | ||
|
|
7e026f2495 | ||
|
|
2e3796cb07 | ||
|
|
b2a5f482ee | ||
|
|
a78622ae02 | ||
|
|
e5444ae658 | ||
|
|
73627df082 | ||
|
|
c0f2d274d7 | ||
|
|
2eb05b5934 | ||
|
|
83689ee519 | ||
|
|
d78b389737 | ||
|
|
20d8ac67dc | ||
|
|
19a56fb77e | ||
|
|
c629e8c899 | ||
|
|
75a5a4d7df | ||
|
|
045a2e5a50 | ||
|
|
11d0712291 | ||
|
|
03ff9512dd | ||
|
|
0ee2e767d2 | ||
|
|
ff97dcbbc9 | ||
|
|
92c1a51726 | ||
|
|
36216e63ea | ||
|
|
db4fc68a55 | ||
|
|
eef58005d9 | ||
|
|
8141ddc4e8 | ||
|
|
004f26465f | ||
|
|
5dc62bbda8 | ||
|
|
f45f1ac74f | ||
|
|
5c11c71783 | ||
|
|
f745c698e9 | ||
|
|
5f4bbab722 | ||
|
|
a483f03711 | ||
|
|
a1207060bf | ||
|
|
5055fdb556 | ||
|
|
a69b69c724 | ||
|
|
90e6d34fb4 | ||
|
|
907ecd43e4 | ||
|
|
c57a921a1a | ||
|
|
83fee4ffbb | ||
|
|
bc7927fe75 | ||
|
|
e67f27018c | ||
|
|
3f38ad6f49 | ||
|
|
96dca0b9e6 | ||
|
|
ad2a96b3f1 | ||
|
|
6284a429a0 | ||
|
|
83d8a39d16 | ||
|
|
4898829384 | ||
|
|
69a7e6d083 | ||
|
|
a5d6c3b747 | ||
|
|
86378cad32 | ||
|
|
8626c6ee24 | ||
|
|
350f8637c3 | ||
|
|
96454d4387 | ||
|
|
8a293e4b0c | ||
|
|
006dd63646 | ||
|
|
b17c11868c | ||
|
|
0c9eecd758 | ||
|
|
5d9d5b1e44 | ||
|
|
b23e814c48 | ||
|
|
b349b400cf | ||
|
|
c86fba6092 | ||
|
|
9742fdc9d9 | ||
|
|
c99b4b8cd1 | ||
|
|
aa8ea96572 | ||
|
|
f6cf88a6bd | ||
|
|
14a5762fbb | ||
|
|
eda24df3fe | ||
|
|
3a5a4b06a9 |
@@ -9,7 +9,7 @@
|
||||
|
||||
<h1>背景</h1>
|
||||
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。</p>
|
||||
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.6及以下的版本(阿里内部mysql 5.6.10, mysql 5.5.18和5.1.40/48)</p>
|
||||
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
|
||||
<p style="font-size: 14px;"> </p>
|
||||
<p style="font-size: 14px;">基于日志增量订阅&消费支持的业务:</p>
|
||||
<ol style="font-size: 14px;">
|
||||
@@ -80,3 +80,10 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
||||
<li>新浪微博: agapple0002 </li>
|
||||
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
|
||||
</ol>
|
||||
|
||||
<pre>
|
||||
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
|
||||
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
|
||||
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
|
||||
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
|
||||
</pre>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
+20
-28
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -107,9 +106,10 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
this.filter = filter;
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when subscribing from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format(
|
||||
"something goes wrong when subscribing from server: %s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||
t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -126,9 +126,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.unsubscribe();
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when unsubscribing from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -144,9 +143,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.get(batchSize);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -162,9 +160,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.get(batchSize, timeout, unit);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -180,9 +177,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.getWithoutAck(batchSize);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -198,9 +194,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -216,9 +211,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.rollback(batchId);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -234,9 +228,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.rollback();
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -253,9 +246,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.ack(batchId);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when acking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
|
||||
-1
@@ -114,5 +114,4 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
return zkClient;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+79
-56
@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -37,7 +41,6 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||
import com.alibaba.otter.canal.protocol.Message;
|
||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
/**
|
||||
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
||||
@@ -57,6 +60,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
private SocketChannel channel;
|
||||
private ReadableByteChannel readableChannel;
|
||||
private WritableByteChannel writableChannel;
|
||||
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
||||
private ClientIdentity clientIdentity;
|
||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||
@@ -130,7 +135,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
address = getNextAddress();
|
||||
}
|
||||
channel.connect(address);
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
readableChannel = Channels.newChannel(channel.socket().getInputStream());
|
||||
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
if (p.getVersion() != 1) {
|
||||
throw new CanalClientException("unsupported version at this client.");
|
||||
}
|
||||
@@ -144,17 +151,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
//
|
||||
ClientAuth ca = ClientAuth.newBuilder()
|
||||
.setUsername(username != null ? username : "")
|
||||
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||
.setNetReadTimeout(soTimeout)
|
||||
.setNetWriteTimeout(soTimeout)
|
||||
.build();
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet ack = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet ack = Packet.parseFrom(readNextPacket());
|
||||
if (ack.getType() != PacketType.ACK) {
|
||||
throw new CanalClientException("unexpected packet type when ack is expected");
|
||||
}
|
||||
@@ -173,16 +180,28 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
|
||||
private void doDisconnnect() throws CanalClientException {
|
||||
if (readableChannel != null) {
|
||||
quietlyClose(readableChannel);
|
||||
readableChannel = null;
|
||||
}
|
||||
if (writableChannel != null) {
|
||||
quietlyClose(writableChannel);
|
||||
writableChannel = null;
|
||||
}
|
||||
if (channel != null) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||
}
|
||||
quietlyClose(channel);
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void quietlyClose(Channel channel) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe() throws CanalClientException {
|
||||
subscribe(""); // 传递空字符即可
|
||||
}
|
||||
@@ -190,19 +209,18 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
public void subscribe(String filter) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
try {
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.SUBSCRIPTION)
|
||||
.setBody(Sub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFilter(filter != null ? filter : "")
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.SUBSCRIPTION)
|
||||
.setBody(Sub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFilter(filter != null ? filter : "")
|
||||
.build()
|
||||
.toByteArray());
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
Ack ack = Ack.parseFrom(p.getBody());
|
||||
if (ack.getErrorCode() > 0) {
|
||||
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
||||
@@ -217,18 +235,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
public void unsubscribe() throws CanalClientException {
|
||||
waitClientRunning();
|
||||
try {
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.UNSUBSCRIPTION)
|
||||
.setBody(Unsub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.UNSUBSCRIPTION)
|
||||
.setBody(Unsub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.build()
|
||||
.toByteArray());
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
Ack ack = Ack.parseFrom(p.getBody());
|
||||
if (ack.getErrorCode() > 0) {
|
||||
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
||||
@@ -261,29 +278,27 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
unit = TimeUnit.MILLISECONDS;
|
||||
}
|
||||
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.GET)
|
||||
.setBody(Get.newBuilder()
|
||||
.setAutoAck(false)
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFetchSize(size)
|
||||
.setTimeout(time)
|
||||
.setUnit(unit.ordinal())
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.GET)
|
||||
.setBody(Get.newBuilder()
|
||||
.setAutoAck(false)
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFetchSize(size)
|
||||
.setTimeout(time)
|
||||
.setUnit(unit.ordinal())
|
||||
.build()
|
||||
.toByteArray());
|
||||
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
return receiveMessages();
|
||||
} catch (IOException e) {
|
||||
throw new CanalClientException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Message receiveMessages() throws InvalidProtocolBufferException, IOException {
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
private Message receiveMessages() throws IOException {
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
switch (p.getType()) {
|
||||
case MESSAGES: {
|
||||
if (!p.getCompression().equals(Compression.NONE)) {
|
||||
@@ -315,7 +330,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
.setBatchId(batchId)
|
||||
.build();
|
||||
try {
|
||||
writeWithHeader(channel, Packet.newBuilder()
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTACK)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
@@ -333,7 +348,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
.setBatchId(batchId)
|
||||
.build();
|
||||
try {
|
||||
writeWithHeader(channel, Packet.newBuilder()
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTROLLBACK)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
@@ -350,7 +365,15 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
// ==================== helper method ====================
|
||||
|
||||
private void writeWithHeader(SocketChannel channel, byte[] body) throws IOException {
|
||||
private void writeWithHeader(byte[] body) throws IOException {
|
||||
writeWithHeader(writableChannel, body);
|
||||
}
|
||||
|
||||
private byte[] readNextPacket() throws IOException {
|
||||
return readNextPacket(readableChannel);
|
||||
}
|
||||
|
||||
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
|
||||
synchronized (writeDataLock) {
|
||||
writeHeader.clear();
|
||||
writeHeader.putInt(body.length);
|
||||
@@ -360,7 +383,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readNextPacket(SocketChannel channel) throws IOException {
|
||||
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
|
||||
synchronized (readDataLock) {
|
||||
readHeader.clear();
|
||||
read(channel, readHeader);
|
||||
@@ -371,7 +394,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private void read(SocketChannel channel, ByteBuffer buffer) throws IOException {
|
||||
private void read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
|
||||
while (buffer.hasRemaining()) {
|
||||
int r = channel.read(buffer);
|
||||
if (r == -1) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
+39
-8
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.common</artifactId>
|
||||
@@ -11,13 +11,18 @@
|
||||
<name>canal common module for otter ${project.version}</name>
|
||||
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<!-- zk -->
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.sgroschupf</groupId>
|
||||
<groupId>com.101tec</groupId>
|
||||
<artifactId>zkclient</artifactId>
|
||||
</dependency>
|
||||
<!-- external -->
|
||||
@@ -28,11 +33,10 @@
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring</artifactId>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
@@ -59,10 +63,37 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package com.alibaba.otter.canal.common.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||
import com.alibaba.fastjson.serializer.SerializeWriter;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
|
||||
@@ -17,6 +24,12 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
static {
|
||||
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
||||
}
|
||||
|
||||
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
|
||||
}
|
||||
@@ -70,4 +83,22 @@ public class JsonUtils {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InetAddressSerializer implements ObjectSerializer {
|
||||
|
||||
public static InetAddressSerializer instance = new InetAddressSerializer();
|
||||
|
||||
@Override
|
||||
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
|
||||
throws IOException {
|
||||
if (object == null) {
|
||||
serializer.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = (InetAddress) object;
|
||||
// 优先使用name
|
||||
serializer.write(address.getHostName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,15 @@ import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.I0Itec.zkclient.IZkConnection;
|
||||
import org.I0Itec.zkclient.ZkConnection;
|
||||
import org.I0Itec.zkclient.exception.ZkException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.zookeeper.ClientCnxn;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.Watcher;
|
||||
import org.apache.zookeeper.ZooDefs.Ids;
|
||||
import org.apache.zookeeper.ZooKeeper;
|
||||
import org.apache.zookeeper.ZooKeeper.States;
|
||||
import org.apache.zookeeper.client.ConnectStringParser;
|
||||
import org.apache.zookeeper.client.HostProvider;
|
||||
import org.apache.zookeeper.client.StaticHostProvider;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author jianghang 2012-7-10 下午02:31:42
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class ZooKeeperx implements IZkConnection {
|
||||
public class ZooKeeperx extends ZkConnection {
|
||||
|
||||
private static final String SERVER_COMMA = ";";
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
|
||||
@@ -40,12 +35,12 @@ public class ZooKeeperx implements IZkConnection {
|
||||
private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
|
||||
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
||||
"serverAddresses");
|
||||
private static final Field zookeeperLockField = ReflectionUtils.findField(ZkConnection.class,
|
||||
"_zookeeperLock");
|
||||
private static final Field zookeeperFiled = ReflectionUtils.findField(ZkConnection.class, "_zk");
|
||||
private static final int DEFAULT_SESSION_TIMEOUT = 90000;
|
||||
|
||||
private ZooKeeper _zk = null;
|
||||
private Lock _zookeeperLock = new ReentrantLock();
|
||||
|
||||
private final List<String> _servers;
|
||||
private final List<String> _serversList;
|
||||
private final int _sessionTimeOut;
|
||||
|
||||
public ZooKeeperx(String zkServers){
|
||||
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
|
||||
}
|
||||
|
||||
public ZooKeeperx(String zkServers, int sessionTimeOut){
|
||||
_servers = Arrays.asList(StringUtils.split(zkServers, SERVER_COMMA));
|
||||
super(zkServers, sessionTimeOut);
|
||||
_serversList = Arrays.asList(StringUtils.split(this.getServers(), SERVER_COMMA));
|
||||
_sessionTimeOut = sessionTimeOut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Watcher watcher) {
|
||||
ReflectionUtils.makeAccessible(zookeeperLockField);
|
||||
ReflectionUtils.makeAccessible(zookeeperFiled);
|
||||
Lock _zookeeperLock = (ReentrantLock) ReflectionUtils.getField(zookeeperLockField, this);
|
||||
ZooKeeper _zk = (ZooKeeper) ReflectionUtils.getField(zookeeperFiled, this);
|
||||
|
||||
_zookeeperLock.lock();
|
||||
try {
|
||||
if (_zk != null) {
|
||||
throw new IllegalStateException("zk client has already been started");
|
||||
}
|
||||
String zkServers = _serversList.get(0);
|
||||
|
||||
try {
|
||||
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
|
||||
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
|
||||
logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
|
||||
_zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
|
||||
configMutliCluster(_zk);
|
||||
ReflectionUtils.setField(zookeeperFiled, this, _zk);
|
||||
} catch (IOException e) {
|
||||
throw new ZkException("Unable to connect to " + _servers, e);
|
||||
throw new ZkException("Unable to connect to " + zkServers, e);
|
||||
}
|
||||
} finally {
|
||||
_zookeeperLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws InterruptedException {
|
||||
_zookeeperLock.lock();
|
||||
try {
|
||||
if (_zk != null) {
|
||||
logger.debug("Closing ZooKeeper connected to " + _servers);
|
||||
_zk.close();
|
||||
_zk = null;
|
||||
}
|
||||
} finally {
|
||||
_zookeeperLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public String create(String path, byte[] data, CreateMode mode) throws KeeperException, InterruptedException {
|
||||
return _zk.create(path, data, Ids.OPEN_ACL_UNSAFE, mode);
|
||||
}
|
||||
|
||||
public void delete(String path) throws InterruptedException, KeeperException {
|
||||
_zk.delete(path, -1);
|
||||
}
|
||||
|
||||
public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException {
|
||||
return _zk.exists(path, watch) != null;
|
||||
}
|
||||
|
||||
public List<String> getChildren(final String path, final boolean watch) throws KeeperException,
|
||||
InterruptedException {
|
||||
return _zk.getChildren(path, watch);
|
||||
}
|
||||
|
||||
public byte[] readData(String path, Stat stat, boolean watch) throws KeeperException, InterruptedException {
|
||||
return _zk.getData(path, watch, stat);
|
||||
}
|
||||
|
||||
public void writeData(String path, byte[] data) throws KeeperException, InterruptedException {
|
||||
writeData(path, data, -1);
|
||||
}
|
||||
|
||||
public void writeData(String path, byte[] data, int version) throws KeeperException, InterruptedException {
|
||||
_zk.setData(path, data, version);
|
||||
}
|
||||
|
||||
public States getZookeeperState() {
|
||||
return _zk != null ? _zk.getState() : null;
|
||||
}
|
||||
|
||||
public ZooKeeper getZookeeper() {
|
||||
return _zk;
|
||||
}
|
||||
|
||||
public long getCreateTime(String path) throws KeeperException, InterruptedException {
|
||||
Stat stat = _zk.exists(path, false);
|
||||
if (stat != null) {
|
||||
return stat.getCtime();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String getServers() {
|
||||
return StringUtils.join(_servers, SERVER_COMMA);
|
||||
}
|
||||
|
||||
// ===============================
|
||||
|
||||
public void configMutliCluster(ZooKeeper zk) {
|
||||
if (_servers.size() == 1) {
|
||||
if (_serversList.size() == 1) {
|
||||
return;
|
||||
}
|
||||
String cluster1 = _servers.get(0);
|
||||
String cluster1 = _serversList.get(0);
|
||||
try {
|
||||
if (_servers.size() > 1) {
|
||||
if (_serversList.size() > 1) {
|
||||
// 强制的声明accessible
|
||||
ReflectionUtils.makeAccessible(clientCnxnField);
|
||||
ReflectionUtils.makeAccessible(hostProviderField);
|
||||
ReflectionUtils.makeAccessible(serverAddressesField);
|
||||
|
||||
// 添加第二组集群列表
|
||||
for (int i = 1; i < _servers.size(); i++) {
|
||||
String cluster = _servers.get(i);
|
||||
for (int i = 1; i < _serversList.size(); i++) {
|
||||
String cluster = _serversList.get(i);
|
||||
// 强制获取zk中的地址信息
|
||||
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
||||
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
||||
|
||||
+4
@@ -87,6 +87,10 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
|
||||
}
|
||||
|
||||
public void init() {
|
||||
processStart();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
processStart();
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -52,23 +52,24 @@ public class JsonConversion {
|
||||
public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE);
|
||||
public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE);
|
||||
|
||||
public static Json_Value parse_value(int type, LogBuffer buffer, long len) {
|
||||
public static Json_Value parse_value(int type, LogBuffer buffer, long len, String charsetName) {
|
||||
buffer = buffer.duplicate(buffer.position(), (int) len);
|
||||
switch (type) {
|
||||
case JSONB_TYPE_SMALL_OBJECT:
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false);
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false, charsetName);
|
||||
case JSONB_TYPE_LARGE_OBJECT:
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true);
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true, charsetName);
|
||||
case JSONB_TYPE_SMALL_ARRAY:
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false);
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false, charsetName);
|
||||
case JSONB_TYPE_LARGE_ARRAY:
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true);
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true, charsetName);
|
||||
default:
|
||||
return parse_scalar(type, buffer, len);
|
||||
return parse_scalar(type, buffer, len, charsetName);
|
||||
}
|
||||
}
|
||||
|
||||
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large) {
|
||||
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large,
|
||||
String charsetName) {
|
||||
long offset_size = large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
if (len < 2 * offset_size) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
@@ -95,7 +96,7 @@ public class JsonConversion {
|
||||
return large ? buffer.getUint32() : buffer.getUint16();
|
||||
}
|
||||
|
||||
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len) {
|
||||
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len, String charsetName) {
|
||||
switch (type) {
|
||||
case JSONB_TYPE_LITERAL:
|
||||
/* purecov: inspected */
|
||||
@@ -146,12 +147,12 @@ public class JsonConversion {
|
||||
}
|
||||
}
|
||||
|
||||
if (str_len == 0 || len < n + str_len) {
|
||||
if (len < n + str_len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(Json_enum_type.STRING, buffer.rewind()
|
||||
.forward((int) n)
|
||||
.getFixString((int) str_len));
|
||||
.getFixString((int) str_len, charsetName));
|
||||
case JSONB_TYPE_OPAQUE:
|
||||
/*
|
||||
* There should always be at least one byte, which tells the
|
||||
@@ -237,7 +238,7 @@ public class JsonConversion {
|
||||
this.m_large = large;
|
||||
}
|
||||
|
||||
public String key(int i) {
|
||||
public String key(int i, String charsetName) {
|
||||
m_data.rewind();
|
||||
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
|
||||
@@ -250,10 +251,10 @@ public class JsonConversion {
|
||||
// entry, always two
|
||||
// bytes.
|
||||
long key_length = m_data.getUint16();
|
||||
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length);
|
||||
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length, charsetName);
|
||||
}
|
||||
|
||||
public Json_Value element(int i) {
|
||||
public Json_Value element(int i, String charsetName) {
|
||||
m_data.rewind();
|
||||
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
|
||||
@@ -266,13 +267,13 @@ public class JsonConversion {
|
||||
int type = m_data.forward(entry_offset).getUint8();
|
||||
if (type == JSONB_TYPE_INT16 || type == JSONB_TYPE_UINT16 || type == JSONB_TYPE_LITERAL
|
||||
|| (m_large && (type == JSONB_TYPE_INT32 || type == JSONB_TYPE_UINT32))) {
|
||||
return parse_scalar(type, m_data, value_entry_size - 1);
|
||||
return parse_scalar(type, m_data, value_entry_size - 1, charsetName);
|
||||
}
|
||||
int value_offset = (int) read_offset_or_size(m_data, m_large);
|
||||
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset);
|
||||
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset, charsetName);
|
||||
}
|
||||
|
||||
public StringBuilder toJsonString(StringBuilder buf) {
|
||||
public StringBuilder toJsonString(StringBuilder buf, String charsetName) {
|
||||
switch (m_type) {
|
||||
case OBJECT:
|
||||
buf.append("{");
|
||||
@@ -280,9 +281,9 @@ public class JsonConversion {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append('"').append(key(i)).append('"');
|
||||
buf.append(" :");
|
||||
element(i).toJsonString(buf);
|
||||
buf.append('"').append(key(i, charsetName)).append('"');
|
||||
buf.append(": ");
|
||||
element(i, charsetName).toJsonString(buf, charsetName);
|
||||
}
|
||||
buf.append("}");
|
||||
break;
|
||||
@@ -292,7 +293,7 @@ public class JsonConversion {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
element(i).toJsonString(buf);
|
||||
element(i, charsetName).toJsonString(buf, charsetName);
|
||||
}
|
||||
buf.append("]");
|
||||
break;
|
||||
@@ -361,13 +362,13 @@ public class JsonConversion {
|
||||
}
|
||||
buf.append('"').append(text).append('"');
|
||||
} else {
|
||||
text = m_data.getFixString((int) m_length);
|
||||
buf.append('"').append(text).append('"');
|
||||
text = m_data.getFixString((int) m_length, charsetName);
|
||||
buf.append('"').append(escapse(text)).append('"');
|
||||
}
|
||||
|
||||
break;
|
||||
case STRING:
|
||||
buf.append('"').append(m_string_value).append('"');
|
||||
buf.append('"').append(escapse(m_string_value)).append('"');
|
||||
break;
|
||||
case ERROR:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
@@ -377,6 +378,21 @@ public class JsonConversion {
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder escapse(String data) {
|
||||
StringBuilder sb = new StringBuilder(data.length());
|
||||
int endIndex = data.length();
|
||||
for (int i = 0; i < endIndex; ++i) {
|
||||
char c = data.charAt(i);
|
||||
if (c == '"') {
|
||||
sb.append('\\');
|
||||
} else if (c == '\\') {
|
||||
sb.append("\\");
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
public static enum Json_enum_type {
|
||||
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
|
||||
}
|
||||
|
||||
@@ -168,8 +168,8 @@ public class LogBuffer {
|
||||
* @param newLimit The new limit value; must be non-negative and no larger
|
||||
* than this buffer's capacity
|
||||
* @return This buffer
|
||||
* @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt>
|
||||
* do not hold
|
||||
* @throws IllegalArgumentException If the preconditions on
|
||||
* <tt>newLimit</tt> do not hold
|
||||
*/
|
||||
public final LogBuffer limit(int newLimit) {
|
||||
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "
|
||||
|
||||
+5
@@ -141,6 +141,11 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
||||
}
|
||||
}
|
||||
|
||||
public FormatDescriptionLogEvent(final int binlogVersion, int binlogChecksum){
|
||||
this(binlogVersion);
|
||||
this.header.checksumAlg = binlogChecksum;
|
||||
}
|
||||
|
||||
public FormatDescriptionLogEvent(final int binlogVersion){
|
||||
this.binlogVersion = binlogVersion;
|
||||
|
||||
|
||||
@@ -330,17 +330,14 @@ public class QueryLogEvent extends LogEvent {
|
||||
* MariaDb type,
|
||||
* sec_part of NOW()
|
||||
*/
|
||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 16 + 1 + 60/*
|
||||
* type
|
||||
* ,
|
||||
* user_len
|
||||
* ,
|
||||
* user
|
||||
* ,
|
||||
* host_len
|
||||
* ,
|
||||
* host
|
||||
*/);
|
||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 32 * 3
|
||||
+ 1 + 60/*
|
||||
* type ,
|
||||
* user_len ,
|
||||
* user ,
|
||||
* host_len ,
|
||||
* host
|
||||
*/);
|
||||
/**
|
||||
* Fixed data part:
|
||||
* <ul>
|
||||
@@ -397,7 +394,7 @@ public class QueryLogEvent extends LogEvent {
|
||||
// inspection by the DBA
|
||||
private final long execTime;
|
||||
private final int errorCode;
|
||||
private final long sessionId; /* thread_id */
|
||||
private final long sessionId; /* thread_id */
|
||||
|
||||
/**
|
||||
* 'flags2' is a second set of flags (on top of those in Log_event), for
|
||||
@@ -499,26 +496,26 @@ public class QueryLogEvent extends LogEvent {
|
||||
}
|
||||
|
||||
/* query event post-header */
|
||||
public static final int Q_THREAD_ID_OFFSET = 0;
|
||||
public static final int Q_EXEC_TIME_OFFSET = 4;
|
||||
public static final int Q_DB_LEN_OFFSET = 8;
|
||||
public static final int Q_ERR_CODE_OFFSET = 9;
|
||||
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
|
||||
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
|
||||
public static final int Q_THREAD_ID_OFFSET = 0;
|
||||
public static final int Q_EXEC_TIME_OFFSET = 4;
|
||||
public static final int Q_DB_LEN_OFFSET = 8;
|
||||
public static final int Q_ERR_CODE_OFFSET = 9;
|
||||
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
|
||||
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
|
||||
|
||||
/* these are codes, not offsets; not more than 256 values (1 byte). */
|
||||
public static final int Q_FLAGS2_CODE = 0;
|
||||
public static final int Q_SQL_MODE_CODE = 1;
|
||||
public static final int Q_FLAGS2_CODE = 0;
|
||||
public static final int Q_SQL_MODE_CODE = 1;
|
||||
|
||||
/**
|
||||
* Q_CATALOG_CODE is catalog with end zero stored; it is used only by MySQL
|
||||
* 5.0.x where 0<=x<=3. We have to keep it to be able to replicate these old
|
||||
* masters.
|
||||
*/
|
||||
public static final int Q_CATALOG_CODE = 2;
|
||||
public static final int Q_AUTO_INCREMENT = 3;
|
||||
public static final int Q_CHARSET_CODE = 4;
|
||||
public static final int Q_TIME_ZONE_CODE = 5;
|
||||
public static final int Q_CATALOG_CODE = 2;
|
||||
public static final int Q_AUTO_INCREMENT = 3;
|
||||
public static final int Q_CHARSET_CODE = 4;
|
||||
public static final int Q_TIME_ZONE_CODE = 5;
|
||||
|
||||
/**
|
||||
* Q_CATALOG_NZ_CODE is catalog withOUT end zero stored; it is used by MySQL
|
||||
@@ -528,31 +525,47 @@ public class QueryLogEvent extends LogEvent {
|
||||
* would crash (segfault etc) because it would expect a 0 when there is
|
||||
* none.
|
||||
*/
|
||||
public static final int Q_CATALOG_NZ_CODE = 6;
|
||||
public static final int Q_CATALOG_NZ_CODE = 6;
|
||||
|
||||
public static final int Q_LC_TIME_NAMES_CODE = 7;
|
||||
public static final int Q_LC_TIME_NAMES_CODE = 7;
|
||||
|
||||
public static final int Q_CHARSET_DATABASE_CODE = 8;
|
||||
public static final int Q_CHARSET_DATABASE_CODE = 8;
|
||||
|
||||
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
|
||||
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
|
||||
|
||||
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
|
||||
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
|
||||
|
||||
public static final int Q_INVOKER = 11;
|
||||
public static final int Q_INVOKER = 11;
|
||||
|
||||
/**
|
||||
* Q_UPDATED_DB_NAMES status variable collects of the updated databases
|
||||
* total number and their names to be propagated to the slave in order to
|
||||
* facilitate the parallel applying of the Query events.
|
||||
*/
|
||||
public static final int Q_UPDATED_DB_NAMES = 12;
|
||||
public static final int Q_UPDATED_DB_NAMES = 12;
|
||||
|
||||
public static final int Q_MICROSECONDS = 13;
|
||||
public static final int Q_MICROSECONDS = 13;
|
||||
/**
|
||||
* A old (unused now) code for Query_log_event status similar to
|
||||
* G_COMMIT_TS.
|
||||
*/
|
||||
public static final int Q_COMMIT_TS = 14;
|
||||
/**
|
||||
* A code for Query_log_event status, similar to G_COMMIT_TS2.
|
||||
*/
|
||||
public static final int Q_COMMIT_TS2 = 15;
|
||||
/**
|
||||
* The master connection @@session.explicit_defaults_for_timestamp which is
|
||||
* recorded for queries, CREATE and ALTER table that is defined with a
|
||||
* TIMESTAMP column, that are dependent on that feature. For pre-WL6292
|
||||
* master's the associated with this code value is zero.
|
||||
*/
|
||||
public static final int Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP = 16;
|
||||
|
||||
/**
|
||||
* FROM MariaDB 5.5.34
|
||||
*/
|
||||
public static final int Q_HRNOW = 128;
|
||||
public static final int Q_HRNOW = 128;
|
||||
|
||||
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
|
||||
int code = -1;
|
||||
@@ -632,6 +645,9 @@ public class QueryLogEvent extends LogEvent {
|
||||
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
|
||||
}
|
||||
break;
|
||||
case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
|
||||
buffer.forward(1);
|
||||
break;
|
||||
case Q_HRNOW:
|
||||
// int when_sec_part = buffer.getUint24();
|
||||
buffer.forward(3);
|
||||
|
||||
@@ -90,6 +90,7 @@ public final class RotateLogEvent extends LogEvent {
|
||||
int filenameLen = buffer.limit() - filenameOffset;
|
||||
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
|
||||
buffer.position(filenameOffset);
|
||||
|
||||
filename = buffer.getFixString(filenameLen);
|
||||
}
|
||||
|
||||
|
||||
@@ -949,19 +949,38 @@ public final class RowsLogBuffer {
|
||||
break;
|
||||
}
|
||||
case LogEvent.MYSQL_TYPE_JSON: {
|
||||
len = buffer.getUint16();
|
||||
buffer.forward(meta - 2);
|
||||
switch (meta) {
|
||||
case 1: {
|
||||
len = buffer.getUint8();
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
len = buffer.getUint16();
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
len = buffer.getUint24();
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
len = (int) buffer.getUint32();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
||||
}
|
||||
// len = buffer.getUint16();
|
||||
// buffer.forward(meta - 4);
|
||||
int position = buffer.position();
|
||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1);
|
||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
jsonValue.toJsonString(builder);
|
||||
jsonValue.toJsonString(builder, charsetName);
|
||||
value = builder.toString();
|
||||
buffer.position(position + len);
|
||||
|
||||
// byte[] binary = new byte[len];
|
||||
// buffer.fillBytes(binary, 0, len);
|
||||
// value = binary;
|
||||
javaType = Types.VARBINARY;
|
||||
javaType = Types.VARCHAR;
|
||||
length = len;
|
||||
break;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Properties;
|
||||
|
||||
import org.I0Itec.zkclient.IZkStateListener;
|
||||
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
||||
@@ -148,6 +149,11 @@ public class CanalController {
|
||||
public void handleNewSession() throws Exception {
|
||||
initCid(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||
logger.error("failed to connect to zookeeper", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
@@ -172,6 +178,8 @@ public class CanalController {
|
||||
if (zkclientx != null) {
|
||||
runningMonitor.setZkClient(zkclientx);
|
||||
}
|
||||
// 触发创建一下cid节点
|
||||
runningMonitor.init();
|
||||
return runningMonitor;
|
||||
}
|
||||
}));
|
||||
@@ -189,10 +197,10 @@ public class CanalController {
|
||||
instanceConfigs.put(destination, config);
|
||||
}
|
||||
|
||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
||||
if (!embededCanalServer.isStart(destination)) {
|
||||
// HA机制启动
|
||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||
if (!runningMonitor.isStart()) {
|
||||
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
@@ -380,6 +388,11 @@ public class CanalController {
|
||||
public void handleNewSession() throws Exception {
|
||||
initCid(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||
logger.error("failed to connect to zookeeper", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 优先启动embeded服务
|
||||
@@ -389,10 +402,10 @@ public class CanalController {
|
||||
final String destination = entry.getKey();
|
||||
InstanceConfig config = entry.getValue();
|
||||
// 创建destination的工作节点
|
||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
||||
if (!embededCanalServer.isStart(destination)) {
|
||||
// HA机制启动
|
||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||
if (!runningMonitor.isStart()) {
|
||||
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
@@ -448,6 +461,9 @@ public class CanalController {
|
||||
String parentDir = path.substring(0, path.lastIndexOf('/'));
|
||||
zkclientx.createPersistent(parentDir, true);
|
||||
zkclientx.createEphemeral(path);
|
||||
} catch (ZkNodeExistsException e) {
|
||||
// ignore
|
||||
// 因为第一次启动时创建了cid,但在stop/start的时可能会关闭和新建,允许出现NodeExists问题s
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.FileInputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -21,6 +20,10 @@ public class CanalLauncher {
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
try {
|
||||
logger.info("## set default uncaught exception handler");
|
||||
setGlobalUncaughtExceptionHandler();
|
||||
|
||||
logger.info("## load canal configurations");
|
||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||
Properties properties = new Properties();
|
||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||
@@ -41,8 +44,7 @@ public class CanalLauncher {
|
||||
logger.info("## stop the canal server");
|
||||
controller.stop();
|
||||
} catch (Throwable e) {
|
||||
logger.warn("##something goes wrong when stopping canal Server:\n{}",
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||
} finally {
|
||||
logger.info("## canal server is down.");
|
||||
}
|
||||
@@ -50,9 +52,17 @@ public class CanalLauncher {
|
||||
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
logger.error("## Something goes wrong when starting up the canal Server:\n{}",
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setGlobalUncaughtExceptionHandler() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
logger.error("UnCaughtException", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+25
-9
@@ -13,7 +13,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -49,6 +48,8 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
|
||||
new NamedThreadFactory("canal-instance-scan"));
|
||||
|
||||
private volatile boolean isFirst = true;
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
Assert.notNull(rootConf, "root conf dir is null!");
|
||||
@@ -58,6 +59,9 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
public void run() {
|
||||
try {
|
||||
scan();
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan failed", e);
|
||||
}
|
||||
@@ -122,13 +126,18 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
|
||||
if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
|
||||
// 存在合法的instance.properties,并且第一次添加时,进行启动操作
|
||||
notifyStart(instanceDir, destination);
|
||||
notifyStart(instanceDir, destination, instanceConfigs);
|
||||
} else if (actions.containsKey(destination)) {
|
||||
// 历史已经启动过
|
||||
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
|
||||
notifyStop(destination);
|
||||
} else {
|
||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
||||
// 历史启动过 所以配置文件信息必然存在
|
||||
if (!isFirst && CollectionUtils.isEmpty(lastFile.getInstanceFiles())) {
|
||||
logger.error("[{}] is started, but not found instance file info.", destination);
|
||||
}
|
||||
|
||||
boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles());
|
||||
// 通知变化
|
||||
if (hasChanged) {
|
||||
@@ -161,13 +170,22 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyStart(File instanceDir, String destination) {
|
||||
private void notifyStart(File instanceDir, String destination, File[] instanceConfigs) {
|
||||
try {
|
||||
defaultAction.start(destination);
|
||||
actions.put(destination, defaultAction);
|
||||
|
||||
// 启动成功后记录配置文件信息
|
||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
||||
List<FileInfo> newFileInfo = new ArrayList<FileInfo>();
|
||||
for (File instanceConfig : instanceConfigs) {
|
||||
newFileInfo.add(new FileInfo(instanceConfig.getName(), instanceConfig.lastModified()));
|
||||
}
|
||||
lastFile.setInstanceFiles(newFileInfo);
|
||||
|
||||
logger.info("auto notify start {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan add found[{}] but start failed", destination, ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan add found[%s] but start failed", destination), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +193,10 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
InstanceAction action = actions.remove(destination);
|
||||
try {
|
||||
action.stop(destination);
|
||||
lastFiles.remove(destination);
|
||||
logger.info("auto notify stop {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan delete found[{}] but stop failed", destination, ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan delete found[%s] but stop failed", destination), e);
|
||||
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
|
||||
}
|
||||
}
|
||||
@@ -189,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
action.reload(destination);
|
||||
logger.info("auto notify reload {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan reload found[{}] but reload failed",
|
||||
destination,
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ canal.instance.network.sendBufferSize = 16384
|
||||
canal.instance.network.soTimeout = 30
|
||||
|
||||
# binlog filter config
|
||||
canal.instance.filter.druid.ddl = true
|
||||
canal.instance.filter.query.dcl = false
|
||||
canal.instance.filter.query.dml = false
|
||||
canal.instance.filter.query.ddl = false
|
||||
@@ -59,9 +60,13 @@ canal.conf.dir = ../conf
|
||||
canal.auto.scan = true
|
||||
canal.auto.scan.interval = 5
|
||||
|
||||
canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
|
||||
#canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml
|
||||
|
||||
canal.instance.global.mode = spring
|
||||
canal.instance.global.lazy = false
|
||||
#canal.instance.global.manager.address = 127.0.0.1:1099
|
||||
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
||||
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
#################################################
|
||||
## mysql serverId
|
||||
canal.instance.mysql.slaveId = 1234
|
||||
|
||||
canal.instance.mysql.slaveId=0
|
||||
# position info
|
||||
canal.instance.master.address = 127.0.0.1:3306
|
||||
canal.instance.master.journal.name =
|
||||
canal.instance.master.position =
|
||||
canal.instance.master.timestamp =
|
||||
canal.instance.master.address=127.0.0.1:3306
|
||||
canal.instance.master.journal.name=
|
||||
canal.instance.master.position=
|
||||
canal.instance.master.timestamp=
|
||||
|
||||
#canal.instance.standby.address =
|
||||
|
||||
# table meta tsdb info
|
||||
canal.instance.tsdb.enable=true
|
||||
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
||||
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||
canal.instance.tsdb.dbUsername=canal
|
||||
canal.instance.tsdb.dbPassword=canal
|
||||
|
||||
|
||||
#canal.instance.standby.address =
|
||||
#canal.instance.standby.journal.name =
|
||||
#canal.instance.standby.position =
|
||||
#canal.instance.standby.timestamp =
|
||||
|
||||
# username/password
|
||||
canal.instance.dbUsername = canal
|
||||
canal.instance.dbPassword = canal
|
||||
canal.instance.defaultDatabaseName =
|
||||
canal.instance.connectionCharset = UTF-8
|
||||
|
||||
canal.instance.dbUsername=canal
|
||||
canal.instance.dbPassword=canal
|
||||
canal.instance.defaultDatabaseName=test
|
||||
canal.instance.connectionCharset=UTF-8
|
||||
# table regex
|
||||
canal.instance.filter.regex = .*\\..*
|
||||
canal.instance.filter.regex=.*\\..*
|
||||
# table black regex
|
||||
canal.instance.filter.black.regex =
|
||||
|
||||
canal.instance.filter.black.regex=
|
||||
#################################################
|
||||
@@ -0,0 +1,32 @@
|
||||
#################################################
|
||||
# rds openapi binlog
|
||||
canal.instance.rds.open.url=https://rds.aliyuncs.com/
|
||||
canal.instance.rds.open.accesskey=
|
||||
canal.instance.rds.open.secretkey=
|
||||
canal.instance.rds.instanceId=
|
||||
canal.instance.rds.startTime=
|
||||
canal.instance.rds.endTime=
|
||||
|
||||
# local binlog dir
|
||||
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
|
||||
# position info
|
||||
canal.instance.master.address=127.0.0.1:3306
|
||||
|
||||
# table meta tsdb info
|
||||
canal.instance.tsdb.enable=true
|
||||
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
||||
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||
#canal.instance.tsdb.dbUsername=canal
|
||||
#canal.instance.tsdb.dbPassword=canal
|
||||
|
||||
# username/password
|
||||
canal.instance.dbUsername=canal
|
||||
canal.instance.dbPassword=canal
|
||||
canal.instance.defaultDatabaseName=test
|
||||
canal.instance.connectionCharset=UTF-8
|
||||
# table regex
|
||||
canal.instance.filter.regex=test\\..*
|
||||
# table black regex
|
||||
canal.instance.filter.black.regex=
|
||||
#################################################
|
||||
@@ -75,9 +75,7 @@
|
||||
</logger>
|
||||
|
||||
<root level="WARN">
|
||||
<!--
|
||||
<appender-ref ref="STDOUT"/>
|
||||
-->
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -128,14 +128,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -178,9 +178,14 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -113,14 +113,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -163,9 +163,14 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -160,6 +160,7 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
@@ -167,7 +168,7 @@
|
||||
|
||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -250,6 +251,7 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||
<property name="propertyEditorRegistrars">
|
||||
<list>
|
||||
<ref bean="socketAddressEditor" />
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="eventParser">
|
||||
<ref local="eventParser" />
|
||||
</property>
|
||||
<property name="eventSink">
|
||||
<ref local="eventSink" />
|
||||
</property>
|
||||
<property name="eventStore">
|
||||
<ref local="eventStore" />
|
||||
</property>
|
||||
<property name="metaManager">
|
||||
<ref local="metaManager" />
|
||||
</property>
|
||||
<property name="alarmHandler">
|
||||
<ref local="alarmHandler" />
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- 报警处理类 -->
|
||||
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
|
||||
|
||||
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
|
||||
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
|
||||
<property name="period" value="${canal.file.flush.period:1000}" />
|
||||
</bean>
|
||||
|
||||
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
|
||||
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
|
||||
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
|
||||
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
|
||||
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
|
||||
</bean>
|
||||
|
||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||
<property name="eventStore" ref="eventStore" />
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="alarmHandler" ref="alarmHandler" />
|
||||
|
||||
<!-- 解析过滤处理 -->
|
||||
<property name="eventFilter">
|
||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<property name="eventBlackFilter">
|
||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
|
||||
<constructor-arg index="1" value="false" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
|
||||
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
|
||||
|
||||
<!-- 解析编码 -->
|
||||
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
||||
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<!-- 解析数据库信息 -->
|
||||
<property name="masterInfo">
|
||||
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
|
||||
<property name="address" value="${canal.instance.master.address}" />
|
||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
|
||||
<property name="directory" value="${canal.instance.parser.directory:}"/>
|
||||
|
||||
<!-- rds相关 -->
|
||||
<property name="url" value="${canal.instance.rds.open.url:}"/>
|
||||
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
|
||||
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
|
||||
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
|
||||
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
|
||||
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -151,9 +151,14 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- 基于db的实现 -->
|
||||
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
|
||||
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
|
||||
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="org.h2.Driver" />
|
||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
|
||||
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
||||
<property name="maxActive" value="30" />
|
||||
<property name="initialSize" value="0" />
|
||||
<property name="minIdle" value="1" />
|
||||
<property name="maxWait" value="10000" />
|
||||
<property name="timeBetweenEvictionRunsMillis" value="60000" />
|
||||
<property name="minEvictableIdleTimeMillis" value="300000" />
|
||||
<property name="testWhileIdle" value="true" />
|
||||
<property name="testOnBorrow" value="false" />
|
||||
<property name="testOnReturn" value="false" />
|
||||
<property name="useUnfairLock" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||
default-autowire="byName">
|
||||
|
||||
<!-- properties -->
|
||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||
<property name="ignoreResourceNotFound" value="true" />
|
||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||
<property name="locationNames">
|
||||
<list>
|
||||
<value>classpath:canal.properties</value>
|
||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- 基于db的实现 -->
|
||||
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
|
||||
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
|
||||
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
|
||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
|
||||
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
||||
<property name="maxActive" value="30" />
|
||||
<property name="initialSize" value="0" />
|
||||
<property name="minIdle" value="1" />
|
||||
<property name="maxWait" value="10000" />
|
||||
<property name="timeBetweenEvictionRunsMillis" value="60000" />
|
||||
<property name="minEvictableIdleTimeMillis" value="300000" />
|
||||
<property name="validationQuery" value="SELECT 1" />
|
||||
<property name="exceptionSorterClassName" value="com.alibaba.druid.pool.vendor.MySqlExceptionSorter" />
|
||||
<property name="validConnectionCheckerClassName" value="com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker" />
|
||||
<property name="testWhileIdle" value="true" />
|
||||
<property name="testOnBorrow" value="false" />
|
||||
<property name="testOnReturn" value="false" />
|
||||
<property name="useUnfairLock" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
|
||||
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
|
||||
<sqlMapConfig>
|
||||
<settings useStatementNamespaces="true"/>
|
||||
<sqlMap resource="spring/tsdb/sql-map/sqlmap_history.xml"/>
|
||||
<sqlMap resource="spring/tsdb/sql-map/sqlmap_snapshot.xml"/>
|
||||
</sqlMapConfig>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
|
||||
<sqlMap namespace="meta_history">
|
||||
<typeAlias alias="metaHistoryDO" type="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDO"/>
|
||||
<sql id="allColumns">
|
||||
<![CDATA[
|
||||
gmt_create,gmt_modified,destination,binlog_file,binlog_offest,binlog_master_id,binlog_timestamp,use_schema,sql_schema,sql_table,sql_text,sql_type,extra
|
||||
]]>
|
||||
</sql>
|
||||
<sql id="allVOColumns">
|
||||
<![CDATA[
|
||||
a.id as id,a.gmt_create as gmtCreate,a.gmt_modified as gmtModified,
|
||||
a.destination as destination,a.binlog_file as binlogFile,a.binlog_offest as binlogOffest,a.binlog_master_id as binlogMasterId,a.binlog_timestamp as binlogTimestamp,
|
||||
a.use_schema as useSchema,a.sql_schema as sqlSchema,a.sql_table as sqlTable,a.sql_text as sqlText,a.sql_type as sqlType,a.extra as extra
|
||||
]]>
|
||||
</sql>
|
||||
|
||||
<select id="findByTimestamp" parameterClass="java.util.Map" resultClass="metaHistoryDO">
|
||||
select
|
||||
<include refid="allVOColumns"/>
|
||||
from meta_history a
|
||||
<![CDATA[
|
||||
where destination = #destination# and binlog_timestamp >= #snapshotTimestamp# and binlog_timestamp <= #timestamp#
|
||||
order by binlog_timestamp asc,id asc
|
||||
]]>
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterClass="metaHistoryDO">
|
||||
insert into meta_history (<include refid="allColumns"/>)
|
||||
values(CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#destination#,#binlogFile#,#binlogOffest#,#binlogMasterId#,#binlogTimestamp#,#useSchema#,#sqlSchema#,#sqlTable#,#sqlText#,#sqlType#,#extra#)
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByName" parameterClass="java.util.Map">
|
||||
delete from meta_history
|
||||
where destination=#destination#
|
||||
</delete>
|
||||
|
||||
|
||||
<delete id="deleteByGmtModified" parameterClass="java.util.Map">
|
||||
<![CDATA[
|
||||
delete from meta_history
|
||||
where gmt_modified < timestamp(#timestamp#)
|
||||
]]>
|
||||
</delete>
|
||||
</sqlMap>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
|
||||
<sqlMap namespace="meta_snapshot">
|
||||
<typeAlias alias="metaSnapshotDO" type="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO"/>
|
||||
<typeAlias alias="tableMetaSnapshotDO"
|
||||
type="com.alibaba.middleware.jingwei.biz.dataobject.CanalTableMetaSnapshotDO"/>
|
||||
<sql id="allColumns">
|
||||
<![CDATA[
|
||||
gmt_create,gmt_modified,destination,binlog_file,binlog_offest,binlog_master_id,binlog_timestamp,data,extra
|
||||
]]>
|
||||
</sql>
|
||||
<sql id="allVOColumns">
|
||||
<![CDATA[
|
||||
a.id as id,a.gmt_create as gmtCreate,a.gmt_modified as gmtModified,
|
||||
a.destination as destination,a.binlog_file as binlogFile,a.binlog_offest as binlogOffest,a.binlog_master_id as binlogMasterId,a.binlog_timestamp as binlogTimestamp,a.data as data,a.extra as extra
|
||||
]]>
|
||||
</sql>
|
||||
|
||||
<select id="findByTimestamp" parameterClass="java.util.Map" resultClass="metaSnapshotDO">
|
||||
select <include refid="allVOColumns"/>
|
||||
<![CDATA[
|
||||
from meta_snapshot a
|
||||
where destination = #destination# and binlog_timestamp < #timestamp#
|
||||
order by binlog_timestamp desc,id desc
|
||||
limit 1
|
||||
]]>
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterClass="metaSnapshotDO">
|
||||
insert into meta_snapshot (<include refid="allColumns"/>)
|
||||
values(CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#destination#,#binlogFile#,#binlogOffest#,#binlogMasterId#,#binlogTimestamp#,#data#,#extra#)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterClass="metaSnapshotDO">
|
||||
update meta_snapshot set gmt_modified=now(),
|
||||
binlog_file=#binlogFile#,binlog_offest=#binlogOffest#,binlog_master_id=#binlogMasterId#,binlog_timestamp=#binlogTimestamp#,data=#data#,extra=#extra#
|
||||
where destination=#destination# and binlog_timestamp=0
|
||||
</update>
|
||||
|
||||
<delete id="deleteByName" parameterClass="java.util.Map">
|
||||
delete from meta_snapshot
|
||||
where destination=#destination#
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByGmtModified" parameterClass="java.util.Map">
|
||||
<![CDATA[
|
||||
delete from meta_snapshot
|
||||
where gmt_modified < timestamp(#timestamp#)
|
||||
]]>
|
||||
</delete>
|
||||
</sqlMap>
|
||||
@@ -0,0 +1,39 @@
|
||||
CREATE TABLE IF NOT EXISTS `meta_snapshot` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`gmt_create` datetime NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
||||
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
|
||||
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
|
||||
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
|
||||
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
|
||||
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
|
||||
`data` longtext DEFAULT NULL COMMENT '表结构数据',
|
||||
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
|
||||
KEY `destination` (`destination`),
|
||||
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
|
||||
KEY `gmt_modified` (`gmt_modified`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构记录表快照表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `meta_history` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`gmt_create` datetime NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
||||
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
|
||||
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
|
||||
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
|
||||
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
|
||||
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
|
||||
`use_schema` varchar(1024) DEFAULT NULL COMMENT '执行sql时对应的schema',
|
||||
`schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema',
|
||||
`table` varchar(1024) DEFAULT NULL COMMENT '对应的table',
|
||||
`sql` longtext DEFAULT NULL COMMENT '执行的sql',
|
||||
`type` varchar(256) DEFAULT NULL COMMENT 'sql类型',
|
||||
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
|
||||
KEY `destination` (`destination`),
|
||||
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
|
||||
KEY `gmt_modified` (`gmt_modified`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构变化明细表';
|
||||
+1
-5
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -33,10 +33,6 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.netty</groupId>
|
||||
<artifactId>netty</artifactId>
|
||||
</dependency>
|
||||
<!-- test dependency -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
+19
-25
@@ -2,11 +2,8 @@ package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -16,6 +13,8 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QuitCommandPack
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.Reply323Packet;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannelPool;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||
|
||||
@@ -35,6 +34,7 @@ public class MysqlConnector {
|
||||
private byte charsetNumber = 33;
|
||||
private String defaultSchema = "retl";
|
||||
private int soTimeout = 30 * 1000;
|
||||
private int connTimeout = 5 * 1000;
|
||||
private int receiveBufferSize = 16 * 1024;
|
||||
private int sendBufferSize = 16 * 1024;
|
||||
|
||||
@@ -64,14 +64,12 @@ public class MysqlConnector {
|
||||
public void connect() throws IOException {
|
||||
if (connected.compareAndSet(false, true)) {
|
||||
try {
|
||||
channel = SocketChannel.open();
|
||||
configChannel(channel);
|
||||
channel = SocketChannelPool.open(address);
|
||||
logger.info("connect MysqlConnection to {}...", address);
|
||||
channel.connect(address);
|
||||
negotiate(channel);
|
||||
} catch (Exception e) {
|
||||
disconnect();
|
||||
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
throw new IOException("connect " + this.address + " failure", e);
|
||||
}
|
||||
} else {
|
||||
logger.error("the channel can't be connected twice.");
|
||||
@@ -91,7 +89,7 @@ public class MysqlConnector {
|
||||
}
|
||||
logger.info("disConnect MysqlConnection to {}...", address);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("disconnect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
throw new IOException("disconnect " + this.address + " failure", e);
|
||||
}
|
||||
|
||||
// 执行一次quit
|
||||
@@ -103,7 +101,8 @@ public class MysqlConnector {
|
||||
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
|
||||
executor.update("KILL CONNECTION " + connectionId);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("KILL DUMP " + connectionId + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
// 忽略具体异常
|
||||
logger.info("KILL DUMP " + connectionId + " failure", e);
|
||||
} finally {
|
||||
if (connector != null) {
|
||||
connector.disconnect();
|
||||
@@ -131,6 +130,7 @@ public class MysqlConnector {
|
||||
connector.setReceiveBufferSize(getReceiveBufferSize());
|
||||
connector.setSendBufferSize(getSendBufferSize());
|
||||
connector.setSoTimeout(getSoTimeout());
|
||||
connector.setConnTimeout(connTimeout);
|
||||
return connector;
|
||||
}
|
||||
|
||||
@@ -141,19 +141,7 @@ public class MysqlConnector {
|
||||
HeaderPacket quitHeader = new HeaderPacket();
|
||||
quitHeader.setPacketBodyLength(cmdBody.length);
|
||||
quitHeader.setPacketSequenceNumber((byte) 0x00);
|
||||
PacketManager.write(channel,
|
||||
new ByteBuffer[] { ByteBuffer.wrap(quitHeader.toBytes()), ByteBuffer.wrap(cmdBody) });
|
||||
}
|
||||
|
||||
// ====================== help method ====================
|
||||
|
||||
private void configChannel(SocketChannel channel) throws IOException {
|
||||
channel.socket().setKeepAlive(true);
|
||||
channel.socket().setReuseAddress(true);
|
||||
channel.socket().setSoTimeout(soTimeout);
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.socket().setReceiveBufferSize(receiveBufferSize);
|
||||
channel.socket().setSendBufferSize(sendBufferSize);
|
||||
PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
|
||||
}
|
||||
|
||||
private void negotiate(SocketChannel channel) throws IOException {
|
||||
@@ -190,8 +178,7 @@ public class MysqlConnector {
|
||||
h.setPacketBodyLength(clientAuthPkgBody.length);
|
||||
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
||||
|
||||
PacketManager.write(channel,
|
||||
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
|
||||
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
|
||||
logger.info("client authentication packet is sent out.");
|
||||
|
||||
// check auth result
|
||||
@@ -227,7 +214,7 @@ public class MysqlConnector {
|
||||
h323.setPacketBodyLength(b323Body.length);
|
||||
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
|
||||
|
||||
PacketManager.write(channel, new ByteBuffer[] { ByteBuffer.wrap(h323.toBytes()), ByteBuffer.wrap(b323Body) });
|
||||
PacketManager.writePkg(channel, h323.toBytes(), b323Body);
|
||||
logger.info("client 323 authentication packet is sent out.");
|
||||
// check auth result
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||
@@ -340,4 +327,11 @@ public class MysqlConnector {
|
||||
this.dumping = dumping;
|
||||
}
|
||||
|
||||
public int getConnTimeout() {
|
||||
return connTimeout;
|
||||
}
|
||||
|
||||
public void setConnTimeout(int connTimeout) {
|
||||
this.connTimeout = connTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
+61
-6
@@ -1,17 +1,18 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QueryCommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.EOFPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetHeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.RowDataPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||
|
||||
/**
|
||||
@@ -24,9 +25,9 @@ public class MysqlQueryExecutor {
|
||||
|
||||
private SocketChannel channel;
|
||||
|
||||
public MysqlQueryExecutor(MysqlConnector connector){
|
||||
public MysqlQueryExecutor(MysqlConnector connector) throws IOException{
|
||||
if (!connector.isConnected()) {
|
||||
throw new RuntimeException("should execute connector.connect() first");
|
||||
throw new IOException("should execute connector.connect() first");
|
||||
}
|
||||
|
||||
this.channel = connector.getChannel();
|
||||
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(queryString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.write(channel, bodyBytes);
|
||||
PacketManager.writeBody(channel, bodyBytes);
|
||||
byte[] body = readNextPacket();
|
||||
|
||||
if (body[0] < 0) {
|
||||
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
|
||||
for (RowDataPacket r : rowData) {
|
||||
resultSet.getFieldValues().addAll(r.getColumns());
|
||||
}
|
||||
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress());
|
||||
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
private void readEofPacket() throws IOException {
|
||||
public List<ResultSetPacket> queryMulti(String queryString) throws IOException {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(queryString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.writeBody(channel, bodyBytes);
|
||||
List<ResultSetPacket> resultSets = new ArrayList<ResultSetPacket>();
|
||||
boolean moreResult = true;
|
||||
while (moreResult) {
|
||||
byte[] body = readNextPacket();
|
||||
if (body[0] < 0) {
|
||||
ErrorPacket packet = new ErrorPacket();
|
||||
packet.fromBytes(body);
|
||||
throw new IOException(packet + "\n with command: " + queryString);
|
||||
}
|
||||
|
||||
ResultSetHeaderPacket rsHeader = new ResultSetHeaderPacket();
|
||||
rsHeader.fromBytes(body);
|
||||
|
||||
List<FieldPacket> fields = new ArrayList<FieldPacket>();
|
||||
for (int i = 0; i < rsHeader.getColumnCount(); i++) {
|
||||
FieldPacket fp = new FieldPacket();
|
||||
fp.fromBytes(readNextPacket());
|
||||
fields.add(fp);
|
||||
}
|
||||
|
||||
moreResult = readEofPacket();
|
||||
|
||||
List<RowDataPacket> rowData = new ArrayList<RowDataPacket>();
|
||||
while (true) {
|
||||
body = readNextPacket();
|
||||
if (body[0] == -2) {
|
||||
break;
|
||||
}
|
||||
RowDataPacket rowDataPacket = new RowDataPacket();
|
||||
rowDataPacket.fromBytes(body);
|
||||
rowData.add(rowDataPacket);
|
||||
}
|
||||
|
||||
ResultSetPacket resultSet = new ResultSetPacket();
|
||||
resultSet.getFieldDescriptors().addAll(fields);
|
||||
for (RowDataPacket r : rowData) {
|
||||
resultSet.getFieldValues().addAll(r.getColumns());
|
||||
}
|
||||
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||
resultSets.add(resultSet);
|
||||
}
|
||||
|
||||
return resultSets;
|
||||
}
|
||||
|
||||
private boolean readEofPacket() throws IOException {
|
||||
byte[] eofBody = readNextPacket();
|
||||
EOFPacket packet = new EOFPacket();
|
||||
packet.fromBytes(eofBody);
|
||||
if (eofBody[0] != -2) {
|
||||
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
|
||||
}
|
||||
|
||||
return (packet.statusFlag & 0x0008) != 0;
|
||||
}
|
||||
|
||||
protected byte[] readNextPacket() throws IOException {
|
||||
|
||||
+10
-10
@@ -1,7 +1,6 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
|
||||
|
||||
private SocketChannel channel;
|
||||
private MysqlConnector connector;
|
||||
|
||||
public MysqlUpdateExecutor(MysqlConnector connector){
|
||||
public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
|
||||
if (!connector.isConnected()) {
|
||||
throw new RuntimeException("should execute connector.connect() first");
|
||||
throw new IOException("should execute connector.connect() first");
|
||||
}
|
||||
|
||||
this.channel = connector.getChannel();
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
public MysqlUpdateExecutor(SocketChannel ch){
|
||||
this.channel = ch;
|
||||
}
|
||||
/*
|
||||
* public MysqlUpdateExecutor(SocketChannel ch){ this.channel = ch; }
|
||||
*/
|
||||
|
||||
public OKPacket update(String updateString) throws IOException {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(updateString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.write(channel, bodyBytes);
|
||||
PacketManager.writeBody(connector.getChannel(), bodyBytes);
|
||||
|
||||
logger.debug("read update result...");
|
||||
byte[] body = PacketManager.readBytes(channel, PacketManager.readHeader(channel, 4).getPacketBodyLength());
|
||||
byte[] body = PacketManager.readBytes(connector.getChannel(),
|
||||
PacketManager.readHeader(connector.getChannel(), 4).getPacketBodyLength());
|
||||
if (body[0] < 0) {
|
||||
ErrorPacket packet = new ErrorPacket();
|
||||
packet.fromBytes(body);
|
||||
|
||||
+4
-3
@@ -47,10 +47,11 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
|
||||
/**
|
||||
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
||||
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
||||
* CLIENT_MULTI_STATEMENTS;
|
||||
*/
|
||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove
|
||||
// client_interactive
|
||||
// feature
|
||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
|
||||
// client_interactive
|
||||
// feature
|
||||
|
||||
// 2. write max_packet_size
|
||||
ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, out);
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class FieldPacket extends PacketWithHeaderPacket {
|
||||
this.decimals = data[index];
|
||||
index++;
|
||||
//
|
||||
index += 2;//skip filter
|
||||
index += 2;// skip filter
|
||||
//
|
||||
if (index < data.length) {
|
||||
reader.setIndex(index);
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
||||
*
|
||||
* @author luoyaogui
|
||||
*/
|
||||
public class SocketChannel {
|
||||
|
||||
private Channel channel = null;
|
||||
private Object lock = new Object();
|
||||
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(1024 * 1024); // 缓存大小
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void writeCache(ByteBuf buf) {
|
||||
synchronized (lock) {
|
||||
cache.discardReadBytes();// 回收内存
|
||||
cache.writeBytes(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChannel(byte[]... buf) throws IOException {
|
||||
if (channel != null && channel.isWritable()) {
|
||||
channel.writeAndFlush(Unpooled.copiedBuffer(buf));
|
||||
} else {
|
||||
throw new IOException("write failed ! please checking !");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] read(int readSize) throws IOException {
|
||||
do {
|
||||
if (readSize > cache.readableBytes()) {
|
||||
if (null == channel) {
|
||||
throw new java.nio.channels.ClosedByInterruptException();
|
||||
}
|
||||
synchronized (this) {
|
||||
try {
|
||||
wait(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new java.nio.channels.ClosedByInterruptException();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte[] back = new byte[readSize];
|
||||
synchronized (lock) {
|
||||
cache.readBytes(back);
|
||||
}
|
||||
return back;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return channel != null ? true : false;
|
||||
}
|
||||
|
||||
public SocketAddress getRemoteSocketAddress() {
|
||||
return channel != null ? channel.remoteAddress() : null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
channel = null;
|
||||
cache.discardReadBytes();// 回收已占用的内存
|
||||
cache.release();// 释放整个内存
|
||||
cache = null;
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.AdaptiveRecvByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||
|
||||
/**
|
||||
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "deprecation" })
|
||||
public abstract class SocketChannelPool {
|
||||
|
||||
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
|
||||
private static Bootstrap boot = new Bootstrap(); // 主
|
||||
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
|
||||
|
||||
static {
|
||||
boot.group(group)
|
||||
.channel(NioSocketChannel.class)
|
||||
.option(ChannelOption.SO_RCVBUF, 32 * 1024)
|
||||
.option(ChannelOption.SO_SNDBUF, 32 * 1024)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
// 如果是延时敏感型应用,建议关闭Nagle算法
|
||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
|
||||
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||
//
|
||||
.handler(new ChannelInitializer() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel arg0) throws Exception {
|
||||
arg0.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||
final SocketChannel socket = new SocketChannel();
|
||||
final BooleanMutex mutex = new BooleanMutex(false);
|
||||
boot.connect(address).addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture arg0) throws Exception {
|
||||
if (arg0.isSuccess()) {
|
||||
socket.setChannel(arg0.channel());
|
||||
}
|
||||
|
||||
mutex.set(true);
|
||||
}
|
||||
});
|
||||
// wait for complete
|
||||
mutex.get();
|
||||
if (null == socket.getChannel()) {
|
||||
throw new IOException("can't create socket!");
|
||||
}
|
||||
chManager.put(socket.getChannel(), socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
public static class BusinessHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private SocketChannel socket = null;
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
socket.setChannel(null);
|
||||
chManager.remove(ctx.channel());// 移除
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (null == socket) socket = chManager.get(ctx.channel());
|
||||
if (socket != null) {
|
||||
socket.writeCache((ByteBuf) msg);
|
||||
}
|
||||
ReferenceCountUtil.release(msg);// 添加防止内存泄漏的
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-44
@@ -1,69 +1,34 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
|
||||
public abstract class PacketManager {
|
||||
|
||||
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.fromBytes(readBytesAsBuffer(ch, len).array());
|
||||
header.fromBytes(ch.read(len));
|
||||
return header;
|
||||
}
|
||||
|
||||
public static ByteBuffer readBytesAsBuffer(SocketChannel ch, int len) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(len);
|
||||
while (buffer.hasRemaining()) {
|
||||
int readNum = ch.read(buffer);
|
||||
if (readNum == -1) {
|
||||
throw new IOException("Unexpected End Stream");
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
||||
return readBytesAsBuffer(ch, len).array();
|
||||
return ch.read(len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since We r using blocking IO, so we will just write once and assert the
|
||||
* length to simplify the read operation.<br>
|
||||
* If the block write doesn't work as we expected, we will change this
|
||||
* implementation as per the result.
|
||||
*
|
||||
* @param ch
|
||||
* @param len
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void write(SocketChannel ch, ByteBuffer[] srcs) throws IOException {
|
||||
@SuppressWarnings("unused")
|
||||
long total = 0;
|
||||
for (ByteBuffer buffer : srcs) {
|
||||
total += buffer.remaining();
|
||||
}
|
||||
|
||||
ch.write(srcs);
|
||||
// https://github.com/alibaba/canal/issues/24
|
||||
// 部分windows用户会出现size != total的情况,jdk为java7/openjdk,估计和java版本有关,暂时不做检查
|
||||
// long size = ch.write(srcs);
|
||||
// if (size != total) {
|
||||
// throw new IOException("unexpected blocking io behavior");
|
||||
// }
|
||||
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
||||
ch.writeChannel(srcs);
|
||||
}
|
||||
|
||||
public static void write(SocketChannel ch, byte[] body) throws IOException {
|
||||
write(ch, body, (byte) 0);
|
||||
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||
writeBody0(ch, body, (byte) 0);
|
||||
}
|
||||
|
||||
public static void write(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
|
||||
public static void writeBody0(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.setPacketBodyLength(body.length);
|
||||
header.setPacketSequenceNumber(packetSeqNumber);
|
||||
write(ch, new ByteBuffer[] { ByteBuffer.wrap(header.toBytes()), ByteBuffer.wrap(body) });
|
||||
ch.writeChannel(header.toBytes(), body);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.alibaba.otter.canal.example;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.client.CanalConnector;
|
||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||
|
||||
@@ -41,7 +39,7 @@ public class ClusterCanalClientTest extends AbstractCanalClientTest {
|
||||
logger.info("## stop the canal client");
|
||||
clientTest.stop();
|
||||
} catch (Throwable e) {
|
||||
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("##something goes wrong when stopping canal:", e);
|
||||
} finally {
|
||||
logger.info("## canal client is down.");
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.alibaba.otter.canal.example;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.client.CanalConnector;
|
||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||
@@ -23,8 +21,11 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
||||
public static void main(String args[]) {
|
||||
// 根据ip,直接创建链接,无HA的功能
|
||||
String destination = "example";
|
||||
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
|
||||
11111), destination, "", "");
|
||||
String ip = AddressUtils.getHostIp();
|
||||
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
|
||||
destination,
|
||||
"",
|
||||
"");
|
||||
|
||||
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
|
||||
clientTest.setConnector(connector);
|
||||
@@ -36,7 +37,7 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
||||
logger.info("## stop the canal client");
|
||||
clientTest.stop();
|
||||
} catch (Throwable e) {
|
||||
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("##something goes wrong when stopping canal:", e);
|
||||
} finally {
|
||||
logger.info("## canal client is down.");
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alibaba.otter.canal.filter;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterELFilter;
|
||||
|
||||
@@ -5,9 +5,8 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.instance.core</artifactId>
|
||||
|
||||
+15
-18
@@ -1,5 +1,11 @@
|
||||
package com.alibaba.otter.canal.instance.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||
@@ -16,30 +22,22 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||
import com.alibaba.otter.canal.store.CanalEventStore;
|
||||
import com.alibaba.otter.canal.store.model.Event;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created with Intellig IDEA.
|
||||
* Author: yinxiu
|
||||
* Date: 2016-01-07
|
||||
* Time: 22:26
|
||||
* Created with Intellig IDEA. Author: yinxiu Date: 2016-01-07 Time: 22:26
|
||||
*/
|
||||
public class AbstractCanalInstance extends AbstractCanalLifeCycle implements CanalInstance {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractCanalInstance.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractCanalInstance.class);
|
||||
|
||||
protected Long canalId; // 和manager交互唯一标示
|
||||
protected String destination; // 队列名字
|
||||
protected CanalEventStore<Event> eventStore; // 有序队列
|
||||
protected Long canalId; // 和manager交互唯一标示
|
||||
protected String destination; // 队列名字
|
||||
protected CanalEventStore<Event> eventStore; // 有序队列
|
||||
|
||||
protected CanalEventParser eventParser; // 解析对应的数据信息
|
||||
protected CanalEventSink<List<CanalEntry.Entry>> eventSink; // 链接parse和store的桥接器
|
||||
protected CanalMetaManager metaManager; // 消费信息管理器
|
||||
protected CanalAlarmHandler alarmHandler; // alarm报警机制
|
||||
protected CanalEventParser eventParser; // 解析对应的数据信息
|
||||
protected CanalEventSink<List<CanalEntry.Entry>> eventSink; // 链接parse和store的桥接器
|
||||
protected CanalMetaManager metaManager; // 消费信息管理器
|
||||
protected CanalAlarmHandler alarmHandler; // alarm报警机制
|
||||
|
||||
@Override
|
||||
public boolean subscribeChange(ClientIdentity identity) {
|
||||
@@ -121,7 +119,6 @@ public class AbstractCanalInstance extends AbstractCanalLifeCycle implements Can
|
||||
alarmHandler.stop();
|
||||
}
|
||||
|
||||
|
||||
logger.info("stop successful....");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
+11
-16
@@ -346,29 +346,24 @@ public class CanalInstanceWithManager extends AbstractCanalInstance {
|
||||
protected CanalLogPositionManager initLogPositionManager() {
|
||||
logger.info("init logPositionPersistManager begin...");
|
||||
IndexMode indexMode = parameters.getIndexMode();
|
||||
CanalLogPositionManager logPositionManager = null;
|
||||
CanalLogPositionManager logPositionManager;
|
||||
if (indexMode.isMemory()) {
|
||||
logPositionManager = new MemoryLogPositionManager();
|
||||
} else if (indexMode.isZookeeper()) {
|
||||
logPositionManager = new ZooKeeperLogPositionManager();
|
||||
((ZooKeeperLogPositionManager) logPositionManager).setZkClientx(getZkclientx());
|
||||
logPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||
} else if (indexMode.isMixed()) {
|
||||
logPositionManager = new PeriodMixedLogPositionManager();
|
||||
|
||||
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager();
|
||||
zooKeeperLogPositionManager.setZkClientx(getZkclientx());
|
||||
((PeriodMixedLogPositionManager) logPositionManager).setZooKeeperLogPositionManager(zooKeeperLogPositionManager);
|
||||
MemoryLogPositionManager memoryLogPositionManager = new MemoryLogPositionManager();
|
||||
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||
logPositionManager = new PeriodMixedLogPositionManager(memoryLogPositionManager,
|
||||
zooKeeperLogPositionManager,
|
||||
1000L);
|
||||
} else if (indexMode.isMeta()) {
|
||||
logPositionManager = new MetaLogPositionManager();
|
||||
((MetaLogPositionManager) logPositionManager).setMetaManager(metaManager);
|
||||
logPositionManager = new MetaLogPositionManager(metaManager);
|
||||
} else if (indexMode.isMemoryMetaFailback()) {
|
||||
MemoryLogPositionManager primaryLogPositionManager = new MemoryLogPositionManager();
|
||||
MetaLogPositionManager failbackLogPositionManager = new MetaLogPositionManager();
|
||||
failbackLogPositionManager.setMetaManager(metaManager);
|
||||
MemoryLogPositionManager primary = new MemoryLogPositionManager();
|
||||
MetaLogPositionManager secondary = new MetaLogPositionManager(metaManager);
|
||||
|
||||
logPositionManager = new FailbackLogPositionManager();
|
||||
((FailbackLogPositionManager) logPositionManager).setPrimary(primaryLogPositionManager);
|
||||
((FailbackLogPositionManager) logPositionManager).setFailback(failbackLogPositionManager);
|
||||
logPositionManager = new FailbackLogPositionManager(primary, secondary);
|
||||
} else {
|
||||
throw new CanalException("unsupport indexMode for " + indexMode);
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@ package com.alibaba.otter.canal.instance.spring;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||
import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
|
||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
@@ -23,7 +23,7 @@ import com.alibaba.otter.canal.store.model.Event;
|
||||
*/
|
||||
public class CanalInstanceWithSpring extends AbstractCanalInstance {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class);
|
||||
|
||||
public void start() {
|
||||
logger.info("start CannalInstance for {}-{} ", new Object[] { 1, destination });
|
||||
|
||||
@@ -120,14 +120,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
|
||||
@@ -102,18 +102,20 @@
|
||||
<!-- 解析编码 -->
|
||||
<!-- property name="connectionCharsetNumber" value="${canal.instance.connectionCharsetNumber:33}" /-->
|
||||
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
||||
|
||||
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg>
|
||||
<ref bean="metaManager"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -367,7 +367,7 @@ public class FileMixedMetaManager extends MemoryMetaManager implements CanalMeta
|
||||
this.dataDir = new File(dataDir);
|
||||
}
|
||||
|
||||
public void setDataDir(File dataDir) {
|
||||
public void setDataDirByFile(File dataDir) {
|
||||
this.dataDir = dataDir;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,8 +142,8 @@ public class PeriodMixedMetaManager extends MemoryMetaManager implements CanalMe
|
||||
}
|
||||
|
||||
public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException {
|
||||
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
|
||||
super.updateCursor(clientIdentity, position);
|
||||
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
|
||||
}
|
||||
|
||||
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
|
||||
|
||||
@@ -5,9 +5,8 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -32,7 +31,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
@Test
|
||||
public void testSubscribeAll() {
|
||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||
metaManager.setDataDir(dataDir);
|
||||
metaManager.setDataDirByFile(dataDir);
|
||||
metaManager.setPeriod(100);
|
||||
|
||||
metaManager.start();
|
||||
@@ -41,7 +40,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
sleep(2000L);
|
||||
// 重新构建一次,能获得上一次zk上的记录
|
||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||
metaManager2.setDataDir(dataDir);
|
||||
metaManager2.setDataDirByFile(dataDir);
|
||||
metaManager2.setPeriod(100);
|
||||
metaManager2.start();
|
||||
|
||||
@@ -53,7 +52,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
@Test
|
||||
public void testBatchAll() {
|
||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||
metaManager.setDataDir(dataDir);
|
||||
metaManager.setDataDirByFile(dataDir);
|
||||
metaManager.setPeriod(100);
|
||||
|
||||
metaManager.start();
|
||||
@@ -68,7 +67,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
@Test
|
||||
public void testCursorAll() {
|
||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||
metaManager.setDataDir(dataDir);
|
||||
metaManager.setDataDirByFile(dataDir);
|
||||
metaManager.setPeriod(100);
|
||||
metaManager.start();
|
||||
|
||||
@@ -77,7 +76,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
sleep(1000L);
|
||||
// 重新构建一次,能获得上一次zk上的记录
|
||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||
metaManager2.setDataDir(dataDir);
|
||||
metaManager2.setDataDirByFile(dataDir);
|
||||
metaManager2.setPeriod(100);
|
||||
metaManager2.start();
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.meta;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.protocol.position.PositionRange;
|
||||
|
||||
@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ package com.alibaba.otter.canal.meta;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
+31
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.22</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.parse</artifactId>
|
||||
@@ -45,11 +45,41 @@
|
||||
<artifactId>canal.parse.driver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.ibatis</groupId>
|
||||
<artifactId>ibatis-sqlmap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
</dependency>
|
||||
<!-- test dependency -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -22,7 +22,6 @@ import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
@@ -97,6 +96,10 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
protected void preDump(ErosaConnection connection) {
|
||||
}
|
||||
|
||||
protected boolean processTableMeta(EntryPosition position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void afterDump(ErosaConnection connection) {
|
||||
}
|
||||
|
||||
@@ -146,7 +149,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
ErosaConnection erosaConnection = null;
|
||||
while (running) {
|
||||
try {
|
||||
|
||||
// 开始执行replication
|
||||
// 1. 构造Erosa连接
|
||||
erosaConnection = buildErosaConnection();
|
||||
@@ -164,6 +166,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
if (startPosition == null) {
|
||||
throw new CanalParseException("can't find start position for " + destination);
|
||||
}
|
||||
|
||||
if (!processTableMeta(startPosition)) {
|
||||
throw new CanalParseException("can't find init table meta for " + destination
|
||||
+ " with position : " + startPosition);
|
||||
}
|
||||
logger.info("find start position : {}", startPosition.toString());
|
||||
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
||||
erosaConnection.reconnect();
|
||||
@@ -174,7 +181,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
|
||||
public boolean sink(EVENT event) {
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, false);
|
||||
|
||||
if (!running) {
|
||||
return false;
|
||||
@@ -192,6 +199,9 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
} catch (TableIdNotFoundException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
if (e.getCause() instanceof TableIdNotFoundException) {
|
||||
throw (TableIdNotFoundException) e.getCause();
|
||||
}
|
||||
// 记录一下,出错的位点信息
|
||||
processSinkError(e,
|
||||
this.lastPosition,
|
||||
@@ -273,7 +283,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
parseThread.setUncaughtExceptionHandler(handler);
|
||||
parseThread.setName(String.format("destination = %s , address = %s , EventParser",
|
||||
destination,
|
||||
runningInfo == null ? null : runningInfo.getAddress().toString()));
|
||||
runningInfo == null ? null : runningInfo.getAddress()));
|
||||
parseThread.start();
|
||||
}
|
||||
|
||||
@@ -318,14 +328,13 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
return result;
|
||||
}
|
||||
|
||||
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod) throws Exception {
|
||||
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod, boolean isSeek) throws Exception {
|
||||
long startTs = -1;
|
||||
boolean enabled = getProfilingEnabled();
|
||||
if (enabled) {
|
||||
startTs = System.currentTimeMillis();
|
||||
}
|
||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
||||
|
||||
CanalEntry.Entry event = binlogParser.parse(bod, isSeek);
|
||||
if (enabled) {
|
||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
||||
}
|
||||
@@ -388,7 +397,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
String name = String.format("destination = %s , address = %s , HeartBeatTimeTask",
|
||||
destination,
|
||||
runningInfo == null ? null : runningInfo.getAddress().toString());
|
||||
synchronized (MysqlEventParser.class) {
|
||||
synchronized (AbstractEventParser.class) {
|
||||
// synchronized (MysqlEventParser.class) {
|
||||
// why use MysqlEventParser.class, u know, MysqlEventParser is
|
||||
// the child class 4 AbstractEventParser,
|
||||
// do this is ...
|
||||
if (timer == null) {
|
||||
timer = new Timer(name, true);
|
||||
}
|
||||
@@ -425,7 +438,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.warn("heartBeat run failed " + ExceptionUtils.getStackTrace(e));
|
||||
logger.warn("heartBeat run failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
*/
|
||||
public interface BinlogParser<T> extends CanalLifeCycle {
|
||||
|
||||
CanalEntry.Entry parse(T event) throws CanalParseException;
|
||||
CanalEntry.Entry parse(T event, boolean isSeek) throws CanalParseException;
|
||||
|
||||
void reset();
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ public interface ErosaConnection {
|
||||
|
||||
public void disconnect() throws IOException;
|
||||
|
||||
public boolean isConnected();
|
||||
|
||||
/**
|
||||
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
||||
*/
|
||||
|
||||
@@ -1,57 +1,132 @@
|
||||
package com.alibaba.otter.canal.parse.inbound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* 1. 主键信息
|
||||
* 2. column name
|
||||
* 3. unsigned字段
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @author jianghang 2013-1-18 下午12:24:59
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class TableMeta {
|
||||
|
||||
private String fullName; // schema.table
|
||||
private List<FieldMeta> fileds;
|
||||
private String schema;
|
||||
private String table;
|
||||
private List<FieldMeta> fields = new ArrayList<TableMeta.FieldMeta>();
|
||||
private String ddl; // 表结构的DDL语句
|
||||
|
||||
public TableMeta(String fullName, List<FieldMeta> fileds){
|
||||
this.fullName = fullName;
|
||||
this.fileds = fileds;
|
||||
public TableMeta(){
|
||||
|
||||
}
|
||||
|
||||
public TableMeta(String schema, String table, List<FieldMeta> fields){
|
||||
this.schema = schema;
|
||||
this.table = table;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
return schema + "." + table;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
public String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
public List<FieldMeta> getFileds() {
|
||||
return fileds;
|
||||
public void setSchema(String schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public void setFileds(List<FieldMeta> fileds) {
|
||||
this.fileds = fileds;
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setTable(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public List<FieldMeta> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<FieldMeta> fileds) {
|
||||
this.fields = fileds;
|
||||
}
|
||||
|
||||
public FieldMeta getFieldMetaByName(String name) {
|
||||
for (FieldMeta meta : fields) {
|
||||
if (meta.getColumnName().equalsIgnoreCase(name)) {
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("unknow column : " + name);
|
||||
}
|
||||
|
||||
public List<FieldMeta> getPrimaryFields() {
|
||||
List<FieldMeta> primarys = new ArrayList<TableMeta.FieldMeta>();
|
||||
for (FieldMeta meta : fields) {
|
||||
if (meta.isKey()) {
|
||||
primarys.add(meta);
|
||||
}
|
||||
}
|
||||
|
||||
return primarys;
|
||||
}
|
||||
|
||||
public String getDdl() {
|
||||
return ddl;
|
||||
}
|
||||
|
||||
public void setDdl(String ddl) {
|
||||
this.ddl = ddl;
|
||||
}
|
||||
|
||||
public void addFieldMeta(FieldMeta fieldMeta) {
|
||||
this.fields.add(fieldMeta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder data = new StringBuilder();
|
||||
data.append("TableMeta [schema=" + schema + ", table=" + table + ", fileds=");
|
||||
for (FieldMeta field : fields) {
|
||||
data.append("\n\t").append(field.toString());
|
||||
}
|
||||
data.append("\n]");
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
public static class FieldMeta {
|
||||
|
||||
private String columnName;
|
||||
private String columnType;
|
||||
private String isNullable;
|
||||
private String iskey;
|
||||
private String defaultValue;
|
||||
private String extra;
|
||||
public FieldMeta(){
|
||||
|
||||
}
|
||||
|
||||
public FieldMeta(String columnName, String columnType, boolean nullable, boolean key, String defaultValue){
|
||||
this.columnName = columnName;
|
||||
this.columnType = columnType;
|
||||
this.nullable = nullable;
|
||||
this.key = key;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
private String columnName;
|
||||
private String columnType;
|
||||
private boolean nullable;
|
||||
private boolean key;
|
||||
private String defaultValue;
|
||||
private String extra;
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
@@ -69,20 +144,8 @@ public class TableMeta {
|
||||
this.columnType = columnType;
|
||||
}
|
||||
|
||||
public String getIsNullable() {
|
||||
return isNullable;
|
||||
}
|
||||
|
||||
public void setIsNullable(String isNullable) {
|
||||
this.isNullable = isNullable;
|
||||
}
|
||||
|
||||
public String getIskey() {
|
||||
return iskey;
|
||||
}
|
||||
|
||||
public void setIskey(String iskey) {
|
||||
this.iskey = iskey;
|
||||
public void setNullable(boolean nullable) {
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
@@ -93,6 +156,22 @@ public class TableMeta {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public boolean isUnsigned() {
|
||||
return StringUtils.containsIgnoreCase(columnType, "unsigned");
|
||||
}
|
||||
|
||||
public boolean isNullable() {
|
||||
return nullable;
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(boolean key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getExtra() {
|
||||
return extra;
|
||||
}
|
||||
@@ -101,22 +180,10 @@ public class TableMeta {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public boolean isUnsigned() {
|
||||
return StringUtils.containsIgnoreCase(columnType, "unsigned");
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
return StringUtils.equalsIgnoreCase(iskey, "PRI");
|
||||
}
|
||||
|
||||
public boolean isNullable() {
|
||||
return StringUtils.equalsIgnoreCase(isNullable, "YES");
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
|
||||
+ defaultValue + ", extra=" + extra + ", isNullable=" + isNullable + ", iskey=" + iskey + "]";
|
||||
+ defaultValue + ", nullable=" + nullable + ", key=" + key + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+69
@@ -7,15 +7,22 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected static final long BINLOG_START_OFFEST = 4L;
|
||||
|
||||
protected boolean enableTsdb = false;
|
||||
protected String tsdbSpringXml;
|
||||
protected TableMetaTSDB tableMetaTSDB;
|
||||
// 编码信息
|
||||
protected byte connectionCharsetNumber = (byte) 33;
|
||||
protected Charset connectionCharset = Charset.forName("UTF-8");
|
||||
@@ -24,6 +31,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
protected boolean filterQueryDdl = false;
|
||||
protected boolean filterRows = false;
|
||||
protected boolean filterTableError = false;
|
||||
protected boolean useDruidDdlFilter = true;
|
||||
|
||||
protected BinlogParser buildParser() {
|
||||
LogEventConvert convert = new LogEventConvert();
|
||||
@@ -53,6 +61,40 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚到指定位点
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
protected boolean processTableMeta(EntryPosition position) {
|
||||
if (tableMetaTSDB != null) {
|
||||
return tableMetaTSDB.rollback(position);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void start() throws CanalParseException {
|
||||
if (enableTsdb) {
|
||||
if (tableMetaTSDB == null) {
|
||||
// 初始化
|
||||
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||
}
|
||||
}
|
||||
|
||||
super.start();
|
||||
}
|
||||
|
||||
public void stop() throws CanalParseException {
|
||||
if (enableTsdb) {
|
||||
TableMetaTSDBBuilder.destory(destination);
|
||||
tableMetaTSDB = null;
|
||||
}
|
||||
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
|
||||
super.setEventBlackFilter(eventBlackFilter);
|
||||
|
||||
@@ -97,4 +139,31 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
this.filterTableError = filterTableError;
|
||||
}
|
||||
|
||||
public boolean isUseDruidDdlFilter() {
|
||||
return useDruidDdlFilter;
|
||||
}
|
||||
|
||||
public void setUseDruidDdlFilter(boolean useDruidDdlFilter) {
|
||||
this.useDruidDdlFilter = useDruidDdlFilter;
|
||||
}
|
||||
|
||||
public void setEnableTsdb(boolean enableTsdb) {
|
||||
this.enableTsdb = enableTsdb;
|
||||
if (this.enableTsdb) {
|
||||
if (tableMetaTSDB == null) {
|
||||
// 初始化
|
||||
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setTsdbSpringXml(String tsdbSpringXml) {
|
||||
this.tsdbSpringXml = tsdbSpringXml;
|
||||
|
||||
if (tableMetaTSDB == null) {
|
||||
// 初始化
|
||||
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-29
@@ -18,6 +18,7 @@ import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.LogPosition;
|
||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
||||
|
||||
/**
|
||||
* local bin log connection (not real connection)
|
||||
@@ -83,7 +84,10 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
|
||||
while (running) {
|
||||
boolean needContinue = true;
|
||||
LogEvent event;
|
||||
LogEvent event = null;
|
||||
// 处理一下binlog文件名
|
||||
event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
|
||||
func.sink(event);
|
||||
while (fetcher.fetch()) {
|
||||
event = decoder.decode(fetcher, context);
|
||||
if (event == null) {
|
||||
@@ -94,14 +98,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
needContinue = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// do {
|
||||
// event = decoder.decode(fetcher, context);
|
||||
// if (event != null && !func.sink(event)) {
|
||||
// needContinue = false;
|
||||
// break;
|
||||
// }
|
||||
// } while (event != null);
|
||||
}
|
||||
|
||||
if (needContinue) {// 读取下一个
|
||||
@@ -119,7 +115,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
}
|
||||
|
||||
current = nextFile;
|
||||
|
||||
fetcher.open(current);
|
||||
context.setLogPosition(new LogPosition(nextFile.getName()));
|
||||
} else {
|
||||
@@ -145,6 +140,7 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
|
||||
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
||||
LogDecoder decoder = new LogDecoder();
|
||||
decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT);
|
||||
decoder.handle(LogEvent.QUERY_EVENT);
|
||||
decoder.handle(LogEvent.XID_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
@@ -158,30 +154,27 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
|
||||
binlogFilename = lastXidLogFilename;
|
||||
binlogFileOffset = lastXidLogFileOffset;
|
||||
L: while (fetcher.fetch()) {
|
||||
LogEvent event;
|
||||
do {
|
||||
event = decoder.decode(fetcher, context);
|
||||
if (event != null) {
|
||||
if (event.getWhen() > timestampSeconds) {
|
||||
break L;
|
||||
}
|
||||
while (fetcher.fetch()) {
|
||||
LogEvent event = decoder.decode(fetcher, context);
|
||||
if (event != null) {
|
||||
if (event.getWhen() > timestampSeconds) {
|
||||
break;
|
||||
}
|
||||
|
||||
needContinue = false;
|
||||
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
||||
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
||||
binlogFilename = lastXidLogFilename;
|
||||
binlogFileOffset = lastXidLogFileOffset;
|
||||
} else if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "COMMIT")) {
|
||||
lastXidLogFilename = current.getName();
|
||||
lastXidLogFileOffset = event.getLogPos();
|
||||
}
|
||||
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
|
||||
needContinue = false;
|
||||
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
||||
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
||||
binlogFilename = lastXidLogFilename;
|
||||
binlogFileOffset = lastXidLogFileOffset;
|
||||
} else if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "COMMIT")) {
|
||||
lastXidLogFilename = current.getName();
|
||||
lastXidLogFileOffset = event.getLogPos();
|
||||
}
|
||||
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
|
||||
lastXidLogFilename = current.getName();
|
||||
lastXidLogFileOffset = event.getLogPos();
|
||||
}
|
||||
} while (event != null);
|
||||
}
|
||||
}
|
||||
|
||||
if (needContinue) {// 读取下一个
|
||||
|
||||
+75
-6
@@ -1,10 +1,15 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
|
||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
@@ -19,12 +24,14 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
|
||||
|
||||
// 数据库信息
|
||||
private AuthenticationInfo masterInfo;
|
||||
private EntryPosition masterPosition; // binlog信息
|
||||
protected AuthenticationInfo masterInfo;
|
||||
protected EntryPosition masterPosition; // binlog信息
|
||||
protected MysqlConnection metaConnection; // 查询meta信息的链接
|
||||
protected TableMetaCache tableMetaCache; // 对应meta
|
||||
|
||||
private String directory;
|
||||
private boolean needWait = false;
|
||||
private int bufferSize = 16 * 1024;
|
||||
protected String directory;
|
||||
protected boolean needWait = false;
|
||||
protected int bufferSize = 16 * 1024;
|
||||
|
||||
public LocalBinlogEventParser(){
|
||||
// this.runningInfo = new AuthenticationInfo();
|
||||
@@ -35,6 +42,37 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
return buildLocalBinLogConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preDump(ErosaConnection connection) {
|
||||
metaConnection = buildMysqlConnection();
|
||||
try {
|
||||
metaConnection.connect();
|
||||
} catch (IOException e) {
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
|
||||
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
|
||||
((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
|
||||
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
|
||||
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
|
||||
}
|
||||
|
||||
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
|
||||
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterDump(ErosaConnection connection) {
|
||||
if (metaConnection != null) {
|
||||
try {
|
||||
metaConnection.disconnect();
|
||||
} catch (IOException e) {
|
||||
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
|
||||
.getAddress(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() throws CanalParseException {
|
||||
if (runningInfo == null) { // 第一次链接主库
|
||||
runningInfo = masterInfo;
|
||||
@@ -43,6 +81,24 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (metaConnection != null) {
|
||||
try {
|
||||
metaConnection.disconnect();
|
||||
} catch (IOException e) {
|
||||
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
|
||||
.getAddress(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (tableMetaCache != null) {
|
||||
tableMetaCache.clearTableMeta();
|
||||
}
|
||||
|
||||
super.stop();
|
||||
}
|
||||
|
||||
private ErosaConnection buildLocalBinLogConnection() {
|
||||
LocalBinLogConnection connection = new LocalBinLogConnection();
|
||||
|
||||
@@ -52,7 +108,20 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
private MysqlConnection buildMysqlConnection() {
|
||||
MysqlConnection connection = new MysqlConnection(runningInfo.getAddress(),
|
||||
runningInfo.getUsername(),
|
||||
runningInfo.getPassword(),
|
||||
connectionCharsetNumber,
|
||||
runningInfo.getDefaultDatabaseName());
|
||||
connection.getConnector().setReceiveBufferSize(64 * 1024);
|
||||
connection.getConnector().setSendBufferSize(64 * 1024);
|
||||
connection.getConnector().setSoTimeout(30 * 1000);
|
||||
connection.setCharset(connectionCharset);
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntryPosition findStartPosition(ErosaConnection connection) {
|
||||
// 处理逻辑
|
||||
|
||||
+68
-14
@@ -2,11 +2,10 @@ package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -21,31 +20,51 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
|
||||
public class MysqlConnection implements ErosaConnection {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class);
|
||||
|
||||
private MysqlConnector connector;
|
||||
private long slaveId;
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
private BinlogFormat binlogFormat;
|
||||
private BinlogImage binlogImage;
|
||||
|
||||
// tsdb releated
|
||||
private AuthenticationInfo authInfo;
|
||||
protected int connTimeout = 5 * 1000; // 5秒
|
||||
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
||||
|
||||
public MysqlConnection(){
|
||||
}
|
||||
|
||||
public MysqlConnection(InetSocketAddress address, String username, String password){
|
||||
|
||||
authInfo = new AuthenticationInfo();
|
||||
authInfo.setAddress(address);
|
||||
authInfo.setUsername(username);
|
||||
authInfo.setPassword(password);
|
||||
connector = new MysqlConnector(address, username, password);
|
||||
// 将connection里面的参数透传下
|
||||
connector.setSoTimeout(soTimeout);
|
||||
connector.setConnTimeout(connTimeout);
|
||||
}
|
||||
|
||||
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
|
||||
String defaultSchema){
|
||||
authInfo = new AuthenticationInfo();
|
||||
authInfo.setAddress(address);
|
||||
authInfo.setUsername(username);
|
||||
authInfo.setPassword(password);
|
||||
authInfo.setDefaultDatabaseName(defaultSchema);
|
||||
connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema);
|
||||
// 将connection里面的参数透传下
|
||||
connector.setSoTimeout(soTimeout);
|
||||
connector.setConnTimeout(connTimeout);
|
||||
}
|
||||
|
||||
public void connect() throws IOException {
|
||||
@@ -69,6 +88,11 @@ public class MysqlConnection implements ErosaConnection {
|
||||
return exector.query(cmd);
|
||||
}
|
||||
|
||||
public List<ResultSetPacket> queryMulti(String cmd) throws IOException {
|
||||
MysqlQueryExecutor exector = new MysqlQueryExecutor(connector);
|
||||
return exector.queryMulti(cmd);
|
||||
}
|
||||
|
||||
public void update(String cmd) throws IOException {
|
||||
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
|
||||
exector.update(cmd);
|
||||
@@ -132,6 +156,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
||||
binlogDumpCmd.binlogFileName = binlogfilename;
|
||||
binlogDumpCmd.binlogPosition = binlogPosition;
|
||||
// binlogDumpCmd.slaveServerId = this.slaveId;
|
||||
binlogDumpCmd.slaveServerId = this.slaveId;
|
||||
byte[] cmdBody = binlogDumpCmd.toBytes();
|
||||
|
||||
@@ -139,9 +164,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
||||
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
||||
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
||||
PacketManager.write(connector.getChannel(), new ByteBuffer[] { ByteBuffer.wrap(binlogDumpHeader.toBytes()),
|
||||
ByteBuffer.wrap(cmdBody) });
|
||||
|
||||
PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody);
|
||||
connector.setDumping(true);
|
||||
}
|
||||
|
||||
@@ -150,6 +173,8 @@ public class MysqlConnection implements ErosaConnection {
|
||||
connection.setCharset(getCharset());
|
||||
connection.setSlaveId(getSlaveId());
|
||||
connection.setConnector(connector.fork());
|
||||
// set authInfo
|
||||
connection.setAuthInfo(authInfo);
|
||||
return connection;
|
||||
}
|
||||
|
||||
@@ -170,25 +195,25 @@ public class MysqlConnection implements ErosaConnection {
|
||||
try {
|
||||
update("set wait_timeout=9999999");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update wait_timeout failed", e);
|
||||
}
|
||||
try {
|
||||
update("set net_write_timeout=1800");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update net_write_timeout failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
update("set net_read_timeout=1800");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update net_read_timeout failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
|
||||
update("set names 'binary'");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update names failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -198,14 +223,24 @@ public class MysqlConnection implements ErosaConnection {
|
||||
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
||||
update("set @master_binlog_checksum= '@@global.binlog_checksum'");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update master_binlog_checksum failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
// 参考:https://github.com/alibaba/canal/issues/284
|
||||
// mysql5.6需要设置slave_uuid避免被server kill链接
|
||||
update("set @slave_uuid=uuid()");
|
||||
} catch (Exception e) {
|
||||
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
|
||||
logger.warn("update slave_uuid failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// mariadb针对特殊的类型,需要设置session变量
|
||||
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update mariadb_slave_capability failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,4 +410,23 @@ public class MysqlConnection implements ErosaConnection {
|
||||
return binlogImage;
|
||||
}
|
||||
|
||||
public InetSocketAddress getAddress() {
|
||||
return authInfo.getAddress();
|
||||
}
|
||||
|
||||
public void setConnTimeout(int connTimeout) {
|
||||
this.connTimeout = connTimeout;
|
||||
}
|
||||
|
||||
public void setSoTimeout(int soTimeout) {
|
||||
this.soTimeout = soTimeout;
|
||||
}
|
||||
|
||||
public AuthenticationInfo getAuthInfo() {
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
public void setAuthInfo(AuthenticationInfo authInfo) {
|
||||
this.authInfo = authInfo;
|
||||
}
|
||||
}
|
||||
|
||||
+89
-24
@@ -1,7 +1,9 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -10,9 +12,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.CanalHASwitchable;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||
@@ -26,6 +28,7 @@ import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
|
||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
@@ -93,6 +96,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
for (BinlogFormat supportFormat : supportBinlogFormats) {
|
||||
if (supportFormat != null && format == supportFormat) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +111,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
for (BinlogImage supportImage : supportBinlogImages) {
|
||||
if (supportImage != null && image == supportImage) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +120,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
}
|
||||
|
||||
tableMetaCache = new TableMetaCache(metaConnection);
|
||||
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
|
||||
((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
|
||||
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
|
||||
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
|
||||
}
|
||||
|
||||
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
|
||||
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||
}
|
||||
}
|
||||
@@ -177,10 +188,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
protected void stopHeartBeat() {
|
||||
TimerTask heartBeatTimerTask = this.heartBeatTimerTask;
|
||||
super.stopHeartBeat();
|
||||
|
||||
if (heartBeatTimerTask != null) {
|
||||
|
||||
if (heartBeatTimerTask != null && heartBeatTimerTask instanceof MysqlDetectingTimeTask) {
|
||||
MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection();
|
||||
try {
|
||||
mysqlConnection.disconnect();
|
||||
@@ -235,19 +245,19 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
((HeartBeatCallback) haController).onFailed(e);
|
||||
}
|
||||
reconnect = true;
|
||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
||||
logger.warn("connect failed by ", e);
|
||||
} catch (IOException e) {
|
||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||
((HeartBeatCallback) haController).onFailed(e);
|
||||
}
|
||||
reconnect = true;
|
||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
||||
logger.warn("connect failed by ", e);
|
||||
} catch (Throwable e) {
|
||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||
((HeartBeatCallback) haController).onFailed(e);
|
||||
}
|
||||
reconnect = true;
|
||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
||||
logger.warn("connect failed by ", e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -305,10 +315,35 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
connection.getConnector().setSendBufferSize(sendBufferSize);
|
||||
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
||||
connection.setCharset(connectionCharset);
|
||||
// 随机生成slaveId
|
||||
if (this.slaveId <= 0) {
|
||||
this.slaveId = generateUniqueServerId();
|
||||
}
|
||||
connection.setSlaveId(this.slaveId);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private final long generateUniqueServerId() {
|
||||
try {
|
||||
// a=`echo $masterip|cut -d\. -f1`
|
||||
// b=`echo $masterip|cut -d\. -f2`
|
||||
// c=`echo $masterip|cut -d\. -f3`
|
||||
// d=`echo $masterip|cut -d\. -f4`
|
||||
// #server_id=`expr $a \* 256 \* 256 \* 256 + $b \* 256 \* 256 + $c
|
||||
// \* 256 + $d `
|
||||
// #server_id=$b$c$d
|
||||
// server_id=`expr $b \* 256 \* 256 + $c \* 256 + $d `
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
byte[] addr = localHost.getAddress();
|
||||
int salt = (destination != null) ? destination.hashCode() : 0;
|
||||
return ((0x7f & salt) << 24) + ((0xff & (int) addr[1]) << 16) // NL
|
||||
+ ((0xff & (int) addr[2]) << 8) // NL
|
||||
+ (0xff & (int) addr[3]);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new CanalParseException("Unknown host", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
|
||||
EntryPosition startPosition = findStartPositionInternal(connection);
|
||||
if (needTransactionPosition.get()) {
|
||||
@@ -331,6 +366,34 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
return endPosition;
|
||||
}
|
||||
|
||||
protected EntryPosition findEndPositionWithMasterIdAndTimestamp(MysqlConnection connection) {
|
||||
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||
final EntryPosition endPosition = findEndPosition(mysqlConnection);
|
||||
if (tableMetaTSDB != null) {
|
||||
long startTimestamp = System.currentTimeMillis();
|
||||
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||
startTimestamp,
|
||||
endPosition,
|
||||
endPosition.getJournalName());
|
||||
} else {
|
||||
return endPosition;
|
||||
}
|
||||
}
|
||||
|
||||
protected EntryPosition findPositionWithMasterIdAndTimestamp(MysqlConnection connection, EntryPosition fixedPosition) {
|
||||
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||
if (tableMetaTSDB != null && (fixedPosition.getTimestamp() == null || fixedPosition.getTimestamp() <= 0)) {
|
||||
// 使用一个未来极大的时间,基于位点进行定位
|
||||
long startTimestamp = System.currentTimeMillis() + 102L * 365 * 24 * 3600 * 1000; // 当前时间的未来102年
|
||||
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||
startTimestamp,
|
||||
fixedPosition,
|
||||
fixedPosition.getJournalName());
|
||||
} else {
|
||||
return fixedPosition;
|
||||
}
|
||||
}
|
||||
|
||||
protected EntryPosition findStartPositionInternal(ErosaConnection connection) {
|
||||
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
||||
@@ -344,7 +407,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
if (entryPosition == null) {
|
||||
entryPosition = findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||
entryPosition = findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||
}
|
||||
|
||||
// 判断一下是否需要按时间订阅
|
||||
@@ -356,13 +419,15 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
|
||||
} else {
|
||||
logger.warn("prepare to find start position just show master status");
|
||||
return findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||
return findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||
}
|
||||
} else {
|
||||
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
|
||||
// 如果指定binlogName + offest,直接返回
|
||||
entryPosition = findPositionWithMasterIdAndTimestamp(mysqlConnection, entryPosition);
|
||||
logger.warn("prepare to find start position {}:{}:{}",
|
||||
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(), "" });
|
||||
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(),
|
||||
entryPosition.getTimestamp() });
|
||||
return entryPosition;
|
||||
} else {
|
||||
EntryPosition specificLogFilePosition = null;
|
||||
@@ -395,7 +460,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
// binlog定位位点失败,可能有两个原因:
|
||||
// 1. binlog位点被删除
|
||||
// 2.vip模式的mysql,发生了主备切换,判断一下serverId是否变化,针对这种模式可以发起一次基于时间戳查找合适的binlog位点
|
||||
boolean case2 = (standbyInfo.getAddress() == null)
|
||||
boolean case2 = (standbyInfo == null || standbyInfo.getAddress() == null)
|
||||
&& logPosition.getPostion().getServerId() != null
|
||||
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
|
||||
if (case2) {
|
||||
@@ -410,7 +475,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
}
|
||||
// 其余情况
|
||||
logger.warn("prepare to find start position just last position");
|
||||
logger.warn("prepare to find start position just last position\n {}",
|
||||
JsonUtils.marshalToString(logPosition));
|
||||
return logPosition.getPostion();
|
||||
} else {
|
||||
// 针对切换的情况,考虑回退时间
|
||||
@@ -435,7 +501,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
|
||||
public boolean sink(LogEvent event) {
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -468,7 +534,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
|
||||
public boolean sink(LogEvent event) {
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -546,9 +612,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
return entryPosition;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("the binlogfile:{} doesn't exist, to continue to search the next binlogfile , caused by {}",
|
||||
startSearchBinlogFile,
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn(String.format("the binlogfile:%s doesn't exist, to continue to search the next binlogfile , caused by",
|
||||
startSearchBinlogFile),
|
||||
e);
|
||||
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
|
||||
if (binlogSeqNum <= 1) {
|
||||
logger.warn("Didn't find the corresponding binlog files");
|
||||
@@ -680,7 +746,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
public boolean sink(LogEvent event) {
|
||||
EntryPosition entryPosition = null;
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -688,6 +754,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
Long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
Long logposTimestamp = entry.getHeader().getExecuteTime();
|
||||
Long serverId = entry.getHeader().getServerId();
|
||||
|
||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
||||
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||
@@ -700,7 +767,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
if (StringUtils.equals(endPosition.getJournalName(), logfilename)
|
||||
&& endPosition.getPosition() <= (logfileoffset + event.getEventLen())) {
|
||||
&& endPosition.getPosition() <= logfileoffset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -708,15 +775,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
// position = current +
|
||||
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
||||
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||
entryPosition = new EntryPosition(logfilename,
|
||||
logfileoffset + event.getEventLen(),
|
||||
logposTimestamp);
|
||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||
entryPosition);
|
||||
logPosition.setPostion(entryPosition);
|
||||
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
||||
// 当前事务开始位点
|
||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp);
|
||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||
entryPosition);
|
||||
logPosition.setPostion(entryPosition);
|
||||
|
||||
+3
-16
@@ -3,13 +3,12 @@ package com.alibaba.otter.canal.parse.inbound.mysql.dbsync;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.taobao.tddl.dbsync.binlog.LogFetcher;
|
||||
|
||||
/**
|
||||
@@ -149,20 +148,8 @@ public class DirectLogFetcher extends LogFetcher {
|
||||
private final boolean fetch0(final int off, final int len) throws IOException {
|
||||
ensureCapacity(off + len);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(this.buffer, off, len);
|
||||
while (buffer.hasRemaining()) {
|
||||
int readNum = channel.read(buffer);
|
||||
if (readNum == -1) {
|
||||
throw new IOException("Unexpected End Stream");
|
||||
}
|
||||
}
|
||||
|
||||
// for (int count, n = 0; n < len; n += count) {
|
||||
// if (0 > (count = input.read(buffer, off + n, len - n))) {
|
||||
// // Reached end of input stream
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
byte[] read = channel.read(len);
|
||||
System.arraycopy(read, 0, this.buffer, off, len);
|
||||
|
||||
if (limit < off + len) limit = off + len;
|
||||
return true;
|
||||
|
||||
+234
-112
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.sql.Types;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,7 +22,9 @@ import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.SimpleDdlParser.DdlResult;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DdlResult;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.SimpleDdlParser;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||
@@ -33,6 +36,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.Type;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
||||
@@ -85,9 +89,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
// 是否跳过table相关的解析异常,比如表不存在或者列数量不匹配,issue 92
|
||||
private boolean filterTableError = false;
|
||||
// 新增rows过滤,用于仅订阅除rows以外的数据
|
||||
private boolean filterRows = false;
|
||||
private boolean filterRows = false;
|
||||
private boolean useDruidDdlFilter = true;
|
||||
|
||||
public Entry parse(LogEvent logEvent) throws CanalParseException {
|
||||
@Override
|
||||
public Entry parse(LogEvent logEvent, boolean isSeek) throws CanalParseException {
|
||||
if (logEvent == null || logEvent instanceof UnknownLogEvent) {
|
||||
return null;
|
||||
}
|
||||
@@ -98,7 +104,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
binlogFileName = ((RotateLogEvent) logEvent).getFilename();
|
||||
break;
|
||||
case LogEvent.QUERY_EVENT:
|
||||
return parseQueryEvent((QueryLogEvent) logEvent);
|
||||
return parseQueryEvent((QueryLogEvent) logEvent, isSeek);
|
||||
case LogEvent.XID_EVENT:
|
||||
return parseXidEvent((XidLogEvent) logEvent);
|
||||
case LogEvent.TABLE_MAP_EVENT:
|
||||
@@ -137,7 +143,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
}
|
||||
}
|
||||
|
||||
private Entry parseQueryEvent(QueryLogEvent event) {
|
||||
private Entry parseQueryEvent(QueryLogEvent event, boolean isSeek) {
|
||||
String queryString = event.getQuery();
|
||||
if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
||||
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
||||
@@ -148,106 +154,139 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
||||
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
||||
} else {
|
||||
// DDL语句处理
|
||||
DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName());
|
||||
|
||||
String schemaName = event.getDbName();
|
||||
if (StringUtils.isNotEmpty(result.getSchemaName())) {
|
||||
schemaName = result.getSchemaName();
|
||||
}
|
||||
|
||||
String tableName = result.getTableName();
|
||||
boolean notFilter = false;
|
||||
EventType type = EventType.QUERY;
|
||||
// fixed issue https://github.com/alibaba/canal/issues/58
|
||||
if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE
|
||||
|| result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE
|
||||
|| result.getType() == EventType.RENAME || result.getType() == EventType.CINDEX
|
||||
|| result.getType() == EventType.DINDEX) { // 针对DDL类型
|
||||
|
||||
if (filterQueryDdl) {
|
||||
return null;
|
||||
String tableName = null;
|
||||
String schemaName = null;
|
||||
if (useDruidDdlFilter) {
|
||||
List<DdlResult> results = DruidDdlParser.parse(queryString, event.getDbName());
|
||||
for (DdlResult result : results) {
|
||||
if (!processFilter(queryString, result)) {
|
||||
// 只要有一个数据不进行过滤
|
||||
notFilter = true;
|
||||
}
|
||||
}
|
||||
if (results.size() > 0) {
|
||||
// 如果针对多行的DDL,只能取第一条
|
||||
type = results.get(0).getType();
|
||||
schemaName = results.get(0).getSchemaName();
|
||||
tableName = results.get(0).getTableName();
|
||||
}
|
||||
} else {
|
||||
DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName());
|
||||
if (!processFilter(queryString, result)) {
|
||||
notFilter = true;
|
||||
}
|
||||
|
||||
type = result.getType();
|
||||
if (StringUtils.isEmpty(tableName)
|
||||
|| (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) {
|
||||
// 如果解析不出tableName,记录一下日志,方便bugfix,目前直接抛出异常,中断解析
|
||||
throw new CanalParseException("SimpleDdlParser process query failed. pls submit issue with this queryString: "
|
||||
+ queryString + " , and DdlResult: " + result.toString());
|
||||
// return null;
|
||||
} else {
|
||||
// check name filter
|
||||
String name = schemaName + "." + tableName;
|
||||
if (nameFilter != null && !nameFilter.filter(name)) {
|
||||
if (result.getType() == EventType.RENAME) {
|
||||
// rename校验只要源和目标满足一个就进行操作
|
||||
if (nameFilter != null
|
||||
&& !nameFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// 其他情况返回null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
schemaName = result.getSchemaName();
|
||||
tableName = result.getTableName();
|
||||
}
|
||||
|
||||
if (nameBlackFilter != null && nameBlackFilter.filter(name)) {
|
||||
if (result.getType() == EventType.RENAME) {
|
||||
// rename校验只要源和目标满足一个就进行操作
|
||||
if (nameBlackFilter != null
|
||||
&& nameBlackFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// 其他情况返回null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (result.getType() == EventType.INSERT || result.getType() == EventType.UPDATE
|
||||
|| result.getType() == EventType.DELETE) {
|
||||
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
|
||||
if (filterQueryDml) {
|
||||
return null;
|
||||
}
|
||||
} else if (filterQueryDcl) {
|
||||
if (!notFilter) {
|
||||
// 如果是过滤的数据就不处理了
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新下table meta cache
|
||||
if (tableMetaCache != null
|
||||
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
|
||||
for (DdlResult renameResult = result; renameResult != null; renameResult = renameResult.getRenameTableResult()) {
|
||||
String schemaName0 = event.getDbName(); // 防止rename语句后产生schema变更带来影响
|
||||
if (StringUtils.isNotEmpty(renameResult.getSchemaName())) {
|
||||
schemaName0 = renameResult.getSchemaName();
|
||||
}
|
||||
|
||||
tableName = renameResult.getTableName();
|
||||
if (StringUtils.isNotEmpty(tableName)) {
|
||||
// 如果解析到了正确的表信息,则根据全名进行清除
|
||||
tableMetaCache.clearTableMeta(schemaName0, tableName);
|
||||
} else {
|
||||
// 如果无法解析正确的表信息,则根据schema进行清除
|
||||
tableMetaCache.clearTableMetaWithSchemaName(schemaName0);
|
||||
}
|
||||
}
|
||||
if (!isSeek) {
|
||||
// 使用新的表结构元数据管理方式
|
||||
EntryPosition position = createPosition(event.getHeader());
|
||||
tableMetaCache.apply(position, event.getDbName(), queryString, null);
|
||||
}
|
||||
|
||||
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
|
||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||
if (result.getType() != EventType.QUERY) {
|
||||
if (type != EventType.QUERY) {
|
||||
rowChangeBuider.setIsDdl(true);
|
||||
}
|
||||
rowChangeBuider.setSql(queryString);
|
||||
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
|
||||
rowChangeBuider.setDdlSchemaName(event.getDbName());
|
||||
}
|
||||
rowChangeBuider.setEventType(result.getType());
|
||||
rowChangeBuider.setEventType(type);
|
||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processFilter(String queryString, DdlResult result) {
|
||||
String schemaName = result.getSchemaName();
|
||||
String tableName = result.getTableName();
|
||||
// fixed issue https://github.com/alibaba/canal/issues/58
|
||||
// 更新下table meta cache
|
||||
if (tableMetaCache != null
|
||||
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
|
||||
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
|
||||
for (DdlResult renameResult = result; renameResult != null; renameResult = renameResult.getRenameTableResult()) {
|
||||
String schemaName0 = renameResult.getSchemaName();
|
||||
String tableName0 = renameResult.getTableName();
|
||||
if (StringUtils.isNotEmpty(tableName0)) {
|
||||
// 如果解析到了正确的表信息,则根据全名进行清除
|
||||
tableMetaCache.clearTableMeta(schemaName0, tableName0);
|
||||
} else {
|
||||
// 如果无法解析正确的表信息,则根据schema进行清除
|
||||
tableMetaCache.clearTableMetaWithSchemaName(schemaName0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixed issue https://github.com/alibaba/canal/issues/58
|
||||
if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE
|
||||
|| result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE
|
||||
|| result.getType() == EventType.RENAME || result.getType() == EventType.CINDEX
|
||||
|| result.getType() == EventType.DINDEX) { // 针对DDL类型
|
||||
|
||||
if (filterQueryDdl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(tableName)
|
||||
|| (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) {
|
||||
// 如果解析不出tableName,记录一下日志,方便bugfix,目前直接抛出异常,中断解析
|
||||
throw new CanalParseException("SimpleDdlParser process query failed. pls submit issue with this queryString: "
|
||||
+ queryString + " , and DdlResult: " + result.toString());
|
||||
// return null;
|
||||
} else {
|
||||
// check name filter
|
||||
String name = schemaName + "." + tableName;
|
||||
if (nameFilter != null && !nameFilter.filter(name)) {
|
||||
if (result.getType() == EventType.RENAME) {
|
||||
// rename校验只要源和目标满足一个就进行操作
|
||||
if (nameFilter != null
|
||||
&& !nameFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 其他情况返回null
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (nameBlackFilter != null && nameBlackFilter.filter(name)) {
|
||||
if (result.getType() == EventType.RENAME) {
|
||||
// rename校验只要源和目标满足一个就进行操作
|
||||
if (nameBlackFilter != null
|
||||
&& nameBlackFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 其他情况返回null
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (result.getType() == EventType.INSERT || result.getType() == EventType.UPDATE
|
||||
|| result.getType() == EventType.DELETE) {
|
||||
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
|
||||
if (filterQueryDml) {
|
||||
return true;
|
||||
}
|
||||
} else if (filterQueryDcl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Entry parseRowsQueryEvent(RowsQueryLogEvent event) {
|
||||
if (filterQueryDml) {
|
||||
return null;
|
||||
@@ -256,7 +295,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
String queryString = null;
|
||||
try {
|
||||
queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name());
|
||||
return buildQueryEntry(queryString, event.getHeader());
|
||||
String tableName = null;
|
||||
if (useDruidDdlFilter) {
|
||||
List<DdlResult> results = DruidDdlParser.parse(queryString, null);
|
||||
if (results.size() > 0) {
|
||||
tableName = results.get(0).getTableName();
|
||||
}
|
||||
}
|
||||
|
||||
return buildQueryEntry(queryString, event.getHeader(), tableName);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
@@ -317,6 +364,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
|
||||
}
|
||||
|
||||
boolean isHeartBeat = isAliSQLHeartBeat(table.getDbName(), table.getTableName());
|
||||
boolean isRDSHeartBeat = tableMetaCache.isOnRDS()
|
||||
&& isRDSHeartBeat(table.getDbName(), table.getTableName());
|
||||
|
||||
String fullname = table.getDbName() + "." + table.getTableName();
|
||||
// check name filter
|
||||
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
||||
@@ -326,6 +377,25 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
return null;
|
||||
}
|
||||
|
||||
// if (isHeartBeat || isRDSHeartBeat) {
|
||||
// // 忽略rds模式的mysql.ha_health_check心跳数据
|
||||
// return null;
|
||||
// }
|
||||
TableMeta tableMeta = null;
|
||||
if (isRDSHeartBeat) {
|
||||
// 处理rds模式的mysql.ha_health_check心跳数据
|
||||
// 主要RDS的心跳表基本无权限,需要mock一个tableMeta
|
||||
FieldMeta idMeta = new FieldMeta("id", "bigint(20)", true, false, "0");
|
||||
FieldMeta typeMeta = new FieldMeta("type", "char(1)", false, true, "0");
|
||||
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
||||
} else if (isHeartBeat) {
|
||||
// 处理alisql模式的test.heartbeat心跳数据
|
||||
// 心跳表基本无权限,需要mock一个tableMeta
|
||||
FieldMeta idMeta = new FieldMeta("id", "smallint(6)", false, true, null);
|
||||
FieldMeta typeMeta = new FieldMeta("type", "int(11)", true, false, null);
|
||||
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
||||
}
|
||||
|
||||
EventType eventType = null;
|
||||
int type = event.getHeader().getType();
|
||||
if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
|
||||
@@ -343,6 +413,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
table.getDbName(),
|
||||
table.getTableName(),
|
||||
eventType);
|
||||
EntryPosition position = createPosition(event.getHeader());
|
||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||
rowChangeBuider.setTableId(event.getTableId());
|
||||
rowChangeBuider.setIsDdl(false);
|
||||
@@ -352,9 +423,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
BitSet columns = event.getColumns();
|
||||
BitSet changeColumns = event.getChangeColumns();
|
||||
boolean tableError = false;
|
||||
TableMeta tableMeta = null;
|
||||
if (tableMetaCache != null) {// 入错存在table meta cache
|
||||
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true);
|
||||
if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
|
||||
// cache
|
||||
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
|
||||
if (tableMeta == null) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
@@ -400,36 +471,61 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
}
|
||||
}
|
||||
|
||||
private EntryPosition createPosition(LogHeader logHeader) {
|
||||
return new EntryPosition(binlogFileName,
|
||||
logHeader.getLogPos(),
|
||||
logHeader.getWhen() * 1000L,
|
||||
logHeader.getServerId()); // 记录到秒
|
||||
}
|
||||
|
||||
private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols,
|
||||
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
|
||||
final int columnCnt = event.getTable().getColumnCnt();
|
||||
final ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
|
||||
int columnCnt = event.getTable().getColumnCnt();
|
||||
ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
|
||||
|
||||
boolean tableError = false;
|
||||
// check table fileds count,只能处理加字段
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
||||
// online ddl增加字段操作步骤:
|
||||
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
||||
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
|
||||
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
||||
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
||||
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
||||
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false);// 强制重新获取一次
|
||||
if (tableMeta == null) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("not found [" + event.getTable().getDbName() + "."
|
||||
+ event.getTable().getTableName() + "] in db , pls check!");
|
||||
boolean existRDSNoPrimaryKey = false;
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
|
||||
if (tableMetaCache.isOnRDS()) {
|
||||
// 特殊处理下RDS的场景
|
||||
List<FieldMeta> primaryKeys = tableMeta.getPrimaryFields();
|
||||
if (primaryKeys == null || primaryKeys.isEmpty()) {
|
||||
if (columnInfo.length == tableMeta.getFields().size() + 1
|
||||
&& columnInfo[columnInfo.length - 1].type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||
existRDSNoPrimaryKey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在做一次判断
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName() + ","
|
||||
+ columnInfo.length + " vs " + tableMeta.getFileds().size());
|
||||
EntryPosition position = createPosition(event.getHeader());
|
||||
if (!existRDSNoPrimaryKey) {
|
||||
// online ddl增加字段操作步骤:
|
||||
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
||||
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
|
||||
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
||||
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
||||
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
||||
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false, position);// 强制重新获取一次
|
||||
if (tableMeta == null) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("not found [" + event.getTable().getDbName() + "."
|
||||
+ event.getTable().getTableName() + "] in db , pls check!");
|
||||
}
|
||||
}
|
||||
|
||||
// 在做一次判断
|
||||
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
|
||||
tableError = true;
|
||||
if (!filterTableError) {
|
||||
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName()
|
||||
+ "," + columnInfo.length + " vs " + tableMeta.getFields().size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("[" + event.getTable().getDbName() + "." + event.getTable().getTableName()
|
||||
+ "] is no primary key , skip alibaba_rds_row_id column");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,12 +536,18 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||
// 不解析最后一列
|
||||
buffer.nextValue(info.type, info.meta, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
Column.Builder columnBuilder = Column.newBuilder();
|
||||
|
||||
FieldMeta fieldMeta = null;
|
||||
if (tableMeta != null && !tableError) {
|
||||
// 处理file meta
|
||||
fieldMeta = tableMeta.getFileds().get(i);
|
||||
fieldMeta = tableMeta.getFields().get(i);
|
||||
columnBuilder.setName(fieldMeta.getColumnName());
|
||||
columnBuilder.setIsKey(fieldMeta.isKey());
|
||||
// 增加mysql type类型,issue 73
|
||||
@@ -465,6 +567,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
}
|
||||
}
|
||||
buffer.nextValue(info.type, info.meta, isBinary);
|
||||
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||
// 不解析最后一列
|
||||
continue;
|
||||
}
|
||||
|
||||
int javaType = buffer.getJavaType();
|
||||
if (buffer.isNull()) {
|
||||
@@ -582,6 +688,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
|
||||
}
|
||||
|
||||
private Entry buildQueryEntry(String queryString, LogHeader logHeader, String tableName) {
|
||||
Header header = createHeader(binlogFileName, logHeader, "", tableName, EventType.QUERY);
|
||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||
rowChangeBuider.setSql(queryString);
|
||||
rowChangeBuider.setEventType(EventType.QUERY);
|
||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||
}
|
||||
|
||||
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
|
||||
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
|
||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||
@@ -640,9 +754,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
return true;
|
||||
}
|
||||
|
||||
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache) {
|
||||
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) {
|
||||
try {
|
||||
return tableMetaCache.getTableMeta(dbName, tbName, useCache);
|
||||
return tableMetaCache.getTableMeta(dbName, tbName, useCache, position);
|
||||
} catch (Exception e) {
|
||||
String message = ExceptionUtils.getRootCauseMessage(e);
|
||||
if (filterTableError) {
|
||||
@@ -659,6 +773,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
return "LONGTEXT".equalsIgnoreCase(columnType) || "MEDIUMTEXT".equalsIgnoreCase(columnType)
|
||||
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType);
|
||||
}
|
||||
|
||||
private boolean isAliSQLHeartBeat(String schema, String table) {
|
||||
return "test".equalsIgnoreCase(schema) && "heartbeat".equalsIgnoreCase(table);
|
||||
}
|
||||
|
||||
private boolean isRDSHeartBeat(String schema, String table) {
|
||||
return "mysql".equalsIgnoreCase(schema) && "ha_health_check".equalsIgnoreCase(table);
|
||||
}
|
||||
|
||||
public static TransactionBegin createTransactionBegin(long threadId) {
|
||||
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
|
||||
@@ -718,7 +840,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
||||
public void setFilterTableError(boolean filterTableError) {
|
||||
this.filterTableError = filterTableError;
|
||||
}
|
||||
|
||||
|
||||
public void setFilterRows(boolean filterRows) {
|
||||
this.filterRows = filterRows;
|
||||
}
|
||||
|
||||
+153
-62
@@ -14,91 +14,77 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.MigrateMap;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
/**
|
||||
* 处理table meta解析和缓存
|
||||
*
|
||||
*
|
||||
* @author jianghang 2013-1-17 下午10:15:16
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class TableMetaCache {
|
||||
|
||||
public static final String COLUMN_NAME = "COLUMN_NAME";
|
||||
public static final String COLUMN_TYPE = "COLUMN_TYPE";
|
||||
public static final String IS_NULLABLE = "IS_NULLABLE";
|
||||
public static final String COLUMN_KEY = "COLUMN_KEY";
|
||||
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
||||
public static final String EXTRA = "EXTRA";
|
||||
private MysqlConnection connection;
|
||||
public static final String COLUMN_NAME = "COLUMN_NAME";
|
||||
public static final String COLUMN_TYPE = "COLUMN_TYPE";
|
||||
public static final String IS_NULLABLE = "IS_NULLABLE";
|
||||
public static final String COLUMN_KEY = "COLUMN_KEY";
|
||||
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
||||
public static final String EXTRA = "EXTRA";
|
||||
private MysqlConnection connection;
|
||||
private boolean isOnRDS = false;
|
||||
|
||||
private TableMetaTSDB tableMetaTSDB;
|
||||
// 第一层tableId,第二层schema.table,解决tableId重复,对应多张表
|
||||
private Map<String, TableMeta> tableMetaCache;
|
||||
private LoadingCache<String, TableMeta> tableMetaDB;
|
||||
|
||||
public TableMetaCache(MysqlConnection con){
|
||||
public TableMetaCache(MysqlConnection con, TableMetaTSDB tableMetaTSDB){
|
||||
this.connection = con;
|
||||
tableMetaCache = MigrateMap.makeComputingMap(new Function<String, TableMeta>() {
|
||||
this.tableMetaTSDB = tableMetaTSDB;
|
||||
// 如果持久存储的表结构为空,从db里面获取下
|
||||
if (tableMetaTSDB == null) {
|
||||
this.tableMetaDB = CacheBuilder.newBuilder().build(new CacheLoader<String, TableMeta>() {
|
||||
|
||||
public TableMeta apply(String name) {
|
||||
try {
|
||||
return getTableMeta0(name);
|
||||
} catch (IOException e) {
|
||||
// 尝试做一次retry操作
|
||||
@Override
|
||||
public TableMeta load(String name) throws Exception {
|
||||
try {
|
||||
connection.reconnect();
|
||||
return getTableMeta0(name);
|
||||
} catch (IOException e1) {
|
||||
throw new CanalParseException("fetch failed by table meta:" + name, e1);
|
||||
return getTableMetaByDB(name);
|
||||
} catch (CanalParseException e) {
|
||||
// 尝试做一次retry操作
|
||||
try {
|
||||
connection.reconnect();
|
||||
return getTableMetaByDB(name);
|
||||
} catch (IOException e1) {
|
||||
throw new CanalParseException("fetch failed by table meta:" + name, e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table) {
|
||||
return getTableMeta(schema, table, true);
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table, boolean useCache) {
|
||||
if (!useCache) {
|
||||
tableMetaCache.remove(getFullName(schema, table));
|
||||
});
|
||||
}
|
||||
|
||||
return tableMetaCache.get(getFullName(schema, table));
|
||||
}
|
||||
|
||||
public void clearTableMeta(String schema, String table) {
|
||||
tableMetaCache.remove(getFullName(schema, table));
|
||||
}
|
||||
|
||||
public void clearTableMetaWithSchemaName(String schema) {
|
||||
// Set<String> removeNames = new HashSet<String>(); //
|
||||
// 存一份临时变量,避免在遍历的时候进行删除
|
||||
for (String name : tableMetaCache.keySet()) {
|
||||
if (StringUtils.startsWithIgnoreCase(name, schema + ".")) {
|
||||
// removeNames.add(name);
|
||||
tableMetaCache.remove(name);
|
||||
try {
|
||||
ResultSetPacket packet = connection.query("show global variables like 'rds\\_%'");
|
||||
if (packet.getFieldValues().size() > 0) {
|
||||
isOnRDS = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
// for (String name : removeNames) {
|
||||
// tables.remove(name);
|
||||
// }
|
||||
}
|
||||
|
||||
public void clearTableMeta() {
|
||||
tableMetaCache.clear();
|
||||
}
|
||||
|
||||
private TableMeta getTableMeta0(String fullname) throws IOException {
|
||||
private TableMeta getTableMetaByDB(String fullname) throws IOException {
|
||||
ResultSetPacket packet = connection.query("desc " + fullname);
|
||||
return new TableMeta(fullname, parserTableMeta(packet));
|
||||
String[] names = StringUtils.split(fullname, "`.`");
|
||||
String schema = names[0];
|
||||
String table = names[1].substring(0, names[1].length());
|
||||
return new TableMeta(schema, table, parserTableMeta(packet));
|
||||
}
|
||||
|
||||
private List<FieldMeta> parserTableMeta(ResultSetPacket packet) {
|
||||
public static List<FieldMeta> parserTableMeta(ResultSetPacket packet) {
|
||||
Map<String, Integer> nameMaps = new HashMap<String, Integer>(6, 1f);
|
||||
|
||||
int index = 0;
|
||||
@@ -114,9 +100,13 @@ public class TableMetaCache {
|
||||
// 做一个优化,使用String.intern(),共享String对象,减少内存使用
|
||||
meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern());
|
||||
meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size));
|
||||
meta.setIsNullable(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i * size));
|
||||
meta.setIskey(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size));
|
||||
meta.setDefaultValue(packet.getFieldValues().get(nameMaps.get(COLUMN_DEFAULT) + i * size));
|
||||
meta.setNullable(StringUtils.equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i
|
||||
* size),
|
||||
"YES"));
|
||||
meta.setKey("PRI".equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size)));
|
||||
// 特殊处理引号
|
||||
meta.setDefaultValue(DruidDdlParser.unescapeQuotaName(packet.getFieldValues()
|
||||
.get(nameMaps.get(COLUMN_DEFAULT) + i * size)));
|
||||
meta.setExtra(packet.getFieldValues().get(nameMaps.get(EXTRA) + i * size));
|
||||
|
||||
result.add(meta);
|
||||
@@ -125,6 +115,98 @@ public class TableMetaCache {
|
||||
return result;
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table) {
|
||||
return getTableMeta(schema, table, true);
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table, boolean useCache) {
|
||||
if (!useCache) {
|
||||
tableMetaDB.invalidate(getFullName(schema, table));
|
||||
}
|
||||
|
||||
return tableMetaDB.getUnchecked(getFullName(schema, table));
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table, EntryPosition position) {
|
||||
return getTableMeta(schema, table, true, position);
|
||||
}
|
||||
|
||||
public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) {
|
||||
TableMeta tableMeta = null;
|
||||
if (tableMetaTSDB != null) {
|
||||
tableMeta = tableMetaTSDB.find(schema, table);
|
||||
if (tableMeta == null) {
|
||||
// 因为条件变化,可能第一次的tableMeta没取到,需要从db获取一次,并记录到snapshot中
|
||||
String fullName = getFullName(schema, table);
|
||||
try {
|
||||
ResultSetPacket packet = connection.query("show create table " + fullName);
|
||||
String createDDL = null;
|
||||
if (packet.getFieldValues().size() > 0) {
|
||||
createDDL = packet.getFieldValues().get(1);
|
||||
}
|
||||
// 强制覆盖掉内存值
|
||||
tableMetaTSDB.apply(position, schema, createDDL, "first");
|
||||
tableMeta = tableMetaTSDB.find(schema, table);
|
||||
} catch (IOException e) {
|
||||
throw new CanalParseException("fetch failed by table meta:" + fullName, e);
|
||||
}
|
||||
}
|
||||
return tableMeta;
|
||||
} else {
|
||||
if (!useCache) {
|
||||
tableMetaDB.invalidate(getFullName(schema, table));
|
||||
}
|
||||
|
||||
return tableMetaDB.getUnchecked(getFullName(schema, table));
|
||||
}
|
||||
}
|
||||
|
||||
public void clearTableMeta(String schema, String table) {
|
||||
if (tableMetaTSDB != null) {
|
||||
// tsdb不需要做,会基于ddl sql自动清理
|
||||
} else {
|
||||
tableMetaDB.invalidate(getFullName(schema, table));
|
||||
}
|
||||
}
|
||||
|
||||
public void clearTableMetaWithSchemaName(String schema) {
|
||||
if (tableMetaTSDB != null) {
|
||||
// tsdb不需要做,会基于ddl sql自动清理
|
||||
} else {
|
||||
for (String name : tableMetaDB.asMap().keySet()) {
|
||||
if (StringUtils.startsWithIgnoreCase(name, schema + ".")) {
|
||||
// removeNames.add(name);
|
||||
tableMetaDB.invalidate(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearTableMeta() {
|
||||
if (tableMetaTSDB != null) {
|
||||
// tsdb不需要做,会基于ddl sql自动清理
|
||||
} else {
|
||||
tableMetaDB.invalidateAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新一下本地的表结构内存
|
||||
*
|
||||
* @param position
|
||||
* @param schema
|
||||
* @param ddl
|
||||
* @return
|
||||
*/
|
||||
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
|
||||
if (tableMetaTSDB != null) {
|
||||
return tableMetaTSDB.apply(position, schema, ddl, extra);
|
||||
} else {
|
||||
// ignore
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private String getFullName(String schema, String table) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
return builder.append('`')
|
||||
@@ -136,4 +218,13 @@ public class TableMetaCache {
|
||||
.append('`')
|
||||
.toString();
|
||||
}
|
||||
|
||||
public boolean isOnRDS() {
|
||||
return isOnRDS;
|
||||
}
|
||||
|
||||
public void setOnRDS(boolean isOnRDS) {
|
||||
this.isOnRDS = isOnRDS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.ddl;
|
||||
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年8月1日 下午7:30:42
|
||||
* @since 3.2.5
|
||||
*/
|
||||
public class DdlResult {
|
||||
|
||||
private String schemaName;
|
||||
private String tableName;
|
||||
private String oriSchemaName; // rename ddl中的源表
|
||||
private String oriTableName; // rename ddl中的目标表
|
||||
private EventType type;
|
||||
private DdlResult renameTableResult; // 多个rename table的存储
|
||||
|
||||
/*
|
||||
* RENAME TABLE tbl_name TO new_tbl_name [, tbl_name2 TO new_tbl_name2] ...
|
||||
*/
|
||||
|
||||
public DdlResult(){
|
||||
}
|
||||
|
||||
public DdlResult(String schemaName){
|
||||
this.schemaName = schemaName;
|
||||
}
|
||||
|
||||
public DdlResult(String schemaName, String tableName){
|
||||
this.schemaName = schemaName;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public DdlResult(String schemaName, String tableName, String oriSchemaName, String oriTableName){
|
||||
this.schemaName = schemaName;
|
||||
this.tableName = tableName;
|
||||
this.oriSchemaName = oriSchemaName;
|
||||
this.oriTableName = oriTableName;
|
||||
}
|
||||
|
||||
public String getSchemaName() {
|
||||
return schemaName;
|
||||
}
|
||||
|
||||
public void setSchemaName(String schemaName) {
|
||||
this.schemaName = schemaName;
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public EventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(EventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getOriSchemaName() {
|
||||
return oriSchemaName;
|
||||
}
|
||||
|
||||
public void setOriSchemaName(String oriSchemaName) {
|
||||
this.oriSchemaName = oriSchemaName;
|
||||
}
|
||||
|
||||
public String getOriTableName() {
|
||||
return oriTableName;
|
||||
}
|
||||
|
||||
public void setOriTableName(String oriTableName) {
|
||||
this.oriTableName = oriTableName;
|
||||
}
|
||||
|
||||
public DdlResult getRenameTableResult() {
|
||||
return renameTableResult;
|
||||
}
|
||||
|
||||
public void setRenameTableResult(DdlResult renameTableResult) {
|
||||
this.renameTableResult = renameTableResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlResult clone() {
|
||||
DdlResult result = new DdlResult();
|
||||
result.setOriSchemaName(oriSchemaName);
|
||||
result.setOriTableName(oriTableName);
|
||||
result.setSchemaName(schemaName);
|
||||
result.setTableName(tableName);
|
||||
// result.setType(type);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
DdlResult ddlResult = this;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
do {
|
||||
sb.append(String.format("DdlResult [schemaName=%s , tableName=%s , oriSchemaName=%s , oriTableName=%s , type=%s ];",
|
||||
ddlResult.schemaName,
|
||||
ddlResult.tableName,
|
||||
ddlResult.oriSchemaName,
|
||||
ddlResult.oriTableName,
|
||||
ddlResult.type));
|
||||
ddlResult = ddlResult.renameTableResult;
|
||||
} while (ddlResult != null);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.ddl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.druid.sql.SQLUtils;
|
||||
import com.alibaba.druid.sql.ast.SQLExpr;
|
||||
import com.alibaba.druid.sql.ast.SQLStatement;
|
||||
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
|
||||
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddConstraint;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddIndex;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropConstraint;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropIndex;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropKey;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableItem;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableRename;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLConstraint;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLTruncateStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLUnique;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
|
||||
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement;
|
||||
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item;
|
||||
import com.alibaba.druid.sql.parser.ParserException;
|
||||
import com.alibaba.druid.util.JdbcConstants;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年7月27日 下午4:05:34
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class DruidDdlParser {
|
||||
|
||||
public static List<DdlResult> parse(String queryString, String schmeaName) {
|
||||
List<SQLStatement> stmtList = null;
|
||||
try {
|
||||
stmtList = SQLUtils.parseStatements(queryString, JdbcConstants.MYSQL, false);
|
||||
} catch (ParserException e) {
|
||||
// 可能存在一些SQL是不支持的,比如存储过程
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
ddlResult.setType(EventType.QUERY);
|
||||
return Arrays.asList(ddlResult);
|
||||
}
|
||||
|
||||
List<DdlResult> ddlResults = new ArrayList<DdlResult>();
|
||||
for (SQLStatement statement : stmtList) {
|
||||
if (statement instanceof SQLCreateTableStatement) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
SQLCreateTableStatement createTable = (SQLCreateTableStatement) statement;
|
||||
processName(ddlResult, schmeaName, createTable.getName(), false);
|
||||
ddlResult.setType(EventType.CREATE);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (statement instanceof SQLAlterTableStatement) {
|
||||
SQLAlterTableStatement alterTable = (SQLAlterTableStatement) statement;
|
||||
for (SQLAlterTableItem item : alterTable.getItems()) {
|
||||
if (item instanceof SQLAlterTableRename) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, alterTable.getName(), true);
|
||||
processName(ddlResult, schmeaName, ((SQLAlterTableRename) item).getToName(), false);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (item instanceof SQLAlterTableAddIndex) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, alterTable.getName(), false);
|
||||
ddlResult.setType(EventType.CINDEX);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (item instanceof SQLAlterTableDropIndex || item instanceof SQLAlterTableDropKey) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, alterTable.getName(), false);
|
||||
ddlResult.setType(EventType.DINDEX);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (item instanceof SQLAlterTableAddConstraint) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, alterTable.getName(), false);
|
||||
SQLConstraint constraint = ((SQLAlterTableAddConstraint) item).getConstraint();
|
||||
if (constraint instanceof SQLUnique) {
|
||||
ddlResult.setType(EventType.CINDEX);
|
||||
ddlResults.add(ddlResult);
|
||||
}
|
||||
} else if (item instanceof SQLAlterTableDropConstraint) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, alterTable.getName(), false);
|
||||
ddlResult.setType(EventType.DINDEX);
|
||||
ddlResults.add(ddlResult);
|
||||
} else {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, alterTable.getName(), false);
|
||||
ddlResult.setType(EventType.ALTER);
|
||||
ddlResults.add(ddlResult);
|
||||
}
|
||||
}
|
||||
} else if (statement instanceof SQLDropTableStatement) {
|
||||
SQLDropTableStatement dropTable = (SQLDropTableStatement) statement;
|
||||
for (SQLExprTableSource tableSource : dropTable.getTableSources()) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, tableSource.getExpr(), false);
|
||||
ddlResult.setType(EventType.ERASE);
|
||||
ddlResults.add(ddlResult);
|
||||
}
|
||||
} else if (statement instanceof SQLCreateIndexStatement) {
|
||||
SQLCreateIndexStatement createIndex = (SQLCreateIndexStatement) statement;
|
||||
SQLTableSource tableSource = createIndex.getTable();
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, ((SQLExprTableSource) tableSource).getExpr(), false);
|
||||
ddlResult.setType(EventType.CINDEX);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (statement instanceof SQLDropIndexStatement) {
|
||||
SQLDropIndexStatement dropIndex = (SQLDropIndexStatement) statement;
|
||||
SQLExprTableSource tableSource = dropIndex.getTableName();
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, tableSource.getExpr(), false);
|
||||
ddlResult.setType(EventType.DINDEX);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (statement instanceof SQLTruncateStatement) {
|
||||
SQLTruncateStatement truncate = (SQLTruncateStatement) statement;
|
||||
for (SQLExprTableSource tableSource : truncate.getTableSources()) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, tableSource.getExpr(), false);
|
||||
ddlResult.setType(EventType.TRUNCATE);
|
||||
ddlResults.add(ddlResult);
|
||||
}
|
||||
} else if (statement instanceof MySqlRenameTableStatement) {
|
||||
MySqlRenameTableStatement rename = (MySqlRenameTableStatement) statement;
|
||||
for (Item item : rename.getItems()) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
processName(ddlResult, schmeaName, item.getName(), true);
|
||||
processName(ddlResult, schmeaName, item.getTo(), false);
|
||||
ddlResult.setType(EventType.RENAME);
|
||||
ddlResults.add(ddlResult);
|
||||
}
|
||||
} else if (statement instanceof SQLInsertStatement) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
SQLInsertStatement insert = (SQLInsertStatement) statement;
|
||||
processName(ddlResult, schmeaName, insert.getTableName(), false);
|
||||
ddlResult.setType(EventType.INSERT);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (statement instanceof SQLUpdateStatement) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
SQLUpdateStatement update = (SQLUpdateStatement) statement;
|
||||
// 拿到的表名可能为null,比如update a,b set a.id=x
|
||||
processName(ddlResult, schmeaName, update.getTableName(), false);
|
||||
ddlResult.setType(EventType.UPDATE);
|
||||
ddlResults.add(ddlResult);
|
||||
} else if (statement instanceof SQLDeleteStatement) {
|
||||
DdlResult ddlResult = new DdlResult();
|
||||
SQLDeleteStatement delete = (SQLDeleteStatement) statement;
|
||||
// 拿到的表名可能为null,比如delete a,b from a where a.id = b.id
|
||||
processName(ddlResult, schmeaName, delete.getTableName(), false);
|
||||
ddlResult.setType(EventType.DELETE);
|
||||
ddlResults.add(ddlResult);
|
||||
}
|
||||
}
|
||||
|
||||
return ddlResults;
|
||||
}
|
||||
|
||||
private static void processName(DdlResult ddlResult, String schema, SQLExpr sqlName, boolean isOri) {
|
||||
if (sqlName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String table = null;
|
||||
if (sqlName instanceof SQLPropertyExpr) {
|
||||
SQLIdentifierExpr owner = (SQLIdentifierExpr) ((SQLPropertyExpr) sqlName).getOwner();
|
||||
schema = unescapeName(owner.getName());
|
||||
table = unescapeName(((SQLPropertyExpr) sqlName).getName());
|
||||
} else if (sqlName instanceof SQLIdentifierExpr) {
|
||||
table = unescapeName(((SQLIdentifierExpr) sqlName).getName());
|
||||
}
|
||||
|
||||
if (isOri) {
|
||||
ddlResult.setOriSchemaName(schema);
|
||||
ddlResult.setOriTableName(table);
|
||||
} else {
|
||||
ddlResult.setSchemaName(schema);
|
||||
ddlResult.setTableName(table);
|
||||
}
|
||||
}
|
||||
|
||||
public static String unescapeName(String name) {
|
||||
if (name != null && name.length() > 2) {
|
||||
char c0 = name.charAt(0);
|
||||
char x0 = name.charAt(name.length() - 1);
|
||||
if ((c0 == '"' && x0 == '"') || (c0 == '`' && x0 == '`')) {
|
||||
return name.substring(1, name.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public static String unescapeQuotaName(String name) {
|
||||
if (name != null && name.length() > 2) {
|
||||
char c0 = name.charAt(0);
|
||||
char x0 = name.charAt(name.length() - 1);
|
||||
if (c0 == '\'' && x0 == '\'') {
|
||||
return name.substring(1, name.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-99
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.dbsync;
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.ddl;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.oro.text.regex.Perl5Matcher;
|
||||
@@ -218,102 +218,4 @@ public class SimpleDdlParser {
|
||||
sql = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DdlResult {
|
||||
|
||||
private String schemaName;
|
||||
private String tableName;
|
||||
private String oriSchemaName; // rename ddl中的源表
|
||||
private String oriTableName; // rename ddl中的目标表
|
||||
private EventType type;
|
||||
private DdlResult renameTableResult; // 多个rename table的存储
|
||||
|
||||
/*
|
||||
* RENAME TABLE tbl_name TO new_tbl_name [, tbl_name2 TO new_tbl_name2]
|
||||
* ...
|
||||
*/
|
||||
|
||||
public DdlResult(){
|
||||
}
|
||||
|
||||
public DdlResult(String schemaName){
|
||||
this.schemaName = schemaName;
|
||||
}
|
||||
|
||||
public DdlResult(String schemaName, String tableName){
|
||||
this.schemaName = schemaName;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public DdlResult(String schemaName, String tableName, String oriSchemaName, String oriTableName){
|
||||
this.schemaName = schemaName;
|
||||
this.tableName = tableName;
|
||||
this.oriSchemaName = oriSchemaName;
|
||||
this.oriTableName = oriTableName;
|
||||
}
|
||||
|
||||
public String getSchemaName() {
|
||||
return schemaName;
|
||||
}
|
||||
|
||||
public void setSchemaName(String schemaName) {
|
||||
this.schemaName = schemaName;
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public EventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(EventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getOriSchemaName() {
|
||||
return oriSchemaName;
|
||||
}
|
||||
|
||||
public void setOriSchemaName(String oriSchemaName) {
|
||||
this.oriSchemaName = oriSchemaName;
|
||||
}
|
||||
|
||||
public String getOriTableName() {
|
||||
return oriTableName;
|
||||
}
|
||||
|
||||
public void setOriTableName(String oriTableName) {
|
||||
this.oriTableName = oriTableName;
|
||||
}
|
||||
|
||||
public DdlResult getRenameTableResult() {
|
||||
return renameTableResult;
|
||||
}
|
||||
|
||||
public void setRenameTableResult(DdlResult renameTableResult) {
|
||||
this.renameTableResult = renameTableResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
DdlResult ddlResult = this;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
do {
|
||||
sb.append(String.format("DdlResult [schemaName=%s , tableName=%s , oriSchemaName=%s , oriTableName=%s , type=%s ];",
|
||||
ddlResult.schemaName,
|
||||
ddlResult.tableName,
|
||||
ddlResult.oriSchemaName,
|
||||
ddlResult.oriTableName,
|
||||
ddlResult.type));
|
||||
ddlResult = ddlResult.renameTableResult;
|
||||
} while (ddlResult != null);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -9,6 +9,8 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||
@@ -188,7 +190,9 @@ public class BinLogFileQueue {
|
||||
files.addAll(FileUtils.listFiles(directory, new IOFileFilter() {
|
||||
|
||||
public boolean accept(File file) {
|
||||
return file.getName().startsWith(baseName);
|
||||
Pattern pattern = Pattern.compile("\\d+$");
|
||||
Matcher matcher = pattern.matcher(file.getName());
|
||||
return file.getName().startsWith(baseName) && matcher.find();
|
||||
}
|
||||
|
||||
public boolean accept(File dir, String name) {
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import static org.apache.http.client.config.RequestConfig.custom;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.cookie.Cookie;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.apache.http.ssl.TrustStrategy;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class HttpHelper {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(HttpHelper.class);
|
||||
|
||||
public static byte[] getBytes(String url, int timeout) throws Exception {
|
||||
long start = System.currentTimeMillis();
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setMaxConnPerRoute(50);
|
||||
builder.setMaxConnTotal(100);
|
||||
CloseableHttpClient httpclient = builder.build();
|
||||
URI uri = new URIBuilder(url).build();
|
||||
RequestConfig config = custom().setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.build();
|
||||
HttpGet httpGet = new HttpGet(uri);
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(config);
|
||||
CloseableHttpResponse response = httpclient.execute(httpGet, context);
|
||||
try {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("post " + url + ", cost : " + cost);
|
||||
}
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
return EntityUtils.toByteArray(response.getEntity());
|
||||
} else {
|
||||
String errorMsg = EntityUtils.toString(response.getEntity());
|
||||
throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
|
||||
+ ", error msg=" + errorMsg);
|
||||
}
|
||||
} finally {
|
||||
response.close();
|
||||
httpGet.releaseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public static String get(String url, int timeout) {
|
||||
// logger.info("get url is :" + url);
|
||||
// 支持采用https协议,忽略证书
|
||||
url = url.trim();
|
||||
if (url.startsWith("https")) {
|
||||
return getIgnoreCerf(url, null, null, timeout);
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setMaxConnPerRoute(50);
|
||||
builder.setMaxConnTotal(100);
|
||||
CloseableHttpClient httpclient = builder.build();
|
||||
CloseableHttpResponse response = null;
|
||||
HttpGet httpGet = null;
|
||||
try {
|
||||
URI uri = new URIBuilder(url).build();
|
||||
RequestConfig config = custom().setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.build();
|
||||
httpGet = new HttpGet(uri);
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(config);
|
||||
response = httpclient.execute(httpGet, context);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
return EntityUtils.toString(response.getEntity());
|
||||
} else {
|
||||
String errorMsg = EntityUtils.toString(response.getEntity());
|
||||
throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
|
||||
+ ", error msg=" + errorMsg);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
String curlRequest = getCurlRequest(url, null, null, cost);
|
||||
throw new RuntimeException("requestGet remote error, request : " + curlRequest, t);
|
||||
} finally {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
printCurlRequest(url, null, null, cost);
|
||||
if (response != null) {
|
||||
try {
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
httpGet.releaseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getIgnoreCerf(String url, CookieStore cookieStore, Map<String, String> params, int timeout) {
|
||||
long start = System.currentTimeMillis();
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setMaxConnPerRoute(50);
|
||||
builder.setMaxConnTotal(100);
|
||||
HttpGet httpGet = null;
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
// 创建支持忽略证书的https
|
||||
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
|
||||
|
||||
@Override
|
||||
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
||||
return true;
|
||||
}
|
||||
}).build();
|
||||
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create()
|
||||
.setSSLContext(sslContext)
|
||||
.setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory> create()
|
||||
.register("http", PlainConnectionSocketFactory.INSTANCE)
|
||||
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
|
||||
.build()))
|
||||
.build();
|
||||
// ---------------- 创建支持https 的client成功---------
|
||||
|
||||
URI uri = new URIBuilder(url).build();
|
||||
RequestConfig config = custom().setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.build();
|
||||
httpGet = new HttpGet(uri);
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(config);
|
||||
response = httpClient.execute(httpGet, context);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
return EntityUtils.toString(response.getEntity());
|
||||
} else {
|
||||
String errorMsg = EntityUtils.toString(response.getEntity());
|
||||
throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
|
||||
+ ", error msg=" + errorMsg);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
|
||||
throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest, t);
|
||||
} finally {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
printCurlRequest(url, null, null, cost);
|
||||
if (response != null) {
|
||||
try {
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (httpGet != null) {
|
||||
httpGet.releaseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String postIgnoreCerf(String url, CookieStore cookieStore, Map<String, String> params, int timeout) {
|
||||
long start = System.currentTimeMillis();
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setMaxConnPerRoute(50);
|
||||
builder.setMaxConnTotal(100);
|
||||
HttpPost httpPost = null;
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
// 创建支持忽略证书的https
|
||||
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
|
||||
|
||||
@Override
|
||||
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
||||
return true;
|
||||
}
|
||||
}).build();
|
||||
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create()
|
||||
.setSSLContext(sslContext)
|
||||
.setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory> create()
|
||||
.register("http", PlainConnectionSocketFactory.INSTANCE)
|
||||
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
|
||||
.build()))
|
||||
.build();
|
||||
// ---------------- 创建支持https 的client成功---------
|
||||
|
||||
URI uri = new URIBuilder(url).build();
|
||||
RequestConfig config = custom().setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.build();
|
||||
httpPost = new HttpPost(uri);
|
||||
List<NameValuePair> parameters = Lists.newArrayList();
|
||||
for (String key : params.keySet()) {
|
||||
NameValuePair nameValuePair = new BasicNameValuePair(key, params.get(key));
|
||||
parameters.add(nameValuePair);
|
||||
}
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8")));
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(config);
|
||||
context.setCookieStore(cookieStore);
|
||||
|
||||
response = httpClient.execute(httpPost, context);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
printCurlRequest(url, cookieStore, params, cost);
|
||||
return EntityUtils.toString(response.getEntity());
|
||||
} else {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
|
||||
throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest
|
||||
+ ", statusCode=" + statusCode + "");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
|
||||
throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest, t);
|
||||
} finally {
|
||||
if (response != null) {
|
||||
try {
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (httpPost != null) {
|
||||
httpPost.releaseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String post(String url, CookieStore cookieStore, Map<String, String> params, int timeout) {
|
||||
url = url.trim();
|
||||
// 支持采用https协议,忽略证书
|
||||
if (url.startsWith("https")) {
|
||||
return postIgnoreCerf(url, cookieStore, params, timeout);
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setMaxConnPerRoute(50);
|
||||
builder.setMaxConnTotal(100);
|
||||
HttpPost httpPost = null;
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
CloseableHttpClient httpclient = builder.build();
|
||||
URI uri = new URIBuilder(url).build();
|
||||
RequestConfig config = custom().setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.build();
|
||||
httpPost = new HttpPost(uri);
|
||||
List<NameValuePair> parameters = Lists.newArrayList();
|
||||
for (String key : params.keySet()) {
|
||||
NameValuePair nameValuePair = new BasicNameValuePair(key, params.get(key));
|
||||
parameters.add(nameValuePair);
|
||||
}
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8")));
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(config);
|
||||
context.setCookieStore(cookieStore);
|
||||
|
||||
response = httpclient.execute(httpPost, context);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
printCurlRequest(url, cookieStore, params, cost);
|
||||
return EntityUtils.toString(response.getEntity());
|
||||
} else {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
|
||||
throw new RuntimeException("requestPost remote error, request : " + curlRequest + ", statusCode="
|
||||
+ statusCode + ";" + EntityUtils.toString(response.getEntity()));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
long end = System.currentTimeMillis();
|
||||
long cost = end - start;
|
||||
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
|
||||
throw new RuntimeException("requestPost remote error, request : " + curlRequest, t);
|
||||
} finally {
|
||||
if (response != null) {
|
||||
try {
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (httpPost != null) {
|
||||
httpPost.releaseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void printCurlRequest(String url, CookieStore cookieStore, Map<String, String> params, long cost) {
|
||||
logger.warn(getCurlRequest(url, cookieStore, params, cost));
|
||||
}
|
||||
|
||||
private static String getCurlRequest(String url, CookieStore cookieStore, Map<String, String> params, long cost) {
|
||||
if (params == null) {
|
||||
return "curl '" + url + "'\ncost : " + cost;
|
||||
} else {
|
||||
StringBuilder paramsStr = new StringBuilder();
|
||||
Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, String> entry = iterator.next();
|
||||
paramsStr.append(entry.getKey() + "=" + entry.getValue());
|
||||
if (iterator.hasNext()) {
|
||||
paramsStr.append("&");
|
||||
}
|
||||
}
|
||||
if (cookieStore == null) {
|
||||
return "curl '" + url + "' -d '" + paramsStr.toString() + "'\ncost : " + cost;
|
||||
} else {
|
||||
StringBuilder cookieStr = new StringBuilder();
|
||||
List<Cookie> cookies = cookieStore.getCookies();
|
||||
Iterator<Cookie> iteratorCookie = cookies.iterator();
|
||||
while (iteratorCookie.hasNext()) {
|
||||
Cookie cookie = iteratorCookie.next();
|
||||
cookieStr.append(cookie.getName() + "=" + cookie.getValue());
|
||||
if (iteratorCookie.hasNext()) {
|
||||
cookieStr.append(";");
|
||||
}
|
||||
}
|
||||
return "curl '" + url + "' -b '" + cookieStr + "' -d '" + paramsStr.toString() + "'\ncost : " + cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年10月14日 下午1:53:52
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsBinlogOpenApi {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class);
|
||||
private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
private static final int TIMEOUT = 10000;
|
||||
private static final String ENCODING = "UTF-8";
|
||||
private static final String MAC_NAME = "HmacSHA1";
|
||||
private static final String API_VERSION = "2014-08-15";
|
||||
private static final String SIGNATURE_VERSION = "1.0";
|
||||
|
||||
public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
|
||||
Date endTime, File destDir) throws Throwable {
|
||||
int pageSize = 100;
|
||||
int pageNumber = 0;
|
||||
int pageRecordCount = 1;
|
||||
String hostInstanceID = null;
|
||||
while (pageRecordCount > 0 && pageRecordCount <= pageSize) {
|
||||
pageNumber += 1;
|
||||
String result = describeBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime, pageSize, pageNumber);
|
||||
JSONObject jsobObj = JSON.parseObject(result);
|
||||
pageRecordCount = jsobObj.getInteger("PageRecordCount");
|
||||
|
||||
if (pageRecordCount > 0) {
|
||||
FileUtils.forceMkdir(destDir);
|
||||
File hostIdFile = new File(destDir, "hostId");
|
||||
if (hostIdFile.exists()) {
|
||||
List<String> lines = IOUtils.readLines(new FileInputStream(hostIdFile));
|
||||
hostInstanceID = StringUtils.join(lines, "\n");
|
||||
}
|
||||
|
||||
String itemStr = jsobObj.getString("Items");
|
||||
JSONObject binLogFileObj = JSONObject.parseObject(itemStr);
|
||||
JSONArray items = binLogFileObj.getJSONArray("BinLogFile");
|
||||
if (items == null || items.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
JSONObject item = (JSONObject) items.get(i);
|
||||
String oneHostInstanceID = item.getString("HostInstanceID");
|
||||
if (hostInstanceID == null) {
|
||||
hostInstanceID = oneHostInstanceID;
|
||||
FileOutputStream hostIdFileOut = null;
|
||||
try {
|
||||
hostIdFileOut = new FileOutputStream(hostIdFile);
|
||||
hostIdFileOut.write(oneHostInstanceID.getBytes());
|
||||
hostIdFileOut.flush();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(hostIdFileOut);
|
||||
}
|
||||
}
|
||||
|
||||
if (hostInstanceID.equals(oneHostInstanceID)) { // 只选择一个host下载
|
||||
String downloadLink = item.getString("DownloadLink");
|
||||
String fileName = StringUtils.substringBetween(downloadLink, "mysql-bin.", ".tar");
|
||||
if (StringUtils.isNotEmpty(fileName)) {
|
||||
File currentFile = new File(destDir, "mysql-bin." + fileName);
|
||||
if (currentFile.isFile() && currentFile.exists()) {
|
||||
// 检查一下文件是否存在,存在就就没必要下载了
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
HttpGet httpGet = new HttpGet(downloadLink);
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create()
|
||||
.setMaxConnPerRoute(50)
|
||||
.setMaxConnTotal(100)
|
||||
.build();
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(TIMEOUT)
|
||||
.setConnectionRequestTimeout(TIMEOUT)
|
||||
.setSocketTimeout(TIMEOUT)
|
||||
.build();
|
||||
httpGet.setConfig(requestConfig);
|
||||
HttpResponse response = httpClient.execute(httpGet);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != HttpResponseStatus.OK.code()) {
|
||||
throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:"
|
||||
+ statusCode);
|
||||
}
|
||||
saveFile(destDir, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveFile(File parentFile, HttpResponse response) throws IOException {
|
||||
InputStream is = response.getEntity().getContent();
|
||||
long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue());
|
||||
String fileName = response.getFirstHeader("Content-Disposition").getValue();
|
||||
fileName = StringUtils.substringAfter(fileName, "filename=");
|
||||
boolean isTar = StringUtils.endsWith(fileName, ".tar");
|
||||
FileUtils.forceMkdir(parentFile);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
if (isTar) {
|
||||
TarArchiveInputStream tais = new TarArchiveInputStream(is);
|
||||
TarArchiveEntry tarArchiveEntry = null;
|
||||
while ((tarArchiveEntry = tais.getNextTarEntry()) != null) {
|
||||
String name = tarArchiveEntry.getName();
|
||||
File tarFile = new File(parentFile, name);
|
||||
logger.info("start to download file " + tarFile.getName());
|
||||
BufferedOutputStream bos = null;
|
||||
try {
|
||||
bos = new BufferedOutputStream(new FileOutputStream(tarFile));
|
||||
int read = -1;
|
||||
byte[] buffer = new byte[1024];
|
||||
while ((read = tais.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, read);
|
||||
}
|
||||
logger.info("download file " + tarFile.getName() + " end!");
|
||||
} finally {
|
||||
IOUtils.closeQuietly(bos);
|
||||
}
|
||||
}
|
||||
tais.close();
|
||||
} else {
|
||||
File file = new File(parentFile, fileName);
|
||||
if (!file.isFile()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
long copySize = 0;
|
||||
long nextPrintProgress = 0;
|
||||
logger.info("start to download file " + file.getName());
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
copySize += len;
|
||||
long progress = copySize * 100 / totalSize;
|
||||
if (progress >= nextPrintProgress) {
|
||||
logger.info("download " + file.getName() + " progress : " + progress
|
||||
+ "% , download size : " + copySize + ", total size : " + totalSize);
|
||||
nextPrintProgress += 10;
|
||||
}
|
||||
}
|
||||
logger.info("download file " + file.getName() + " end!");
|
||||
fos.flush();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fos);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fos);
|
||||
}
|
||||
}
|
||||
|
||||
public static String describeBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
|
||||
Date endTime, int pageSize, int pageNumber) throws Exception {
|
||||
Map<String, String> paramMap = new HashMap<String, String>();
|
||||
paramMap.put("Action", "DescribeBinlogFiles");
|
||||
paramMap.put("DBInstanceId", dbInstanceId); // rds实例id
|
||||
paramMap.put("StartTime", formatIso8601Date(startTime));
|
||||
paramMap.put("EndTime", formatIso8601Date(endTime));
|
||||
paramMap.put("PageSize", String.valueOf(pageSize));
|
||||
paramMap.put("PageNumber", String.valueOf(pageNumber));
|
||||
return doRequest(url, paramMap, ak, sk);
|
||||
}
|
||||
|
||||
private static String doRequest(String domin, Map<String, String> param, String ak, String sk) throws Exception {
|
||||
param.put("AccessKeyId", ak);
|
||||
param.put("SignatureMethod", "HMAC-SHA1");
|
||||
param.put("SignatureVersion", SIGNATURE_VERSION);
|
||||
param.put("Version", API_VERSION);
|
||||
param.put("SignatureNonce", UUID.randomUUID().toString());
|
||||
param.put("Format", "JSON");
|
||||
param.put("Timestamp", formatIso8601Date(new Date()));
|
||||
String signStr = generate("POST", param, sk);
|
||||
param.put("Signature", signStr);
|
||||
String request = concatQueryString(param);
|
||||
String url = domin + "?" + request;
|
||||
String result = HttpHelper.post(url, null, Collections.EMPTY_MAP, TIMEOUT);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String concatQueryString(Map<String, String> parameters) throws UnsupportedEncodingException {
|
||||
if (null == parameters) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder urlBuilder = new StringBuilder("");
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String val = entry.getValue();
|
||||
urlBuilder.append(encode(key));
|
||||
if (val != null) {
|
||||
urlBuilder.append("=").append(encode(val));
|
||||
}
|
||||
urlBuilder.append("&");
|
||||
}
|
||||
int strIndex = urlBuilder.length();
|
||||
if (parameters.size() > 0) {
|
||||
urlBuilder.deleteCharAt(strIndex - 1);
|
||||
}
|
||||
return urlBuilder.toString();
|
||||
}
|
||||
|
||||
public static String encode(String value) throws UnsupportedEncodingException {
|
||||
return URLEncoder.encode(value, "UTF-8");
|
||||
}
|
||||
|
||||
private static String formatIso8601Date(Date date) {
|
||||
SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT);
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 HMAC-SHA1 签名方法对对encryptText进行签名
|
||||
*
|
||||
* @param encryptText 被签名的字符串
|
||||
* @param encryptKey 密钥
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
|
||||
byte[] data = encryptKey.getBytes(ENCODING);
|
||||
// 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
|
||||
SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
|
||||
// 生成一个指定 Mac 算法 的 Mac 对象
|
||||
Mac mac = Mac.getInstance(MAC_NAME);
|
||||
// 用给定密钥初始化 Mac 对象
|
||||
mac.init(secretKey);
|
||||
|
||||
byte[] text = encryptText.getBytes(ENCODING);
|
||||
// 完成 Mac 操作
|
||||
return mac.doFinal(text);
|
||||
}
|
||||
|
||||
private static String base64(byte input[]) throws UnsupportedEncodingException {
|
||||
return new String(Base64.encodeBase64(input), ENCODING);
|
||||
}
|
||||
|
||||
/** 对参数名称和参数值进行URL编码 **/
|
||||
public static String generate(String method, Map<String, String> parameter, String accessKeySecret)
|
||||
throws Exception {
|
||||
String signString = generateSignString(method, parameter);
|
||||
byte[] signBytes = HmacSHA1Encrypt(signString, accessKeySecret + "&");
|
||||
String signature = base64(signBytes);
|
||||
if ("POST".equals(method)) {
|
||||
return signature;
|
||||
}
|
||||
return URLEncoder.encode(signature, "UTF-8");
|
||||
}
|
||||
|
||||
private static String generateQueryString(TreeMap<String, String> treeMap) {
|
||||
StringBuilder canonicalizedQueryString = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (String key : treeMap.navigableKeySet()) {
|
||||
String value = treeMap.get(key);
|
||||
if (!first) {
|
||||
canonicalizedQueryString.append("&");
|
||||
}
|
||||
first = false;
|
||||
canonicalizedQueryString.append(percentEncode(key)).append("=").append(percentEncode(value));
|
||||
}
|
||||
return canonicalizedQueryString.toString();
|
||||
}
|
||||
|
||||
public static String generateSignString(String httpMethod, Map<String, String> parameter) throws IOException {
|
||||
TreeMap<String, String> sortParameter = new TreeMap<String, String>();
|
||||
sortParameter.putAll(parameter);
|
||||
String canonicalizedQueryString = generateQueryString(sortParameter);
|
||||
if (null == httpMethod) {
|
||||
throw new RuntimeException("httpMethod can not be empty");
|
||||
}
|
||||
/** 构造待签名的字符串* */
|
||||
StringBuilder stringToSign = new StringBuilder();
|
||||
stringToSign.append(httpMethod).append("&");
|
||||
stringToSign.append(percentEncode("/")).append("&");
|
||||
stringToSign.append(percentEncode(canonicalizedQueryString));
|
||||
return stringToSign.toString();
|
||||
}
|
||||
|
||||
public static String percentEncode(String value) {
|
||||
try {
|
||||
return value == null ? null : URLEncoder.encode(value, ENCODING)
|
||||
.replaceAll("\\+", "%20")
|
||||
.replaceAll("\\*", "%2A")
|
||||
.replaceAll("%7E", "~");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
/**
|
||||
* 基于rds binlog备份文件的复制
|
||||
*
|
||||
* @author agapple 2017年10月15日 下午1:27:36
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser {
|
||||
|
||||
private String url = "https://rds.aliyuncs.com/"; // openapi地址
|
||||
private String accesskey; // 云账号的ak
|
||||
private String secretkey; // 云账号sk
|
||||
private String instanceId; // rds实例id
|
||||
private Long startTime;
|
||||
private Long endTime;
|
||||
|
||||
public RdsLocalBinlogEventParser(){
|
||||
}
|
||||
|
||||
public void start() throws CanalParseException {
|
||||
try {
|
||||
Assert.notNull(startTime);
|
||||
Assert.notNull(accesskey);
|
||||
Assert.notNull(secretkey);
|
||||
Assert.notNull(instanceId);
|
||||
Assert.notNull(url);
|
||||
if (endTime == null) {
|
||||
endTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
RdsBinlogOpenApi.downloadBinlogFiles(url,
|
||||
accesskey,
|
||||
secretkey,
|
||||
instanceId,
|
||||
new Date(startTime),
|
||||
new Date(endTime),
|
||||
new File(directory));
|
||||
|
||||
// 更新一下时间戳
|
||||
masterPosition = new EntryPosition(startTime);
|
||||
} catch (Throwable e) {
|
||||
logger.error("download binlog failed", e);
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
|
||||
super.start();
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
if (StringUtils.isNotEmpty(url)) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAccesskey() {
|
||||
return accesskey;
|
||||
}
|
||||
|
||||
public void setAccesskey(String accesskey) {
|
||||
this.accesskey = accesskey;
|
||||
}
|
||||
|
||||
public String getSecretkey() {
|
||||
return secretkey;
|
||||
}
|
||||
|
||||
public void setSecretkey(String secretkey) {
|
||||
this.secretkey = secretkey;
|
||||
}
|
||||
|
||||
public String getInstanceId() {
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
public void setInstanceId(String instanceId) {
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
public Long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(Long startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public Long getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(Long endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
}
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.beanutils.BeanUtils;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import com.alibaba.druid.sql.repository.Schema;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DdlResult;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDO;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
/**
|
||||
* 基于db远程管理 see internal class: CanalTableMeta , ConsoleTableMetaTSDB
|
||||
*
|
||||
* @author agapple 2017年7月27日 下午10:47:55
|
||||
* @since 3.2.5
|
||||
*/
|
||||
public class DatabaseTableMeta implements TableMetaTSDB {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class);
|
||||
private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'");
|
||||
private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation");
|
||||
private static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L);
|
||||
private String destination;
|
||||
private MemoryTableMeta memoryTableMeta;
|
||||
private MysqlConnection connection; // 查询meta信息的链接
|
||||
private CanalEventFilter filter;
|
||||
private CanalEventFilter blackFilter;
|
||||
private EntryPosition lastPosition;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private MetaHistoryDAO metaHistoryDAO;
|
||||
private MetaSnapshotDAO metaSnapshotDAO;
|
||||
|
||||
public DatabaseTableMeta(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean init(final String destination) {
|
||||
this.destination = destination;
|
||||
this.memoryTableMeta = new MemoryTableMeta();
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r, "[scheduler-table-meta-snapshot]");
|
||||
}
|
||||
});
|
||||
|
||||
// 24小时生成一份snapshot
|
||||
scheduler.scheduleWithFixedDelay(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
MDC.put("destination", destination);
|
||||
applySnapshotToDB(lastPosition, false);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scheudle applySnapshotToDB faield", e);
|
||||
}
|
||||
}
|
||||
}, 24, 24, TimeUnit.HOURS);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableMeta find(String schema, String table) {
|
||||
synchronized (memoryTableMeta) {
|
||||
return memoryTableMeta.find(schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
|
||||
// 首先记录到内存结构
|
||||
synchronized (memoryTableMeta) {
|
||||
if (memoryTableMeta.apply(position, schema, ddl, extra)) {
|
||||
this.lastPosition = position;
|
||||
// 同步每次变更给远程做历史记录
|
||||
return applyHistoryToDB(position, schema, ddl, extra);
|
||||
} else {
|
||||
throw new RuntimeException("apply to memory is failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rollback(EntryPosition position) {
|
||||
// 每次rollback需要重新构建一次memory data
|
||||
this.memoryTableMeta = new MemoryTableMeta();
|
||||
boolean flag = false;
|
||||
EntryPosition snapshotPosition = buildMemFromSnapshot(position);
|
||||
if (snapshotPosition != null) {
|
||||
applyHistoryOnMemory(snapshotPosition, position);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
// 如果没有任何数据,则为初始化状态,全量dump一份关注的表
|
||||
if (dumpTableMeta(connection, filter)) {
|
||||
// 记录一下snapshot结果,方便快速恢复
|
||||
flag = applySnapshotToDB(INIT_POSITION, true);
|
||||
}
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> snapshot() {
|
||||
return memoryTableMeta.snapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化的时候dump一下表结构
|
||||
*/
|
||||
private boolean dumpTableMeta(MysqlConnection connection, final CanalEventFilter filter) {
|
||||
try {
|
||||
ResultSetPacket packet = connection.query("show databases");
|
||||
List<String> schemas = new ArrayList<String>();
|
||||
for (String schema : packet.getFieldValues()) {
|
||||
schemas.add(schema);
|
||||
}
|
||||
|
||||
for (String schema : schemas) {
|
||||
packet = connection.query("show tables from `" + schema + "`");
|
||||
List<String> tables = new ArrayList<String>();
|
||||
for (String table : packet.getFieldValues()) {
|
||||
String fullName = schema + "." + table;
|
||||
if (blackFilter == null || !blackFilter.filter(fullName)) {
|
||||
if (filter == null || filter.filter(fullName)) {
|
||||
tables.add(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tables.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringBuilder sql = new StringBuilder();
|
||||
for (String table : tables) {
|
||||
sql.append("show create table `" + schema + "`.`" + table + "`;");
|
||||
}
|
||||
|
||||
List<ResultSetPacket> packets = connection.queryMulti(sql.toString());
|
||||
for (ResultSetPacket onePacket : packets) {
|
||||
if (onePacket.getFieldValues().size() > 1) {
|
||||
String oneTableCreateSql = onePacket.getFieldValues().get(1);
|
||||
memoryTableMeta.apply(INIT_POSITION, schema, oneTableCreateSql, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyHistoryToDB(EntryPosition position, String schema, String ddl, String extra) {
|
||||
Map<String, String> content = new HashMap<String, String>();
|
||||
content.put("destination", destination);
|
||||
content.put("binlogFile", position.getJournalName());
|
||||
content.put("binlogOffest", String.valueOf(position.getPosition()));
|
||||
content.put("binlogMasterId", String.valueOf(position.getServerId()));
|
||||
content.put("binlogTimestamp", String.valueOf(position.getTimestamp()));
|
||||
content.put("useSchema", schema);
|
||||
if (content.isEmpty()) {
|
||||
throw new RuntimeException("apply failed caused by content is empty in applyHistoryToDB");
|
||||
}
|
||||
// 待补充
|
||||
List<DdlResult> ddlResults = DruidDdlParser.parse(ddl, schema);
|
||||
if (ddlResults.size() > 0) {
|
||||
DdlResult ddlResult = ddlResults.get(0);
|
||||
content.put("sqlSchema", ddlResult.getSchemaName());
|
||||
content.put("sqlTable", ddlResult.getTableName());
|
||||
content.put("sqlType", ddlResult.getType().name());
|
||||
content.put("sqlText", ddl);
|
||||
content.put("extra", extra);
|
||||
}
|
||||
|
||||
MetaHistoryDO metaDO = new MetaHistoryDO();
|
||||
try {
|
||||
BeanUtils.populate(metaDO, content);
|
||||
// 会建立唯一约束,解决:
|
||||
// 1. 重复的binlog file+offest
|
||||
// 2. 重复的masterId+timestamp
|
||||
metaHistoryDAO.insert(metaDO);
|
||||
} catch (Throwable e) {
|
||||
if (isUkDuplicateException(e)) {
|
||||
// 忽略掉重复的位点
|
||||
logger.warn("dup apply for sql : " + ddl);
|
||||
} else {
|
||||
throw new CanalParseException("apply history to db failed caused by : " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布数据到console上
|
||||
*/
|
||||
private boolean applySnapshotToDB(EntryPosition position, boolean init) {
|
||||
// 获取一份快照
|
||||
MemoryTableMeta tmpMemoryTableMeta = new MemoryTableMeta();
|
||||
Map<String, String> schemaDdls = null;
|
||||
synchronized (memoryTableMeta) {
|
||||
if (!init && position == null) {
|
||||
// 如果是持续构建,则识别一下是否有DDL变更过,如果没有就忽略了
|
||||
return false;
|
||||
}
|
||||
schemaDdls = memoryTableMeta.snapshot();
|
||||
for (Map.Entry<String, String> entry : schemaDdls.entrySet()) {
|
||||
tmpMemoryTableMeta.apply(position, entry.getKey(), entry.getValue(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// 基于临时内存对象进行对比
|
||||
boolean compareAll = true;
|
||||
for (Schema schema : tmpMemoryTableMeta.getRepository().getSchemas()) {
|
||||
for (String table : schema.showTables()) {
|
||||
if (!compareTableMetaDbAndMemory(connection, schema.getName(), table)) {
|
||||
compareAll = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compareAll) {
|
||||
Map<String, String> content = new HashMap<String, String>();
|
||||
content.put("destination", destination);
|
||||
content.put("binlogFile", position.getJournalName());
|
||||
content.put("binlogOffest", String.valueOf(position.getPosition()));
|
||||
content.put("binlogMasterId", String.valueOf(position.getServerId()));
|
||||
content.put("binlogTimestamp", String.valueOf(position.getTimestamp()));
|
||||
content.put("data", JSON.toJSONString(schemaDdls));
|
||||
if (content.isEmpty()) {
|
||||
throw new RuntimeException("apply failed caused by content is empty in applySnapshotToDB");
|
||||
}
|
||||
|
||||
MetaSnapshotDO snapshotDO = new MetaSnapshotDO();
|
||||
try {
|
||||
BeanUtils.populate(snapshotDO, content);
|
||||
metaSnapshotDAO.insert(snapshotDO);
|
||||
} catch (Throwable e) {
|
||||
if (isUkDuplicateException(e)) {
|
||||
// 忽略掉重复的位点
|
||||
logger.info("dup apply snapshot use position : " + position + " , just ignore");
|
||||
} else {
|
||||
throw new CanalParseException("apply failed caused by : " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
logger.error("compare failed , check log");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean compareTableMetaDbAndMemory(MysqlConnection connection, final String schema, final String table) {
|
||||
TableMeta tableMetaFromMem = memoryTableMeta.find(schema, table);
|
||||
|
||||
TableMeta tableMetaFromDB = new TableMeta();
|
||||
tableMetaFromDB.setSchema(schema);
|
||||
tableMetaFromDB.setTable(table);
|
||||
try {
|
||||
ResultSetPacket packet = connection.query("desc " + getFullName(schema, table));
|
||||
tableMetaFromDB.setFields(TableMetaCache.parserTableMeta(packet));
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().contains("errorNumber=1146")) {
|
||||
logger.error("table not exist in db , pls check :" + getFullName(schema, table) + " , mem : "
|
||||
+ tableMetaFromMem);
|
||||
return false;
|
||||
}
|
||||
throw new CanalParseException(e);
|
||||
}
|
||||
|
||||
boolean result = compareTableMeta(tableMetaFromMem, tableMetaFromDB);
|
||||
if (!result) {
|
||||
String createDDL = null;
|
||||
try {
|
||||
ResultSetPacket packet = connection.query("show create table " + getFullName(schema, table));
|
||||
if (packet.getFieldValues().size() > 1) {
|
||||
createDDL = packet.getFieldValues().get(1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
logger.error("pls submit github issue, show create table ddl:" + createDDL + " , compare failed . \n db : "
|
||||
+ tableMetaFromDB + " \n mem : " + tableMetaFromMem);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private EntryPosition buildMemFromSnapshot(EntryPosition position) {
|
||||
try {
|
||||
MetaSnapshotDO snapshotDO = metaSnapshotDAO.findByTimestamp(destination, position.getTimestamp());
|
||||
if (snapshotDO == null) {
|
||||
return null;
|
||||
}
|
||||
String binlogFile = snapshotDO.getBinlogFile();
|
||||
Long binlogOffest = snapshotDO.getBinlogOffest();
|
||||
String binlogMasterId = snapshotDO.getBinlogMasterId();
|
||||
Long binlogTimestamp = snapshotDO.getBinlogTimestamp();
|
||||
|
||||
EntryPosition snapshotPosition = new EntryPosition(binlogFile,
|
||||
binlogOffest == null ? 0l : binlogOffest,
|
||||
binlogTimestamp == null ? 0l : binlogTimestamp,
|
||||
Long.valueOf(binlogMasterId == null ? "-2" : binlogMasterId));
|
||||
// data存储为Map<String,String>,每个分库一套建表
|
||||
String sqlData = snapshotDO.getData();
|
||||
JSONObject jsonObj = JSON.parseObject(sqlData);
|
||||
for (Map.Entry entry : jsonObj.entrySet()) {
|
||||
// 记录到内存
|
||||
if (!memoryTableMeta.apply(snapshotPosition,
|
||||
ObjectUtils.toString(entry.getKey()),
|
||||
ObjectUtils.toString(entry.getValue()),
|
||||
null)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return snapshotPosition;
|
||||
} catch (Throwable e) {
|
||||
throw new CanalParseException("apply failed caused by : " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyHistoryOnMemory(EntryPosition position, EntryPosition rollbackPosition) {
|
||||
try {
|
||||
List<MetaHistoryDO> metaHistoryDOList = metaHistoryDAO.findByTimestamp(destination,
|
||||
position.getTimestamp(),
|
||||
rollbackPosition.getTimestamp());
|
||||
if (metaHistoryDOList == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (MetaHistoryDO metaHistoryDO : metaHistoryDOList) {
|
||||
String binlogFile = metaHistoryDO.getBinlogFile();
|
||||
Long binlogOffest = metaHistoryDO.getBinlogOffest();
|
||||
String binlogMasterId = metaHistoryDO.getBinlogMasterId();
|
||||
Long binlogTimestamp = metaHistoryDO.getBinlogTimestamp();
|
||||
String useSchema = metaHistoryDO.getUseSchema();
|
||||
String sqlData = metaHistoryDO.getSqlText();
|
||||
EntryPosition snapshotPosition = new EntryPosition(binlogFile,
|
||||
binlogOffest == null ? 0L : binlogOffest,
|
||||
binlogTimestamp == null ? 0L : binlogTimestamp,
|
||||
Long.valueOf(binlogMasterId == null ? "-2" : binlogMasterId));
|
||||
|
||||
// 如果是同一秒内,对比一下history的位点,如果比期望的位点要大,忽略之
|
||||
if (snapshotPosition.getTimestamp() > rollbackPosition.getTimestamp()) {
|
||||
continue;
|
||||
} else if (rollbackPosition.getServerId() == snapshotPosition.getServerId()
|
||||
&& snapshotPosition.compareTo(rollbackPosition) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 记录到内存
|
||||
if (!memoryTableMeta.apply(snapshotPosition, useSchema, sqlData, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return metaHistoryDOList.size() > 0;
|
||||
} catch (Throwable e) {
|
||||
throw new CanalParseException("apply failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getFullName(String schema, String table) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
return builder.append('`')
|
||||
.append(schema)
|
||||
.append('`')
|
||||
.append('.')
|
||||
.append('`')
|
||||
.append(table)
|
||||
.append('`')
|
||||
.toString();
|
||||
}
|
||||
|
||||
private boolean compareTableMeta(TableMeta source, TableMeta target) {
|
||||
if (!StringUtils.equalsIgnoreCase(source.getSchema(), target.getSchema())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StringUtils.equalsIgnoreCase(source.getTable(), target.getTable())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<FieldMeta> sourceFields = source.getFields();
|
||||
List<FieldMeta> targetFields = target.getFields();
|
||||
if (sourceFields.size() != targetFields.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sourceFields.size(); i++) {
|
||||
FieldMeta sourceField = sourceFields.get(i);
|
||||
FieldMeta targetField = targetFields.get(i);
|
||||
if (!StringUtils.equalsIgnoreCase(sourceField.getColumnName(), targetField.getColumnName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StringUtils.equalsIgnoreCase(sourceField.getColumnType(), targetField.getColumnType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StringUtils.equalsIgnoreCase(sourceField.getDefaultValue(), targetField.getDefaultValue())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceField.isNullable() != targetField.isNullable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceField.isKey() != targetField.isKey()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setConnection(MysqlConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public void setFilter(CanalEventFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public MetaHistoryDAO getMetaHistoryDAO() {
|
||||
return metaHistoryDAO;
|
||||
}
|
||||
|
||||
public void setMetaHistoryDAO(MetaHistoryDAO metaHistoryDAO) {
|
||||
this.metaHistoryDAO = metaHistoryDAO;
|
||||
}
|
||||
|
||||
public MetaSnapshotDAO getMetaSnapshotDAO() {
|
||||
return metaSnapshotDAO;
|
||||
}
|
||||
|
||||
public void setMetaSnapshotDAO(MetaSnapshotDAO metaSnapshotDAO) {
|
||||
this.metaSnapshotDAO = metaSnapshotDAO;
|
||||
}
|
||||
|
||||
public void setBlackFilter(CanalEventFilter blackFilter) {
|
||||
this.blackFilter = blackFilter;
|
||||
}
|
||||
|
||||
public MysqlConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public boolean isUkDuplicateException(Throwable t) {
|
||||
if (pattern.matcher(t.getMessage()).find() || h2Pattern.matcher(t.getMessage()).find()) {
|
||||
// 违反外键约束时也抛出这种异常,所以这里还要判断包含字符串Duplicate entry
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.druid.sql.ast.SQLDataType;
|
||||
import com.alibaba.druid.sql.ast.SQLDataTypeImpl;
|
||||
import com.alibaba.druid.sql.ast.SQLExpr;
|
||||
import com.alibaba.druid.sql.ast.SQLStatement;
|
||||
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
|
||||
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
|
||||
import com.alibaba.druid.sql.ast.expr.SQLNullExpr;
|
||||
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLColumnConstraint;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLColumnPrimaryKey;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLNullConstraint;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
|
||||
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
|
||||
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey;
|
||||
import com.alibaba.druid.sql.repository.Schema;
|
||||
import com.alibaba.druid.sql.repository.SchemaObject;
|
||||
import com.alibaba.druid.sql.repository.SchemaRepository;
|
||||
import com.alibaba.druid.util.JdbcConstants;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
||||
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
/**
|
||||
* 基于DDL维护的内存表结构
|
||||
*
|
||||
* @author agapple 2017年7月27日 下午4:19:40
|
||||
* @since 3.2.5
|
||||
*/
|
||||
public class MemoryTableMeta implements TableMetaTSDB {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MemoryTableMeta.class);
|
||||
private Map<List<String>, TableMeta> tableMetas = new ConcurrentHashMap<List<String>, TableMeta>();
|
||||
private SchemaRepository repository = new SchemaRepository(JdbcConstants.MYSQL);
|
||||
|
||||
public MemoryTableMeta(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean init(String destination) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
|
||||
tableMetas.clear();
|
||||
synchronized (this) {
|
||||
if (StringUtils.isNotEmpty(schema)) {
|
||||
repository.setDefaultSchema(schema);
|
||||
}
|
||||
|
||||
try {
|
||||
// druid暂时flush privileges语法解析有问题
|
||||
if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")) {
|
||||
repository.console(ddl);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.warn("parse faield : " + ddl, e);
|
||||
}
|
||||
}
|
||||
|
||||
// TableMeta meta = find("tddl5_00", "ab");
|
||||
// if (meta != null) {
|
||||
// repository.setDefaultSchema("tddl5_00");
|
||||
// System.out.println(repository.console("show create table tddl5_00.ab"));
|
||||
// System.out.println(repository.console("show columns from tddl5_00.ab"));
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableMeta find(String schema, String table) {
|
||||
List<String> keys = Arrays.asList(schema, table);
|
||||
TableMeta tableMeta = tableMetas.get(keys);
|
||||
if (tableMeta == null) {
|
||||
synchronized (this) {
|
||||
tableMeta = tableMetas.get(keys);
|
||||
if (tableMeta == null) {
|
||||
Schema schemaRep = repository.findSchema(schema);
|
||||
if (schema == null) {
|
||||
return null;
|
||||
}
|
||||
SchemaObject data = schemaRep.findTable(table);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
SQLStatement statement = data.getStatement();
|
||||
if (statement == null) {
|
||||
return null;
|
||||
}
|
||||
if (statement instanceof SQLCreateTableStatement) {
|
||||
tableMeta = parse((SQLCreateTableStatement) statement);
|
||||
}
|
||||
if (tableMeta != null) {
|
||||
if (table != null) {
|
||||
tableMeta.setTable(table);
|
||||
}
|
||||
if (schema != null) {
|
||||
tableMeta.setSchema(schema);
|
||||
}
|
||||
|
||||
tableMetas.put(keys, tableMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tableMeta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rollback(EntryPosition position) {
|
||||
throw new RuntimeException("not support for memory");
|
||||
}
|
||||
|
||||
public Map<String, String> snapshot() {
|
||||
Map<String, String> schemaDdls = new HashMap<String, String>();
|
||||
for (Schema schema : repository.getSchemas()) {
|
||||
StringBuffer data = new StringBuffer(4 * 1024);
|
||||
for (String table : schema.showTables()) {
|
||||
SchemaObject schemaObject = schema.findTable(table);
|
||||
schemaObject.getStatement().output(data);
|
||||
data.append("; \n");
|
||||
}
|
||||
schemaDdls.put(schema.getName(), data.toString());
|
||||
}
|
||||
|
||||
return schemaDdls;
|
||||
}
|
||||
|
||||
private TableMeta parse(SQLCreateTableStatement statement) {
|
||||
int size = statement.getTableElementList().size();
|
||||
if (size > 0) {
|
||||
TableMeta tableMeta = new TableMeta();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
SQLTableElement element = statement.getTableElementList().get(i);
|
||||
processTableElement(element, tableMeta);
|
||||
}
|
||||
return tableMeta;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processTableElement(SQLTableElement element, TableMeta tableMeta) {
|
||||
if (element instanceof SQLColumnDefinition) {
|
||||
FieldMeta fieldMeta = new FieldMeta();
|
||||
SQLColumnDefinition column = (SQLColumnDefinition) element;
|
||||
String name = getSqlName(column.getName());
|
||||
// String charset = getSqlName(column.getCharsetExpr());
|
||||
SQLDataType dataType = column.getDataType();
|
||||
String dataTypStr = dataType.getName();
|
||||
if (dataType.getArguments().size() > 0) {
|
||||
dataTypStr += "(";
|
||||
for (int i = 0; i < column.getDataType().getArguments().size(); i++) {
|
||||
if (i != 0) {
|
||||
dataTypStr += ",";
|
||||
}
|
||||
SQLExpr arg = column.getDataType().getArguments().get(i);
|
||||
dataTypStr += arg.toString();
|
||||
}
|
||||
dataTypStr += ")";
|
||||
}
|
||||
|
||||
if (dataType instanceof SQLDataTypeImpl) {
|
||||
SQLDataTypeImpl dataTypeImpl = (SQLDataTypeImpl) dataType;
|
||||
if (dataTypeImpl.isUnsigned()) {
|
||||
dataTypStr += " unsigned";
|
||||
}
|
||||
|
||||
if (dataTypeImpl.isZerofill()) {
|
||||
dataTypStr += " zerofill";
|
||||
}
|
||||
}
|
||||
|
||||
if (column.getDefaultExpr() == null || column.getDefaultExpr() instanceof SQLNullExpr) {
|
||||
fieldMeta.setDefaultValue(null);
|
||||
} else {
|
||||
fieldMeta.setDefaultValue(DruidDdlParser.unescapeQuotaName(getSqlName(column.getDefaultExpr())));
|
||||
}
|
||||
|
||||
fieldMeta.setColumnName(name);
|
||||
fieldMeta.setColumnType(dataTypStr);
|
||||
fieldMeta.setNullable(true);
|
||||
List<SQLColumnConstraint> constraints = column.getConstraints();
|
||||
for (SQLColumnConstraint constraint : constraints) {
|
||||
if (constraint instanceof SQLNotNullConstraint) {
|
||||
fieldMeta.setNullable(false);
|
||||
} else if (constraint instanceof SQLNullConstraint) {
|
||||
fieldMeta.setNullable(true);
|
||||
} else if (constraint instanceof SQLColumnPrimaryKey) {
|
||||
fieldMeta.setKey(true);
|
||||
}
|
||||
}
|
||||
tableMeta.addFieldMeta(fieldMeta);
|
||||
} else if (element instanceof MySqlPrimaryKey) {
|
||||
MySqlPrimaryKey column = (MySqlPrimaryKey) element;
|
||||
List<SQLSelectOrderByItem> pks = column.getColumns();
|
||||
for (SQLSelectOrderByItem pk : pks) {
|
||||
String name = getSqlName(pk.getExpr());
|
||||
FieldMeta field = tableMeta.getFieldMetaByName(name);
|
||||
field.setKey(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getSqlName(SQLExpr sqlName) {
|
||||
if (sqlName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sqlName instanceof SQLPropertyExpr) {
|
||||
SQLIdentifierExpr owner = (SQLIdentifierExpr) ((SQLPropertyExpr) sqlName).getOwner();
|
||||
return DruidDdlParser.unescapeName(owner.getName()) + "."
|
||||
+ DruidDdlParser.unescapeName(((SQLPropertyExpr) sqlName).getName());
|
||||
} else if (sqlName instanceof SQLIdentifierExpr) {
|
||||
return DruidDdlParser.unescapeName(((SQLIdentifierExpr) sqlName).getName());
|
||||
} else if (sqlName instanceof SQLCharExpr) {
|
||||
return ((SQLCharExpr) sqlName).getText();
|
||||
} else {
|
||||
return sqlName.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SchemaRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||
|
||||
/**
|
||||
* 表结构的时间序列存储
|
||||
*
|
||||
* @author agapple 2017年7月27日 下午4:06:30
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public interface TableMetaTSDB {
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public boolean init(String destination);
|
||||
|
||||
/**
|
||||
* 获取当前的表结构
|
||||
*/
|
||||
public TableMeta find(String schema, String table);
|
||||
|
||||
/**
|
||||
* 添加ddl到时间序列库中
|
||||
*/
|
||||
public boolean apply(EntryPosition position, String schema, String ddl, String extra);
|
||||
|
||||
/**
|
||||
* 回滚到指定位点的表结构
|
||||
*/
|
||||
public boolean rollback(EntryPosition position);
|
||||
|
||||
/**
|
||||
* 生成快照内容
|
||||
*/
|
||||
public Map<String/* schema */, String> snapshot();
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年10月11日 下午8:45:40
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class TableMetaTSDBBuilder {
|
||||
|
||||
protected final static Logger logger = LoggerFactory.getLogger(TableMetaTSDBBuilder.class);
|
||||
private static ConcurrentMap<String, ClassPathXmlApplicationContext> contexts = Maps.newConcurrentMap();
|
||||
|
||||
/**
|
||||
* 代理一下tableMetaTSDB的获取,使用隔离的spring定义
|
||||
*/
|
||||
public static TableMetaTSDB build(String destination, String springXml) {
|
||||
if (StringUtils.isNotEmpty(springXml)) {
|
||||
ClassPathXmlApplicationContext applicationContext = contexts.get(destination);
|
||||
if (applicationContext == null) {
|
||||
synchronized (contexts) {
|
||||
if (applicationContext == null) {
|
||||
applicationContext = new ClassPathXmlApplicationContext(springXml);
|
||||
contexts.put(destination, applicationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
TableMetaTSDB tableMetaTSDB = (TableMetaTSDB) applicationContext.getBean("tableMetaTSDB");
|
||||
tableMetaTSDB.init(destination);
|
||||
logger.info("{} init TableMetaTSDB with {}", destination, springXml);
|
||||
return tableMetaTSDB;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void destory(String destination) {
|
||||
ClassPathXmlApplicationContext context = contexts.remove(destination);
|
||||
if (context != null) {
|
||||
logger.info("{} destory TableMetaTSDB", destination);
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年10月14日 上午1:05:22
|
||||
* @since 1.0.25
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class MetaBaseDAO extends SqlMapClientDaoSupport {
|
||||
|
||||
protected boolean isH2 = false;
|
||||
|
||||
protected void initTable(String tableName) throws Exception {
|
||||
Connection conn = null;
|
||||
InputStream input = null;
|
||||
try {
|
||||
DataSource dataSource = getDataSource();
|
||||
conn = dataSource.getConnection();
|
||||
String name = "mysql";
|
||||
isH2 = isH2(conn);
|
||||
if (isH2) {
|
||||
name = "h2";
|
||||
}
|
||||
input = Thread.currentThread()
|
||||
.getContextClassLoader()
|
||||
.getResourceAsStream("ddl/" + name + "/" + tableName + ".sql");
|
||||
if (input == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String sql = StringUtils.join(IOUtils.readLines(input), "\n");
|
||||
Statement stmt = conn.createStatement();
|
||||
stmt.execute(sql);
|
||||
stmt.close();
|
||||
} catch (Throwable e) {
|
||||
logger.warn("init " + tableName + " failed", e);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(input);
|
||||
if (conn != null) {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isH2(Connection conn) throws SQLException {
|
||||
String product = conn.getMetaData().getDatabaseProductName();
|
||||
return StringUtils.containsIgnoreCase(product, "H2");
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* canal数据的存储
|
||||
*
|
||||
* @author wanshao 2017年7月27日 下午10:51:55
|
||||
* @since 3.2.5
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class MetaHistoryDAO extends MetaBaseDAO {
|
||||
|
||||
public Long insert(MetaHistoryDO metaDO) {
|
||||
return (Long) getSqlMapClientTemplate().insert("meta_history.insert", metaDO);
|
||||
}
|
||||
|
||||
public List<MetaHistoryDO> findByTimestamp(String destination, Long snapshotTimestamp, Long timestamp) {
|
||||
HashMap params = Maps.newHashMapWithExpectedSize(2);
|
||||
params.put("destination", destination);
|
||||
params.put("snapshotTimestamp", snapshotTimestamp == null ? 0L : snapshotTimestamp);
|
||||
params.put("timestamp", timestamp == null ? 0L : timestamp);
|
||||
return (List<MetaHistoryDO>) getSqlMapClientTemplate().queryForList("meta_history.findByTimestamp", params);
|
||||
}
|
||||
|
||||
public Integer deleteByName(String destination) {
|
||||
HashMap params = Maps.newHashMapWithExpectedSize(2);
|
||||
params.put("destination", destination);
|
||||
return getSqlMapClientTemplate().delete("meta_history.deleteByName", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除interval秒之前的数据
|
||||
*/
|
||||
public Integer deleteByGmtModified(int interval) {
|
||||
HashMap params = Maps.newHashMapWithExpectedSize(2);
|
||||
long timestamp = System.currentTimeMillis() - interval * 1000;
|
||||
Date date = new Date(timestamp);
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
params.put("timestamp", format.format(date));
|
||||
return getSqlMapClientTemplate().delete("meta_history.deleteByGmtModified", params);
|
||||
}
|
||||
|
||||
protected void initDao() throws Exception {
|
||||
initTable("meta_history");
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年7月27日 下午11:09:41
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class MetaHistoryDO {
|
||||
|
||||
private Long id;
|
||||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
private String destination;
|
||||
private String binlogFile;
|
||||
private Long binlogOffest;
|
||||
private String binlogMasterId;
|
||||
private Long binlogTimestamp;
|
||||
private String useSchema;
|
||||
private String sqlSchema;
|
||||
private String sqlTable;
|
||||
private String sqlText;
|
||||
private String sqlType;
|
||||
private String extra;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getGmtCreate() {
|
||||
return gmtCreate;
|
||||
}
|
||||
|
||||
public void setGmtCreate(Date gmtCreate) {
|
||||
this.gmtCreate = gmtCreate;
|
||||
}
|
||||
|
||||
public Date getGmtModified() {
|
||||
return gmtModified;
|
||||
}
|
||||
|
||||
public void setGmtModified(Date gmtModified) {
|
||||
this.gmtModified = gmtModified;
|
||||
}
|
||||
|
||||
public String getBinlogFile() {
|
||||
return binlogFile;
|
||||
}
|
||||
|
||||
public void setBinlogFile(String binlogFile) {
|
||||
this.binlogFile = binlogFile;
|
||||
}
|
||||
|
||||
public Long getBinlogOffest() {
|
||||
return binlogOffest;
|
||||
}
|
||||
|
||||
public void setBinlogOffest(Long binlogOffest) {
|
||||
this.binlogOffest = binlogOffest;
|
||||
}
|
||||
|
||||
public String getBinlogMasterId() {
|
||||
return binlogMasterId;
|
||||
}
|
||||
|
||||
public void setBinlogMasterId(String binlogMasterId) {
|
||||
this.binlogMasterId = binlogMasterId;
|
||||
}
|
||||
|
||||
public Long getBinlogTimestamp() {
|
||||
return binlogTimestamp;
|
||||
}
|
||||
|
||||
public void setBinlogTimestamp(Long binlogTimestamp) {
|
||||
this.binlogTimestamp = binlogTimestamp;
|
||||
}
|
||||
|
||||
public String getUseSchema() {
|
||||
return useSchema;
|
||||
}
|
||||
|
||||
public void setUseSchema(String useSchema) {
|
||||
this.useSchema = useSchema;
|
||||
}
|
||||
|
||||
|
||||
public String getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public void setExtra(String extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public String getSqlSchema() {
|
||||
return sqlSchema;
|
||||
}
|
||||
|
||||
public void setSqlSchema(String sqlSchema) {
|
||||
this.sqlSchema = sqlSchema;
|
||||
}
|
||||
|
||||
public String getSqlTable() {
|
||||
return sqlTable;
|
||||
}
|
||||
|
||||
public void setSqlTable(String sqlTable) {
|
||||
this.sqlTable = sqlTable;
|
||||
}
|
||||
|
||||
public String getSqlText() {
|
||||
return sqlText;
|
||||
}
|
||||
|
||||
public void setSqlText(String sqlText) {
|
||||
this.sqlText = sqlText;
|
||||
}
|
||||
|
||||
public String getSqlType() {
|
||||
return sqlType;
|
||||
}
|
||||
|
||||
public void setSqlType(String sqlType) {
|
||||
this.sqlType = sqlType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaHistoryDO [id=" + id + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified
|
||||
+ ", destination=" + destination + ", binlogFile=" + binlogFile + ", binlogOffest=" + binlogOffest
|
||||
+ ", binlogMasterId=" + binlogMasterId + ", binlogTimestamp=" + binlogTimestamp + ", useSchema="
|
||||
+ useSchema + ", sqlSchema=" + sqlSchema + ", sqlTable=" + sqlTable + ", sqlText=" + sqlText
|
||||
+ ", sqlType=" + sqlType + ", extra=" + extra + "]";
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* canal数据的存储
|
||||
*
|
||||
* @author wanshao 2017年7月27日 下午10:51:55
|
||||
* @since 3.2.5
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class MetaSnapshotDAO extends MetaBaseDAO {
|
||||
|
||||
public Long insert(MetaSnapshotDO snapshotDO) {
|
||||
return (Long) getSqlMapClientTemplate().insert("meta_snapshot.insert", snapshotDO);
|
||||
}
|
||||
|
||||
public Long update(MetaSnapshotDO snapshotDO) {
|
||||
return (Long) getSqlMapClientTemplate().insert("meta_snapshot.update", snapshotDO);
|
||||
}
|
||||
|
||||
public MetaSnapshotDO findByTimestamp(String destination, Long timestamp) {
|
||||
HashMap params = Maps.newHashMapWithExpectedSize(2);
|
||||
params.put("timestamp", timestamp == null ? 0L : timestamp);
|
||||
params.put("destination", destination);
|
||||
|
||||
return (MetaSnapshotDO) getSqlMapClientTemplate().queryForObject("meta_snapshot.findByTimestamp", params);
|
||||
}
|
||||
|
||||
public Integer deleteByName(String destination) {
|
||||
HashMap params = Maps.newHashMapWithExpectedSize(2);
|
||||
params.put("destination", destination);
|
||||
return getSqlMapClientTemplate().delete("meta_snapshot.deleteByName", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除interval秒之前的数据
|
||||
*/
|
||||
public Integer deleteByGmtModified(int interval) {
|
||||
HashMap params = Maps.newHashMapWithExpectedSize(2);
|
||||
long timestamp = System.currentTimeMillis() - interval * 1000;
|
||||
Date date = new Date(timestamp);
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
params.put("timestamp", format.format(date));
|
||||
return getSqlMapClientTemplate().delete("meta_snapshot.deleteByGmtModified", params);
|
||||
}
|
||||
|
||||
protected void initDao() throws Exception {
|
||||
initTable("meta_snapshot");
|
||||
}
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author agapple 2017年7月27日 下午11:09:41
|
||||
* @since 1.0.25
|
||||
*/
|
||||
public class MetaSnapshotDO {
|
||||
|
||||
private Long id;
|
||||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
private String destination;
|
||||
private String binlogFile;
|
||||
private Long binlogOffest;
|
||||
private String binlogMasterId;
|
||||
private Long binlogTimestamp;
|
||||
private String data;
|
||||
private String extra;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getGmtCreate() {
|
||||
return gmtCreate;
|
||||
}
|
||||
|
||||
public void setGmtCreate(Date gmtCreate) {
|
||||
this.gmtCreate = gmtCreate;
|
||||
}
|
||||
|
||||
public Date getGmtModified() {
|
||||
return gmtModified;
|
||||
}
|
||||
|
||||
public void setGmtModified(Date gmtModified) {
|
||||
this.gmtModified = gmtModified;
|
||||
}
|
||||
|
||||
public String getBinlogFile() {
|
||||
return binlogFile;
|
||||
}
|
||||
|
||||
public void setBinlogFile(String binlogFile) {
|
||||
this.binlogFile = binlogFile;
|
||||
}
|
||||
|
||||
public Long getBinlogOffest() {
|
||||
return binlogOffest;
|
||||
}
|
||||
|
||||
public void setBinlogOffest(Long binlogOffest) {
|
||||
this.binlogOffest = binlogOffest;
|
||||
}
|
||||
|
||||
public String getBinlogMasterId() {
|
||||
return binlogMasterId;
|
||||
}
|
||||
|
||||
public void setBinlogMasterId(String binlogMasterId) {
|
||||
this.binlogMasterId = binlogMasterId;
|
||||
}
|
||||
|
||||
public Long getBinlogTimestamp() {
|
||||
return binlogTimestamp;
|
||||
}
|
||||
|
||||
public void setBinlogTimestamp(Long binlogTimestamp) {
|
||||
this.binlogTimestamp = binlogTimestamp;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public void setExtra(String extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaSnapshotDO [id=" + id + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified
|
||||
+ ", destination=" + destination + ", binlogFile=" + binlogFile + ", binlogOffest=" + binlogOffest
|
||||
+ ", binlogMasterId=" + binlogMasterId + ", binlogTimestamp=" + binlogTimestamp + ", data=" + data
|
||||
+ ", extra=" + extra + "]";
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.otter.canal.parse.index;
|
||||
|
||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||
|
||||
/**
|
||||
* Created by yinxiu on 17/3/17. Email: marklin.hz@gmail.com
|
||||
*/
|
||||
public abstract class AbstractLogPositionManager extends AbstractCanalLifeCycle implements CanalLogPositionManager {
|
||||
}
|
||||
+2
-4
@@ -5,14 +5,12 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||
|
||||
/**
|
||||
* 接口组合
|
||||
*
|
||||
* @author jianghang 2012-7-7 上午10:02:02
|
||||
* @version 1.0.0
|
||||
* Created by yinxiu on 17/3/17. Email: marklin.hz@gmail.com
|
||||
*/
|
||||
public interface CanalLogPositionManager extends CanalLifeCycle {
|
||||
|
||||
LogPosition getLatestIndexBy(String destination);
|
||||
|
||||
void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException;
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user