接过火炬,升级canal-client-springboot-starter,一个支持rabbitmq的CanalClient

一、探索之路

升级canal.client支持rabbitmq

canal-client-springboot-starter引用jar包canal-client升级1.1.5。

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>${canal-client.version}</version>
</dependency>

1.1.5中把protocol包单独设置了一个模块。

所以,需要加一个包。

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.protocol</artifactId>
    <version>${canal-client.version}</version>
</dependency>

探索结果

本来想在canal-client-springboot-starter中集成rabbitmq,但是发现官方提供的canal.client jar包中没有rabbitmq相关的连接包,自己再去扩展的话还得去编译canal的源码——暂时只好放弃。

Exception in thread "canal-client-thread" java.lang.NoClassDefFoundError: com/rabbitmq/client/ConnectionFactory
	at com.alibaba.otter.canal.client.rabbitmq.RabbitMQCanalConnector.connect(RabbitMQCanalConnector.java:67)
	at top.javatool.canal.client.client.RabbitMqCanalClient.process(RabbitMqCanalClient.java:34)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: com.rabbitmq.client.ConnectionFactory
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	... 3 more

RabbitMQCanalConnector.java连接rabbitmq的部分源码。

    public void connect() throws CanalClientException {
        ConnectionFactory factory = new ConnectionFactory();
        if (accessKey.length() > 0 && secretKey.length() > 0) {
            factory.setCredentialsProvider(new AliyunCredentialsProvider(accessKey, secretKey, resourceOwnerId));
        } else {
            factory.setUsername(username);
            factory.setPassword(password);
        }
        factory.setHost(nameServer);
        factory.setAutomaticRecoveryEnabled(true);
        factory.setNetworkRecoveryInterval(5000);
        factory.setVirtualHost(vhost);
        try {
            connect = factory.newConnection();
            channel = connect.createChannel();
        } catch (IOException | TimeoutException e) {
            throw new CanalClientException("Start RabbitMQ producer error", e);
        }
    }

不过也是,截止2021年10月26日,canal官方推荐的正式版本仍然是1.1.4,对客户端支持rabbitmq还没有做足够的支持。

二、最终方案

参考canal-client-springboot-starter自己构建了一个easy-canal-client的项目。

源码已开源:https://gitee.com/cowboy2014/easy-canal-client.git

架构示意图如下:

2-1 canal server升级1.1.5

canal需要升级为1.1.5,canal把binlog数据解析完成后,就把数据直接投递给rabbitmq了——1.1.5可以把数据直接投递给rabbitmq。

canal.properties参考配置:

# tcp, kafka, rocketMQ, rabbitMQ
canal.serverMode = rabbitMQ

##################################################
#########                   RabbitMQ         #############
##################################################
rabbitmq.host = 172.16.150.11
rabbitmq.virtual.host = /
rabbitmq.exchange = canal-exchange
rabbitmq.username = canal
rabbitmq.password = canal%123
rabbitmq.deliveryMode =

conf/example/instance.properties参考配置:

# position info
canal.instance.master.address=172.16.150.12:3306

# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal%123

# table regex
canal.instance.filter.regex=lpm-center\\.lpm_(park|company|store|route)

# mq config
canal.mq.topic=shangwt

2-2 业务模块集成Rabbitmq

业务模块直接集成rabbitmq,作为消费者进行数据的解析、同步。

2-2 使用方法

1. 新建Handler,用@CanalTable注解标注
@Component
@CanalTable(value = "lpm_park")
@Slf4j
public class ParkHandler implements EntryHandler<Park> {
    @Resource
    private ElSearchParkServiceImpl elSearchParkService;
    @Resource
    private EsIndexes esIndexes;

    @Override
    public void insert(Park park) {
        log.info("insert message  {}", park);
        try {
            elSearchParkService.synchronous(esIndexes.getPark(), park.getId());
        } catch (IOException e) {
            log.error("es insert wrong!");
        }
    }

    @Override
    public void update(Park before, Park park) {
        try {
            if (ObjectUtil.isNotEmpty(before.getDeleted()) && !before.getDeleted().equals(park.getDeleted()) && park.getDeleted() == 1){
                this.delete(park);
            }
            if (ObjectUtil.isNotEmpty(before.getDeleted()) && !before.getDeleted().equals(park.getDeleted()) && park.getDeleted() == 0){
                this.insert(park);
            }

            elSearchParkService.synchronous(esIndexes.getPark(), park.getId());
        } catch (IOException e) {
            log.error("es insert wrong!");
        }
        log.info("update after {}", park);
    }

    @Override
    public void delete(Park park) {
        log.info("delete  {}", park);
        elSearchParkService.deleteById(esIndexes.getPark(), park.getId().intValue());
    }
}
2. rabbitmq消费者端监听队列,调用对应的Handler进行消息消费
    /**
     * canal消息同步
     * @param info
     */
    @RabbitListener(queues = {"shangwtQueue"},containerFactory = "multiListenerContainer")
    public <T> void consumeMsg(FlatMessage info){
        try {
            handlerUtil.handleMessage(info);
        }catch (Exception e){
            log.error("canal消息-监听者-发生异常:",e.fillInStackTrace());
        }
    }
3. 经验补充:canal消息消费心得,字字珠玑

我们知道canal数据处理是在mysql事务之外的,一定会产生延迟,延迟期间最新的数据被消费了怎么办?

  1. 判断变更数据是否与最新数据保持一致;不一致则舍弃;
  2. 执行删除逻辑时,缓存执行删除逻辑;
  3. 执行新增逻辑时,缓存执行新增逻辑;
  4. 更新逻辑时,判断是否为软删除逻辑,如果是则执行删除;判断是否为软新增(即把deleted字段值改为0),是则缓存执行新增逻辑;

早期的数据延迟消费,可能会造成“无用功”(幂等性OK的情况下),甚至是错误操作缓存数据。所以务必要重视步骤1的执行。

如何判断消息数据为最新?表中添加一个时间戳字段,设置自动更新。通过比较时间戳,判断消息是否过期。

三、参考资料

https://github.com/NormanGyllenhaal/canal-client

感谢您的赏读~
如果您对我的文章感兴趣的话,欢迎留下您的问题让我们一起探讨!一起进步!!
还可以关注我的微信公众号,回复“Canal”获取我的Canal学习脑图哦~👇👇👇

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ih68eiNw-1622215244567)(https://gitee.com/cowboy2014/cloud2020-config/raw/master//pictures/%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E6%A0%87%E5%87%86%E8%89%B2%E7%89%88.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子涵先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值