mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
59 Commits
canal-1.0.
...
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 | ||
|
|
a78622ae02 | ||
|
|
c0f2d274d7 |
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -107,9 +106,10 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when subscribing from server:{}\n{}",
|
logger.warn(String.format(
|
||||||
currentConnector.getAddress(),
|
"something goes wrong when subscribing from server: %s",
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -126,9 +126,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.unsubscribe();
|
currentConnector.unsubscribe();
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when unsubscribing from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -144,9 +143,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
Message msg = currentConnector.get(batchSize);
|
Message msg = currentConnector.get(batchSize);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
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);
|
Message msg = currentConnector.get(batchSize, timeout, unit);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -180,9 +177,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
Message msg = currentConnector.getWithoutAck(batchSize);
|
Message msg = currentConnector.getWithoutAck(batchSize);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
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);
|
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -216,9 +211,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.rollback(batchId);
|
currentConnector.rollback(batchId);
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -234,9 +228,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.rollback();
|
currentConnector.rollback();
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -253,9 +246,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.ack(batchId);
|
currentConnector.ack(batchId);
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when acking data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
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.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.Message;
|
||||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
* 基于{@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 readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||||
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||||
private SocketChannel channel;
|
private SocketChannel channel;
|
||||||
|
private ReadableByteChannel readableChannel;
|
||||||
|
private WritableByteChannel writableChannel;
|
||||||
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
||||||
private ClientIdentity clientIdentity;
|
private ClientIdentity clientIdentity;
|
||||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||||
@@ -130,7 +135,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
address = getNextAddress();
|
address = getNextAddress();
|
||||||
}
|
}
|
||||||
channel.connect(address);
|
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) {
|
if (p.getVersion() != 1) {
|
||||||
throw new CanalClientException("unsupported version at this client.");
|
throw new CanalClientException("unsupported version at this client.");
|
||||||
}
|
}
|
||||||
@@ -144,17 +151,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
//
|
//
|
||||||
ClientAuth ca = ClientAuth.newBuilder()
|
ClientAuth ca = ClientAuth.newBuilder()
|
||||||
.setUsername(username != null ? username : "")
|
.setUsername(username != null ? username : "")
|
||||||
|
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||||
.setNetReadTimeout(soTimeout)
|
.setNetReadTimeout(soTimeout)
|
||||||
.setNetWriteTimeout(soTimeout)
|
.setNetWriteTimeout(soTimeout)
|
||||||
.build();
|
.build();
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
.setBody(ca.toByteString())
|
||||||
.setBody(ca.toByteString())
|
.build()
|
||||||
.build()
|
.toByteArray());
|
||||||
.toByteArray());
|
|
||||||
//
|
//
|
||||||
Packet ack = Packet.parseFrom(readNextPacket(channel));
|
Packet ack = Packet.parseFrom(readNextPacket());
|
||||||
if (ack.getType() != PacketType.ACK) {
|
if (ack.getType() != PacketType.ACK) {
|
||||||
throw new CanalClientException("unexpected packet type when ack is expected");
|
throw new CanalClientException("unexpected packet type when ack is expected");
|
||||||
}
|
}
|
||||||
@@ -173,16 +180,28 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doDisconnnect() throws CanalClientException {
|
private void doDisconnnect() throws CanalClientException {
|
||||||
|
if (readableChannel != null) {
|
||||||
|
quietlyClose(readableChannel);
|
||||||
|
readableChannel = null;
|
||||||
|
}
|
||||||
|
if (writableChannel != null) {
|
||||||
|
quietlyClose(writableChannel);
|
||||||
|
writableChannel = null;
|
||||||
|
}
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
try {
|
quietlyClose(channel);
|
||||||
channel.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
|
||||||
}
|
|
||||||
channel = null;
|
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 {
|
public void subscribe() throws CanalClientException {
|
||||||
subscribe(""); // 传递空字符即可
|
subscribe(""); // 传递空字符即可
|
||||||
}
|
}
|
||||||
@@ -190,19 +209,18 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
public void subscribe(String filter) throws CanalClientException {
|
public void subscribe(String filter) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
.setType(PacketType.SUBSCRIPTION)
|
||||||
.setType(PacketType.SUBSCRIPTION)
|
.setBody(Sub.newBuilder()
|
||||||
.setBody(Sub.newBuilder()
|
.setDestination(clientIdentity.getDestination())
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
.setFilter(filter != null ? filter : "")
|
||||||
.setFilter(filter != null ? filter : "")
|
|
||||||
.build()
|
|
||||||
.toByteString())
|
|
||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteString())
|
||||||
|
.build()
|
||||||
|
.toByteArray());
|
||||||
//
|
//
|
||||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
Packet p = Packet.parseFrom(readNextPacket());
|
||||||
Ack ack = Ack.parseFrom(p.getBody());
|
Ack ack = Ack.parseFrom(p.getBody());
|
||||||
if (ack.getErrorCode() > 0) {
|
if (ack.getErrorCode() > 0) {
|
||||||
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
||||||
@@ -217,18 +235,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
public void unsubscribe() throws CanalClientException {
|
public void unsubscribe() throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
.setType(PacketType.UNSUBSCRIPTION)
|
||||||
.setType(PacketType.UNSUBSCRIPTION)
|
.setBody(Unsub.newBuilder()
|
||||||
.setBody(Unsub.newBuilder()
|
.setDestination(clientIdentity.getDestination())
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
|
||||||
.build()
|
|
||||||
.toByteString())
|
|
||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteString())
|
||||||
|
.build()
|
||||||
|
.toByteArray());
|
||||||
//
|
//
|
||||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
Packet p = Packet.parseFrom(readNextPacket());
|
||||||
Ack ack = Ack.parseFrom(p.getBody());
|
Ack ack = Ack.parseFrom(p.getBody());
|
||||||
if (ack.getErrorCode() > 0) {
|
if (ack.getErrorCode() > 0) {
|
||||||
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
||||||
@@ -261,29 +278,27 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
unit = TimeUnit.MILLISECONDS;
|
unit = TimeUnit.MILLISECONDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
.setType(PacketType.GET)
|
||||||
.setType(PacketType.GET)
|
.setBody(Get.newBuilder()
|
||||||
.setBody(Get.newBuilder()
|
.setAutoAck(false)
|
||||||
.setAutoAck(false)
|
.setDestination(clientIdentity.getDestination())
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
.setFetchSize(size)
|
||||||
.setFetchSize(size)
|
.setTimeout(time)
|
||||||
.setTimeout(time)
|
.setUnit(unit.ordinal())
|
||||||
.setUnit(unit.ordinal())
|
|
||||||
.build()
|
|
||||||
.toByteString())
|
|
||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteString())
|
||||||
|
.build()
|
||||||
|
.toByteArray());
|
||||||
return receiveMessages();
|
return receiveMessages();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CanalClientException(e);
|
throw new CanalClientException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Message receiveMessages() throws InvalidProtocolBufferException, IOException {
|
private Message receiveMessages() throws IOException {
|
||||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
Packet p = Packet.parseFrom(readNextPacket());
|
||||||
switch (p.getType()) {
|
switch (p.getType()) {
|
||||||
case MESSAGES: {
|
case MESSAGES: {
|
||||||
if (!p.getCompression().equals(Compression.NONE)) {
|
if (!p.getCompression().equals(Compression.NONE)) {
|
||||||
@@ -315,7 +330,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.setBatchId(batchId)
|
.setBatchId(batchId)
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel, Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.CLIENTACK)
|
.setType(PacketType.CLIENTACK)
|
||||||
.setBody(ca.toByteString())
|
.setBody(ca.toByteString())
|
||||||
.build()
|
.build()
|
||||||
@@ -333,7 +348,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.setBatchId(batchId)
|
.setBatchId(batchId)
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel, Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.CLIENTROLLBACK)
|
.setType(PacketType.CLIENTROLLBACK)
|
||||||
.setBody(ca.toByteString())
|
.setBody(ca.toByteString())
|
||||||
.build()
|
.build()
|
||||||
@@ -350,7 +365,15 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
// ==================== helper method ====================
|
// ==================== 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) {
|
synchronized (writeDataLock) {
|
||||||
writeHeader.clear();
|
writeHeader.clear();
|
||||||
writeHeader.putInt(body.length);
|
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) {
|
synchronized (readDataLock) {
|
||||||
readHeader.clear();
|
readHeader.clear();
|
||||||
read(channel, readHeader);
|
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()) {
|
while (buffer.hasRemaining()) {
|
||||||
int r = channel.read(buffer);
|
int r = channel.read(buffer);
|
||||||
if (r == -1) {
|
if (r == -1) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="WARN">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.common</artifactId>
|
<artifactId>canal.common</artifactId>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<artifactId>zookeeper</artifactId>
|
<artifactId>zookeeper</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.sgroschupf</groupId>
|
<groupId>com.101tec</groupId>
|
||||||
<artifactId>zkclient</artifactId>
|
<artifactId>zkclient</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- external -->
|
<!-- external -->
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
<artifactId>commons-lang</artifactId>
|
<artifactId>commons-lang</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>spring</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
@@ -63,10 +63,37 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</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 -->
|
<!-- junit -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -8,20 +8,15 @@ import java.util.List;
|
|||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import org.I0Itec.zkclient.IZkConnection;
|
import org.I0Itec.zkclient.ZkConnection;
|
||||||
import org.I0Itec.zkclient.exception.ZkException;
|
import org.I0Itec.zkclient.exception.ZkException;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.zookeeper.ClientCnxn;
|
import org.apache.zookeeper.ClientCnxn;
|
||||||
import org.apache.zookeeper.CreateMode;
|
|
||||||
import org.apache.zookeeper.KeeperException;
|
|
||||||
import org.apache.zookeeper.Watcher;
|
import org.apache.zookeeper.Watcher;
|
||||||
import org.apache.zookeeper.ZooDefs.Ids;
|
|
||||||
import org.apache.zookeeper.ZooKeeper;
|
import org.apache.zookeeper.ZooKeeper;
|
||||||
import org.apache.zookeeper.ZooKeeper.States;
|
|
||||||
import org.apache.zookeeper.client.ConnectStringParser;
|
import org.apache.zookeeper.client.ConnectStringParser;
|
||||||
import org.apache.zookeeper.client.HostProvider;
|
import org.apache.zookeeper.client.HostProvider;
|
||||||
import org.apache.zookeeper.client.StaticHostProvider;
|
import org.apache.zookeeper.client.StaticHostProvider;
|
||||||
import org.apache.zookeeper.data.Stat;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
|
|||||||
* @author jianghang 2012-7-10 下午02:31:42
|
* @author jianghang 2012-7-10 下午02:31:42
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class ZooKeeperx implements IZkConnection {
|
public class ZooKeeperx extends ZkConnection {
|
||||||
|
|
||||||
private static final String SERVER_COMMA = ";";
|
private static final String SERVER_COMMA = ";";
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
|
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 hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
|
||||||
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
||||||
"serverAddresses");
|
"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 static final int DEFAULT_SESSION_TIMEOUT = 90000;
|
||||||
|
|
||||||
private ZooKeeper _zk = null;
|
private final List<String> _serversList;
|
||||||
private Lock _zookeeperLock = new ReentrantLock();
|
|
||||||
|
|
||||||
private final List<String> _servers;
|
|
||||||
private final int _sessionTimeOut;
|
private final int _sessionTimeOut;
|
||||||
|
|
||||||
public ZooKeeperx(String zkServers){
|
public ZooKeeperx(String zkServers){
|
||||||
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ZooKeeperx(String zkServers, int sessionTimeOut){
|
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;
|
_sessionTimeOut = sessionTimeOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connect(Watcher watcher) {
|
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();
|
_zookeeperLock.lock();
|
||||||
try {
|
try {
|
||||||
if (_zk != null) {
|
if (_zk != null) {
|
||||||
throw new IllegalStateException("zk client has already been started");
|
throw new IllegalStateException("zk client has already been started");
|
||||||
}
|
}
|
||||||
|
String zkServers = _serversList.get(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
|
logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
|
||||||
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
|
_zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
|
||||||
configMutliCluster(_zk);
|
configMutliCluster(_zk);
|
||||||
|
ReflectionUtils.setField(zookeeperFiled, this, _zk);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ZkException("Unable to connect to " + _servers, e);
|
throw new ZkException("Unable to connect to " + zkServers, e);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_zookeeperLock.unlock();
|
_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) {
|
public void configMutliCluster(ZooKeeper zk) {
|
||||||
if (_servers.size() == 1) {
|
if (_serversList.size() == 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String cluster1 = _servers.get(0);
|
String cluster1 = _serversList.get(0);
|
||||||
try {
|
try {
|
||||||
if (_servers.size() > 1) {
|
if (_serversList.size() > 1) {
|
||||||
// 强制的声明accessible
|
// 强制的声明accessible
|
||||||
ReflectionUtils.makeAccessible(clientCnxnField);
|
ReflectionUtils.makeAccessible(clientCnxnField);
|
||||||
ReflectionUtils.makeAccessible(hostProviderField);
|
ReflectionUtils.makeAccessible(hostProviderField);
|
||||||
ReflectionUtils.makeAccessible(serverAddressesField);
|
ReflectionUtils.makeAccessible(serverAddressesField);
|
||||||
|
|
||||||
// 添加第二组集群列表
|
// 添加第二组集群列表
|
||||||
for (int i = 1; i < _servers.size(); i++) {
|
for (int i = 1; i < _serversList.size(); i++) {
|
||||||
String cluster = _servers.get(i);
|
String cluster = _serversList.get(i);
|
||||||
// 强制获取zk中的地址信息
|
// 强制获取zk中的地址信息
|
||||||
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
||||||
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -363,12 +363,12 @@ public class JsonConversion {
|
|||||||
buf.append('"').append(text).append('"');
|
buf.append('"').append(text).append('"');
|
||||||
} else {
|
} else {
|
||||||
text = m_data.getFixString((int) m_length, charsetName);
|
text = m_data.getFixString((int) m_length, charsetName);
|
||||||
buf.append('"').append(text).append('"');
|
buf.append('"').append(escapse(text)).append('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case STRING:
|
case STRING:
|
||||||
buf.append('"').append(m_string_value).append('"');
|
buf.append('"').append(escapse(m_string_value)).append('"');
|
||||||
break;
|
break;
|
||||||
case ERROR:
|
case ERROR:
|
||||||
throw new IllegalArgumentException("illegal json data");
|
throw new IllegalArgumentException("illegal json data");
|
||||||
@@ -378,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 {
|
public static enum Json_enum_type {
|
||||||
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
|
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -949,8 +949,28 @@ public final class RowsLogBuffer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LogEvent.MYSQL_TYPE_JSON: {
|
case LogEvent.MYSQL_TYPE_JSON: {
|
||||||
len = buffer.getUint16();
|
switch (meta) {
|
||||||
buffer.forward(meta - 2);
|
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();
|
int position = buffer.position();
|
||||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -149,6 +149,11 @@ public class CanalController {
|
|||||||
public void handleNewSession() throws Exception {
|
public void handleNewSession() throws Exception {
|
||||||
initCid(path);
|
initCid(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||||
|
logger.error("failed to connect to zookeeper", error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -383,6 +388,11 @@ public class CanalController {
|
|||||||
public void handleNewSession() throws Exception {
|
public void handleNewSession() throws Exception {
|
||||||
initCid(path);
|
initCid(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||||
|
logger.error("failed to connect to zookeeper", error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 优先启动embeded服务
|
// 优先启动embeded服务
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.FileInputStream;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -21,6 +20,10 @@ public class CanalLauncher {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) throws Throwable {
|
||||||
try {
|
try {
|
||||||
|
logger.info("## set default uncaught exception handler");
|
||||||
|
setGlobalUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
logger.info("## load canal configurations");
|
||||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
@@ -41,8 +44,7 @@ public class CanalLauncher {
|
|||||||
logger.info("## stop the canal server");
|
logger.info("## stop the canal server");
|
||||||
controller.stop();
|
controller.stop();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("##something goes wrong when stopping canal Server:\n{}",
|
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
|
||||||
} finally {
|
} finally {
|
||||||
logger.info("## canal server is down.");
|
logger.info("## canal server is down.");
|
||||||
}
|
}
|
||||||
@@ -50,9 +52,17 @@ public class CanalLauncher {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("## Something goes wrong when starting up the canal Server:\n{}",
|
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("UnCaughtException", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -186,7 +185,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
|
|
||||||
logger.info("auto notify start {} successful.", destination);
|
logger.info("auto notify start {} successful.", destination);
|
||||||
} catch (Throwable e) {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +196,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
lastFiles.remove(destination);
|
lastFiles.remove(destination);
|
||||||
logger.info("auto notify stop {} successful.", destination);
|
logger.info("auto notify stop {} successful.", destination);
|
||||||
} catch (Throwable e) {
|
} 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时再执行删除
|
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
action.reload(destination);
|
action.reload(destination);
|
||||||
logger.info("auto notify reload {} successful.", destination);
|
logger.info("auto notify reload {} successful.", destination);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("scan reload found[{}] but reload failed",
|
logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
|
||||||
destination,
|
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ canal.instance.network.sendBufferSize = 16384
|
|||||||
canal.instance.network.soTimeout = 30
|
canal.instance.network.soTimeout = 30
|
||||||
|
|
||||||
# binlog filter config
|
# binlog filter config
|
||||||
|
canal.instance.filter.druid.ddl = true
|
||||||
canal.instance.filter.query.dcl = false
|
canal.instance.filter.query.dcl = false
|
||||||
canal.instance.filter.query.dml = false
|
canal.instance.filter.query.dml = false
|
||||||
canal.instance.filter.query.ddl = false
|
canal.instance.filter.query.ddl = false
|
||||||
@@ -59,9 +60,13 @@ canal.conf.dir = ../conf
|
|||||||
canal.auto.scan = true
|
canal.auto.scan = true
|
||||||
canal.auto.scan.interval = 5
|
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.mode = spring
|
||||||
canal.instance.global.lazy = false
|
canal.instance.global.lazy = false
|
||||||
#canal.instance.global.manager.address = 127.0.0.1:1099
|
#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/memory-instance.xml
|
||||||
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
||||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
#################################################
|
#################################################
|
||||||
## mysql serverId
|
## mysql serverId
|
||||||
canal.instance.mysql.slaveId = 1234
|
canal.instance.mysql.slaveId=0
|
||||||
|
|
||||||
# position info
|
# position info
|
||||||
canal.instance.master.address = 127.0.0.1:3306
|
canal.instance.master.address=127.0.0.1:3306
|
||||||
canal.instance.master.journal.name =
|
canal.instance.master.journal.name=
|
||||||
canal.instance.master.position =
|
canal.instance.master.position=
|
||||||
canal.instance.master.timestamp =
|
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.journal.name =
|
||||||
#canal.instance.standby.position =
|
#canal.instance.standby.position =
|
||||||
#canal.instance.standby.timestamp =
|
#canal.instance.standby.timestamp =
|
||||||
|
|
||||||
# username/password
|
# username/password
|
||||||
canal.instance.dbUsername = canal
|
canal.instance.dbUsername=canal
|
||||||
canal.instance.dbPassword = canal
|
canal.instance.dbPassword=canal
|
||||||
canal.instance.defaultDatabaseName =
|
canal.instance.defaultDatabaseName=test
|
||||||
canal.instance.connectionCharset = UTF-8
|
canal.instance.connectionCharset=UTF-8
|
||||||
|
|
||||||
# table regex
|
# table regex
|
||||||
canal.instance.filter.regex = .*\\..*
|
canal.instance.filter.regex=.*\\..*
|
||||||
# table black regex
|
# table black regex
|
||||||
canal.instance.filter.black.regex =
|
canal.instance.filter.black.regex=
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
32
deployer/src/main/resources/example/rds_instance.properties
Normal file
32
deployer/src/main/resources/example/rds_instance.properties
Normal file
@@ -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>
|
</logger>
|
||||||
|
|
||||||
<root level="WARN">
|
<root level="WARN">
|
||||||
<!--
|
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
-->
|
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -128,14 +128,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -178,9 +178,14 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<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>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -113,14 +113,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -163,9 +163,14 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<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>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -160,6 +160,7 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -250,6 +251,7 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
|
|||||||
@@ -48,41 +48,14 @@
|
|||||||
<property name="alarmHandler">
|
<property name="alarmHandler">
|
||||||
<ref local="alarmHandler" />
|
<ref local="alarmHandler" />
|
||||||
</property>
|
</property>
|
||||||
<property name="eventFetcher">
|
|
||||||
<ref local="eventFetcher"/>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="eventFetcher" class="com.alibaba.otter.canal.mq.fetch.CanalEventFetcherWithMq">
|
|
||||||
<property name="enable" value="${canal.instance.mq.enable}"/>
|
|
||||||
<property name="sendMode" value="${canal.instance.mq.sendMode}"/>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- 报警处理类 -->
|
<!-- 报警处理类 -->
|
||||||
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.MsgAlarmHandler">
|
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
|
||||||
<property name="enabled" value="${canal.alarm.enable}" />
|
|
||||||
<property name="environment" value="${canal.alarm.environment}" />
|
|
||||||
<property name="intercenterApi" value="${canal.alarm.intercenter.api}" />
|
|
||||||
<property name="administrators" value="${canal.alarm.administrators}" />
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="zkClientx" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" >
|
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
|
||||||
<property name="targetClass" value="com.alibaba.otter.canal.common.zookeeper.ZkClientx" />
|
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
|
||||||
<property name="targetMethod" value="getZkClient" />
|
<property name="period" value="${canal.file.flush.period:1000}" />
|
||||||
<property name="arguments">
|
|
||||||
<list>
|
|
||||||
<value>${canal.zkServers:127.0.0.1:2181}</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="metaManager" class="com.alibaba.otter.canal.meta.PeriodMixedMetaManager">
|
|
||||||
<property name="zooKeeperMetaManager">
|
|
||||||
<bean class="com.alibaba.otter.canal.meta.ZooKeeperMetaManager">
|
|
||||||
<property name="zkClientx" ref="zkClientx" />
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
<property name="period" value="${canal.zookeeper.flush.period:1000}" />
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
|
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
|
||||||
@@ -96,10 +69,10 @@
|
|||||||
<property name="eventStore" ref="eventStore" />
|
<property name="eventStore" ref="eventStore" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
<property name="alarmHandler" ref="alarmHandler" />
|
<property name="alarmHandler" ref="alarmHandler" />
|
||||||
|
|
||||||
<!-- 解析过滤处理 -->
|
<!-- 解析过滤处理 -->
|
||||||
<property name="eventFilter">
|
<property name="eventFilter">
|
||||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||||
@@ -123,14 +96,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -144,20 +117,25 @@
|
|||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<!-- 解析起始位点 -->
|
|
||||||
<property name="masterPosition">
|
|
||||||
<bean class="com.alibaba.otter.canal.protocol.position.EntryPosition">
|
|
||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="needWait" value="${canal.instance.parser.needWait:true}"/>
|
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
|
||||||
<property name="directory" value="${canal.instance.parser.directory}"/>
|
<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>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -151,9 +151,14 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<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>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
60
deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
Normal file
60
deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
Normal file
@@ -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>
|
||||||
63
deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
Normal file
63
deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
Normal file
@@ -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>
|
||||||
39
deployer/src/main/resources/spring/tsdb/sql/create_table.sql
Normal file
39
deployer/src/main/resources/spring/tsdb/sql/create_table.sql
Normal file
@@ -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='表结构变化明细表';
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -33,10 +33,6 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.netty</groupId>
|
|
||||||
<artifactId>netty</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<!-- test dependency -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ package com.alibaba.otter.canal.parse.driver.mysql;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.ErrorPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
|
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.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.MySQLPasswordEncrypter;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
|
|
||||||
@@ -35,6 +34,7 @@ public class MysqlConnector {
|
|||||||
private byte charsetNumber = 33;
|
private byte charsetNumber = 33;
|
||||||
private String defaultSchema = "retl";
|
private String defaultSchema = "retl";
|
||||||
private int soTimeout = 30 * 1000;
|
private int soTimeout = 30 * 1000;
|
||||||
|
private int connTimeout = 5 * 1000;
|
||||||
private int receiveBufferSize = 16 * 1024;
|
private int receiveBufferSize = 16 * 1024;
|
||||||
private int sendBufferSize = 16 * 1024;
|
private int sendBufferSize = 16 * 1024;
|
||||||
|
|
||||||
@@ -64,14 +64,12 @@ public class MysqlConnector {
|
|||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
if (connected.compareAndSet(false, true)) {
|
if (connected.compareAndSet(false, true)) {
|
||||||
try {
|
try {
|
||||||
channel = SocketChannel.open();
|
channel = SocketChannelPool.open(address);
|
||||||
configChannel(channel);
|
|
||||||
logger.info("connect MysqlConnection to {}...", address);
|
logger.info("connect MysqlConnection to {}...", address);
|
||||||
channel.connect(address);
|
|
||||||
negotiate(channel);
|
negotiate(channel);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
disconnect();
|
disconnect();
|
||||||
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
throw new IOException("connect " + this.address + " failure", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.error("the channel can't be connected twice.");
|
logger.error("the channel can't be connected twice.");
|
||||||
@@ -91,7 +89,7 @@ public class MysqlConnector {
|
|||||||
}
|
}
|
||||||
logger.info("disConnect MysqlConnection to {}...", address);
|
logger.info("disConnect MysqlConnection to {}...", address);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException("disconnect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
throw new IOException("disconnect " + this.address + " failure", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行一次quit
|
// 执行一次quit
|
||||||
@@ -104,7 +102,7 @@ public class MysqlConnector {
|
|||||||
executor.update("KILL CONNECTION " + connectionId);
|
executor.update("KILL CONNECTION " + connectionId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 忽略具体异常
|
// 忽略具体异常
|
||||||
logger.info("KILL DUMP " + connectionId + " failure:" + ExceptionUtils.getStackTrace(e));
|
logger.info("KILL DUMP " + connectionId + " failure", e);
|
||||||
} finally {
|
} finally {
|
||||||
if (connector != null) {
|
if (connector != null) {
|
||||||
connector.disconnect();
|
connector.disconnect();
|
||||||
@@ -132,6 +130,7 @@ public class MysqlConnector {
|
|||||||
connector.setReceiveBufferSize(getReceiveBufferSize());
|
connector.setReceiveBufferSize(getReceiveBufferSize());
|
||||||
connector.setSendBufferSize(getSendBufferSize());
|
connector.setSendBufferSize(getSendBufferSize());
|
||||||
connector.setSoTimeout(getSoTimeout());
|
connector.setSoTimeout(getSoTimeout());
|
||||||
|
connector.setConnTimeout(connTimeout);
|
||||||
return connector;
|
return connector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,19 +141,7 @@ public class MysqlConnector {
|
|||||||
HeaderPacket quitHeader = new HeaderPacket();
|
HeaderPacket quitHeader = new HeaderPacket();
|
||||||
quitHeader.setPacketBodyLength(cmdBody.length);
|
quitHeader.setPacketBodyLength(cmdBody.length);
|
||||||
quitHeader.setPacketSequenceNumber((byte) 0x00);
|
quitHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
PacketManager.write(channel,
|
PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void negotiate(SocketChannel channel) throws IOException {
|
private void negotiate(SocketChannel channel) throws IOException {
|
||||||
@@ -191,8 +178,7 @@ public class MysqlConnector {
|
|||||||
h.setPacketBodyLength(clientAuthPkgBody.length);
|
h.setPacketBodyLength(clientAuthPkgBody.length);
|
||||||
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
||||||
|
|
||||||
PacketManager.write(channel,
|
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
|
||||||
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
|
|
||||||
logger.info("client authentication packet is sent out.");
|
logger.info("client authentication packet is sent out.");
|
||||||
|
|
||||||
// check auth result
|
// check auth result
|
||||||
@@ -228,7 +214,7 @@ public class MysqlConnector {
|
|||||||
h323.setPacketBodyLength(b323Body.length);
|
h323.setPacketBodyLength(b323Body.length);
|
||||||
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
|
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.");
|
logger.info("client 323 authentication packet is sent out.");
|
||||||
// check auth result
|
// check auth result
|
||||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||||
@@ -341,4 +327,11 @@ public class MysqlConnector {
|
|||||||
this.dumping = dumping;
|
this.dumping = dumping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getConnTimeout() {
|
||||||
|
return connTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnTimeout(int connTimeout) {
|
||||||
|
this.connTimeout = connTimeout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
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.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.ErrorPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
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.ResultSetHeaderPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
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.packets.server.RowDataPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
|
|||||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||||
cmd.setQueryString(queryString);
|
cmd.setQueryString(queryString);
|
||||||
byte[] bodyBytes = cmd.toBytes();
|
byte[] bodyBytes = cmd.toBytes();
|
||||||
PacketManager.write(channel, bodyBytes);
|
PacketManager.writeBody(channel, bodyBytes);
|
||||||
byte[] body = readNextPacket();
|
byte[] body = readNextPacket();
|
||||||
|
|
||||||
if (body[0] < 0) {
|
if (body[0] < 0) {
|
||||||
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
|
|||||||
for (RowDataPacket r : rowData) {
|
for (RowDataPacket r : rowData) {
|
||||||
resultSet.getFieldValues().addAll(r.getColumns());
|
resultSet.getFieldValues().addAll(r.getColumns());
|
||||||
}
|
}
|
||||||
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress());
|
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||||
|
|
||||||
return resultSet;
|
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();
|
byte[] eofBody = readNextPacket();
|
||||||
|
EOFPacket packet = new EOFPacket();
|
||||||
|
packet.fromBytes(eofBody);
|
||||||
if (eofBody[0] != -2) {
|
if (eofBody[0] != -2) {
|
||||||
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
|
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 {
|
protected byte[] readNextPacket() throws IOException {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
|
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
|
||||||
|
|
||||||
private SocketChannel channel;
|
private MysqlConnector connector;
|
||||||
|
|
||||||
public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
|
public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
|
||||||
if (!connector.isConnected()) {
|
if (!connector.isConnected()) {
|
||||||
throw new IOException("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 {
|
public OKPacket update(String updateString) throws IOException {
|
||||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||||
cmd.setQueryString(updateString);
|
cmd.setQueryString(updateString);
|
||||||
byte[] bodyBytes = cmd.toBytes();
|
byte[] bodyBytes = cmd.toBytes();
|
||||||
PacketManager.write(channel, bodyBytes);
|
PacketManager.writeBody(connector.getChannel(), bodyBytes);
|
||||||
|
|
||||||
logger.debug("read update result...");
|
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) {
|
if (body[0] < 0) {
|
||||||
ErrorPacket packet = new ErrorPacket();
|
ErrorPacket packet = new ErrorPacket();
|
||||||
packet.fromBytes(body);
|
packet.fromBytes(body);
|
||||||
|
|||||||
@@ -47,10 +47,11 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
|
|||||||
/**
|
/**
|
||||||
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
||||||
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
||||||
|
* CLIENT_MULTI_STATEMENTS;
|
||||||
*/
|
*/
|
||||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove
|
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
|
||||||
// client_interactive
|
// client_interactive
|
||||||
// feature
|
// feature
|
||||||
|
|
||||||
// 2. write max_packet_size
|
// 2. write max_packet_size
|
||||||
ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, out);
|
ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, out);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import io.netty.channel.Channel;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
||||||
@@ -18,19 +17,14 @@ public class SocketChannel {
|
|||||||
|
|
||||||
private Channel channel = null;
|
private Channel channel = null;
|
||||||
private Object lock = new Object();
|
private Object lock = new Object();
|
||||||
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(1024 * 1024 * 5); // 缓存大小
|
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(1024 * 1024); // 缓存大小
|
||||||
|
|
||||||
public Channel getChannel() {
|
public Channel getChannel() {
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChannel(Channel channel, boolean notify) {
|
public void setChannel(Channel channel) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
if (notify) {// 是否需要通知,主要是channel不可用时
|
|
||||||
synchronized (this) {
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeCache(ByteBuf buf) {
|
public void writeCache(ByteBuf buf) {
|
||||||
@@ -38,9 +32,6 @@ public class SocketChannel {
|
|||||||
cache.discardReadBytes();// 回收内存
|
cache.discardReadBytes();// 回收内存
|
||||||
cache.writeBytes(buf);
|
cache.writeBytes(buf);
|
||||||
}
|
}
|
||||||
synchronized (this) {
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeChannel(byte[]... buf) throws IOException {
|
public void writeChannel(byte[]... buf) throws IOException {
|
||||||
@@ -51,24 +42,27 @@ public class SocketChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(ByteBuffer buffer) throws IOException {
|
public byte[] read(int readSize) throws IOException {
|
||||||
if (null == channel) {
|
do {
|
||||||
throw new IOException("socket has Interrupted !");
|
if (readSize > cache.readableBytes()) {
|
||||||
}
|
if (null == channel) {
|
||||||
if (cache.readableBytes() < buffer.remaining()) {
|
throw new java.nio.channels.ClosedByInterruptException();
|
||||||
synchronized (this) {
|
|
||||||
try {
|
|
||||||
wait();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new IOException("socket has Interrupted !");
|
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
} else {
|
} while (true);
|
||||||
synchronized (lock) {
|
|
||||||
cache.readBytes(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import java.net.SocketAddress;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
||||||
*/
|
*/
|
||||||
@@ -53,21 +55,20 @@ public abstract class SocketChannelPool {
|
|||||||
|
|
||||||
public static SocketChannel open(SocketAddress address) throws Exception {
|
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||||
final SocketChannel socket = new SocketChannel();
|
final SocketChannel socket = new SocketChannel();
|
||||||
|
final BooleanMutex mutex = new BooleanMutex(false);
|
||||||
boot.connect(address).addListener(new ChannelFutureListener() {
|
boot.connect(address).addListener(new ChannelFutureListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void operationComplete(ChannelFuture arg0) throws Exception {
|
public void operationComplete(ChannelFuture arg0) throws Exception {
|
||||||
if (arg0.isSuccess()) {
|
if (arg0.isSuccess()) {
|
||||||
socket.setChannel(arg0.channel(), false);
|
socket.setChannel(arg0.channel());
|
||||||
}
|
|
||||||
synchronized (socket) {
|
|
||||||
socket.notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex.set(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
synchronized (socket) {
|
// wait for complete
|
||||||
socket.wait();
|
mutex.get();
|
||||||
}
|
|
||||||
if (null == socket.getChannel()) {
|
if (null == socket.getChannel()) {
|
||||||
throw new IOException("can't create socket!");
|
throw new IOException("can't create socket!");
|
||||||
}
|
}
|
||||||
@@ -79,21 +80,22 @@ public abstract class SocketChannelPool {
|
|||||||
|
|
||||||
private SocketChannel socket = null;
|
private SocketChannel socket = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
socket.setChannel(null, true);
|
socket.setChannel(null);
|
||||||
chManager.remove(ctx.channel());// 移除
|
chManager.remove(ctx.channel());// 移除
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
if (null == socket) {
|
if (null == socket) socket = chManager.get(ctx.channel());
|
||||||
socket = chManager.get(ctx.channel());
|
|
||||||
}
|
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
socket.writeCache((ByteBuf) msg);
|
socket.writeCache((ByteBuf) msg);
|
||||||
}
|
}
|
||||||
ReferenceCountUtil.release(msg);// 添加防止内存泄漏的
|
ReferenceCountUtil.release(msg);// 添加防止内存泄漏的
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,34 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.packets.HeaderPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||||
|
|
||||||
public abstract class PacketManager {
|
public abstract class PacketManager {
|
||||||
|
|
||||||
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
||||||
HeaderPacket header = new HeaderPacket();
|
HeaderPacket header = new HeaderPacket();
|
||||||
header.fromBytes(readBytesAsBuffer(ch, len).array());
|
header.fromBytes(ch.read(len));
|
||||||
return header;
|
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 {
|
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
||||||
return readBytesAsBuffer(ch, len).array();
|
return ch.read(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
||||||
* Since We r using blocking IO, so we will just write once and assert the
|
ch.writeChannel(srcs);
|
||||||
* 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 write(SocketChannel ch, byte[] body) throws IOException {
|
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||||
write(ch, body, (byte) 0);
|
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();
|
HeaderPacket header = new HeaderPacket();
|
||||||
header.setPacketBodyLength(body.length);
|
header.setPacketBodyLength(body.length);
|
||||||
header.setPacketSequenceNumber(packetSeqNumber);
|
header.setPacketSequenceNumber(packetSeqNumber);
|
||||||
write(ch, new ByteBuffer[] { ByteBuffer.wrap(header.toBytes()), ByteBuffer.wrap(body) });
|
ch.writeChannel(header.toBytes(), body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.alibaba.otter.canal.example;
|
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.CanalConnector;
|
||||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ public class ClusterCanalClientTest extends AbstractCanalClientTest {
|
|||||||
logger.info("## stop the canal client");
|
logger.info("## stop the canal client");
|
||||||
clientTest.stop();
|
clientTest.stop();
|
||||||
} catch (Throwable e) {
|
} 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 {
|
} finally {
|
||||||
logger.info("## canal client is down.");
|
logger.info("## canal client is down.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.alibaba.otter.canal.example;
|
|||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.CanalConnector;
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
@@ -23,8 +21,11 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
|||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
// 根据ip,直接创建链接,无HA的功能
|
// 根据ip,直接创建链接,无HA的功能
|
||||||
String destination = "example";
|
String destination = "example";
|
||||||
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
|
String ip = AddressUtils.getHostIp();
|
||||||
11111), destination, "", "");
|
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
|
||||||
|
destination,
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
|
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
|
||||||
clientTest.setConnector(connector);
|
clientTest.setConnector(connector);
|
||||||
@@ -36,7 +37,7 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
|||||||
logger.info("## stop the canal client");
|
logger.info("## stop the canal client");
|
||||||
clientTest.stop();
|
clientTest.stop();
|
||||||
} catch (Throwable e) {
|
} 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 {
|
} finally {
|
||||||
logger.info("## canal client is down.");
|
logger.info("## canal client is down.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.instance.core</artifactId>
|
<artifactId>canal.instance.core</artifactId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -346,29 +346,24 @@ public class CanalInstanceWithManager extends AbstractCanalInstance {
|
|||||||
protected CanalLogPositionManager initLogPositionManager() {
|
protected CanalLogPositionManager initLogPositionManager() {
|
||||||
logger.info("init logPositionPersistManager begin...");
|
logger.info("init logPositionPersistManager begin...");
|
||||||
IndexMode indexMode = parameters.getIndexMode();
|
IndexMode indexMode = parameters.getIndexMode();
|
||||||
CanalLogPositionManager logPositionManager = null;
|
CanalLogPositionManager logPositionManager;
|
||||||
if (indexMode.isMemory()) {
|
if (indexMode.isMemory()) {
|
||||||
logPositionManager = new MemoryLogPositionManager();
|
logPositionManager = new MemoryLogPositionManager();
|
||||||
} else if (indexMode.isZookeeper()) {
|
} else if (indexMode.isZookeeper()) {
|
||||||
logPositionManager = new ZooKeeperLogPositionManager();
|
logPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||||
((ZooKeeperLogPositionManager) logPositionManager).setZkClientx(getZkclientx());
|
|
||||||
} else if (indexMode.isMixed()) {
|
} else if (indexMode.isMixed()) {
|
||||||
logPositionManager = new PeriodMixedLogPositionManager();
|
MemoryLogPositionManager memoryLogPositionManager = new MemoryLogPositionManager();
|
||||||
|
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||||
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager();
|
logPositionManager = new PeriodMixedLogPositionManager(memoryLogPositionManager,
|
||||||
zooKeeperLogPositionManager.setZkClientx(getZkclientx());
|
zooKeeperLogPositionManager,
|
||||||
((PeriodMixedLogPositionManager) logPositionManager).setZooKeeperLogPositionManager(zooKeeperLogPositionManager);
|
1000L);
|
||||||
} else if (indexMode.isMeta()) {
|
} else if (indexMode.isMeta()) {
|
||||||
logPositionManager = new MetaLogPositionManager();
|
logPositionManager = new MetaLogPositionManager(metaManager);
|
||||||
((MetaLogPositionManager) logPositionManager).setMetaManager(metaManager);
|
|
||||||
} else if (indexMode.isMemoryMetaFailback()) {
|
} else if (indexMode.isMemoryMetaFailback()) {
|
||||||
MemoryLogPositionManager primaryLogPositionManager = new MemoryLogPositionManager();
|
MemoryLogPositionManager primary = new MemoryLogPositionManager();
|
||||||
MetaLogPositionManager failbackLogPositionManager = new MetaLogPositionManager();
|
MetaLogPositionManager secondary = new MetaLogPositionManager(metaManager);
|
||||||
failbackLogPositionManager.setMetaManager(metaManager);
|
|
||||||
|
|
||||||
logPositionManager = new FailbackLogPositionManager();
|
logPositionManager = new FailbackLogPositionManager(primary, secondary);
|
||||||
((FailbackLogPositionManager) logPositionManager).setPrimary(primaryLogPositionManager);
|
|
||||||
((FailbackLogPositionManager) logPositionManager).setFailback(failbackLogPositionManager);
|
|
||||||
} else {
|
} else {
|
||||||
throw new CanalException("unsupport indexMode for " + indexMode);
|
throw new CanalException("unsupport indexMode for " + indexMode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -120,14 +120,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
|||||||
@@ -102,18 +102,20 @@
|
|||||||
<!-- 解析编码 -->
|
<!-- 解析编码 -->
|
||||||
<!-- property name="connectionCharsetNumber" value="${canal.instance.connectionCharsetNumber:33}" /-->
|
<!-- property name="connectionCharsetNumber" value="${canal.instance.connectionCharsetNumber:33}" /-->
|
||||||
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
||||||
|
|
||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg>
|
||||||
|
<ref bean="metaManager"/>
|
||||||
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ public class FileMixedMetaManager extends MemoryMetaManager implements CanalMeta
|
|||||||
this.dataDir = new File(dataDir);
|
this.dataDir = new File(dataDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDataDir(File dataDir) {
|
public void setDataDirByFile(File dataDir) {
|
||||||
this.dataDir = dataDir;
|
this.dataDir = dataDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSubscribeAll() {
|
public void testSubscribeAll() {
|
||||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||||
metaManager.setDataDir(dataDir);
|
metaManager.setDataDirByFile(dataDir);
|
||||||
metaManager.setPeriod(100);
|
metaManager.setPeriod(100);
|
||||||
|
|
||||||
metaManager.start();
|
metaManager.start();
|
||||||
@@ -40,7 +40,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
sleep(2000L);
|
sleep(2000L);
|
||||||
// 重新构建一次,能获得上一次zk上的记录
|
// 重新构建一次,能获得上一次zk上的记录
|
||||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||||
metaManager2.setDataDir(dataDir);
|
metaManager2.setDataDirByFile(dataDir);
|
||||||
metaManager2.setPeriod(100);
|
metaManager2.setPeriod(100);
|
||||||
metaManager2.start();
|
metaManager2.start();
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testBatchAll() {
|
public void testBatchAll() {
|
||||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||||
metaManager.setDataDir(dataDir);
|
metaManager.setDataDirByFile(dataDir);
|
||||||
metaManager.setPeriod(100);
|
metaManager.setPeriod(100);
|
||||||
|
|
||||||
metaManager.start();
|
metaManager.start();
|
||||||
@@ -67,7 +67,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCursorAll() {
|
public void testCursorAll() {
|
||||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||||
metaManager.setDataDir(dataDir);
|
metaManager.setDataDirByFile(dataDir);
|
||||||
metaManager.setPeriod(100);
|
metaManager.setPeriod(100);
|
||||||
metaManager.start();
|
metaManager.start();
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
sleep(1000L);
|
sleep(1000L);
|
||||||
// 重新构建一次,能获得上一次zk上的记录
|
// 重新构建一次,能获得上一次zk上的记录
|
||||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||||
metaManager2.setDataDir(dataDir);
|
metaManager2.setDataDirByFile(dataDir);
|
||||||
metaManager2.setPeriod(100);
|
metaManager2.setPeriod(100);
|
||||||
metaManager2.start();
|
metaManager2.start();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.24</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.parse</artifactId>
|
<artifactId>canal.parse</artifactId>
|
||||||
@@ -45,11 +45,41 @@
|
|||||||
<artifactId>canal.parse.driver</artifactId>
|
<artifactId>canal.parse.driver</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
protected void preDump(ErosaConnection connection) {
|
protected void preDump(ErosaConnection connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean processTableMeta(EntryPosition position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected void afterDump(ErosaConnection connection) {
|
protected void afterDump(ErosaConnection connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +149,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
ErosaConnection erosaConnection = null;
|
ErosaConnection erosaConnection = null;
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 开始执行replication
|
// 开始执行replication
|
||||||
// 1. 构造Erosa连接
|
// 1. 构造Erosa连接
|
||||||
erosaConnection = buildErosaConnection();
|
erosaConnection = buildErosaConnection();
|
||||||
@@ -163,6 +166,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
if (startPosition == null) {
|
if (startPosition == null) {
|
||||||
throw new CanalParseException("can't find start position for " + destination);
|
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());
|
logger.info("find start position : {}", startPosition.toString());
|
||||||
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
||||||
erosaConnection.reconnect();
|
erosaConnection.reconnect();
|
||||||
@@ -173,7 +181,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
|
|
||||||
public boolean sink(EVENT event) {
|
public boolean sink(EVENT event) {
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, false);
|
||||||
|
|
||||||
if (!running) {
|
if (!running) {
|
||||||
return false;
|
return false;
|
||||||
@@ -191,6 +199,9 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
} catch (TableIdNotFoundException e) {
|
} catch (TableIdNotFoundException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
if (e.getCause() instanceof TableIdNotFoundException) {
|
||||||
|
throw (TableIdNotFoundException) e.getCause();
|
||||||
|
}
|
||||||
// 记录一下,出错的位点信息
|
// 记录一下,出错的位点信息
|
||||||
processSinkError(e,
|
processSinkError(e,
|
||||||
this.lastPosition,
|
this.lastPosition,
|
||||||
@@ -272,7 +283,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
parseThread.setUncaughtExceptionHandler(handler);
|
parseThread.setUncaughtExceptionHandler(handler);
|
||||||
parseThread.setName(String.format("destination = %s , address = %s , EventParser",
|
parseThread.setName(String.format("destination = %s , address = %s , EventParser",
|
||||||
destination,
|
destination,
|
||||||
runningInfo == null ? null : runningInfo.getAddress().toString()));
|
runningInfo == null ? null : runningInfo.getAddress()));
|
||||||
parseThread.start();
|
parseThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,13 +328,13 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod) throws Exception {
|
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod, boolean isSeek) throws Exception {
|
||||||
long startTs = -1;
|
long startTs = -1;
|
||||||
boolean enabled = getProfilingEnabled();
|
boolean enabled = getProfilingEnabled();
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
startTs = System.currentTimeMillis();
|
startTs = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
CanalEntry.Entry event = binlogParser.parse(bod, isSeek);
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
this.parsingInterval = System.currentTimeMillis() - startTs;
|
||||||
}
|
}
|
||||||
@@ -427,7 +438,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} 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 {
|
public interface BinlogParser<T> extends CanalLifeCycle {
|
||||||
|
|
||||||
CanalEntry.Entry parse(T event) throws CanalParseException;
|
CanalEntry.Entry parse(T event, boolean isSeek) throws CanalParseException;
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ public interface ErosaConnection {
|
|||||||
|
|
||||||
public void disconnect() throws IOException;
|
public void disconnect() throws IOException;
|
||||||
|
|
||||||
public boolean isConnected();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,51 +3,79 @@ package com.alibaba.otter.canal.parse.inbound;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
|
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* 1. 主键信息
|
* 1. 主键信息
|
||||||
* 2. column name
|
* 2. column name
|
||||||
* 3. unsigned字段
|
* 3. unsigned字段
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author jianghang 2013-1-18 下午12:24:59
|
* @author jianghang 2013-1-18 下午12:24:59
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class TableMeta {
|
public class TableMeta {
|
||||||
|
|
||||||
private String fullName; // schema.table
|
private String schema;
|
||||||
private List<FieldMeta> fileds;
|
private String table;
|
||||||
|
private List<FieldMeta> fields = new ArrayList<TableMeta.FieldMeta>();
|
||||||
|
private String ddl; // 表结构的DDL语句
|
||||||
|
|
||||||
public TableMeta(String fullName, List<FieldMeta> fileds){
|
public TableMeta(){
|
||||||
this.fullName = fullName;
|
|
||||||
this.fileds = fileds;
|
}
|
||||||
|
|
||||||
|
public TableMeta(String schema, String table, List<FieldMeta> fields){
|
||||||
|
this.schema = schema;
|
||||||
|
this.table = table;
|
||||||
|
this.fields = fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
return fullName;
|
return schema + "." + table;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFullName(String fullName) {
|
public String getSchema() {
|
||||||
this.fullName = fullName;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FieldMeta> getFileds() {
|
public void setSchema(String schema) {
|
||||||
return fileds;
|
this.schema = schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFileds(List<FieldMeta> fileds) {
|
public String getTable() {
|
||||||
this.fileds = fileds;
|
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() {
|
public List<FieldMeta> getPrimaryFields() {
|
||||||
List<FieldMeta> primarys = new ArrayList<TableMeta.FieldMeta>();
|
List<FieldMeta> primarys = new ArrayList<TableMeta.FieldMeta>();
|
||||||
for (FieldMeta meta : fileds) {
|
for (FieldMeta meta : fields) {
|
||||||
if (meta.isKey()) {
|
if (meta.isKey()) {
|
||||||
primarys.add(meta);
|
primarys.add(meta);
|
||||||
}
|
}
|
||||||
@@ -56,14 +84,49 @@ public class TableMeta {
|
|||||||
return primarys;
|
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 {
|
public static class FieldMeta {
|
||||||
|
|
||||||
private String columnName;
|
public FieldMeta(){
|
||||||
private String columnType;
|
|
||||||
private String isNullable;
|
}
|
||||||
private String iskey;
|
|
||||||
private String defaultValue;
|
public FieldMeta(String columnName, String columnType, boolean nullable, boolean key, String defaultValue){
|
||||||
private String extra;
|
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() {
|
public String getColumnName() {
|
||||||
return columnName;
|
return columnName;
|
||||||
@@ -81,20 +144,8 @@ public class TableMeta {
|
|||||||
this.columnType = columnType;
|
this.columnType = columnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIsNullable() {
|
public void setNullable(boolean nullable) {
|
||||||
return isNullable;
|
this.nullable = nullable;
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsNullable(String isNullable) {
|
|
||||||
this.isNullable = isNullable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIskey() {
|
|
||||||
return iskey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIskey(String iskey) {
|
|
||||||
this.iskey = iskey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultValue() {
|
public String getDefaultValue() {
|
||||||
@@ -105,6 +156,22 @@ public class TableMeta {
|
|||||||
this.defaultValue = defaultValue;
|
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() {
|
public String getExtra() {
|
||||||
return extra;
|
return extra;
|
||||||
}
|
}
|
||||||
@@ -113,22 +180,10 @@ public class TableMeta {
|
|||||||
this.extra = extra;
|
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() {
|
public String toString() {
|
||||||
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
|
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
|
||||||
+ defaultValue + ", extra=" + extra + ", isNullable=" + isNullable + ", iskey=" + iskey + "]";
|
+ defaultValue + ", nullable=" + nullable + ", key=" + key + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,22 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
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.AbstractEventParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
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.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 {
|
public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||||
|
|
||||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
protected static final long BINLOG_START_OFFEST = 4L;
|
protected static final long BINLOG_START_OFFEST = 4L;
|
||||||
|
|
||||||
|
protected boolean enableTsdb = false;
|
||||||
|
protected String tsdbSpringXml;
|
||||||
|
protected TableMetaTSDB tableMetaTSDB;
|
||||||
// 编码信息
|
// 编码信息
|
||||||
protected byte connectionCharsetNumber = (byte) 33;
|
protected byte connectionCharsetNumber = (byte) 33;
|
||||||
protected Charset connectionCharset = Charset.forName("UTF-8");
|
protected Charset connectionCharset = Charset.forName("UTF-8");
|
||||||
@@ -24,6 +31,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
protected boolean filterQueryDdl = false;
|
protected boolean filterQueryDdl = false;
|
||||||
protected boolean filterRows = false;
|
protected boolean filterRows = false;
|
||||||
protected boolean filterTableError = false;
|
protected boolean filterTableError = false;
|
||||||
|
protected boolean useDruidDdlFilter = true;
|
||||||
|
|
||||||
protected BinlogParser buildParser() {
|
protected BinlogParser buildParser() {
|
||||||
LogEventConvert convert = new LogEventConvert();
|
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) {
|
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
|
||||||
super.setEventBlackFilter(eventBlackFilter);
|
super.setEventBlackFilter(eventBlackFilter);
|
||||||
|
|
||||||
@@ -97,4 +139,31 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
this.filterTableError = filterTableError;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
||||||
@@ -84,26 +85,19 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
while (running) {
|
while (running) {
|
||||||
boolean needContinue = true;
|
boolean needContinue = true;
|
||||||
LogEvent event = null;
|
LogEvent event = null;
|
||||||
L: while (fetcher.fetch()) {
|
// 处理一下binlog文件名
|
||||||
/*
|
event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
|
||||||
* event = decoder.decode(fetcher, context); if (event ==
|
func.sink(event);
|
||||||
* null) { throw new CanalParseException("parse failed"); }
|
while (fetcher.fetch()) {
|
||||||
* if (!func.sink(event)) { needContinue = false; break; }
|
event = decoder.decode(fetcher, context);
|
||||||
*/
|
if (event == null) {
|
||||||
|
throw new CanalParseException("parse failed");
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
if (!func.sink(event)) {
|
||||||
if (event == null) {
|
needContinue = false;
|
||||||
event = new RotateLogEvent(context.getLogPosition().getFileName(), context.getLogPosition()
|
break;
|
||||||
.getPosition());
|
}
|
||||||
} else {
|
|
||||||
event = decoder.decode(fetcher, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event != null && !func.sink(event)) {
|
|
||||||
needContinue = false;
|
|
||||||
break L;
|
|
||||||
}
|
|
||||||
} while (event != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needContinue) {// 读取下一个
|
if (needContinue) {// 读取下一个
|
||||||
@@ -121,7 +115,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
current = nextFile;
|
current = nextFile;
|
||||||
|
|
||||||
fetcher.open(current);
|
fetcher.open(current);
|
||||||
context.setLogPosition(new LogPosition(nextFile.getName()));
|
context.setLogPosition(new LogPosition(nextFile.getName()));
|
||||||
} else {
|
} else {
|
||||||
@@ -161,30 +154,27 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
|
|
||||||
binlogFilename = lastXidLogFilename;
|
binlogFilename = lastXidLogFilename;
|
||||||
binlogFileOffset = lastXidLogFileOffset;
|
binlogFileOffset = lastXidLogFileOffset;
|
||||||
L: while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
LogEvent event;
|
LogEvent event = decoder.decode(fetcher, context);
|
||||||
do {
|
if (event != null) {
|
||||||
event = decoder.decode(fetcher, context);
|
if (event.getWhen() > timestampSeconds) {
|
||||||
if (event != null) {
|
break;
|
||||||
if (event.getWhen() > timestampSeconds) {
|
}
|
||||||
break L;
|
|
||||||
}
|
|
||||||
|
|
||||||
needContinue = false;
|
needContinue = false;
|
||||||
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
||||||
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
||||||
binlogFilename = lastXidLogFilename;
|
binlogFilename = lastXidLogFilename;
|
||||||
binlogFileOffset = lastXidLogFileOffset;
|
binlogFileOffset = lastXidLogFileOffset;
|
||||||
} else if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "COMMIT")) {
|
} 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();
|
lastXidLogFilename = current.getName();
|
||||||
lastXidLogFileOffset = event.getLogPos();
|
lastXidLogFileOffset = event.getLogPos();
|
||||||
}
|
}
|
||||||
|
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
|
||||||
|
lastXidLogFilename = current.getName();
|
||||||
|
lastXidLogFileOffset = event.getLogPos();
|
||||||
}
|
}
|
||||||
} while (event != null);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needContinue) {// 读取下一个
|
if (needContinue) {// 读取下一个
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
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.LogEventConvert;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
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.index.CanalLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
@@ -23,14 +24,14 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
|
|||||||
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
|
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
|
||||||
|
|
||||||
// 数据库信息
|
// 数据库信息
|
||||||
private AuthenticationInfo masterInfo;
|
protected AuthenticationInfo masterInfo;
|
||||||
private EntryPosition masterPosition; // binlog信息
|
protected EntryPosition masterPosition; // binlog信息
|
||||||
private MysqlConnection metaConnection; // 查询meta信息的链接
|
protected MysqlConnection metaConnection; // 查询meta信息的链接
|
||||||
private TableMetaCache tableMetaCache; // 对应meta
|
protected TableMetaCache tableMetaCache; // 对应meta
|
||||||
|
|
||||||
private String directory;
|
protected String directory;
|
||||||
private boolean needWait = false;
|
protected boolean needWait = false;
|
||||||
private int bufferSize = 16 * 1024;
|
protected int bufferSize = 16 * 1024;
|
||||||
|
|
||||||
public LocalBinlogEventParser(){
|
public LocalBinlogEventParser(){
|
||||||
// this.runningInfo = new AuthenticationInfo();
|
// this.runningInfo = new AuthenticationInfo();
|
||||||
@@ -50,7 +51,13 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
|||||||
throw new CanalParseException(e);
|
throw new CanalParseException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package com.alibaba.otter.canal.parse.inbound.mysql;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -22,34 +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.ErosaConnection;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
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.LogContext;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogPosition;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
|
||||||
|
|
||||||
public class MysqlConnection implements ErosaConnection {
|
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 MysqlConnector connector;
|
||||||
private long slaveId;
|
private long slaveId;
|
||||||
private Charset charset = Charset.forName("UTF-8");
|
private Charset charset = Charset.forName("UTF-8");
|
||||||
private BinlogFormat binlogFormat;
|
private BinlogFormat binlogFormat;
|
||||||
private BinlogImage binlogImage;
|
private BinlogImage binlogImage;
|
||||||
private int binlogChecksum;
|
|
||||||
|
// tsdb releated
|
||||||
|
private AuthenticationInfo authInfo;
|
||||||
|
protected int connTimeout = 5 * 1000; // 5秒
|
||||||
|
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
||||||
|
|
||||||
public MysqlConnection(){
|
public MysqlConnection(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnection(InetSocketAddress address, String username, String password){
|
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);
|
connector = new MysqlConnector(address, username, password);
|
||||||
|
// 将connection里面的参数透传下
|
||||||
|
connector.setSoTimeout(soTimeout);
|
||||||
|
connector.setConnTimeout(connTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
|
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
|
||||||
String defaultSchema){
|
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);
|
connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema);
|
||||||
|
// 将connection里面的参数透传下
|
||||||
|
connector.setSoTimeout(soTimeout);
|
||||||
|
connector.setConnTimeout(connTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
@@ -73,6 +88,11 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
return exector.query(cmd);
|
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 {
|
public void update(String cmd) throws IOException {
|
||||||
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
|
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
|
||||||
exector.update(cmd);
|
exector.update(cmd);
|
||||||
@@ -83,7 +103,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
*/
|
*/
|
||||||
public void seek(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void seek(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
@@ -93,8 +113,6 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
decoder.handle(LogEvent.QUERY_EVENT);
|
decoder.handle(LogEvent.QUERY_EVENT);
|
||||||
decoder.handle(LogEvent.XID_EVENT);
|
decoder.handle(LogEvent.XID_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
context.setLogPosition(new LogPosition(binlogfilename));
|
|
||||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
LogEvent event = null;
|
LogEvent event = null;
|
||||||
event = decoder.decode(fetcher, context);
|
event = decoder.decode(fetcher, context);
|
||||||
@@ -111,14 +129,11 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
|
|
||||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
context.setLogPosition(new LogPosition(binlogfilename));
|
|
||||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
LogEvent event = null;
|
LogEvent event = null;
|
||||||
event = decoder.decode(fetcher, context);
|
event = decoder.decode(fetcher, context);
|
||||||
@@ -141,6 +156,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
||||||
binlogDumpCmd.binlogFileName = binlogfilename;
|
binlogDumpCmd.binlogFileName = binlogfilename;
|
||||||
binlogDumpCmd.binlogPosition = binlogPosition;
|
binlogDumpCmd.binlogPosition = binlogPosition;
|
||||||
|
// binlogDumpCmd.slaveServerId = this.slaveId;
|
||||||
binlogDumpCmd.slaveServerId = this.slaveId;
|
binlogDumpCmd.slaveServerId = this.slaveId;
|
||||||
byte[] cmdBody = binlogDumpCmd.toBytes();
|
byte[] cmdBody = binlogDumpCmd.toBytes();
|
||||||
|
|
||||||
@@ -148,9 +164,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
||||||
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
||||||
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
PacketManager.write(connector.getChannel(), new ByteBuffer[] { ByteBuffer.wrap(binlogDumpHeader.toBytes()),
|
PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody);
|
||||||
ByteBuffer.wrap(cmdBody) });
|
|
||||||
|
|
||||||
connector.setDumping(true);
|
connector.setDumping(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +173,8 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
connection.setCharset(getCharset());
|
connection.setCharset(getCharset());
|
||||||
connection.setSlaveId(getSlaveId());
|
connection.setSlaveId(getSlaveId());
|
||||||
connection.setConnector(connector.fork());
|
connection.setConnector(connector.fork());
|
||||||
|
// set authInfo
|
||||||
|
connection.setAuthInfo(authInfo);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,25 +195,25 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
try {
|
try {
|
||||||
update("set wait_timeout=9999999");
|
update("set wait_timeout=9999999");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update wait_timeout failed", e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
update("set net_write_timeout=1800");
|
update("set net_write_timeout=1800");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update net_write_timeout failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
update("set net_read_timeout=1800");
|
update("set net_read_timeout=1800");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update net_read_timeout failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
|
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
|
||||||
update("set names 'binary'");
|
update("set names 'binary'");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update names failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -205,12 +221,9 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
// 如果不设置会出现错误: Slave can not handle replication events with the
|
// 如果不设置会出现错误: Slave can not handle replication events with the
|
||||||
// checksum that master is configured to log
|
// checksum that master is configured to log
|
||||||
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
||||||
// '@@global.binlog_checksum'需要去掉单引号,在mysql 5.6.29下导致master退出
|
update("set @master_binlog_checksum= '@@global.binlog_checksum'");
|
||||||
update("set @master_binlog_checksum= @@global.binlog_checksum");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
|
logger.warn("update master_binlog_checksum failed", e);
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -219,7 +232,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
update("set @slave_uuid=uuid()");
|
update("set @slave_uuid=uuid()");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
|
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update slave_uuid failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +240,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
// mariadb针对特殊的类型,需要设置session变量
|
// mariadb针对特殊的类型,需要设置session变量
|
||||||
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update mariadb_slave_capability failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,28 +291,6 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取主库checksum信息
|
|
||||||
* https://dev.mysql.com/doc/refman/5.6/en/replication-options
|
|
||||||
* -binary-log.html#option_mysqld_binlog-checksum
|
|
||||||
*/
|
|
||||||
private void loadBinlogChecksum() {
|
|
||||||
ResultSetPacket rs = null;
|
|
||||||
try {
|
|
||||||
rs = query("select @master_binlog_checksum");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new CanalParseException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> columnValues = rs.getFieldValues();
|
|
||||||
if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0) != null
|
|
||||||
&& columnValues.get(0).toUpperCase().equals("CRC32")) {
|
|
||||||
binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32;
|
|
||||||
} else {
|
|
||||||
binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static enum BinlogFormat {
|
public static enum BinlogFormat {
|
||||||
|
|
||||||
STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED");
|
STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED");
|
||||||
@@ -419,4 +410,23 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
return binlogImage;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -10,7 +12,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
@@ -27,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.MysqlConnection.BinlogImage;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
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.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.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
@@ -118,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);
|
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,19 +245,19 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
((HeartBeatCallback) haController).onFailed(e);
|
((HeartBeatCallback) haController).onFailed(e);
|
||||||
}
|
}
|
||||||
reconnect = true;
|
reconnect = true;
|
||||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
logger.warn("connect failed by ", e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||||
((HeartBeatCallback) haController).onFailed(e);
|
((HeartBeatCallback) haController).onFailed(e);
|
||||||
}
|
}
|
||||||
reconnect = true;
|
reconnect = true;
|
||||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
logger.warn("connect failed by ", e);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||||
((HeartBeatCallback) haController).onFailed(e);
|
((HeartBeatCallback) haController).onFailed(e);
|
||||||
}
|
}
|
||||||
reconnect = true;
|
reconnect = true;
|
||||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
logger.warn("connect failed by ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -307,10 +315,35 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
connection.getConnector().setSendBufferSize(sendBufferSize);
|
connection.getConnector().setSendBufferSize(sendBufferSize);
|
||||||
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
||||||
connection.setCharset(connectionCharset);
|
connection.setCharset(connectionCharset);
|
||||||
|
// 随机生成slaveId
|
||||||
|
if (this.slaveId <= 0) {
|
||||||
|
this.slaveId = generateUniqueServerId();
|
||||||
|
}
|
||||||
connection.setSlaveId(this.slaveId);
|
connection.setSlaveId(this.slaveId);
|
||||||
return connection;
|
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 {
|
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
|
||||||
EntryPosition startPosition = findStartPositionInternal(connection);
|
EntryPosition startPosition = findStartPositionInternal(connection);
|
||||||
if (needTransactionPosition.get()) {
|
if (needTransactionPosition.get()) {
|
||||||
@@ -333,6 +366,34 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return endPosition;
|
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) {
|
protected EntryPosition findStartPositionInternal(ErosaConnection connection) {
|
||||||
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||||
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
||||||
@@ -346,7 +407,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entryPosition == null) {
|
if (entryPosition == null) {
|
||||||
entryPosition = findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
entryPosition = findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断一下是否需要按时间订阅
|
// 判断一下是否需要按时间订阅
|
||||||
@@ -358,13 +419,15 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
|
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
|
||||||
} else {
|
} else {
|
||||||
logger.warn("prepare to find start position just show master status");
|
logger.warn("prepare to find start position just show master status");
|
||||||
return findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
return findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
|
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
|
||||||
// 如果指定binlogName + offest,直接返回
|
// 如果指定binlogName + offest,直接返回
|
||||||
|
entryPosition = findPositionWithMasterIdAndTimestamp(mysqlConnection, entryPosition);
|
||||||
logger.warn("prepare to find start position {}:{}:{}",
|
logger.warn("prepare to find start position {}:{}:{}",
|
||||||
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(), "" });
|
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(),
|
||||||
|
entryPosition.getTimestamp() });
|
||||||
return entryPosition;
|
return entryPosition;
|
||||||
} else {
|
} else {
|
||||||
EntryPosition specificLogFilePosition = null;
|
EntryPosition specificLogFilePosition = null;
|
||||||
@@ -438,7 +501,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
|
|
||||||
public boolean sink(LogEvent event) {
|
public boolean sink(LogEvent event) {
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -471,7 +534,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
|
|
||||||
public boolean sink(LogEvent event) {
|
public boolean sink(LogEvent event) {
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -549,9 +612,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return entryPosition;
|
return entryPosition;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("the binlogfile:{} doesn't exist, to continue to search the next binlogfile , caused by {}",
|
logger.warn(String.format("the binlogfile:%s doesn't exist, to continue to search the next binlogfile , caused by",
|
||||||
startSearchBinlogFile,
|
startSearchBinlogFile),
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
e);
|
||||||
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
|
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
|
||||||
if (binlogSeqNum <= 1) {
|
if (binlogSeqNum <= 1) {
|
||||||
logger.warn("Didn't find the corresponding binlog files");
|
logger.warn("Didn't find the corresponding binlog files");
|
||||||
@@ -683,7 +746,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
public boolean sink(LogEvent event) {
|
public boolean sink(LogEvent event) {
|
||||||
EntryPosition entryPosition = null;
|
EntryPosition entryPosition = null;
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -691,6 +754,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
String logfilename = entry.getHeader().getLogfileName();
|
String logfilename = entry.getHeader().getLogfileName();
|
||||||
Long logfileoffset = entry.getHeader().getLogfileOffset();
|
Long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||||
Long logposTimestamp = entry.getHeader().getExecuteTime();
|
Long logposTimestamp = entry.getHeader().getExecuteTime();
|
||||||
|
Long serverId = entry.getHeader().getServerId();
|
||||||
|
|
||||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
||||||
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||||
@@ -703,7 +767,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.equals(endPosition.getJournalName(), logfilename)
|
if (StringUtils.equals(endPosition.getJournalName(), logfilename)
|
||||||
&& endPosition.getPosition() <= (logfileoffset + event.getEventLen())) {
|
&& endPosition.getPosition() <= logfileoffset) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,15 +775,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// position = current +
|
// position = current +
|
||||||
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
||||||
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||||
entryPosition = new EntryPosition(logfilename,
|
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||||
logfileoffset + event.getEventLen(),
|
|
||||||
logposTimestamp);
|
|
||||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
entryPosition);
|
entryPosition);
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
} 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...",
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
entryPosition);
|
entryPosition);
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ package com.alibaba.otter.canal.parse.inbound.mysql.dbsync;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.ClosedByInterruptException;
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogFetcher;
|
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 {
|
private final boolean fetch0(final int off, final int len) throws IOException {
|
||||||
ensureCapacity(off + len);
|
ensureCapacity(off + len);
|
||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(this.buffer, off, len);
|
byte[] read = channel.read(len);
|
||||||
while (buffer.hasRemaining()) {
|
System.arraycopy(read, 0, this.buffer, off, len);
|
||||||
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;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (limit < off + len) limit = off + len;
|
if (limit < off + len) limit = off + len;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.List;
|
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.BinlogParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
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.Column;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
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.TransactionBegin;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd;
|
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Type;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Type;
|
||||||
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
||||||
@@ -86,8 +90,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
private boolean filterTableError = false;
|
private boolean filterTableError = false;
|
||||||
// 新增rows过滤,用于仅订阅除rows以外的数据
|
// 新增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) {
|
if (logEvent == null || logEvent instanceof UnknownLogEvent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -98,7 +104,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
binlogFileName = ((RotateLogEvent) logEvent).getFilename();
|
binlogFileName = ((RotateLogEvent) logEvent).getFilename();
|
||||||
break;
|
break;
|
||||||
case LogEvent.QUERY_EVENT:
|
case LogEvent.QUERY_EVENT:
|
||||||
return parseQueryEvent((QueryLogEvent) logEvent);
|
return parseQueryEvent((QueryLogEvent) logEvent, isSeek);
|
||||||
case LogEvent.XID_EVENT:
|
case LogEvent.XID_EVENT:
|
||||||
return parseXidEvent((XidLogEvent) logEvent);
|
return parseXidEvent((XidLogEvent) logEvent);
|
||||||
case LogEvent.TABLE_MAP_EVENT:
|
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();
|
String queryString = event.getQuery();
|
||||||
if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
||||||
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
||||||
@@ -148,106 +154,139 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
||||||
} else {
|
} else {
|
||||||
// DDL语句处理
|
boolean notFilter = false;
|
||||||
DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName());
|
|
||||||
|
|
||||||
String schemaName = event.getDbName();
|
|
||||||
if (StringUtils.isNotEmpty(result.getSchemaName())) {
|
|
||||||
schemaName = result.getSchemaName();
|
|
||||||
}
|
|
||||||
|
|
||||||
String tableName = result.getTableName();
|
|
||||||
EventType type = EventType.QUERY;
|
EventType type = EventType.QUERY;
|
||||||
// fixed issue https://github.com/alibaba/canal/issues/58
|
String tableName = null;
|
||||||
if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE
|
String schemaName = null;
|
||||||
|| result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE
|
if (useDruidDdlFilter) {
|
||||||
|| result.getType() == EventType.RENAME || result.getType() == EventType.CINDEX
|
List<DdlResult> results = DruidDdlParser.parse(queryString, event.getDbName());
|
||||||
|| result.getType() == EventType.DINDEX) { // 针对DDL类型
|
for (DdlResult result : results) {
|
||||||
|
if (!processFilter(queryString, result)) {
|
||||||
if (filterQueryDdl) {
|
// 只要有一个数据不进行过滤
|
||||||
return null;
|
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();
|
type = result.getType();
|
||||||
if (StringUtils.isEmpty(tableName)
|
schemaName = result.getSchemaName();
|
||||||
|| (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) {
|
tableName = result.getTableName();
|
||||||
// 如果解析不出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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameBlackFilter != null && nameBlackFilter.filter(name)) {
|
if (!notFilter) {
|
||||||
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) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新下table meta cache
|
if (!isSeek) {
|
||||||
if (tableMetaCache != null
|
// 使用新的表结构元数据管理方式
|
||||||
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
for (DdlResult renameResult = result; renameResult != null; renameResult = renameResult.getRenameTableResult()) {
|
tableMetaCache.apply(position, event.getDbName(), queryString, null);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
|
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
if (result.getType() != EventType.QUERY) {
|
if (type != EventType.QUERY) {
|
||||||
rowChangeBuider.setIsDdl(true);
|
rowChangeBuider.setIsDdl(true);
|
||||||
}
|
}
|
||||||
rowChangeBuider.setSql(queryString);
|
rowChangeBuider.setSql(queryString);
|
||||||
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
|
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
|
||||||
rowChangeBuider.setDdlSchemaName(event.getDbName());
|
rowChangeBuider.setDdlSchemaName(event.getDbName());
|
||||||
}
|
}
|
||||||
rowChangeBuider.setEventType(result.getType());
|
rowChangeBuider.setEventType(type);
|
||||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
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) {
|
private Entry parseRowsQueryEvent(RowsQueryLogEvent event) {
|
||||||
if (filterQueryDml) {
|
if (filterQueryDml) {
|
||||||
return null;
|
return null;
|
||||||
@@ -256,7 +295,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
String queryString = null;
|
String queryString = null;
|
||||||
try {
|
try {
|
||||||
queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name());
|
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) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new CanalParseException(e);
|
throw new CanalParseException(e);
|
||||||
}
|
}
|
||||||
@@ -317,6 +364,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
|
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();
|
String fullname = table.getDbName() + "." + table.getTableName();
|
||||||
// check name filter
|
// check name filter
|
||||||
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
||||||
@@ -326,9 +377,23 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tableMetaCache.isOnRDS() && "mysql.ha_health_check".equals(fullname)) {
|
// if (isHeartBeat || isRDSHeartBeat) {
|
||||||
// 忽略rds模式的mysql.ha_health_check心跳数据
|
// // 忽略rds模式的mysql.ha_health_check心跳数据
|
||||||
return null;
|
// 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;
|
EventType eventType = null;
|
||||||
@@ -348,6 +413,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
table.getDbName(),
|
table.getDbName(),
|
||||||
table.getTableName(),
|
table.getTableName(),
|
||||||
eventType);
|
eventType);
|
||||||
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setTableId(event.getTableId());
|
rowChangeBuider.setTableId(event.getTableId());
|
||||||
rowChangeBuider.setIsDdl(false);
|
rowChangeBuider.setIsDdl(false);
|
||||||
@@ -357,9 +423,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
BitSet columns = event.getColumns();
|
BitSet columns = event.getColumns();
|
||||||
BitSet changeColumns = event.getChangeColumns();
|
BitSet changeColumns = event.getChangeColumns();
|
||||||
boolean tableError = false;
|
boolean tableError = false;
|
||||||
TableMeta tableMeta = null;
|
if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
|
||||||
if (tableMetaCache != null) {// 入错存在table meta cache
|
// cache
|
||||||
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true);
|
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
|
||||||
if (tableMeta == null) {
|
if (tableMeta == null) {
|
||||||
tableError = true;
|
tableError = true;
|
||||||
if (!filterTableError) {
|
if (!filterTableError) {
|
||||||
@@ -405,6 +471,13 @@ 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,
|
private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols,
|
||||||
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
|
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
|
||||||
int columnCnt = event.getTable().getColumnCnt();
|
int columnCnt = event.getTable().getColumnCnt();
|
||||||
@@ -413,18 +486,19 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
boolean tableError = false;
|
boolean tableError = false;
|
||||||
// check table fileds count,只能处理加字段
|
// check table fileds count,只能处理加字段
|
||||||
boolean existRDSNoPrimaryKey = false;
|
boolean existRDSNoPrimaryKey = false;
|
||||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
|
||||||
if (tableMetaCache.isOnRDS()) {
|
if (tableMetaCache.isOnRDS()) {
|
||||||
// 特殊处理下RDS的场景
|
// 特殊处理下RDS的场景
|
||||||
List<FieldMeta> primaryKeys = tableMeta.getPrimaryFields();
|
List<FieldMeta> primaryKeys = tableMeta.getPrimaryFields();
|
||||||
if (primaryKeys == null || primaryKeys.isEmpty()) {
|
if (primaryKeys == null || primaryKeys.isEmpty()) {
|
||||||
if (columnInfo.length == tableMeta.getFileds().size() + 1
|
if (columnInfo.length == tableMeta.getFields().size() + 1
|
||||||
&& columnInfo[columnInfo.length - 1].type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
&& columnInfo[columnInfo.length - 1].type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||||
existRDSNoPrimaryKey = true;
|
existRDSNoPrimaryKey = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
if (!existRDSNoPrimaryKey) {
|
if (!existRDSNoPrimaryKey) {
|
||||||
// online ddl增加字段操作步骤:
|
// online ddl增加字段操作步骤:
|
||||||
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
||||||
@@ -432,7 +506,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
||||||
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
||||||
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
||||||
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false);// 强制重新获取一次
|
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false, position);// 强制重新获取一次
|
||||||
if (tableMeta == null) {
|
if (tableMeta == null) {
|
||||||
tableError = true;
|
tableError = true;
|
||||||
if (!filterTableError) {
|
if (!filterTableError) {
|
||||||
@@ -442,11 +516,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 在做一次判断
|
// 在做一次判断
|
||||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
|
||||||
tableError = true;
|
tableError = true;
|
||||||
if (!filterTableError) {
|
if (!filterTableError) {
|
||||||
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName()
|
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName()
|
||||||
+ "," + columnInfo.length + " vs " + tableMeta.getFileds().size());
|
+ "," + columnInfo.length + " vs " + tableMeta.getFields().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -473,7 +547,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
FieldMeta fieldMeta = null;
|
FieldMeta fieldMeta = null;
|
||||||
if (tableMeta != null && !tableError) {
|
if (tableMeta != null && !tableError) {
|
||||||
// 处理file meta
|
// 处理file meta
|
||||||
fieldMeta = tableMeta.getFileds().get(i);
|
fieldMeta = tableMeta.getFields().get(i);
|
||||||
columnBuilder.setName(fieldMeta.getColumnName());
|
columnBuilder.setName(fieldMeta.getColumnName());
|
||||||
columnBuilder.setIsKey(fieldMeta.isKey());
|
columnBuilder.setIsKey(fieldMeta.isKey());
|
||||||
// 增加mysql type类型,issue 73
|
// 增加mysql type类型,issue 73
|
||||||
@@ -614,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) {
|
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
|
||||||
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
|
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
@@ -672,9 +754,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache) {
|
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) {
|
||||||
try {
|
try {
|
||||||
return tableMetaCache.getTableMeta(dbName, tbName, useCache);
|
return tableMetaCache.getTableMeta(dbName, tbName, useCache, position);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String message = ExceptionUtils.getRootCauseMessage(e);
|
String message = ExceptionUtils.getRootCauseMessage(e);
|
||||||
if (filterTableError) {
|
if (filterTableError) {
|
||||||
@@ -691,6 +773,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return "LONGTEXT".equalsIgnoreCase(columnType) || "MEDIUMTEXT".equalsIgnoreCase(columnType)
|
return "LONGTEXT".equalsIgnoreCase(columnType) || "MEDIUMTEXT".equalsIgnoreCase(columnType)
|
||||||
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".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) {
|
public static TransactionBegin createTransactionBegin(long threadId) {
|
||||||
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
|
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
|
||||||
|
|||||||
@@ -14,48 +14,58 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
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.MysqlConnection;
|
||||||
import com.google.common.base.Function;
|
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||||
import com.google.common.collect.MigrateMap;
|
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解析和缓存
|
* 处理table meta解析和缓存
|
||||||
*
|
*
|
||||||
* @author jianghang 2013-1-17 下午10:15:16
|
* @author jianghang 2013-1-17 下午10:15:16
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class TableMetaCache {
|
public class TableMetaCache {
|
||||||
|
|
||||||
public static final String COLUMN_NAME = "COLUMN_NAME";
|
public static final String COLUMN_NAME = "COLUMN_NAME";
|
||||||
public static final String COLUMN_TYPE = "COLUMN_TYPE";
|
public static final String COLUMN_TYPE = "COLUMN_TYPE";
|
||||||
public static final String IS_NULLABLE = "IS_NULLABLE";
|
public static final String IS_NULLABLE = "IS_NULLABLE";
|
||||||
public static final String COLUMN_KEY = "COLUMN_KEY";
|
public static final String COLUMN_KEY = "COLUMN_KEY";
|
||||||
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
||||||
public static final String EXTRA = "EXTRA";
|
public static final String EXTRA = "EXTRA";
|
||||||
private MysqlConnection connection;
|
private MysqlConnection connection;
|
||||||
private boolean isOnRDS = false;
|
private boolean isOnRDS = false;
|
||||||
|
|
||||||
|
private TableMetaTSDB tableMetaTSDB;
|
||||||
// 第一层tableId,第二层schema.table,解决tableId重复,对应多张表
|
// 第一层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;
|
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) {
|
@Override
|
||||||
try {
|
public TableMeta load(String name) throws Exception {
|
||||||
return getTableMeta0(name);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// 尝试做一次retry操作
|
|
||||||
try {
|
try {
|
||||||
connection.reconnect();
|
return getTableMetaByDB(name);
|
||||||
return getTableMeta0(name);
|
} catch (CanalParseException e) {
|
||||||
} catch (IOException e1) {
|
// 尝试做一次retry操作
|
||||||
throw new CanalParseException("fetch failed by table meta:" + name, e1);
|
try {
|
||||||
|
connection.reconnect();
|
||||||
|
return getTableMetaByDB(name);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new CanalParseException("fetch failed by table meta:" + name, e1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ResultSetPacket packet = connection.query("show global variables like 'rds\\_%'");
|
ResultSetPacket packet = connection.query("show global variables like 'rds\\_%'");
|
||||||
@@ -66,47 +76,15 @@ public class TableMetaCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TableMeta getTableMeta(String schema, String table) {
|
private TableMeta getTableMetaByDB(String fullname) throws IOException {
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for (String name : removeNames) {
|
|
||||||
// tables.remove(name);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearTableMeta() {
|
|
||||||
tableMetaCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TableMeta getTableMeta0(String fullname) throws IOException {
|
|
||||||
ResultSetPacket packet = connection.query("desc " + fullname);
|
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);
|
Map<String, Integer> nameMaps = new HashMap<String, Integer>(6, 1f);
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -122,9 +100,13 @@ public class TableMetaCache {
|
|||||||
// 做一个优化,使用String.intern(),共享String对象,减少内存使用
|
// 做一个优化,使用String.intern(),共享String对象,减少内存使用
|
||||||
meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern());
|
meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern());
|
||||||
meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size));
|
meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size));
|
||||||
meta.setIsNullable(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i * size));
|
meta.setNullable(StringUtils.equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i
|
||||||
meta.setIskey(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size));
|
* size),
|
||||||
meta.setDefaultValue(packet.getFieldValues().get(nameMaps.get(COLUMN_DEFAULT) + 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));
|
meta.setExtra(packet.getFieldValues().get(nameMaps.get(EXTRA) + i * size));
|
||||||
|
|
||||||
result.add(meta);
|
result.add(meta);
|
||||||
@@ -133,6 +115,98 @@ public class TableMetaCache {
|
|||||||
return result;
|
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) {
|
private String getFullName(String schema, String table) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
return builder.append('`')
|
return builder.append('`')
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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.commons.lang.StringUtils;
|
||||||
import org.apache.oro.text.regex.Perl5Matcher;
|
import org.apache.oro.text.regex.Perl5Matcher;
|
||||||
@@ -218,102 +218,4 @@ public class SimpleDdlParser {
|
|||||||
sql = sb.toString();
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -5,14 +5,12 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
|||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口组合
|
* Created by yinxiu on 17/3/17. Email: marklin.hz@gmail.com
|
||||||
*
|
|
||||||
* @author jianghang 2012-7-7 上午10:02:02
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public interface CanalLogPositionManager extends CanalLifeCycle {
|
public interface CanalLogPositionManager extends CanalLifeCycle {
|
||||||
|
|
||||||
LogPosition getLatestIndexBy(String destination);
|
LogPosition getLatestIndexBy(String destination);
|
||||||
|
|
||||||
void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException;
|
void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,83 @@
|
|||||||
package com.alibaba.otter.canal.parse.index;
|
package com.alibaba.otter.canal.parse.index;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Created by yinxiu on 17/3/18. Email: marklin.hz@gmail.com
|
||||||
* 实现基于failover查找的机制完成meta的操作
|
* 实现基于failover查找的机制完成meta的操作
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* 应用场景:比如针对内存buffer,出现HA切换,先尝试从内存buffer区中找到lastest position,如果不存在才尝试找一下meta里消费的信息
|
* 应用场景:比如针对内存buffer,出现HA切换,先尝试从内存buffer区中找到lastest position,如果不存在才尝试找一下meta里消费的信息
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
|
||||||
* @author jianghang 2012-7-20 下午02:33:20
|
|
||||||
*/
|
*/
|
||||||
public class FailbackLogPositionManager extends AbstractCanalLifeCycle implements CanalLogPositionManager {
|
public class FailbackLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
private CanalLogPositionManager primary;
|
private final static Logger logger = LoggerFactory.getLogger(FailbackLogPositionManager.class);
|
||||||
private CanalLogPositionManager failback;
|
|
||||||
|
|
||||||
|
private final CanalLogPositionManager primary;
|
||||||
|
private final CanalLogPositionManager secondary;
|
||||||
|
|
||||||
|
public FailbackLogPositionManager(CanalLogPositionManager primary, CanalLogPositionManager secondary){
|
||||||
|
if (primary == null) {
|
||||||
|
throw new NullPointerException("nul primary LogPositionManager");
|
||||||
|
}
|
||||||
|
if (secondary == null) {
|
||||||
|
throw new NullPointerException("nul secondary LogPositionManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.primary = primary;
|
||||||
|
this.secondary = secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
Assert.notNull(primary);
|
|
||||||
Assert.notNull(failback);
|
|
||||||
|
|
||||||
if (!primary.isStart()) {
|
if (!primary.isStart()) {
|
||||||
primary.start();
|
primary.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failback.isStart()) {
|
if (!secondary.isStart()) {
|
||||||
failback.start();
|
secondary.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
|
if (secondary.isStart()) {
|
||||||
|
secondary.stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (primary.isStart()) {
|
if (primary.isStart()) {
|
||||||
primary.stop();
|
primary.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failback.isStart()) {
|
|
||||||
failback.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
LogPosition logPosition = primary.getLatestIndexBy(destination);
|
LogPosition logPosition = primary.getLatestIndexBy(destination);
|
||||||
if (logPosition == null) {
|
if (logPosition != null) {
|
||||||
return failback.getLatestIndexBy(destination);
|
|
||||||
} else {
|
|
||||||
return logPosition;
|
return logPosition;
|
||||||
}
|
}
|
||||||
|
return secondary.getLatestIndexBy(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
try {
|
try {
|
||||||
primary.persistLogPosition(destination, logPosition);
|
primary.persistLogPosition(destination, logPosition);
|
||||||
} catch (CanalParseException e) {
|
} catch (CanalParseException e) {
|
||||||
failback.persistLogPosition(destination, logPosition);
|
logger.warn("persistLogPosition use primary log position manager exception. destination: {}, logPosition: {}",
|
||||||
|
destination,
|
||||||
|
logPosition,
|
||||||
|
e);
|
||||||
|
secondary.persistLogPosition(destination, logPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPrimary(CanalLogPositionManager primary) {
|
|
||||||
this.primary = primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFailback(CanalLogPositionManager failback) {
|
|
||||||
this.failback = failback;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,45 +16,73 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
import com.alibaba.otter.canal.meta.exception.CanalMetaManagerException;
|
import com.alibaba.otter.canal.meta.exception.CanalMetaManagerException;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.MigrateMap;
|
import com.google.common.collect.MigrateMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于文件刷新的log position实现
|
* Created by yinxiu on 17/3/18. Email: marklin.hz@gmail.com 基于文件刷新的log
|
||||||
*
|
* position实现
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* 策略:
|
* 策略:
|
||||||
* 1. 先写内存,然后定时刷新数据到File
|
* 1. 先写内存,然后定时刷新数据到File
|
||||||
* 2. 数据采取overwrite模式(只保留最后一次)
|
* 2. 数据采取overwrite模式(只保留最后一次)
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
|
||||||
* @author jianghang 2013-4-15 下午09:40:48
|
|
||||||
* @version 1.0.4
|
|
||||||
*/
|
*/
|
||||||
public class FileMixedLogPositionManager extends MemoryLogPositionManager {
|
public class FileMixedLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(FileMixedLogPositionManager.class);
|
||||||
|
private final static Charset charset = Charset.forName("UTF-8");
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FileMixedLogPositionManager.class);
|
|
||||||
private static final Charset charset = Charset.forName("UTF-8");
|
|
||||||
private File dataDir;
|
private File dataDir;
|
||||||
private String dataFileName = "parse.dat";
|
|
||||||
private Map<String, File> dataFileCaches;
|
private Map<String, File> dataFileCaches;
|
||||||
private ScheduledExecutorService executor;
|
|
||||||
|
private ScheduledExecutorService executorService;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private final LogPosition nullPosition = new LogPosition() {
|
private final LogPosition nullPosition = new LogPosition() {
|
||||||
};
|
};
|
||||||
|
|
||||||
private long period = 1000; // 单位ms
|
private MemoryLogPositionManager memoryLogPositionManager;
|
||||||
|
|
||||||
|
private long period;
|
||||||
private Set<String> persistTasks;
|
private Set<String> persistTasks;
|
||||||
|
|
||||||
|
public FileMixedLogPositionManager(File dataDir, long period, MemoryLogPositionManager memoryLogPositionManager){
|
||||||
|
if (dataDir == null) {
|
||||||
|
throw new NullPointerException("null dataDir");
|
||||||
|
}
|
||||||
|
if (period <= 0) {
|
||||||
|
throw new IllegalArgumentException("period must be positive, given: " + period);
|
||||||
|
}
|
||||||
|
if (memoryLogPositionManager == null) {
|
||||||
|
throw new NullPointerException("null memoryLogPositionManager");
|
||||||
|
}
|
||||||
|
this.dataDir = dataDir;
|
||||||
|
this.period = period;
|
||||||
|
this.memoryLogPositionManager = memoryLogPositionManager;
|
||||||
|
|
||||||
|
this.dataFileCaches = MigrateMap.makeComputingMap(new Function<String, File>() {
|
||||||
|
|
||||||
|
public File apply(String destination) {
|
||||||
|
return getDataFile(destination);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.executorService = Executors.newScheduledThreadPool(1);
|
||||||
|
this.persistTasks = Collections.synchronizedSet(new HashSet<String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
Assert.notNull(dataDir);
|
|
||||||
if (!dataDir.exists()) {
|
if (!dataDir.exists()) {
|
||||||
try {
|
try {
|
||||||
FileUtils.forceMkdir(dataDir);
|
FileUtils.forceMkdir(dataDir);
|
||||||
@@ -67,30 +95,12 @@ public class FileMixedLogPositionManager extends MemoryLogPositionManager {
|
|||||||
throw new CanalMetaManagerException("dir[" + dataDir.getPath() + "] can not read/write");
|
throw new CanalMetaManagerException("dir[" + dataDir.getPath() + "] can not read/write");
|
||||||
}
|
}
|
||||||
|
|
||||||
dataFileCaches = MigrateMap.makeComputingMap(new Function<String, File>() {
|
if (!memoryLogPositionManager.isStart()) {
|
||||||
|
memoryLogPositionManager.start();
|
||||||
public File apply(String destination) {
|
}
|
||||||
return getDataFile(destination);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
executor = Executors.newScheduledThreadPool(1);
|
|
||||||
positions = MigrateMap.makeComputingMap(new Function<String, LogPosition>() {
|
|
||||||
|
|
||||||
public LogPosition apply(String destination) {
|
|
||||||
LogPosition logPosition = loadDataFromFile(dataFileCaches.get(destination));
|
|
||||||
if (logPosition == null) {
|
|
||||||
return nullPosition;
|
|
||||||
} else {
|
|
||||||
return logPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
persistTasks = Collections.synchronizedSet(new HashSet<String>());
|
|
||||||
|
|
||||||
// 启动定时工作任务
|
// 启动定时工作任务
|
||||||
executor.scheduleAtFixedRate(new Runnable() {
|
executorService.scheduleAtFixedRate(new Runnable() {
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
List<String> tasks = new ArrayList<String>(persistTasks);
|
List<String> tasks = new ArrayList<String>(persistTasks);
|
||||||
@@ -106,28 +116,35 @@ public class FileMixedLogPositionManager extends MemoryLogPositionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, period, period, TimeUnit.MILLISECONDS);
|
}, period, period, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
flushDataToFile();
|
flushDataToFile();
|
||||||
executor.shutdownNow();
|
executorService.shutdown();
|
||||||
positions.clear();
|
memoryLogPositionManager.stop();
|
||||||
}
|
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
|
||||||
persistTasks.add(destination);// 添加到任务队列中进行触发
|
|
||||||
super.persistLogPosition(destination, logPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
LogPosition logPostion = super.getLatestIndexBy(destination);
|
LogPosition logPosition = memoryLogPositionManager.getLatestIndexBy(destination);
|
||||||
if (logPostion == nullPosition) {
|
if (logPosition != null) {
|
||||||
return null;
|
return logPosition;
|
||||||
} else {
|
|
||||||
return logPostion;
|
|
||||||
}
|
}
|
||||||
|
logPosition = loadDataFromFile(dataFileCaches.get(destination));
|
||||||
|
if (logPosition == null) {
|
||||||
|
return nullPosition;
|
||||||
|
}
|
||||||
|
return logPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
persistTasks.add(destination);
|
||||||
|
memoryLogPositionManager.persistLogPosition(destination, logPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================ helper method ======================
|
// ============================ helper method ======================
|
||||||
@@ -142,11 +159,12 @@ public class FileMixedLogPositionManager extends MemoryLogPositionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String dataFileName = "parse.dat";
|
||||||
return new File(destinationMetaDir, dataFileName);
|
return new File(destinationMetaDir, dataFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushDataToFile() {
|
private void flushDataToFile() {
|
||||||
for (String destination : positions.keySet()) {
|
for (String destination : memoryLogPositionManager.destinations()) {
|
||||||
flushDataToFile(destination);
|
flushDataToFile(destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +174,7 @@ public class FileMixedLogPositionManager extends MemoryLogPositionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void flushDataToFile(String destination, File dataFile) {
|
private void flushDataToFile(String destination, File dataFile) {
|
||||||
LogPosition position = positions.get(destination);
|
LogPosition position = memoryLogPositionManager.getLatestIndexBy(destination);
|
||||||
if (position != null && position != nullPosition) {
|
if (position != null && position != nullPosition) {
|
||||||
String json = JsonUtils.marshalToString(position);
|
String json = JsonUtils.marshalToString(position);
|
||||||
try {
|
try {
|
||||||
@@ -179,16 +197,4 @@ public class FileMixedLogPositionManager extends MemoryLogPositionManager {
|
|||||||
throw new CanalMetaManagerException(e);
|
throw new CanalMetaManagerException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDataDir(String dataDir) {
|
|
||||||
this.dataDir = new File(dataDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDataDir(File dataDir) {
|
|
||||||
this.dataDir = dataDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPeriod(long period) {
|
|
||||||
this.period = period;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,43 @@
|
|||||||
package com.alibaba.otter.canal.parse.index;
|
package com.alibaba.otter.canal.parse.index;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
import com.google.common.collect.MapMaker;
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于内存的实现
|
* Created by yinxiu on 17/3/17. Email: marklin.hz@gmail.com
|
||||||
*
|
|
||||||
* @author jianghang 2012-7-7 上午10:17:23
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public class MemoryLogPositionManager extends AbstractCanalLifeCycle implements CanalLogPositionManager {
|
public class MemoryLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
protected Map<String, LogPosition> positions;
|
private Map<String, LogPosition> positions;
|
||||||
|
|
||||||
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
positions = new MapMaker().makeMap();
|
positions = new MapMaker().makeMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
positions.clear();
|
positions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
return positions.get(destination);
|
return positions.get(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
positions.put(destination, logPosition);
|
positions.put(destination, logPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> destinations() {
|
||||||
|
return positions.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,52 +4,56 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
|
||||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
import com.alibaba.otter.canal.store.helper.CanalEventUtils;
|
import com.alibaba.otter.canal.store.helper.CanalEventUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于meta信息的实现
|
* Created by yinxiu on 17/3/18. Email: marklin.hz@gmail.com
|
||||||
*
|
|
||||||
* @author jianghang 2012-7-10 下午05:02:33
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public class MetaLogPositionManager extends AbstractCanalLifeCycle implements CanalLogPositionManager {
|
public class MetaLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MetaLogPositionManager.class);
|
private final static Logger logger = LoggerFactory.getLogger(MetaLogPositionManager.class);
|
||||||
private CanalMetaManager metaManager;
|
|
||||||
|
|
||||||
public void start() {
|
private final CanalMetaManager metaManager;
|
||||||
super.start();
|
|
||||||
Assert.notNull(metaManager);
|
public MetaLogPositionManager(CanalMetaManager metaManager){
|
||||||
if (!metaManager.isStart()) {
|
if (metaManager == null) {
|
||||||
metaManager.start();
|
throw new NullPointerException("null metaManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.metaManager = metaManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
if (metaManager.isStart()) {
|
if (metaManager.isStart()) {
|
||||||
metaManager.stop();
|
metaManager.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
@Override
|
||||||
// do nothing
|
public void start() {
|
||||||
logger.info("persist LogPosition:{}", destination, logPosition);
|
super.start();
|
||||||
|
|
||||||
|
if (!metaManager.isStart()) {
|
||||||
|
metaManager.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
|
List<ClientIdentity> clientIdentities = metaManager.listAllSubscribeInfo(destination);
|
||||||
LogPosition result = null;
|
LogPosition result = null;
|
||||||
if (!CollectionUtils.isEmpty(clientIdentitys)) {
|
if (!CollectionUtils.isEmpty(clientIdentities)) {
|
||||||
// 尝试找到一个最小的logPosition
|
// 尝试找到一个最小的logPosition
|
||||||
for (ClientIdentity clientIdentity : clientIdentitys) {
|
for (ClientIdentity clientIdentity : clientIdentities) {
|
||||||
LogPosition position = (LogPosition) metaManager.getCursor(clientIdentity);
|
LogPosition position = (LogPosition) metaManager.getCursor(clientIdentity);
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
continue;
|
continue;
|
||||||
@@ -66,8 +70,9 @@ public class MetaLogPositionManager extends AbstractCanalLifeCycle implements Ca
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMetaManager(CanalMetaManager metaManager) {
|
@Override
|
||||||
this.metaManager = metaManager;
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
// do nothing
|
||||||
|
logger.info("persist LogPosition:{}", destination, logPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,86 +5,83 @@ import java.util.concurrent.Executors;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.MigrateMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 混合memory + zookeeper的存储模式
|
* Created by yinxiu on 17/3/17. Email: marklin.hz@gmail.com Memory first.
|
||||||
*
|
* Asynchronous commit position info to ZK.
|
||||||
* @author jianghang 2012-7-7 上午10:33:19
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public class MixedLogPositionManager extends MemoryLogPositionManager implements CanalLogPositionManager {
|
public class MixedLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MixedLogPositionManager.class);
|
private final Logger logger = LoggerFactory.getLogger(MixedLogPositionManager.class);
|
||||||
private ZooKeeperLogPositionManager zooKeeperLogPositionManager;
|
|
||||||
private ExecutorService executor;
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
private final LogPosition nullPosition = new LogPosition() {
|
|
||||||
};
|
|
||||||
|
|
||||||
|
private final MemoryLogPositionManager memoryLogPositionManager;
|
||||||
|
private final ZooKeeperLogPositionManager zooKeeperLogPositionManager;
|
||||||
|
|
||||||
|
private final ExecutorService executor;
|
||||||
|
|
||||||
|
public MixedLogPositionManager(ZkClientx zkClient){
|
||||||
|
if (zkClient == null) {
|
||||||
|
throw new NullPointerException("null zkClient");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.memoryLogPositionManager = new MemoryLogPositionManager();
|
||||||
|
this.zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(zkClient);
|
||||||
|
|
||||||
|
this.executor = Executors.newFixedThreadPool(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
Assert.notNull(zooKeeperLogPositionManager);
|
if (!memoryLogPositionManager.isStart()) {
|
||||||
|
memoryLogPositionManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
if (!zooKeeperLogPositionManager.isStart()) {
|
if (!zooKeeperLogPositionManager.isStart()) {
|
||||||
zooKeeperLogPositionManager.start();
|
zooKeeperLogPositionManager.start();
|
||||||
}
|
}
|
||||||
executor = Executors.newFixedThreadPool(1);
|
|
||||||
positions = MigrateMap.makeComputingMap(new Function<String, LogPosition>() {
|
|
||||||
|
|
||||||
public LogPosition apply(String destination) {
|
|
||||||
LogPosition logPosition = zooKeeperLogPositionManager.getLatestIndexBy(destination);
|
|
||||||
if (logPosition == null) {
|
|
||||||
return nullPosition;
|
|
||||||
} else {
|
|
||||||
return logPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
if (zooKeeperLogPositionManager.isStart()) {
|
executor.shutdown();
|
||||||
zooKeeperLogPositionManager.stop();
|
zooKeeperLogPositionManager.stop();
|
||||||
}
|
memoryLogPositionManager.stop();
|
||||||
executor.shutdownNow();
|
|
||||||
positions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void persistLogPosition(final String destination, final LogPosition logPosition) {
|
@Override
|
||||||
super.persistLogPosition(destination, logPosition);
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
|
LogPosition logPosition = memoryLogPositionManager.getLatestIndexBy(destination);
|
||||||
|
if (logPosition != null) {
|
||||||
|
return logPosition;
|
||||||
|
}
|
||||||
|
logPosition = zooKeeperLogPositionManager.getLatestIndexBy(destination);
|
||||||
|
// 这里保持和重构前的逻辑一致,重新添加到Memory中
|
||||||
|
if (logPosition != null) {
|
||||||
|
memoryLogPositionManager.persistLogPosition(destination, logPosition);
|
||||||
|
}
|
||||||
|
return logPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void persistLogPosition(final String destination, final LogPosition logPosition) throws CanalParseException {
|
||||||
|
memoryLogPositionManager.persistLogPosition(destination, logPosition);
|
||||||
executor.submit(new Runnable() {
|
executor.submit(new Runnable() {
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
zooKeeperLogPositionManager.persistLogPosition(destination, logPosition);
|
zooKeeperLogPositionManager.persistLogPosition(destination, logPosition);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("ERROR # persist to zookeepr has an error", e);
|
logger.error("ERROR # persist to zookeeper has an error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
|
||||||
LogPosition logPosition = super.getLatestIndexBy(destination);
|
|
||||||
if (logPosition == nullPosition) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return logPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== setter / getter ======================
|
|
||||||
|
|
||||||
public void setZooKeeperLogPositionManager(ZooKeeperLogPositionManager zooKeeperLogPositionManager) {
|
|
||||||
this.zooKeeperLogPositionManager = zooKeeperLogPositionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,54 +11,78 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.MigrateMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于定时刷新的策略的mixed实现
|
* Created by yinxiu on 17/3/18. Email: marklin.hz@gmail.com
|
||||||
*
|
|
||||||
* @author jianghang 2012-9-12 上午11:18:14
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public class PeriodMixedLogPositionManager extends MemoryLogPositionManager implements CanalLogPositionManager {
|
public class PeriodMixedLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PeriodMixedLogPositionManager.class);
|
private static final Logger logger = LoggerFactory.getLogger(PeriodMixedLogPositionManager.class);
|
||||||
|
|
||||||
|
private MemoryLogPositionManager memoryLogPositionManager;
|
||||||
private ZooKeeperLogPositionManager zooKeeperLogPositionManager;
|
private ZooKeeperLogPositionManager zooKeeperLogPositionManager;
|
||||||
private ScheduledExecutorService executor;
|
private ScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
private long period;
|
||||||
|
private Set<String> persistTasks;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private final LogPosition nullPosition = new LogPosition() {
|
private final LogPosition nullPosition = new LogPosition() {
|
||||||
};
|
};
|
||||||
|
|
||||||
private long period = 1000; // 单位ms
|
public PeriodMixedLogPositionManager(MemoryLogPositionManager memoryLogPositionManager,
|
||||||
private Set<String> persistTasks;
|
ZooKeeperLogPositionManager zooKeeperLogPositionManager, long period){
|
||||||
|
if (memoryLogPositionManager == null) {
|
||||||
|
throw new NullPointerException("null memoryLogPositionManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zooKeeperLogPositionManager == null) {
|
||||||
|
throw new NullPointerException("null zooKeeperLogPositionManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period <= 0) {
|
||||||
|
throw new IllegalArgumentException("period must be positive, given: " + period);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.memoryLogPositionManager = memoryLogPositionManager;
|
||||||
|
this.zooKeeperLogPositionManager = zooKeeperLogPositionManager;
|
||||||
|
this.period = period;
|
||||||
|
this.persistTasks = Collections.synchronizedSet(new HashSet<String>());
|
||||||
|
this.executorService = Executors.newScheduledThreadPool(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
|
||||||
|
if (zooKeeperLogPositionManager.isStart()) {
|
||||||
|
zooKeeperLogPositionManager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memoryLogPositionManager.isStart()) {
|
||||||
|
memoryLogPositionManager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
Assert.notNull(zooKeeperLogPositionManager);
|
if (!memoryLogPositionManager.isStart()) {
|
||||||
|
memoryLogPositionManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
if (!zooKeeperLogPositionManager.isStart()) {
|
if (!zooKeeperLogPositionManager.isStart()) {
|
||||||
zooKeeperLogPositionManager.start();
|
zooKeeperLogPositionManager.start();
|
||||||
}
|
}
|
||||||
executor = Executors.newScheduledThreadPool(1);
|
|
||||||
positions = MigrateMap.makeComputingMap(new Function<String, LogPosition>() {
|
|
||||||
|
|
||||||
public LogPosition apply(String destination) {
|
|
||||||
LogPosition logPosition = zooKeeperLogPositionManager.getLatestIndexBy(destination);
|
|
||||||
if (logPosition == null) {
|
|
||||||
return nullPosition;
|
|
||||||
} else {
|
|
||||||
return logPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
persistTasks = Collections.synchronizedSet(new HashSet<String>());
|
|
||||||
|
|
||||||
// 启动定时工作任务
|
// 启动定时工作任务
|
||||||
executor.scheduleAtFixedRate(new Runnable() {
|
executorService.scheduleAtFixedRate(new Runnable() {
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
List<String> tasks = new ArrayList<String>(persistTasks);
|
List<String> tasks = new ArrayList<String>(persistTasks);
|
||||||
@@ -76,36 +100,19 @@ public class PeriodMixedLogPositionManager extends MemoryLogPositionManager impl
|
|||||||
}, period, period, TimeUnit.MILLISECONDS);
|
}, period, period, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
@Override
|
||||||
super.stop();
|
|
||||||
|
|
||||||
if (zooKeeperLogPositionManager.isStart()) {
|
|
||||||
zooKeeperLogPositionManager.stop();
|
|
||||||
}
|
|
||||||
executor.shutdownNow();
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
|
||||||
persistTasks.add(destination);// 添加到任务队列中进行触发
|
|
||||||
super.persistLogPosition(destination, logPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
LogPosition logPostion = super.getLatestIndexBy(destination);
|
LogPosition logPosition = memoryLogPositionManager.getLatestIndexBy(destination);
|
||||||
if (logPostion == nullPosition) {
|
if (logPosition == nullPosition) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return logPostion;
|
return logPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZooKeeperLogPositionManager(ZooKeeperLogPositionManager zooKeeperLogPositionManager) {
|
@Override
|
||||||
this.zooKeeperLogPositionManager = zooKeeperLogPositionManager;
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
persistTasks.add(destination);
|
||||||
|
memoryLogPositionManager.persistLogPosition(destination, logPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPeriod(long period) {
|
|
||||||
this.period = period;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
package com.alibaba.otter.canal.parse.index;
|
package com.alibaba.otter.canal.parse.index;
|
||||||
|
|
||||||
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
|
||||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于zk的实现
|
* Created by yinxiu on 17/3/17. Email: marklin.hz@gmail.com
|
||||||
*
|
|
||||||
* @author jianghang 2012-7-7 上午10:08:27
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public class ZooKeeperLogPositionManager extends AbstractCanalLifeCycle implements CanalLogPositionManager {
|
public class ZooKeeperLogPositionManager extends AbstractLogPositionManager {
|
||||||
|
|
||||||
private ZkClientx zkClientx;
|
private final ZkClientx zkClientx;
|
||||||
|
|
||||||
public void start() {
|
public ZooKeeperLogPositionManager(ZkClientx zkClient){
|
||||||
super.start();
|
if (zkClient == null) {
|
||||||
Assert.notNull(zkClientx);
|
throw new NullPointerException("null zkClient");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
this.zkClientx = zkClient;
|
||||||
super.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
String path = ZookeeperPathUtils.getParsePath(destination);
|
String path = ZookeeperPathUtils.getParsePath(destination);
|
||||||
byte[] data = zkClientx.readData(path, true);
|
byte[] data = zkClientx.readData(path, true);
|
||||||
@@ -38,7 +34,8 @@ public class ZooKeeperLogPositionManager extends AbstractCanalLifeCycle implemen
|
|||||||
return JsonUtils.unmarshalFromByte(data, LogPosition.class);
|
return JsonUtils.unmarshalFromByte(data, LogPosition.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
String path = ZookeeperPathUtils.getParsePath(destination);
|
String path = ZookeeperPathUtils.getParsePath(destination);
|
||||||
byte[] data = JsonUtils.marshalToByte(logPosition);
|
byte[] data = JsonUtils.marshalToByte(logPosition);
|
||||||
try {
|
try {
|
||||||
@@ -48,10 +45,4 @@ public class ZooKeeperLogPositionManager extends AbstractCanalLifeCycle implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================== setter / getter =================
|
|
||||||
|
|
||||||
public void setZkClientx(ZkClientx zkClientx) {
|
|
||||||
this.zkClientx = zkClientx;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
22
parse/src/main/resources/ddl/derby/meta_history.sql
Normal file
22
parse/src/main/resources/ddl/derby/meta_history.sql
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
CREATE TABLE meta_history (
|
||||||
|
id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
|
||||||
|
gmt_create timestamp NOT NULL,
|
||||||
|
gmt_modified timestamp NOT NULL,
|
||||||
|
destination varchar(128) DEFAULT NULL,
|
||||||
|
binlog_file varchar(64) DEFAULT NULL,
|
||||||
|
binlog_offest bigint DEFAULT NULL,
|
||||||
|
binlog_master_id varchar(64) DEFAULT NULL,
|
||||||
|
binlog_timestamp bigint DEFAULT NULL,
|
||||||
|
use_schema varchar(1024) DEFAULT NULL,
|
||||||
|
sql_schema varchar(1024) DEFAULT NULL,
|
||||||
|
sql_table varchar(1024) DEFAULT NULL,
|
||||||
|
sql_text clob(16 M) DEFAULT NULL,
|
||||||
|
sql_type varchar(1024) DEFAULT NULL,
|
||||||
|
extra varchar(512) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
CONSTRAINT meta_history_binlog_file_offest UNIQUE (destination,binlog_master_id,binlog_file,binlog_offest)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index meta_history_destination on meta_history(destination);
|
||||||
|
create index meta_history_destination_timestamp on meta_history(destination,binlog_timestamp);
|
||||||
|
create index meta_history_gmt_modified on meta_history(gmt_modified);
|
||||||
18
parse/src/main/resources/ddl/derby/meta_snapshot.sql
Normal file
18
parse/src/main/resources/ddl/derby/meta_snapshot.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE meta_snapshot (
|
||||||
|
id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
|
||||||
|
gmt_create timestamp NOT NULL,
|
||||||
|
gmt_modified timestamp NOT NULL,
|
||||||
|
destination varchar(128) DEFAULT NULL,
|
||||||
|
binlog_file varchar(64) DEFAULT NULL,
|
||||||
|
binlog_offest bigint DEFAULT NULL,
|
||||||
|
binlog_master_id varchar(64) DEFAULT NULL,
|
||||||
|
binlog_timestamp bigint DEFAULT NULL,
|
||||||
|
data clob(16 M) DEFAULT NULL,
|
||||||
|
extra varchar(512) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
CONSTRAINT meta_snapshot_binlog_file_offest UNIQUE (destination,binlog_master_id,binlog_file,binlog_offest)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index meta_snapshot_destination on meta_snapshot(destination);
|
||||||
|
create index meta_snapshot_destination_timestamp on meta_snapshot(destination,binlog_timestamp);
|
||||||
|
create index meta_snapshot_gmt_modified on meta_snapshot(gmt_modified);
|
||||||
21
parse/src/main/resources/ddl/h2/meta_history.sql
Normal file
21
parse/src/main/resources/ddl/h2/meta_history.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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',
|
||||||
|
`sql_schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema',
|
||||||
|
`sql_table` varchar(1024) DEFAULT NULL COMMENT '对应的table',
|
||||||
|
`sql_text` longtext DEFAULT NULL COMMENT '执行的sql',
|
||||||
|
`sql_type` varchar(256) DEFAULT NULL COMMENT 'sql类型',
|
||||||
|
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY meta_history_binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
|
||||||
|
KEY `meta_history_destination` (`destination`),
|
||||||
|
KEY `meta_history_destination_timestamp` (`destination`,`binlog_timestamp`),
|
||||||
|
KEY `meta_history_gmt_modified` (`gmt_modified`)
|
||||||
|
);
|
||||||
17
parse/src/main/resources/ddl/h2/meta_snapshot.sql
Normal file
17
parse/src/main/resources/ddl/h2/meta_snapshot.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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 meta_snapshot_binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
|
||||||
|
KEY `meta_snapshot_destination` (`destination`),
|
||||||
|
KEY `meta_snapshot_destination_timestamp` (`destination`,`binlog_timestamp`),
|
||||||
|
KEY `meta_snapshot_gmt_modified` (`gmt_modified`)
|
||||||
|
);
|
||||||
21
parse/src/main/resources/ddl/mysql/meta_history.sql
Normal file
21
parse/src/main/resources/ddl/mysql/meta_history.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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',
|
||||||
|
`sql_schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema',
|
||||||
|
`sql_table` varchar(1024) DEFAULT NULL COMMENT '对应的table',
|
||||||
|
`sql_text` longtext DEFAULT NULL COMMENT '执行的sql',
|
||||||
|
`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='表结构变化明细表';
|
||||||
17
parse/src/main/resources/ddl/mysql/meta_snapshot.sql
Normal file
17
parse/src/main/resources/ddl/mysql/meta_snapshot.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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='表结构记录表快照表';
|
||||||
@@ -2,7 +2,6 @@ package com.alibaba.otter.canal.parse;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -94,7 +93,6 @@ public class DirectLogFetcherTest {
|
|||||||
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
||||||
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
||||||
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
PacketManager.write(connector.getChannel(), new ByteBuffer[] { ByteBuffer.wrap(binlogDumpHeader.toBytes()),
|
PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody);
|
||||||
ByteBuffer.wrap(cmdBody) });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,40 @@ package com.alibaba.otter.canal.parse.inbound;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
|
||||||
|
|
||||||
public class TableMetaCacheTest {
|
public class TableMetaCacheTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSimple() {
|
public void testSimple() throws IOException {
|
||||||
|
MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello");
|
||||||
MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "xxxxx", "xxxxx");
|
|
||||||
try {
|
try {
|
||||||
connection.connect();
|
connection.connect();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Assert.fail(e.getMessage());
|
Assert.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
TableMetaCache cache = new TableMetaCache(connection);
|
List<ResultSetPacket> packets = connection.queryMulti("show create table test.ljh_test");
|
||||||
TableMeta meta = cache.getTableMeta("otter1", "otter_stability1");
|
String createDDL = null;
|
||||||
Assert.assertNotNull(meta);
|
if (packets.get(0).getFieldValues().size() > 0) {
|
||||||
for (FieldMeta field : meta.getFileds()) {
|
createDDL = packets.get(0).getFieldValues().get(1);
|
||||||
System.out.println("filed :" + field.getColumnName() + " , isKey : " + field.isKey() + " , isNull : "
|
|
||||||
+ field.isNullable());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.out.println(createDDL);
|
||||||
|
|
||||||
|
// TableMetaCache cache = new TableMetaCache(connection);
|
||||||
|
// TableMeta meta = cache.getTableMeta("otter1", "otter_stability1");
|
||||||
|
// Assert.assertNotNull(meta);
|
||||||
|
// for (FieldMeta field : meta.getFields()) {
|
||||||
|
// System.out.println("filed :" + field.getColumnName() + " , isKey : "
|
||||||
|
// + field.isKey() + " , isNull : "
|
||||||
|
// + field.isNullable());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.AbstractBinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.AbstractBinlogParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalLogPositionManager;
|
import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
@@ -70,15 +70,17 @@ public class GroupEventPaserTest {
|
|||||||
mysqlEventPaser.setMasterPosition(defaultPosition);
|
mysqlEventPaser.setMasterPosition(defaultPosition);
|
||||||
mysqlEventPaser.setBinlogParser(buildParser(buildAuthentication()));
|
mysqlEventPaser.setBinlogParser(buildParser(buildAuthentication()));
|
||||||
mysqlEventPaser.setEventSink(new EntryEventSink());
|
mysqlEventPaser.setEventSink(new EntryEventSink());
|
||||||
mysqlEventPaser.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
mysqlEventPaser.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
|
||||||
// System.out.println(logPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
System.out.println(logPosition);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return mysqlEventPaser;
|
return mysqlEventPaser;
|
||||||
}
|
}
|
||||||
@@ -86,8 +88,8 @@ public class GroupEventPaserTest {
|
|||||||
private BinlogParser buildParser(AuthenticationInfo info) {
|
private BinlogParser buildParser(AuthenticationInfo info) {
|
||||||
return new AbstractBinlogParser<LogEvent>() {
|
return new AbstractBinlogParser<LogEvent>() {
|
||||||
|
|
||||||
public Entry parse(LogEvent event) throws CanalParseException {
|
@Override
|
||||||
// return _parser.parse(event);
|
public Entry parse(LogEvent event, boolean isSeek) throws CanalParseException {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import java.util.List;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
|
import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalLogPositionManager;
|
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
@@ -24,11 +25,11 @@ public class LocalBinlogDumpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSimple() {
|
public void testSimple() {
|
||||||
String directory = "/home/jianghang/tmp/binlog";
|
String directory = "/Users/wanshao/projects/canal/parse/src/test/resources/binlog/tsdb";
|
||||||
final LocalBinlogEventParser controller = new LocalBinlogEventParser();
|
final LocalBinlogEventParser controller = new LocalBinlogEventParser();
|
||||||
final EntryPosition startPosition = new EntryPosition("mysql-bin.000006", 4L);
|
final EntryPosition startPosition = new EntryPosition("mysql-bin.000003", 123L);
|
||||||
|
|
||||||
controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "xxxxx", "xxxxx"));
|
controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "canal", "canal"));
|
||||||
controller.setConnectionCharset(Charset.forName("UTF-8"));
|
controller.setConnectionCharset(Charset.forName("UTF-8"));
|
||||||
controller.setDirectory(directory);
|
controller.setDirectory(directory);
|
||||||
controller.setMasterPosition(startPosition);
|
controller.setMasterPosition(startPosition);
|
||||||
@@ -80,16 +81,17 @@ public class LocalBinlogDumpTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
|
||||||
System.out.println(logPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
System.out.println(logPosition);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.start();
|
controller.start();
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.helper.TimeoutChecker;
|
import com.alibaba.otter.canal.parse.helper.TimeoutChecker;
|
||||||
|
import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalLogPositionManager;
|
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
@@ -23,15 +23,15 @@ import com.alibaba.otter.canal.sink.exception.CanalSinkException;
|
|||||||
public class LocalBinlogEventParserTest {
|
public class LocalBinlogEventParserTest {
|
||||||
|
|
||||||
private static final String MYSQL_ADDRESS = "127.0.0.1";
|
private static final String MYSQL_ADDRESS = "127.0.0.1";
|
||||||
private static final String USERNAME = "xxxxx";
|
private static final String USERNAME = "canal";
|
||||||
private static final String PASSWORD = "xxxxx";
|
private static final String PASSWORD = "canal";
|
||||||
private String directory;
|
private String directory;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
URL url = Thread.currentThread().getContextClassLoader().getResource("dummy.txt");
|
URL url = Thread.currentThread().getContextClassLoader().getResource("dummy.txt");
|
||||||
File dummyFile = new File(url.getFile());
|
File dummyFile = new File(url.getFile());
|
||||||
directory = new File(dummyFile.getParent() + "/binlog").getPath();
|
directory = new File(dummyFile + "/binlog/tsdb").getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -40,7 +40,7 @@ public class LocalBinlogEventParserTest {
|
|||||||
final AtomicLong entryCount = new AtomicLong(0);
|
final AtomicLong entryCount = new AtomicLong(0);
|
||||||
final EntryPosition entryPosition = new EntryPosition();
|
final EntryPosition entryPosition = new EntryPosition();
|
||||||
|
|
||||||
final EntryPosition defaultPosition = buildPosition("mysql-bin.000001", 6163L, 1322803601000L);
|
final EntryPosition defaultPosition = buildPosition("mysql-bin.000003", 219L, 1505467103000L);
|
||||||
final LocalBinlogEventParser controller = new LocalBinlogEventParser();
|
final LocalBinlogEventParser controller = new LocalBinlogEventParser();
|
||||||
controller.setMasterPosition(defaultPosition);
|
controller.setMasterPosition(defaultPosition);
|
||||||
controller.setMasterInfo(buildAuthentication());
|
controller.setMasterInfo(buildAuthentication());
|
||||||
@@ -70,7 +70,7 @@ public class LocalBinlogEventParserTest {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||||
System.out.println(logPosition);
|
System.out.println(logPosition);
|
||||||
@@ -103,7 +103,7 @@ public class LocalBinlogEventParserTest {
|
|||||||
final AtomicLong entryCount = new AtomicLong(0);
|
final AtomicLong entryCount = new AtomicLong(0);
|
||||||
final EntryPosition entryPosition = new EntryPosition();
|
final EntryPosition entryPosition = new EntryPosition();
|
||||||
|
|
||||||
final EntryPosition defaultPosition = buildPosition("mysql-bin.000001", null, 1322803601000L);
|
final EntryPosition defaultPosition = buildPosition("mysql-bin.000003", null, 1505467103000L);
|
||||||
final LocalBinlogEventParser controller = new LocalBinlogEventParser();
|
final LocalBinlogEventParser controller = new LocalBinlogEventParser();
|
||||||
controller.setMasterPosition(defaultPosition);
|
controller.setMasterPosition(defaultPosition);
|
||||||
controller.setMasterInfo(buildAuthentication());
|
controller.setMasterInfo(buildAuthentication());
|
||||||
@@ -133,7 +133,7 @@ public class LocalBinlogEventParserTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||||
System.out.println(logPosition);
|
System.out.println(logPosition);
|
||||||
@@ -156,15 +156,15 @@ public class LocalBinlogEventParserTest {
|
|||||||
Assert.assertTrue(entryCount.get() > 0);
|
Assert.assertTrue(entryCount.get() > 0);
|
||||||
|
|
||||||
// 对比第一条数据和起始的position相同
|
// 对比第一条数据和起始的position相同
|
||||||
Assert.assertEquals(entryPosition.getJournalName(), "mysql-bin.000001");
|
Assert.assertEquals(entryPosition.getJournalName(), "mysql-bin.000003");
|
||||||
Assert.assertTrue(entryPosition.getPosition() <= 6163L);
|
Assert.assertTrue(entryPosition.getPosition() <= 300L);
|
||||||
Assert.assertTrue(entryPosition.getTimestamp() <= defaultPosition.getTimestamp());
|
Assert.assertTrue(entryPosition.getTimestamp() <= defaultPosition.getTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_no_position() throws InterruptedException {
|
public void test_no_position() throws InterruptedException {
|
||||||
final TimeoutChecker timeoutChecker = new TimeoutChecker(3 * 1000);
|
final TimeoutChecker timeoutChecker = new TimeoutChecker(3 * 1000);
|
||||||
final EntryPosition defaultPosition = buildPosition("mysql-bin.000002",
|
final EntryPosition defaultPosition = buildPosition("mysql-bin.000003",
|
||||||
null,
|
null,
|
||||||
new Date().getTime() + 1000 * 1000L);
|
new Date().getTime() + 1000 * 1000L);
|
||||||
final AtomicLong entryCount = new AtomicLong(0);
|
final AtomicLong entryCount = new AtomicLong(0);
|
||||||
@@ -198,7 +198,7 @@ public class LocalBinlogEventParserTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||||
System.out.println(logPosition);
|
System.out.println(logPosition);
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import java.util.List;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
|
import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalLogPositionManager;
|
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
@@ -25,13 +27,18 @@ public class MysqlDumpTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSimple() {
|
public void testSimple() {
|
||||||
final MysqlEventParser controller = new MysqlEventParser();
|
final MysqlEventParser controller = new MysqlEventParser();
|
||||||
final EntryPosition startPosition = new EntryPosition("mysql-bin.000003", 4L);
|
final EntryPosition startPosition = new EntryPosition("mysql-bin.000001", 104606L);
|
||||||
|
|
||||||
controller.setConnectionCharset(Charset.forName("UTF-8"));
|
controller.setConnectionCharset(Charset.forName("UTF-8"));
|
||||||
controller.setSlaveId(3344L);
|
controller.setSlaveId(3344L);
|
||||||
controller.setDetectingEnable(false);
|
controller.setDetectingEnable(false);
|
||||||
controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "xxxxx", "xxxxx"));
|
controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "canal", "canal"));
|
||||||
controller.setMasterPosition(startPosition);
|
controller.setMasterPosition(startPosition);
|
||||||
|
controller.setEnableTsdb(true);
|
||||||
|
controller.setDestination("example");
|
||||||
|
controller.setTsdbSpringXml("classpath:tsdb/h2-tsdb.xml");
|
||||||
|
controller.setEventFilter(new AviaterRegexFilter("test\\..*"));
|
||||||
|
controller.setEventBlackFilter(new AviaterRegexFilter("canal_tsdb\\..*"));
|
||||||
controller.setEventSink(new AbstractCanalEventSinkTest<List<Entry>>() {
|
controller.setEventSink(new AbstractCanalEventSinkTest<List<Entry>>() {
|
||||||
|
|
||||||
public boolean sink(List<Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
public boolean sink(List<Entry> entrys, InetSocketAddress remoteAddress, String destination)
|
||||||
@@ -83,22 +90,23 @@ public class MysqlDumpTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
|
||||||
System.out.println(logPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
System.out.println(logPosition);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.start();
|
controller.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100 * 1000L);
|
Thread.sleep(100 * 1000 * 1000L);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Assert.fail(e.getMessage());
|
Assert.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.helper.TimeoutChecker;
|
import com.alibaba.otter.canal.parse.helper.TimeoutChecker;
|
||||||
|
import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest;
|
||||||
import com.alibaba.otter.canal.parse.stub.AbstractCanalLogPositionManager;
|
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||||
@@ -23,8 +24,8 @@ public class MysqlEventParserTest {
|
|||||||
|
|
||||||
private static final String DETECTING_SQL = "insert into retl.xdual values(1,now()) on duplicate key update x=now()";
|
private static final String DETECTING_SQL = "insert into retl.xdual values(1,now()) on duplicate key update x=now()";
|
||||||
private static final String MYSQL_ADDRESS = "127.0.0.1";
|
private static final String MYSQL_ADDRESS = "127.0.0.1";
|
||||||
private static final String USERNAME = "root";
|
private static final String USERNAME = "canal";
|
||||||
private static final String PASSWORD = "xxxxxx";
|
private static final String PASSWORD = "canal";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_position() throws InterruptedException {
|
public void test_position() throws InterruptedException {
|
||||||
@@ -33,7 +34,7 @@ public class MysqlEventParserTest {
|
|||||||
final EntryPosition entryPosition = new EntryPosition();
|
final EntryPosition entryPosition = new EntryPosition();
|
||||||
|
|
||||||
final MysqlEventParser controller = new MysqlEventParser();
|
final MysqlEventParser controller = new MysqlEventParser();
|
||||||
final EntryPosition defaultPosition = buildPosition("mysql-bin.000001", 6163L, 1322803601000L);
|
final EntryPosition defaultPosition = buildPosition("mysql-bin.000003", 4690L, 1505481064000L);
|
||||||
|
|
||||||
controller.setSlaveId(3344L);
|
controller.setSlaveId(3344L);
|
||||||
controller.setDetectingEnable(true);
|
controller.setDetectingEnable(true);
|
||||||
@@ -67,16 +68,17 @@ public class MysqlEventParserTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
|
||||||
System.out.println(logPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogPosition getLatestIndexBy(String destination) {
|
public LogPosition getLatestIndexBy(String destination) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
|
||||||
|
System.out.println(logPosition);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.start();
|
controller.start();
|
||||||
@@ -136,7 +138,7 @@ public class MysqlEventParserTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||||
System.out.println(logPosition);
|
System.out.println(logPosition);
|
||||||
@@ -205,7 +207,7 @@ public class MysqlEventParserTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||||
System.out.println(logPosition);
|
System.out.println(logPosition);
|
||||||
@@ -281,7 +283,7 @@ public class MysqlEventParserTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setLogPositionManager(new AbstractCanalLogPositionManager() {
|
controller.setLogPositionManager(new AbstractLogPositionManager() {
|
||||||
|
|
||||||
public void persistLogPosition(String destination, LogPosition logPosition) {
|
public void persistLogPosition(String destination, LogPosition logPosition) {
|
||||||
System.out.println(logPosition);
|
System.out.println(logPosition);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user