使用canal不消耗数据库连接监听数据库变更,异步ACK

 

canal是什么

摘自项目github

基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了mysql

原理相对比较简单:
canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
mysql master收到dump请求,开始推送binary log给slave(也就是canal)
canal解析binary log对象(原始为byte流)

canal解决了什么问题

起源:
早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。

本文在探讨什么

canal的基础操作,本文不再赘述,可参考github上的quick start和client api,包含了demo
本文探讨的是,基于canal的流式api的消息分发,以及如何防止消息丢失和重复处理

什么是流式API

摘自项目github
流式api设计:

11554693-6e1699a0e570d4b6

image

  • 每次get操作都会在meta中产生一个mark,mark标记会递增,保证运行过程中mark的唯一性
  • 每次的get操作,都会在上一次的mark操作记录的cursor继续往后取,如果mark不存在,则在last ack cursor继续往后取
  • 进行ack时,需要按照mark的顺序进行数序ack,不能跳跃ack. ack会删除当前的mark标记,并将对应的mark位置更新为last ack cursor
  • 一旦出现异常情况,客户端可发起rollback情况,重新置位:删除所有的mark, 清理get请求位置,下次请求会从last ack cursor继续往后取

11554693-e309da92a2a8eb14.png

image.png

关注点

异步的ack带来了更好的性能,也带来了一些问题
rollback后,mark会清空,回到上次ack的位置。如果get的速度比ack快,当rollback()后,就会出现重复消息
本文针对这个问题,给出一个较为简单的解决方案

思路

mark清空后,再次get,获取到的batchId会继续递增(保存在服务端),但是消息是已经处理过的,此时我们不希望消息继续被分发或者处理
如何判断消息是否消费过,或者说,该次数据库变更,是否已经解析过
1.在业务上进行判断
2.更好的方式,通过当前处理的消息在binlog中的位置进行判断

String logFileName = entry.getHeader().getLogfileName();
long offset = entry.getHeader().getLogfileOffset();

使用这两行代码,就可以方便的获取当前消息在具体哪个binlog文件的哪个位置,输出类似如下

logfileName = mysql-bin.000001,offset = 41919

代码

测试代码,请勿用于生产

    public static void main(String args[]) {
        new Thread(SimpleCanalClient::receiver).start();
        new Thread(SimpleCanalClient::ack).start();
    }

开局启动两个线程,一个接收,一个确认

private static void receiver() {
        // 创建链接
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
                11111), "example", "", "");
        int batchSize = 1;
        int count = 0;
        try {
            connector.connect();
            connector.subscribe(".*\\..*");
            connector.rollback();
            int total = 20;
            while (count < total) {
                count++;
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId != -1 && size != 0) {
                    printEntry(message.getEntries(),batchId);
                }
                try {
       
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值