Java代码中,如何监控Mysql的binlog?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Java代码中,如何监控Mysql的binlog?

最近在工作中,遇到了这样一个业务场景,我们需要关注一个业务系统数据库中某几张表的数据,当数据发生新增或修改时,将它同步到另一个业务系统数据库中的表中。

一提到数据库的同步,估计大家第一时间想到的就是基于binlog的主从复制了,但是放在我们的场景中,还有几个问题:

  • 第一,并不是需要复制所有表的数据,复制对象只有少量的几张表
  • 第二,也是比较麻烦的,两个业务系统数据库表结构可能不一致。例如,要同步数据库1的A表中的某些字段到数据库2的B表中,在这一过程中,A表和B表的字段并不是完全相同

这样的话,我们只能通过代码的方式,首先获取到数据库1表中数据的变动,再通过手动映射的方式,插入到数据库2的表中。但是,获取变动数据的这一过程,还是离不开binlog,因此我们就需要在代码中对binlog进行一下监控。

先说结论,我们最终使用了一个开源工具mysql-binlog-connector-java,用来监控binlog变化并获取数据,获取数据后再手动插入到另一个库的表中,基于它来实现了数据的同步。这个工具的git项目地址如下:

https://github.com/shyiko/mysql-binlog-connector-java

在正式开始前,还是先简单介绍一下mysqlbinlogbinlog是一个二进制文件,它保存在磁盘中,是用来记录数据库表结构变更、表数据修改的二进制日志。其实除了数据复制外,它还可以实现数据恢复、增量备份等功能。

启动项目前,首先需要确保mysql服务已经启用了binlog

show variables like 'log_bin';

如果为值为OFF,表示没有启用,那么需要首先启用binlog,修改配置文件:

log_bin=mysql-bin
binlog-format=ROW
server-id=1

对参数做一个简要说明:

  • 在配置文件中加入了log_bin配置项后,表示启用了binlog
  • binlog-formatbinlog的日志格式,支持三种类型,分别是STATEMENTROWMIXED,我们在这里使用ROW模式
  • server-id用于标识一个sql语句是从哪一个server写入的,这里一定要进行设置,否则我们在后面的代码中会无法正常监听到事件

在更改完配置文件后,重启mysql服务。再次查看是否启用binlog,返回为ON,表示已经开启成功。

在Java项目中,首先引入maven坐标:

<dependency>
    <groupId>com.github.shyiko</groupId>
    <artifactId>mysql-binlog-connector-java</artifactId>
    <version>0.21.0</version>
</dependency>

写一段简单的示例,看看它的具体使用方式:

public static void main(String[] args) {
    BinaryLogClient client = new BinaryLogClient("127.0.0.1", 3306, "hydra", "123456");
    client.setServerId(2);
    client.registerEventListener(event -> {
        EventData data = event.getData();
        if (data instanceof TableMapEventData) {
            System.out.println("Table:");
            TableMapEventData tableMapEventData = (TableMapEventData) data;
            System.out.println(tableMapEventData.getTableId()+": ["+tableMapEventData.getDatabase() + "-" + tableMapEventData.getTable()+"]");
        }
        if (data instanceof UpdateRowsEventData) {
            System.out.println("Update:");
            System.out.println(data.toString());
        } else if (data instanceof WriteRowsEventData) {
            System.out.println("Insert:");
            System.out.println(data.toString());
        } else if (data instanceof DeleteRowsEventData) {
            System.out.println("Delete:");
            System.out.println(data.toString());
        }
    });
    try {
        client.connect();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

首先,创建一个BinaryLogClient客户端对象,初始化时需要传入mysql的连接信息,创建完成后,给客户端注册一个监听器,来实现它对binlog的监听和解析。在监听器中,我们暂时只对4种类型的事件数据进行了处理,除了WriteRowsEventDataDeleteRowsEventDataUpdateRowsEventData对应增删改操作类型的事件数据外,还有一个TableMapEventData类型的数据,包含了表的对应关系,在后面的例子中再具体说明。

在这里,客户端监听到的是数据库级别的所有事件,并且可以监听到表的DML语句和DDL语句,所以我们只需要处理我们关心的事件数据就行,否则会收到大量的冗余数据。

启动程序,控制台输出:

com.github.shyiko.mysql.binlog.BinaryLogClient openChannelToBinaryLogStream
信息: Connected to 127.0.0.1:3306 at mysql-bin.000002/1046 (sid:2, cid:10)

连接mysql的binlog成功,接下来,我们在数据库中插入一条数据,这里操作的数据库名字是tenant,表是dept

insert into dept VALUES(8,"人力","","1");

这时,控制台就会打印监听到事件的数据:

Table:
108: [tenant-dept]
Insert:
WriteRowsEventData{tableId=108, includedColumns={0, 1, 2, 3}, rows=[
    [8, 人力, , 1]
]}

我们监听到的事件类型数据有两类,第一类是TableMapEventData,通过它可以获取操作的数据库名称、表名称以及表的id。之所以我们要监听这个事件,是因为之后监听的实际操作中返回数据中包含了表的id,而没有表名等信息,所以如果我们想知道具体的操作是在哪一张表的话,就要先维护一个id与表的对应关系。

第二个打印出来的监听事件数据是WriteRowsEventData,其中记录了insert语句作用的表,插入涉及到的列,以及实际插入的数据。另外,如果我们只需要对特定的一张或几张表进行处理的话,也可以提前设置表的名单,在这里根据表id到表名的映射关系,实现数据的过滤,

接下来,我们再执行一条update语句:

update dept set tenant_id=3 where id=8 or id=9

控制台输出:

Table:
108: [tenant-dept]
Update:
UpdateRowsEventData{tableId=108, includedColumnsBeforeUpdate={0, 1, 2, 3}, includedColumns={0, 1, 2, 3}, rows=[
    {before=[8, 人力, , 1], after=[8, 人力, , 3]},
    {before=[9, 人力, , 1], after=[9, 人力, , 3]}
]}

在执行update语句时,可能会作用于多条数据,因此在实际修改的数据中,可能包含多行记录,这一点体现在上面的rows中,包含了id为8和9的两条数据。

最后,再执行一条delete语句:

delete from dept where tenant_id=3

控制台打印如下,rows中同样返回了生效的两条数据:

Table:
108: [tenant-dept]
Delete:
DeleteRowsEventData{tableId=108, includedColumns={0, 1, 2, 3}, rows=[
    [8, 人力, , 3],
    [9, 人力, , 3]
]}

简单的使用原理介绍完成后,再回到我们原先的需求上,需要将一张表中新增或修改的数据同步到另一张表中,问题还有一个,就是如何将返回的数据对应到所在的列上。这时应该怎么实现呢?

update操作为例,我们要对提取的数据后进行一下处理,更改上面例子中的方法:

if (data instanceof UpdateRowsEventData) {
    System.out.println("Update:");
    UpdateRowsEventData updateRowsEventData = (UpdateRowsEventData) data;
    for (Map.Entry<Serializable[], Serializable[]> row : updateRowsEventData.getRows()) {
        List<Serializable> entries = Arrays.asList(row.getValue());
        System.out.println(entries);
        JSONObject dataObject = getDataObject(entries);
        System.out.println(dataObject);
    }
}

在将data强制转换为UpdateRowsEventData后,可以使用getRows方法获取到更新的行数据,并且能够取到每一列的值。

之后,调用了一个自己实现的getDataObject方法,用它来实现数据到列的绑定过程:

private static JSONObject getDataObject(List message) {
    JSONObject resultObject = new JSONObject();
    String format = "{\"id\":\"0\",\"dept_name\":\"1\",\"comment\":\"2\",\"tenant_id\":\"3\"}";
    JSONObject json = JSON.parseObject(format);
    for (String key : json.keySet()) {
        resultObject.put(key, message.get(json.getInteger(key)));
    }
    return resultObject;
}

format字符串中,提前维护了一个数据库表的字段顺序的字符串,标识了每个字段位于顺序中的第几个位置。通过上面这个函数,能够实现数据到列的填装过程,我们再执行一条update语句来查看一下结果:

update dept set tenant_id=3,comment="1" where id=8

控制台打印结果如下:

Table:
108: [tenant-dept]
Update:
[8, 人力, 1, 3]
{"tenant_id":3,"dept_name":"人力","comment":"1","id":8}

可以看到,将修改后的这一条记录中的属性填装到了它对应的列中,之后我们再根据具体的业务逻辑,就可以根据字段名取出数据,将数据同步到其他的表了。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
9天前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
94 2
|
12天前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
88 3
|
12天前
|
Java
怎么用Java 代码示例来展示继承的实现
本文通过Java代码示例展示继承机制:Animal为父类,Cat和Dog继承其属性与方法,并实现构造函数调用、方法重写与特有功能扩展,体现代码复用与多态特性。
54 4
|
27天前
|
Java API 开发工具
【Azure Developer】Java代码实现获取Azure 资源的指标数据却报错 "invalid time interval input"
在使用 Java 调用虚拟机 API 获取指标数据时,因本地时区设置非 UTC,导致时间格式解析错误。解决方法是在代码中手动指定时区为 UTC,使用 `ZoneOffset.ofHours(0)` 并结合 `withOffsetSameInstant` 方法进行时区转换,从而避免因时区差异引发的时间格式问题。
139 3
|
13天前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
196 0
|
2月前
|
人工智能 监控 安全
智慧工地解决方案,java智慧工地程序代码
智慧工地系统融合物联网、AI、大数据等技术,实现对施工现场“人、机、料、法、环”的全面智能监控与管理,提升安全、效率与决策水平。
|
1月前
|
SQL Java 关系型数据库
Java连接MySQL数据库环境设置指南
请注意,在实际部署时应该避免将敏感信息(如用户名和密码)硬编码在源码文件里面;应该使用配置文件或者环境变量等更为安全可靠地方式管理这些信息。此外,在处理大量数据时考虑使用PreparedStatement而不是Statement可以提高性能并防止SQL注入攻击;同时也要注意正确处理异常情况,并且确保所有打开过得资源都被正确关闭释放掉以防止内存泄漏等问题发生。
72 13
|
21天前
|
SQL 运维 关系型数据库
深入探讨MySQL的二进制日志(binlog)选项
总结而言,对MySQL binlogs深度理解并妥善配置对数据库运维管理至关重要;它不仅关系到系统性能优化也是实现高可靠性架构设计必须考虑因素之一。通过精心规划与周密部署可以使得该机能充分发挥作用而避免潜在风险带来影响。
56 6
|
1月前
|
缓存 Java 开发者
Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
108 3
|
2月前
|
存储 SQL 关系型数据库
MySQL中binlog、redolog与undolog的不同之处解析
每个都扮演回答回溯与错误修正机构角色: BinLog像历史记载员详细记载每件大大小小事件; RedoLog则像紧急救援队伍遇见突發情況追踪最后活动轨迹尽力补救; UndoLog就类似时间机器可倒带历史让一切归位原始样貌同时兼具平行宇宙观察能让多人同时看见各自期望看见历程而互不干扰.
152 9

推荐镜像

更多