1、版本:springboot 2.7.1
2、目录结构
3、项目文件
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.me.starter.canal.config.CanalAutoConfig,\
com.me.starter.canal.server.CanalSlave
CanalAutoConfig
package com.me.starter.canal.config;
import com.me.starter.canal.properties.CanalProperties;
import com.me.starter.canal.server.CanalSlave;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
@EnableConfigurationProperties({CanalProperties.class})
public class CanalAutoConfig {
protected final static Logger logger = LoggerFactory.getLogger(CanalAutoConfig.class);
@Autowired
private CanalProperties canalProperties;
@Autowired
private CanalSlave canalSlave;
@PostConstruct
public void startCanal(){
if(!canalProperties.isAutoStarted()){
logger.warn("starter.canal.AutoStarted = false ,canal do not start,If you want to start, please set starter.canal.AutoStarted = true");
return;
}
start();
}
public void start(){
if(!canalProperties.isEnabled()){
logger.warn("starter.canal.enabled = false ,canal do not start,If you want to start, please set starter.canal.enabled = true");
return;
}
if(StringUtils.isEmpty(canalProperties.getDestination())){
logger.error("starter.canal.destination is null ");
return;
}
if(StringUtils.isEmpty(canalProperties.getUsername())){
logger.error("starter.canal.username is null ");
return;
}
if(StringUtils.isEmpty(canalProperties.getPassword())){
logger.error("starter.canal.password is null ");
return;
}
if(StringUtils.isEmpty(canalProperties.getZookeeper())){
logger.error("starter.canal.zookeeper is null ");
return;
}
if(StringUtils.isEmpty(canalProperties.getSchema())){
logger.error("starter.canal.schema is null ");
return;
}
if(canalProperties.isSaslClient() && StringUtils.isEmpty(canalProperties.getZkAuthConfig())){
logger.error("starter.canal.saslClient use , ZkAuthConfig mast not be null");
return;
}
if(StringUtils.isNotEmpty(canalProperties.getFilter())){
//todo checkFilter
}
if(canalSlave.getHandlerMap().size() == 0 ){
logger.error("app has no canal handler ,canal cannot start ,please define handler extend AbstractCanalHandler ");
return;
}
canalSlave.startUp();
}
public void stop(){
canalSlave.stop();
}
}
CanalHandler
package com.me.starter.canal.handler;
import com.alibaba.otter.canal.protocol.CanalEntry;
import java.io.IOException;
import java.util.concurrent.ConcurrentSkipListSet;
public interface CanalHandler {
public ConcurrentSkipListSet<String> allTableSet = new ConcurrentSkipListSet<String>();
String getHandlerName();
boolean handle(CanalEntry.Entry entry,CanalEntry.RowChange rowChange) throws IOException;
public default CanalHandler instance(String tableName) {
if(getTableSet().contains(tableName)){
doPreparatoryWork(tableName);
return this;
}
return null;
}
void doPreparatoryWork(String toUpperCase);
ConcurrentSkipListSet<String> getTableSet();
}
AbstractCanalHandler
package com.me.starter.canal.handler;
import com.me.starter.canal.server.AbstractCanalClient;
import javax.annotation.PostConstruct;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* @author wanbo
*/
public abstract class AbstractCanalHandler implements CanalHandler{
public ConcurrentSkipListSet<String> tableSet = new ConcurrentSkipListSet<String>();
public AbstractCanalHandler() {
tableSet.addAll(getTableNames());
allTableSet.addAll(tableSet);
AbstractCanalClient.registerHandler(this);
}
public abstract Set getTableNames();
@PostConstruct
protected void postConstruct(){
init();
}
protected void init(){
// default nothing to do
}
@Override
public ConcurrentSkipListSet<String> getTableSet() {
return tableSet;
}
}
CanalProperties
package com.me.starter.canal.properties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties("starter.canal")
public class CanalProperties {
private boolean autoStarted;
private boolean enabled;
private String filter;
private String destination;
private String schema;
private String posFile;
private String zookeeper;
private String username;
private String password;
private boolean saslClient;
private String zkAuthConfig;
}
AbstractCanalClient
package com.me.starter.canal.server;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.me.starter.canal.handler.CanalHandler;
import com.me.starter.canal.properties.CanalProperties;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.util.Assert;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 测试基类
*
* @author jianghang 2013-4-15 下午04:17:12
* @version 1.0.4
*/
public class AbstractCanalClient extends BaseCanalClient {
private static HashMap<String , CanalHandler> handlerMap = new HashMap();
private CanalProperties properties;
public CanalProperties getProperties() {
return properties;
}
public void setProperties(CanalProperties properties) {
this.properties = properties;
this.destination = properties.getDestination();
}
// ExecutorService threadPool = SingleMustDoneThreadPool.newThreadPool();
private static BlockingQueue<LogBinlogInfo> posQueue = new LinkedBlockingQueue();
public HashMap<String, CanalHandler> getHandlerMap() {
return handlerMap;
}
public AbstractCanalClient() {
}
/**
* 注册handler
* @param handler
*/
public static void registerHandler(CanalHandler handler){
handlerMap.put(handler.getHandlerName(),handler);
}
protected Thread posMonitor = null;
public void start() throws InterruptedException {
while (true){
if(instance == null ){
Assert.notNull(connector, "connector is null");
instance = new Thread(this::process);
posMonitor = new Thread(this::posMonitor);
instance.setUncaughtExceptionHandler(handler);
running.getAndSet(true);
instance.start();
posMonitor.start();
}
Thread.sleep(5 * 1000);
}
}
public void stop() {
if (!running.get()) {
return;
}
running.getAndSet(false);
if (instance != null) {
try {
instance.join();
posMonitor.join();
} catch (InterruptedException e) {
// ignore
}
}
instance = null;
posMonitor = null;
MDC.remove("destination");
}
protected void process() {
int batchSize = 5 * 1024;
starUp:while (running.get()) {
try {
MDC.put("destination", destination);
logger.info("destination : " + destination);
connector.connect();
if(properties.getFilter()!=null && !"".equals(properties.getFilter())){
connector.subscribe(properties.getFilter());
}else {
connector.subscribe();
}
StringBuilder stringBuilder = new StringBuilder();
for (String tableName :CanalHandler.allTableSet){
stringBuilder.append(properties.getSchema() + "." + tableName + ",");
}
String substring = stringBuilder.substring(0, stringBuilder.length() - 1);
logger.info("subscribe:" + substring);
connector.subscribe(substring);
work:while (running.get()) {
Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
long batchId = message.getId();
int size = message.getEntries().size();
boolean flag = true;
if (batchId == -1 || size == 0) {
} else {
//TODO 数据处理
for (CanalEntry.Entry entry : message.getEntries()) {
if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
String schema = entry.getHeader().getSchemaName();
logger.info(schema);
if(!schema.equalsIgnoreCase(properties.getSchema())){
continue;
}
CanalEntry.RowChange rowChange = null;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("parse event has an error , data:" + entry.toString(), e);
}
CanalEntry.EventType eventType = rowChange.getEventType();
if (eventType == CanalEntry.EventType.QUERY || rowChange.getIsDdl()) {
logger.info("ddl : " + rowChange.getIsDdl() + " , sql ----> " + rowChange.getSql() + SEP);
continue;
}
//记录binlog 信息
posQueue.put(new LogBinlogInfo(entry.getHeader().getLogfileName(),entry.getHeader().getLogfileOffset()));
logger.info(entry.getHeader().getTableName());
List<CanalHandler> handlers = getHandlers(entry.getHeader().getTableName());
if(handlers != null && handlers.size() > 0 ){
for(CanalHandler handler : handlers){
flag = handler.handle(entry,rowChange);
}
}
}
}
}
if (batchId != -1 ) {
connector.ack(batchId); // 提交确认
}
}
} catch (Exception e) {
logger.error("process error!", e);
try {
connector.rollback(); // 处理失败, 回滚数据
}catch (CanalClientException cce){
cce.printStackTrace();
}
} finally {
try {
connector.disconnect();
}catch (CanalClientException cce){
cce.printStackTrace();
}
MDC.remove("destination");
logger.error(" sleep 10s and try");
try {
Thread.currentThread().sleep(30000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
continue starUp;
}
}
instance.interrupt();
instance = null;
}
protected void posMonitor() {
work:while (running.get()) {
LogBinlogInfo logBinlogInfo = null;
try {
logBinlogInfo = posQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
setVodInfo(logBinlogInfo.getBinlogOffset(),logBinlogInfo.getBinlogFileName());
}
}
/**
* 记录 binlog pos 文件命
* @param pos
* @param vodFileName
*/
public void setVodInfo(long pos,String vodFileName) {
Writer w = null;
try {
if(StringUtils.isEmpty(properties.getPosFile())){
return;
}
File posFile = new File(properties.getPosFile());
if(!(posFile).exists()){
return;
}
w = new FileWriter(posFile);
if( w == null){
return;
}
w.append("position=" + pos + "\n");
w.append("vodFileName=" + vodFileName + "\n");
w.append("#create_time " + new Date() + "\n");
// log.error("setPositon: " + pos);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(w != null ){
w.flush();
w.close();
}
} catch (IOException e) {
logger.error("---------->", e);
}
}
}
/**
* 根据变更表名称 获取对应的hanlder
* @param tableName
* @return
*/
private CanalHandler getHandler(String tableName) {
CanalHandler returnHandler = null;
for(CanalHandler handler : handlerMap.values()){
returnHandler = handler.instance(tableName);
if(returnHandler != null){
break;
}
}
return returnHandler;
}
/**
* 根据变更表名称 获取对应的hanlder
* @param tableName
* @return
*/
private List<CanalHandler> getHandlers(String tableName) {
List<CanalHandler> handlers = new ArrayList<CanalHandler>();
for(CanalHandler handler : handlerMap.values()){
CanalHandler instance = handler.instance(tableName);
if( instance != null){
handlers.add(instance);
}
}
return handlers;
}
public static class LogBinlogInfo{
private String binlogFileName;
private long binlogOffset;
public String getBinlogFileName() {
return binlogFileName;
}
public long getBinlogOffset() {
return binlogOffset;
}
public LogBinlogInfo(String binlogFileName, long binlogOffset) {
this.binlogFileName = binlogFileName;
this.binlogOffset = binlogOffset;
}
}
}
BaseCanalClient
package com.me.starter.canal.server;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class BaseCanalClient {
protected final static Logger logger = LoggerFactory.getLogger(BaseCanalClient.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;
public AtomicBoolean running = new AtomicBoolean(false);
protected Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error",e);
protected Thread instance = 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(Header header) {
List<Pair> props = header.getPropsList();
if (props != null && props.size() > 0) {
for (Pair pair : props) {
if ("curtGtid".equals(pair.getKey())) {
return pair.getValue();
}
}
}
return "";
}
/**
* 获取当前Entry的 GTID Sequence No信息示例
*
* @param header
* @return
*/
public static String getCurrentGtidSn(Header header) {
List<Pair> props = header.getPropsList();
if (props != null && props.size() > 0) {
for (Pair pair : props) {
if ("curtGtidSn".equals(pair.getKey())) {
return pair.getValue();
}
}
}
return "";
}
/**
* 获取当前Entry的 GTID Last Committed信息示例
*
* @param header
* @return
*/
public static String getCurrentGtidLct(Header header) {
List<Pair> props = header.getPropsList();
if (props != null && props.size() > 0) {
for (Pair pair : props) {
if ("curtGtidLct".equals(pair.getKey())) {
return pair.getValue();
}
}
}
return "";
}
}
CanalSlave
package com.me.starter.canal.server;
import com.alibaba.otter.canal.client.impl.ClusterCanalConnector;
import com.alibaba.otter.canal.client.impl.ClusterNodeAccessStrategy;
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
import com.me.starter.canal.handler.CanalHandler;
import com.me.starter.canal.properties.CanalProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Set;
@Configuration
@EnableConfigurationProperties({CanalProperties.class})
public class CanalSlave extends AbstractCanalClient {
@Autowired
private CanalProperties properties;
public CanalProperties getProperties() {
return properties;
}
@PostConstruct
public void init(){
System.out.println("CanalSlave init");
super.setProperties(properties);
}
public void startUp() {
super.setProperties(properties);
String destination = properties.getDestination();
ClusterCanalConnector connector;
if(properties.isSaslClient()) {
System.setProperty("java.security.auth.login.config", properties.getZkAuthConfig());
System.setProperty("zookeeper.sasl.client", properties.isSaslClient() + "");
}
// 基于zookeeper动态获取canal server的地址,建立链接,其中一台server发生crash,可以支持failover
ZkClientx zkClientx = ZkClientx.getZkClient(properties.getZookeeper());
connector = new ClusterCanalConnector(properties.getUsername(),
properties.getPassword(),
destination,
new ClusterNodeAccessStrategy(destination, zkClientx));
connector.setSoTimeout(60 * 1000);
connector.setIdleTimeout(60 * 60 * 1000);
this.setConnector(connector);
try {
this.start();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
logger.info("## stop the canal client");
this.stop();
} catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:", e);
} finally {
logger.info("## canal client is down.");
}
}));
}
}
CanalSource
暂无实际使用
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.me.starter</groupId>
<artifactId>spring-boot-starter-canal</artifactId>
<version>2.7.1-SNAPSHOT</version>
<name>spring-boot-starter-canal</name>
<description>spring-boot-starter-canal</description>
<properties>
<java.version>8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version> <!-- 确保使用支持 release的插件版本 -->
<!-- <configuration>-->
<!-- <release>1.8</release> <!– 指定release版本 –>-->
<!-- </configuration>-->
</plugin>
</plugins>
</build>
</project>
4、测试使用
测试项目目录结构
CanalstarterDemoApplication
package com.example.canalstarterDemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CanalstarterDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CanalstarterDemoApplication.class, args);
}
}
SysLogCanalHandler
package com.example.canalstarterDemo.cannelHandler;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.cntv.starter.canal.handler.AbstractCanalHandler;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
/**
* @author wanbo
*/
@Service
public class SysLogCanalHandler extends AbstractCanalHandler {
//增加监控的表 当该表数据变化时执行对应操作
@Override
public Set getTableNames() {
Set<String > set = new HashSet<>();
set.add("tb_syslog");
return set;
}
@Override
public String getHandlerName() {
return "SyslogHandler";
}
@Override
public boolean handle(CanalEntry.Entry entry, CanalEntry.RowChange rowChange) throws IOException {
CanalEntry.EventType eventType = rowChange.getEventType();
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
if (eventType == CanalEntry.EventType.DELETE) {
System.out.println("do DELETE");
} else if (eventType == CanalEntry.EventType.INSERT) {
System.out.println("do INSERT");
} else {
System.out.println("do UPDATE");
}
}
return true;
}
@Override
public void doPreparatoryWork(String s) {
System.out.println("----------------------doPreparatoryWork-----------------------");
}
}
application.yml
spring:
application:
name: op-monitor
starter:
canal:
enabled: true
destination: op-monitor
username: canal
schema: vdn
filter: vdn.tb_syslog
zookeeper: 10.70.37.xxx:2181,10.70.37.xxx:2181,10.70.37.xxx:2181#根据自己的zk集群
password: Ekt0Pnjcoxfwsnwb
auto-started: true
data:
log:
path: log/
启动完成,测试成功。