最近在项目中使用到缓存,感觉优雅的使用缓存工具比较麻烦,业界主流的缓存使用方法有先删除缓存在更新数据库,或者先更新数据库在删除缓存,只不过在业务代码中对数据操作不止一处,若想实现缓存数据的清除,必须在多处地方调用清除缓存的地方,麻烦不说,要是忘记某处数据更新未同步更新缓存数据,还会造成数据不一致的问题。

  前几天在研究数据库主从同步的时候,发现 mysql 的 binlog 对数据库的数据更新会做日志记录,那么只要监听数据库的更新,是不是代表着可以在一处统一维护数据和缓存的一致性。

  下面就简单介绍下 mysql 的 binlog

什么是 Binlog

Binlog 是 MySQL Server 维护的一种二进制日志,主要是用来记录对 MySQL 数据更新或潜在发生更新的 SQL 语句,并以"事务"的形式保存在磁盘中(文件)

主要用途

  • 1. 复制:MySQL 的 Master-Slave 协议,让 Slave 可以通过监听 Binlog 实现数据复制,达到数据一致的目的
  • 2. 数据恢复:通过 mysqlbinlog 工具恢复数据
  • 3. 增量备份

支持的格式

  • ROW

仅保存记录被修改细节,不记录 SQL 语句上下文相关信息:能非常清晰的记录下每行数据的修改细节,不需要记录上下文相关信息,因此不会发生某些特定情况下的 procedure、function、及 trigger 的调用触发无法被正确复制的问题,任何情况都可以被复制,且能加快从库重放日志的效率,保证从库数据的一致性。

  • STATEMENT

每一条会修改数据的 SQL 都会记录在 Binlog 中:只需要记录执行语句的细节和上下文环境,避免了记录每一行的变化,在一些修改记录较多的情况下相比 ROW 类型能大大减少 Binlog 日志量,节约IO,提高性能;还可以用于实时的还原;同时主从版本可以不一样,从服务器版本可以比主服务器版本高。

  • MIXED

以上两种类型的混合使用。经过前面的对比,可以发现 ROW 类型和 STATEMENT 类型各有优势,如能根据 SQL 语句取舍可能会有更好地性能和效果;MIXED 便是以上两种类型的结合。

Binlog 的相关命令

相关变量

-- Binlog 开关变量
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
1 row in set (0.30 sec)

-- Binlog 日志的格式
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+
1 row in set (0.00 sec)

常用操作命令

SQL语句

语句含义

show master logs;

查看所有 Binlog 的日志列表

show master status;

查看最后一个 Binlog 日志的编号名称,及最后一个事件结束的位置(pos)

flush logs;

刷新 Binlog,此刻开始产生一个新编号的 Binlog 日志文件

reset master;

清空所有的 Binlog 日志

show binlog events;

查看第一个 Binlog 日志

show binlog events in ‘binlog.000030’;

查看指定的 Binlog 日志

show binlog events in ‘binlog.000030’ from 931;

从指定的位置开始,查看指定的 Binlog 日志

show binlog events in ‘binlog.000030’ from 931 limit 2;

从指定的位置开始,查看指定的 Binlog 日志,限制查询的条数

show binlog events in ‘binlog.000030’ from 931 limit 1, 2;

从指定的位置开始,带有偏移,查看指定的 Binlog 日志,限制查询的条数

Binlog 的 Event 类型

MySQL Binlog Event 类型有很多种(MySQL 官方定义了 36 种),例如:XID、TABLE_MAP、QUERY 等等。但是,我们需要了解的(也是最常用的)类型不是很多。

Event Type

事件

重要程度

QUERY_EVENT

与数据无关的操作, begin、drop table、truncate table 等

了解即可

XID_EVENT

标记事务提交

了解即可

TABLE_MAP_EVENT

记录下一个操作所对应的表信息,存储了数据库名和表名

非常重要

WRITE_ROWS_EVENT

插入数据,即 insert 操作

非常重要

UPDATE_ROWS_EVENT

更新数据,即 update 操作

非常重要

DELETE_ROWS_EVENT

删除数据,即 delete 操作

非常重要

最好的方式是在数据库中建一张测试表,然后对表进行增删改操作,再去查看下 Binlog 里面都有些什么,如下图所示

mysql 监控cpu mysql 监控binlog_mysql

说明

对于 MySQL Binlog,我们可以不用过分追究 Binlog 里面到底包含了些什么,对于应用的话,我们最重要要搞清楚 Binlog 的 Event:每个 Event 包含 header 和 data 两个部分;header 提供了 Event 的创建时间,哪个服务器等信息,data 部分提供的是针对该 Event 的具体信息,如具体数据的修改。我们对 Binlog 的解析,即为对 Event 的解析。

  • Binlog 的 EventType (需要注意,不同版本的 MySQL,EventType 可能会不同)
  • Binlog 中并不会打印数据表的列名

既然了解了 binlog 那么就说说 binlog在项目中如何使用:

mysql-binlog-connector-java (监听解析 Binlog 的开源工具)

地址说明

  • github 地址

mysql-binlog-connector-java

  • Maven 仓库地址
<dependency>
	<groupId>com.github.shyiko</groupId>
	<artifactId>mysql-binlog-connector-java</artifactId>
	<version>0.21.0</version>
</dependency>

使用说明

首先,我们需要清楚,我们的目的是实现对 MySQL 数据表状态的变更有所感知,也就是能够实现对 Binlog 的监听、并解析成我们 ”想要的格式(Java 对象)”。

  • 简单使用
import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.*;
import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;

import java.io.IOException;

/**
 * @author gaobo
 * @version 1.0.0
 * @ClassName Test
 * @Description TODO
 * @Date 2020/8/2 22:14
 */
public class Test {

    public static void main(String[] args) {
        BinaryLogClient client = new BinaryLogClient("127.0.0.1", 3306, "root", "123456");
        EventDeserializer eventDeserializer = new EventDeserializer();
        eventDeserializer.setCompatibilityMode(
                EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG,
                EventDeserializer.CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY
        );

        new Thread(() -> {
            client.registerEventListener(event -> {
                final EventData data = event.getData();
                if (data instanceof WriteRowsEventData) {
                    WriteRowsEventData writeRowsEventData = (WriteRowsEventData) data;
                    System.out.println(writeRowsEventData);
                } else if (data instanceof UpdateRowsEventData) {
                    UpdateRowsEventData updateRowsEventData = (UpdateRowsEventData) data;
                    System.out.println(updateRowsEventData);
                } else if (data instanceof DeleteRowsEventData) {
                    DeleteRowsEventData deleteRowsEventData = (DeleteRowsEventData) data;
                    System.out.println(deleteRowsEventData);
                }
            });
            try {
                client.connect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
  • 看一看 client 监听到的 event 里面都有啥
public class Event implements Serializable {

    // Binlog 中的两个最核心的元素:header 和 data
    private EventHeader header;
    // 这里需要注意,EventData 是个接口,不同的 EventType 对应到不同的实现,例如:WriteRowsEventData、UpdateRowsEventData 等等
    private EventData data;

    // data.toString() 打印的格式在这里定义的
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("Event");
        sb.append("{header=").append(header);
        sb.append(", data=").append(data);
        sb.append('}');
        return sb.toString();
    }
}
  • 自定义 Binlog 的监听器

目前已经知道了 BinaryLogClient 的基础用法,要实现监听解析 Binlog,我们只需要定义监听器即可,这就要去实现 BinaryLogClient.EventListener 接口

public interface EventListener {

    // 只需要实现 onEvent 方法,然后自定义解析 Event 的方法即可
    // 核心思想是两个步骤:
    //    1. 当前的 Event 是否需要处理 -- 对 header 部分的处理
    //    2. 怎么去处理  -- 对 data 部分的处理
    void onEvent(Event event);
}

看起来是不是很简单了,那么下一篇 我们就进入实战代码中,使用binlog优雅的实现数据库监听