使用canal-deployer实现mysql数据同步

shigen坚持更新文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。

shigen之前的文章当中,苦于mysql和elasticsearch之间的数据同步问题,甚至尝试开源一款mysql-es数据同步工具 - 掘金。觉得可以自己去实现这些同步。但是遇到了的问题也很多:

尤其是这样的下了血本,还是没有人使用怎么办,或者还是比不上cannel怎么办?人家毕竟是阿里推出的,能扛大数据的。最终我选择了妥协,趁着上午的时间研究了一下canal-deployer

要学习这个,我觉得最好的工具依然是官网:QuickStart

按照这个过程,我首先启动了我的mysql主从节点,相关的教程可参考shigen之前的文章:mysql主从服务的搭建 - 掘金

并确保主从服务是可以正常的同步的:

现在的操作我们严格按照官网的提示来做,可能很多人包括我自己遇到问题的时候也会接住于各种搜索引擎搜索,尤其是某DN,但是垃圾文章太多。这里,首推还是官方文档

这里,我们再次确认主节点开启了mysql的bin-log日志:

sql 复制代码
 #是否开启binlog,ROW模式
 show variables like 'log_bin%';
 show variables like 'binlog_format%';
 show variables like '%server_id%';

一切正常,创建canal用户实现数据的同步:

sql 复制代码
 CREATE USER canal IDENTIFIED BY 'canal';
 GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
 FLUSH PRIVILEGES;

好的,准备工作就绪。现在只需要修改一下canal-deployer的配置文件即可。

bash 复制代码
 vim ./conf/example/instance.properties

修改对应的mysql地址即可。

启动canal-deployer服务:

bash 复制代码
 bash ./bin/startup.sh

这时我们只能看到对应的启动参数,看不到日志。

看日志,我们使用这样的命令:

bash 复制代码
 tail -f ./logs/example/example.log

大致是这样的,表明我们的canal服务启动成功。

启动成功的标志之一就是:我们查看slave的节点,多了一项:

ini 复制代码
 show slave hosts;

接下来的使用官方也给了案例:ClientExample

大体的意思就是导入依赖,跑代码即可。

xml 复制代码
 <dependency>
     <groupId>com.alibaba.otter</groupId>
     <artifactId>canal.client</artifactId>
     <version>1.1.0</version>
 </dependency>

案例代码:

ini 复制代码
 public class SimpleCanalClientExample {
 ​
 ​
     public static void main(String[] args) {
         // 创建链接
         CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
             11111), "example", "", "");
         int batchSize = 1000;
         int emptyCount = 0;
         try {
             connector.connect();
             connector.subscribe(".*\..*");
             connector.rollback();
             int totalEmptyCount = 120;
             while (emptyCount < totalEmptyCount) {
                 Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                 long batchId = message.getId();
                 int size = message.getEntries().size();
                 if (batchId == -1 || size == 0) {
                     emptyCount++;
                     System.out.println("empty count : " + emptyCount);
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                     }
                 } else {
                     emptyCount = 0;
                     System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
                     printEntry(message.getEntries());
                 }
 ​
                 connector.ack(batchId); // 提交确认
                 // connector.rollback(batchId); // 处理失败, 回滚数据
             }
 ​
             System.out.println("empty too many times, exit");
         } finally {
             connector.disconnect();
         }
     }
 ​
     private static void printEntry(List<Entry> entrys) {
         for (Entry entry : entrys) {
             if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                 continue;
             }
 ​
             RowChange rowChage = null;
             try {
                 rowChage = RowChange.parseFrom(entry.getStoreValue());
             } catch (Exception e) {
                 throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                     e);
             }
 ​
             EventType eventType = rowChage.getEventType();
             System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                 entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                 entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                 eventType));
 ​
             for (RowData rowData : rowChage.getRowDatasList()) {
                 if (eventType == EventType.DELETE) {
                     printColumn(rowData.getBeforeColumnsList());
                 } else if (eventType == EventType.INSERT) {
                     printColumn(rowData.getAfterColumnsList());
                 } else {
                     System.out.println("-------&gt; before");
                     printColumn(rowData.getBeforeColumnsList());
                     System.out.println("-------&gt; after");
                     printColumn(rowData.getAfterColumnsList());
                 }
             }
         }
     }
 ​
     private static void printColumn(List<Column> columns) {
         for (Column column : columns) {
             System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
         }
     }
 ​
 }

我在数据库中随意的更改数据,观察控制台输出:

那么数据的更新包括字段的更新就显而易见了。

那这个跟elasticsearch的数据同步有什么关系呢?毕竟官方出品了:ClientAdapter。直接几个配置文件搞定同步,根本不需要写任何的代码。

shigen的看法是:萝卜青菜,各有所爱。采用这种同步的方式,我们的逻辑里可以有更多自定义的操作。如:我们删除了文件记录,也要删除对应的磁盘文件,那这就需要我们自定义同步的逻辑了。


以上就是今天分享的全部内容了,觉得不错的话,记得点赞 在看 关注支持一下哈,您的鼓励和支持将是shigen坚持日更的动力。同时,shigen在多个平台都有文章的同步,也可以同步的浏览和订阅:

平台 账号 链接
CSDN shigen01 shigen的CSDN主页
知乎 gen-2019 shigen的知乎主页
掘金 shigen01 shigen的掘金主页
腾讯云开发者社区 shigen shigen的腾讯云开发者社区主页
微信公众平台 shigen 公众号名:shigen

shigen一起,每天不一样!

相关推荐
yhole17 小时前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
BingoGo17 小时前
Laravel 13 正式发布 使用 Laravel AI 无缝平滑升级
后端·php
l软件定制开发工作室18 小时前
Spring开发系列教程(34)——打包Spring Boot应用
java·spring boot·后端·spring·springboot
随风,奔跑18 小时前
Spring MVC
java·后端·spring
美团技术团队18 小时前
美团 BI 在指标平台和分析引擎上的探索和实践
后端
JimmtButler19 小时前
我用 Claude Code 给 Claude Code 做了一个 DevTools
后端·claude
Java水解19 小时前
Java 中实现多租户架构:数据隔离策略与实践指南
java·后端
Master_Azur19 小时前
Java面向对象之多态与重写
后端
ywf121519 小时前
Spring Integration + MQTT
java·后端·spring
武超杰20 小时前
SpringMVC核心功能详解:从RESTful到JSON数据处理
后端·json·restful