持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
Java监听binlog日志客户端mysql-binlog-connector-java
前言
在一些业务场景下, 我们需要根据表数据的变化做一些特定的操作, 如: ElasticSearch索引更新、同步更新其他数据、缓存失效等 这些场景中我们可以使用:
Python脚本监听binlogJava客户端监听binlog(对Java程序员更友好)MySQL触发器(只能实现简单功能, 维护性差, 有死锁风险)- 直接
耦合在业务代码中(强耦合, 扩展性差) ......等众多的实现方式
下面介绍一下以Java客户端的实现方式
maven
<dependency>
<groupId>com.github.shyiko</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.16.1</version>
</dependency>
环境
MySQL binlog相关配置
# server id 确保唯一
server-id=1
# 开启binlog
log-bin=binlog
# binlog记录方式
binlog_format=Row
# 日志刷盘方式, 有
innodb_flush_log_at_trx_commit=2
部分参数解释
binlog_format binlog记录方式
Statement(记录SQL)Row(记录数据变化)Mixed(混合模式)
innodb_flush_log_at_trx_commit 日志刷盘方式
- 0 速度最快, 每隔1s进行写一次log file并flush磁盘, mysql挂掉会丢失上1s的数据,
最不安全 - 1 速度最慢, 每次都写入log file并flush磁盘,
最安全 - 2 速度较快, 每次都写入log file, 每隔1s flush磁盘,
操作系统崩溃/系统断电会丢失上1s的数据
生成的binlog文件如下:
binlog文件记录的是二进制数据, 可以使用mysqlbinlog工具输出格式化后的日志
mysqlbinlog -v /var/lib/mysql/binlog.00001 --start-datetime="2022-05-29 00:00:00"
相关代码
数据库配置
// 数据库地址
private static final String HOST = "127.0.0.1";
// 端口
private static final int PORT = 3306;
// 数据库
private static final String SCHEMA = "book";
// 用户名
private static final String USERNAME = "root";
// 密码
private static final String PASSWORD = "root";
// 需要监听的表
public static List<String> DATABASE_LIST = Arrays.asList("tb_01", "tb_02", "tb_03");
binlog监听操作记录值转化成对象工具
由于监听的binlog日志返回的是List<Object>只包含值, 所以这里通过获取表的所有列名称和binlog进行合并转换为Java对象
/**
* 表的所有列名称(COLUMNS_MAP: key => 表名,
* value => 表的所有列名)
* select mc.COLUMN_NAME from
* information_schema.COLUMNS mc
* where TABLE_SCHEMA = (select database())
* and TABLE_NAME=#{tableName}
**/
public static Map<String, List<String>> COLUMNS_MAP = new HashMap<>();
/**
* value to bean
**/
private <T> T toBean(List<String> columnNames, List<Object> values, Class<T> beanClass) {
Map<String, Object> map = new HashMap<>();
int len = columnNames.size();
for (int i = 0; i < len; i++) {
map.put(columnNames.get(i), values.get(i));
}
// map->bean 忽略大小写
return BeanUtil.mapToBeanIgnoreCase(map, beanClass, false);
}
初始化
BinaryLogClient client = new BinaryLogClient(HOST, PORT, SCHEMA, USERNAME, PASSWORD);
// 设置监听服务id(保证唯一)
client.setServerId(1);
// 设置起始读取文件名称(如果不指定就是监听最后一个binlog文件)
// client.setBinlogFilename("");
// 设置起始读取位置(如果不指定就是从最后的位置开始)
// client.setBinlogPosition(0L);
// 注册一个监听事件
client.registerEventListener((event -> {
event(event, client);
}));
事件处理
private static void event(Event event, BinaryLogClient client) {
// 获取监听结果数据集
EventData data = event.getData();
if (data != null) {
if (data instanceof TableMapEventData) {
TableMapEventData tableMapEventData = (TableMapEventData) data;
TABLE_MAP.put(tableMapEventData.getTableId(), tableMapEventData.getTable());
}
// 新增数据事件
if (data instanceof WriteRowsEventData) {
WriteRowsEventData writeRowsEventData = (WriteRowsEventData) data;
String tableName = TABLE_MAP.get(writeRowsEventData.getTableId());
if (tableName != null && DATABASE_LIST.contains(tableName)) {
for (Serializable[] row : writeRowsEventData.getRows()) {
List<Object> objectList = (List<Object>) JSONObject.toJSON(row);
}
}
}
// 删除数据事件
else if (data instanceof DeleteRowsEventData) {
DeleteRowsEventData deleteRowsEventData = (DeleteRowsEventData) data;
String tableName = TABLE_MAP.get(deleteRowsEventData.getTableId());
if (tableName != null && DATABASE_LIST.contains(tableName)) {
for (Serializable[] row : deleteRowsEventData.getRows()) {
List<Object> objectList = (List<Object>) JSONObject.toJSON(row);
}
}
}
// 更新数据事件
else if (data instanceof UpdateRowsEventData) {
UpdateRowsEventData updateRowsEventData = (UpdateRowsEventData) data;
String tableName = TABLE_MAP.get(updateRowsEventData.getTableId());
if (tableName != null && DATABASE_LIST.contains(tableName)) {
for (Map.Entry<Serializable[], Serializable[]> row : updateRowsEventData.getRows()) {
// 修改之前的数据
List<Object> beforeObjectList = (List<Object>) JSONObject.toJSON(row.getKey());
// 修改之后的数据
List<Object> afterObjectList = (List<Object>) JSONObject.toJSON(row.getValue());
}
}
}
}
}