image-20250507215333869

public class BaseCanalClientTest {

protected final static Logger logger = LoggerFactory
.getLogger(AbstractCanalClientTest.class);
protected static final String SEP = SystemUtils.LINE_SEPARATOR;
protected static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
protected volatile boolean running = false;
protected Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error",
e);
protected Thread thread = null;
protected CanalConnector connector;
protected static String context_format = null;
protected static String row_format = null;
protected static String transaction_format = null;
protected String destination;

static {
context_format = SEP + "****************************************************" + SEP;
context_format += "* Batch Id: [{}] ,count : [{}] , memsize : [{}] , Time : {}" + SEP;
context_format += "* Start : [{}] " + SEP;
context_format += "* End : [{}] " + SEP;
context_format += "****************************************************" + SEP;

row_format = SEP
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {}({}) , gtid : ({}) , delay : {} ms"
+ SEP;

transaction_format = SEP + "================> binlog[{}:{}] , executeTime : {}({}) , gtid : ({}) , delay : {}ms"
+ SEP;

}

protected void printSummary(Message message, long batchId, int size) {
long memsize = 0;
for (Entry entry : message.getEntries()) {
memsize += entry.getHeader().getEventLength();
}

String startPosition = null;
String endPosition = null;
if (!CollectionUtils.isEmpty(message.getEntries())) {
startPosition = buildPositionForDump(message.getEntries().get(0));
endPosition = buildPositionForDump(message.getEntries().get(message.getEntries().size() - 1));
}

SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
logger.info(context_format,
new Object[] { batchId, size, memsize, format.format(new Date()), startPosition, endPosition });
}

protected String buildPositionForDump(Entry entry) {
long time = entry.getHeader().getExecuteTime();
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
String position = entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
+ entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")";
if (StringUtils.isNotEmpty(entry.getHeader().getGtid())) {
position += " gtid(" + entry.getHeader().getGtid() + ")";
}
return position;
}

protected void printEntry(List<Entry> entrys) {
for (Entry entry : entrys) {
long executeTime = entry.getHeader().getExecuteTime();
long delayTime = new Date().getTime() - executeTime;
Date date = new Date(entry.getHeader().getExecuteTime());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN
|| entry.getEntryType() == EntryType.TRANSACTIONEND) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
TransactionBegin begin = null;
try {
begin = TransactionBegin.parseFrom(entry.getStoreValue());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("parse event has an error , data:" + entry.toString(), e);
}
// 打印事务头信息,执行的线程id,事务耗时
logger.info(transaction_format,
new Object[] { entry.getHeader().getLogfileName(),
String.valueOf(entry.getHeader().getLogfileOffset()),
String.valueOf(entry.getHeader().getExecuteTime()),
simpleDateFormat.format(date), entry.getHeader().getGtid(),
String.valueOf(delayTime) });
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
printXAInfo(begin.getPropsList());
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
TransactionEnd end = null;
try {
end = TransactionEnd.parseFrom(entry.getStoreValue());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("parse event has an error , data:" + entry.toString(), e);
}
// 打印事务提交信息,事务id
logger.info("----------------\n");
logger.info(" END ----> transaction id: {}", end.getTransactionId());
printXAInfo(end.getPropsList());
logger.info(transaction_format,
new Object[] { entry.getHeader().getLogfileName(),
String.valueOf(entry.getHeader().getLogfileOffset()),
String.valueOf(entry.getHeader().getExecuteTime()),
simpleDateFormat.format(date), entry.getHeader().getGtid(),
String.valueOf(delayTime) });
}

continue;
}

if (entry.getEntryType() == EntryType.ROWDATA) {
RowChange rowChange = null;
try {
rowChange = RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("parse event has an error , data:" + entry.toString(), e);
}

EventType eventType = rowChange.getEventType();

logger.info(row_format,
new Object[] { entry.getHeader().getLogfileName(),
String.valueOf(entry.getHeader().getLogfileOffset()),
entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType,
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
entry.getHeader().getGtid(), String.valueOf(delayTime) });

if (eventType == EventType.QUERY || rowChange.getIsDdl()) {
logger.info("ddl : " + rowChange.getIsDdl() + " , sql ----> " + rowChange.getSql() + SEP);
continue;
}

printXAInfo(rowChange.getPropsList());
for (RowData rowData : rowChange.getRowDatasList()) {
if (eventType == EventType.DELETE) {
printColumn(rowData.getBeforeColumnsList());
} else if (eventType == EventType.INSERT) {
printColumn(rowData.getAfterColumnsList());
} else {
printColumn(rowData.getAfterColumnsList());
}
}
}
}
}

protected void printColumn(List<Column> columns) {
for (Column column : columns) {
StringBuilder builder = new StringBuilder();
try {
if (StringUtils.containsIgnoreCase(column.getMysqlType(), "BLOB")
|| StringUtils.containsIgnoreCase(column.getMysqlType(), "BINARY")) {
// get value bytes
builder.append(
column.getName() + " : " + new String(column.getValue().getBytes("ISO-8859-1"), "UTF-8"));
} else {
builder.append(column.getName() + " : " + column.getValue());
}
} catch (UnsupportedEncodingException e) {
}
builder.append(" type=" + column.getMysqlType());
if (column.getUpdated()) {
builder.append(" update=" + column.getUpdated());
}
builder.append(SEP);
logger.info(builder.toString());
}
}

protected void printXAInfo(List<Pair> pairs) {
if (pairs == null) {
return;
}

String xaType = null;
String xaXid = null;
for (Pair pair : pairs) {
String key = pair.getKey();
if (StringUtils.endsWithIgnoreCase(key, "XA_TYPE")) {
xaType = pair.getValue();
} else if (StringUtils.endsWithIgnoreCase(key, "XA_XID")) {
xaXid = pair.getValue();
}
}

if (xaType != null && xaXid != null) {
logger.info(" ------> " + xaType + " " + xaXid);
}
}

public void setConnector(CanalConnector connector) {
this.connector = connector;
}

/**
* 获取当前Entry的 GTID信息示例
*
* @param header
* @return
*/
public static String getCurrentGtid(CanalEntry.Header header) {
List<CanalEntry.Pair> props = header.getPropsList();
if (props != null && props.size() > 0) {
for (CanalEntry.Pair pair : props) {
if ("curtGtid".equals(pair.getKey())) {
return pair.getValue();
}
}
}
return "";
}

/**
* 获取当前Entry的 GTID Sequence No信息示例
*
* @param header
* @return
*/
public static String getCurrentGtidSn(CanalEntry.Header header) {
List<CanalEntry.Pair> props = header.getPropsList();
if (props != null && props.size() > 0) {
for (CanalEntry.Pair pair : props) {
if ("curtGtidSn".equals(pair.getKey())) {
return pair.getValue();
}
}
}
return "";
}

/**
* 获取当前Entry的 GTID Last Committed信息示例
*
* @param header
* @return
*/
public static String getCurrentGtidLct(CanalEntry.Header header) {
List<CanalEntry.Pair> props = header.getPropsList();
if (props != null && props.size() > 0) {
for (CanalEntry.Pair pair : props) {
if ("curtGtidLct".equals(pair.getKey())) {
return pair.getValue();
}
}
}
return "";
}

}

1. AbstractRocektMQTest

public abstract class AbstractRocektMQTest extends BaseCanalClientTest {

public static String topic = "example";
public static String groupId = "group";
public static String nameServers = "127.0.0.1:9876";
public static String accessKey = "";
public static String secretKey = "";
public static boolean enableMessageTrace = false;
public static String accessChannel = "local";
public static String namespace = "";
}

1.1 CanalRocketMQClientExample

public class CanalRocketMQClientExample extends AbstractRocektMQTest {

protected final static Logger logger = LoggerFactory.getLogger(CanalRocketMQClientExample.class);

private RocketMQCanalConnector connector;

private static volatile boolean running = false;

private Thread thread = null;

private Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error", e);

public CanalRocketMQClientExample(String nameServers, String topic, String groupId){
connector = new RocketMQCanalConnector(nameServers, topic, groupId, 500, false);
}

public CanalRocketMQClientExample(String nameServers, String topic, String groupId, boolean enableMessageTrace,
String accessKey, String secretKey, String accessChannel, String namespace){
connector = new RocketMQCanalConnector(nameServers,
topic,
groupId,
accessKey,
secretKey,
-1,
false,
enableMessageTrace,
null,
accessChannel,
namespace);
}

public static void main(String[] args) {
try {
final CanalRocketMQClientExample rocketMQClientExample = new CanalRocketMQClientExample(nameServers,
topic,
groupId,
enableMessageTrace,
accessKey,
secretKey,
accessChannel,
namespace);
logger.info("## Start the rocketmq consumer: {}-{}", topic, groupId);
rocketMQClientExample.start();
logger.info("## The canal rocketmq consumer is running now ......");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
logger.info("## Stop the rocketmq consumer");
rocketMQClientExample.stop();
} catch (Throwable e) {
logger.warn("## Something goes wrong when stopping rocketmq consumer:", e);
} finally {
logger.info("## Rocketmq consumer is down.");
}
}));
while (running)
;
} catch (Throwable e) {
logger.error("## Something going wrong when starting up the rocketmq consumer:", e);
System.exit(0);
}
}

public void start() {
Assert.notNull(connector, "connector is null");
thread = new Thread(this::process);
thread.setUncaughtExceptionHandler(handler);
thread.start();
running = true;
}

public void stop() {
if (!running) {
return;
}
running = false;
if (thread != null) {
try {
thread.join();
} catch (InterruptedException e) {
// ignore
}
}
}

private void process() {
while (!running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}

while (running) {
try {
connector.connect();
connector.subscribe();
while (running) {
List<Message> messages = connector.getListWithoutAck(1000L, TimeUnit.MILLISECONDS); // 获取message
for (Message message : messages) {
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// }
} else {
printSummary(message, batchId, size);
printEntry(message.getEntries());
// logger.info(message.toString());
}
}

connector.ack(); // 提交确认
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}

connector.unsubscribe();
// connector.stopRunning();
}
}

1.2 CanalRocketMQClientFlatMessageExample

public class CanalRocketMQClientFlatMessageExample extends AbstractRocektMQTest {

protected final static Logger logger = LoggerFactory
.getLogger(CanalRocketMQClientFlatMessageExample.class);

private RocketMQCanalConnector connector;

private static volatile boolean running = false;

private Thread thread = null;

private Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error", e);

public CanalRocketMQClientFlatMessageExample(String nameServers, String topic, String groupId){
connector = new RocketMQCanalConnector(nameServers, topic, groupId, 500, true);
}

public static void main(String[] args) {
try {
final CanalRocketMQClientFlatMessageExample rocketMQClientExample = new CanalRocketMQClientFlatMessageExample(
nameServers,
topic,
groupId);
logger.info("## Start the rocketmq consumer: {}-{}", topic, groupId);
rocketMQClientExample.start();
logger.info("## The canal rocketmq consumer is running now ......");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
logger.info("## Stop the rocketmq consumer");
rocketMQClientExample.stop();
} catch (Throwable e) {
logger.warn("## Something goes wrong when stopping rocketmq consumer:", e);
} finally {
logger.info("## Rocketmq consumer is down.");
}
}));
while (running)
;
} catch (Throwable e) {
logger.error("## Something going wrong when starting up the rocketmq consumer:", e);
System.exit(0);
}
}

public void start() {
Assert.notNull(connector, "connector is null");
thread = new Thread(this::process);
thread.setUncaughtExceptionHandler(handler);
thread.start();
running = true;
}

public void stop() {
if (!running) {
return;
}
running = false;
if (thread != null) {
try {
thread.join();
} catch (InterruptedException e) {
// ignore
}
}
}

private void process() {
while (!running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}

while (running) {
try {
connector.connect();
connector.subscribe();
while (running) {
List<FlatMessage> messages = connector.getFlatList(100L, TimeUnit.MILLISECONDS); // 获取message
for (FlatMessage message : messages) {
long batchId = message.getId();
if (batchId == -1 || message.getData() == null) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// }
} else {
logger.info(message.toString());
}
}

connector.ack(); // 提交确认
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}

connector.unsubscribe();
// connector.stopRunning();
}
}

2. AbstractCanalClientTest

public class AbstractCanalClientTest extends BaseCanalClientTest {

public AbstractCanalClientTest(String destination){
this(destination, null);
}

public AbstractCanalClientTest(String destination, CanalConnector connector){
this.destination = destination;
this.connector = connector;
}

protected void start() {
Assert.notNull(connector, "connector is null");
thread = new Thread(this::process);

thread.setUncaughtExceptionHandler(handler);
running = true;
thread.start();
}

protected void stop() {
if (!running) {
return;
}
running = false;
if (thread != null) {
try {
thread.join();
} catch (InterruptedException e) {
// ignore
}
}

MDC.remove("destination");
}

protected void process() {
int batchSize = 5 * 1024;
while (running) {
try {
MDC.put("destination", destination);
connector.connect();
connector.subscribe();
while (running) {
Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// }
} else {
printSummary(message, batchId, size);
printEntry(message.getEntries());
}

if (batchId != -1) {
connector.ack(batchId); // 提交确认
}
}
} catch (Throwable e) {
logger.error("process error!", e);
try {
Thread.sleep(1000L);
} catch (InterruptedException e1) {
// ignore
}

connector.rollback(); // 处理失败, 回滚数据
} finally {
connector.disconnect();
MDC.remove("destination");
}
}
}

}

2.1 ClusterCanalClientTest

public class ClusterCanalClientTest extends AbstractCanalClientTest {

public ClusterCanalClientTest(String destination){
super(destination);
}

public static void main(String args[]) {
String destination = "product-syn";

// 基于固定canal server的地址,建立链接,其中一台server发生crash,可以支持failover
// CanalConnector connector = CanalConnectors.newClusterConnector(
// Arrays.asList(new InetSocketAddress(
// AddressUtils.getHostIp(),
// 11111)),
// "stability_test", "", "");

// 基于zookeeper动态获取canal server的地址,建立链接,其中一台server发生crash,可以支持failover
CanalConnector connector = CanalConnectors.newClusterConnector("192.168.100.111:2181",
destination,
"canal",
"canal");

final ClusterCanalClientTest clientTest = new ClusterCanalClientTest(destination);
clientTest.setConnector(connector);
clientTest.start();

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
logger.info("## stop the canal client");
clientTest.stop();
} catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:", e);
} finally {
logger.info("## canal client is down.");
}
}));
}
}

2.2 SimpleCanalClientTest

public class SimpleCanalClientTest extends AbstractCanalClientTest {

public SimpleCanalClientTest(String destination){
super(destination);
}

public static void main(String args[]) {
// 根据ip,直接创建链接,无HA的功能
String destination = "product-syn";
String ip = "192.168.100.111";
CanalConnector connector = CanalConnectors
.newSingleConnector(new InetSocketAddress(ip, 11111), destination, "canal", "canal");

final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
clientTest.setConnector(connector);
clientTest.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
logger.info("## stop the canal client");
clientTest.stop();
} catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:", e);
} finally {
logger.info("## canal client is down.");
}
}));
}

}

3. AbstractKafkaTest

public abstract class AbstractKafkaTest extends BaseCanalClientTest {

public static String topic = "example";
public static Integer partition = null;
public static String groupId = "g4";
public static String servers = "127.0.0.1:9092";
public static String zkServers = "127.0.0.1:2181";

public void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
}
}
}

3.1 KafkaClientRunningTest

public class KafkaClientRunningTest extends AbstractKafkaTest {

private Logger logger = LoggerFactory.getLogger(KafkaClientRunningTest.class);

private boolean running = true;

public void testKafkaConsumer() {
final ExecutorService executor = Executors.newFixedThreadPool(1);
final KafkaCanalConnector connector = new KafkaCanalConnector(servers, topic, partition, groupId, null, false);
executor.submit(() -> {
connector.connect();
connector.subscribe();
while (running) {
List<Message> messages = connector.getList(3L, TimeUnit.SECONDS);
if (messages != null) {
System.out.println(messages);
}
connector.ack();
}
connector.unsubscribe();
connector.disconnect();
});

sleep(60000);
running = false;
executor.shutdown();
logger.info("shutdown completed");
}

}

本站由 卡卡龙 使用 Stellar 1.33.1主题创建

本站访问量 次. 本文阅读量 次.