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

一提到数据库的同步,估计大家第一时间想到的就是基于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}

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

如果文章对您有所帮助,欢迎关注公众号 码农参上

Java代码中,如何监控Mysql的binlog?的更多相关文章

  1. Spring MVC框架下在java代码中访问applicationContext.xml文件中配置的文件(可以用于读取配置文件内容)

    <bean id="propertyConfigurer" class="com.****.framework.core.SpringPropertiesUtil& ...

  2. 使用mongo-java-driver3.0.2.jar和mongodb3.0在java代码中的用户验证4

    以下是使用mongo-java-driver3.0.2.jar和mongodb3.0.4在java代码中的用户验证: ServerAddress sa = new ServerAddress(host ...

  3. Android color(颜色) 在XML文件和java代码中

    Android color(颜色) 在XML文件和java代码中,有需要的朋友可以参考下. 1.使用Color类的常量,如: int color = Color.BLUE;//创建一个蓝色 是使用An ...

  4. 关于在Java代码中写Sql语句需要注意的问题

    最近做程序,时不时需要自己去手动将sql语句直接写入到Java代码中,写入sql语句时,需要注意几个小问题. 先看我之前写的几句简单的sql语句,自以为没有问题,但是编译直接报错. String st ...

  5. java代码中获取进程process id(转)

    另一方面,线程ID=进程ID+内部线程对象ID并不成立,    参考: blog.csdn.net/heyetina/article/details/6633901     如何在java代码中获取进 ...

  6. android中在java代码中设置Button按钮的背景颜色

    android中在java代码中设置Button按钮的背景颜色 1.设置背景图片,图片来源于drawable: flightInfoPanel.setBackgroundDrawable(getRes ...

  7. Java代码中获取Json的key值

    测试json字符串: {"access_token":"hkbQl5o_l67dZ7_vJRATKBwTLk9Yj5QyMuOJThAr8Baj0xWf4wxW1p4ym ...

  8. 在java代码中执行js脚本,实现计算出字符串“(1+2)*(1+3)”的结果

            今天在公司项目中,发现一个计算运费的妙招.由于运费规则各种各样,因此写一个公式存到数据库.下次需要计算运费时,直接取出这个公式,把公式的未知变量给替换掉,然后计算出结果就是ok了. 一 ...

  9. 在java代码中,用xslt处理xml文件

    http://blog.csdn.net/zhou_lei/article/details/2661735 ********************************************** ...

  10. char和String 在jsp java代码中与jstl代码中的区别

    在 jsp java代码中 '0' ,这种代表char 在jstl中 '0' 会被解释为 String  所以也可以用  .equals  方法

随机推荐

  1. 注册中心ZooKeeper,Eureka,Consul,Nacos对比

    简介 ​ 服务注册中心本质上是为了解耦服务提供者和服务消费者.对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的.更进一步,为了支持弹性扩缩容特性,一个微服务的提供者 ...

  2. Vue项目的开发流程

    我先安装的node.js 1.确认已安装了node.js,可在cmd中输入( node -v和npm -v),如显示出版号,说明安装成功 2.安装webpack 和webpack-cli 在全局下安装 ...

  3. java特点了解及JDK初谈

    java特性: 1.跨平台:主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行. 2.简单:相比与C ...

  4. js笔记8

    1.js数据类型 基本数据类型:string.undefined.null.boolean.nember 引用数据类型:object.array.function 二者的区别? 基本的数据类型就是简单 ...

  5. Unity3D-UI--Layout组件

    Layout组件 自动排版 Layout Group Vertical Layout Group 垂直布局 [垂直布局组]组件将其子布局元素彼此重叠.它们的高度由各自的最小高度,首选高度和柔性高度决定 ...

  6. Kubernetes之Ingress

    在Service篇里面介绍了像集群外部的客户端公开服务的两种方法,还有另一种方法---创建Ingress资源. 定义Ingress (名词)-进入或进入的行为;进入的权利;进入的手段或地点;入口. 接 ...

  7. MySQL数据库性能优化与监控实战(阶段四)

    MySQL数据库性能优化与监控实战(阶段四) 作者 刘畅 时间 2020-10-20 目录 1 sys数据库 1 2 系统变量 1 3 性能优化 1 3.1 硬件层 1 3.2 系统层 1 3.3 软 ...

  8. Java Set HashSet

    import java.util.HashSet; import java.util.Set; /** Set存储特点:数据无序.不可重复 Set接口的实现类: HashSet:Set接口的主要实现类 ...

  9. LRU工程实现源码(一):Redis 内存淘汰策略

    目录 内存淘汰是什么?什么时候内存淘汰 内存淘汰策略 Redis中的LRU淘汰算法 源码剖析 第一步:什么时候开始淘汰key 配置读取 检查时机 getMaxmemoryState 第二步:淘汰哪些k ...

  10. Window server 2016 搭建Java Web环境

    系统下载 下载种子(迅雷下载): ed2k://|file|cn_windows_server_2016_updated_feb_2018_x64_dvd_11636703.iso|629426585 ...