Java监听binlog日志客户端mysql-binlog-connector-java

1,494 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

Java监听binlog日志客户端mysql-binlog-connector-java

前言

在一些业务场景下, 我们需要根据表数据的变化做一些特定的操作, 如: ElasticSearch索引更新、同步更新其他数据、缓存失效等 这些场景中我们可以使用:

  • Python脚本监听binlog
  • Java客户端监听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日志文件.png 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());
                }
            }
        }
    }
}