canal框架的详细使用

Canal的搭建

一. mysql的配置

修改 my.ini 文件

mysqld]
character-set-server=utf8
basedir=E:\mysql\mysql-5.7.21-winx64
datadir=E:\mysql\mysql-5.7.21-winx64\data

# servier-id 是一个数字  
server-id=2
# 开启mysql的binlog
log-bin=mysql-bin
# binlog记录的操作的格式
binlog-format=ROW
# 表示记录那个表,对于我们配置就是 es 这个库下的所有的表
binlog-do-db=es

修改配置之后,重启mysql

需要创建一个用户 canal/canal, 命令如下:

# 授权
grant select, replication slave, replication client on *.* to 'canel'@'%' identified by 'canal';
# 让权限生效
flush privileges; 

二. canal的使用

一. 下载地址:https://github.com/alibaba/canal/releases

二. canal必须在Linux系统上运行,首页加压

三. 修改 CANAL_HOME/conf/example/instance.properties 配置文件

请添加图片描述
请添加图片描述

四. 启动 canal, 进入到 CANAL_HOME/bin, 执行如下命令

./startup.sh

三. Java程序连接Canal

3.1 引入依赖
<dependency>
      <groupId>com.alibaba.otter</groupId>
      <artifactId>canal.client</artifactId>
      <version>1.1.4</version>
</dependency>
3.1 创建Canal的连接对象
/**
 * DisposableBean 这个接口提供了一个 destroy() 方法, 如果我们某个 bean 实现了这个接口,
 * 在bean被销毁的时候, 会自动执行这个方法, 通常的作用是用来释放资源.
 * 对于我们目前的目的来说: 就是当该bean销毁的时候, 释放canal的连接用的.
 */
@Configuration
public class CanalConfig implements DisposableBean {

    private CanalConnector canalConnector;

    @Bean
    public CanalConnector canalConnector() {
        this.canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("192.168.52.137", 11111),    // ip 和 端口号
                "example",                       // 目标, 就是 CANAL_HOME/config/example
                "canal",                         // 连接到 canal 的用户名
                "canal"
        );
        // 正式建立连接
        this.canalConnector.connect();
        // 如果什么都不传入, 表示获取到 canal 拿到的所有记录; es.t_goods 表示我们该应用只获取 t_goods 的操作记录
//        this.canalConnector.subscribe(".*\\..*");   // grant all on * .  *
        this.canalConnector.subscribe("es.t_goods");
        // 回到上一次的拉去操作记录的位置
        this.canalConnector.rollback();
//        System.out.println(canalConnector);
        return canalConnector;
    }

    @Override
    public void destroy() throws Exception {
        if(null != canalConnector) {
            canalConnector.disconnect();  // 关闭连接
        }
    }
}
3.3 准实时获取数据的修改

通过定时器来完成的

@Component
public class SyncDataTask {

    private CanalConnector canalConnector;

    public SyncDataTask(CanalConnector canalConnector) {
        this.canalConnector = canalConnector;
    }

    // num 表示每次从canal中获取多少个操作, 当然这个操作中包含有事务的命令.
    private int num = 200;

    /**
     * fixedRate: 固定频率
     * fixedDelay: 固定时间间隔
     */
    @Scheduled(fixedDelay = 100)
    public void read() {
        // 每次获取 200 条操作, 统一被 Canal 封装成一个 Message
        Message message = canalConnector.getWithoutAck(num);

        // 获取当前所拉去的数据的第一个操作的id, 如果没有操作返回 -1
        long batchId = message.getId();

        if(batchId != -1 && message.getEntries().size() > 0) {
            /**
             * CanalEntry.Entry 指的是一个个操作, 一个操作中可能影响多条数据
             */
            List<CanalEntry.Entry> entries = message.getEntries();
            for(Entry entry : entries) {
                /**
                 * 如果这条操作是事务的开启或者回滚操作. begin  commit(rollback)
                 */
                if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                    continue;
                }
                try {
                    /**
                     * RowChange 就表示用户的这条操作记录的信息
                     */
                    RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
                    // 每个操作的操作类型, 可能是 Select、insert 、delete、update
                    EventType eventType = rowChange.getEventType();
                    // rowChange.getRowDatasList() 表示的意思, 是当前这个操作所影响所有的数据行
                    // 因为delete或者update, 可能改变了很多条记录
                    for(RowData rowData : rowChange.getRowDatasList()) {
                        processRowData(rowData, eventType);
                    }
                } catch (InvalidProtocolBufferException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 对于删除操作, 只存在之前的数据.
     * 对于插入操作, 只存在之后的数据.
     * 对于更新操作, 只存在之后、之后的数据.
     */
    private void processRowData(RowData rowData, EventType eventType) {
        if(EventType.DELETE == eventType) {  // 表示删除操作
            System.out.println("删除操作");
            // getBeforeColumnsList() 拿到的是一条记录中 一列 一列的
            List<CanalEntry.Column> columns = rowData.getBeforeColumnsList();
            printInfo(columns);
        }else if(EventType.INSERT == eventType) {
            List<CanalEntry.Column> columns = rowData.getAfterColumnsList();
            printInfo(columns);
        }else if(EventType.UPDATE == eventType) {
            List<CanalEntry.Column> columns = rowData.getAfterColumnsList();
            printInfo(columns);
        }
    }

    private void printInfo(List<CanalEntry.Column> columns) {
        for(Column col : columns) {
            System.out.println(col.getName() + "=" + col.getValue());
        }
    }
}

四. 自定义starter

自定义starter的目的,就是作为一个 “轮子” 可以被任何人拿来直接使用。自定义starter的流程:

  1. 不是作为一个工程来使用了,所以不能引入入 spring-boot-starter-web
  2. 包名要保持独立,不能和项目的包是一样的。原因在于,如果包名,一致那么springboot在启动的过程可以直接扫描到了,就不具备了可移植。报名不一致,就可用通过自动装配的方式来进行加载。
  3. resources 目录下创建一个文件夹 META-INF, 然后在下面新建一个文件 spring.factories, 文件的内容跟 properties 有些类似(都是键值对),不一样在于文件中可以有多个值,使用逗号分隔。说明,文件中 \ 是换行,是为了兼容考虑,这种写法在 windowLinux 都是兼容的。
  4. spring.factories 文件中的键 org.springframework.boot.autoconfigure.EnableAutoConfiguration, 值就是整个装配的入口类。
  5. 当一个springboot工程引入该starter,启动的时候,会扫描 Jar包下 META-INF/spring.factories 文件,然后加载 EnableAutoConfiguration 对应的值,放到IOC容器中。
4.1 引入依赖
<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.3.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure-processor</artifactId>
      <version>2.3.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba.otter</groupId>
      <artifactId>canal.client</artifactId>
      <version>1.1.4</version>
    </dependency>
</dependencies>
4.2 需要配置spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.canal.autoconfig.CanalAutoConfig
4.3 编写自动装配的类
@Configuration
@EnableConfigurationProperties(CanalProperties.class)
public class CanalAutoConfig {

    private CanalConnector canalConnector;

    @Autowired
    private CanalProperties canalProperties;

    /**
     * ConditionalOn -> 当 什么 什么 的时候
     * @ConditionalOnMissingBean(CanalConnector.class) 表示IOC容器中如果不存在
     * CanalConnector的实例的bean的时候, 那么我们这个Bean才生效, 换句话来说,就是有可能
     * 用户自己定义 CanalConnector 的实例的时候, 我们这里定义的bean是无效的.
     */
    @Bean
    @ConditionalOnMissingBean(CanalConnector.class)
    public CanalConnector canalConnector() {
        this.canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(canalProperties.getHost(), canalProperties.getPort()),    // ip 和 端口号
                canalProperties.getInstanceName(),                                        // 目标, 就是 CANAL_HOME/config/example
                canalProperties.getUsername(),                                          // 连接到 canal 的用户名
                canalProperties.getPassword()
        );
        this.canalConnector.connect();
        this.canalConnector.subscribe(canalProperties.getSubscribe());
        this.canalConnector.rollback();

        return canalConnector;
    }

    @Bean
    public CanalListenerProcessor canalListenerProcessor() {
        return new CanalListenerProcessor();
    }
}
4.4 BeanPostProcessor

Post 在 spring 中有特殊的方法, 凡事方法中带有 Post 都是表示 “在什么之后.” .

Spring中有两类接口非常特殊:

  1. XXXXXPostProcessor: BeanPostProcessor、BeanFactoryPostProcessor
  2. XXXXXAware: ApplicationContextAware
// 该接口是Bean的后置处理,当某个Bean被纳入到IOC容器中就是,那么对应的两个方法都会得到执行,
// 并且会将刚刚纳入到IOC容器的bean, 作为两个方法的参数传人进去
public interface BeanPostProcessor {
		Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
        // 我们在自定义注解,或者对bean进行增强的时候,会调用这个方法。
		Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值