springboot集成canal canal starter 小工具

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> &lt;!&ndash; 指定release版本 &ndash;&gt;-->
<!--                </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/
启动完成,测试成功。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot是一个基于Java的开发框架,而Canal是一种用于增量数据订阅和消费的开源工具。在Spring Boot中整合Canal可以方便地将数据库中的变更数据同步到其他系统中。 首先,我们需要在Spring Boot项目的pom.xml文件中添加Canal的依赖。然后,在application.properties配置文件中配置Canal的相关参数,包括Canal的服务地址、用户名、密码等。 接下来,在代码中可以通过编写监听器来实现对变更数据的订阅和消费。可以使用Canal提供的Java API来连接Canal服务,并注册事件监听器,监听数据库的变更事件。当有数据变更时,Canal会将相关的事件通知到监听器中。 在监听器中,可以获取到变更操作的数据库名、表名以及具体的变更数据。通过对这些数据的处理,可以实现将变更数据同步到其他系统中的逻辑。 例如,可以在监听器中使用Spring Boot的JdbcTemplate来向其他系统发送变更数据,或者将变更数据存储到缓存中,以供其他部分使用。 整合Canal的好处是可以实现数据的实时同步,将数据库中的变更数据推送到其他系统中,从而实现数据的实时更新和共享。同时,Canal提供了丰富的API和事件监听机制,可以满足不同场景下的需求,提供灵活的数据同步方案。 总之,通过Spring Boot整合Canal可以方便地将数据库的变更数据同步到其他系统中,实现数据的实时更新和共享。这为开发实时应用和数据集成提供了便利。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值