日常分享系列之:学习mysql-binlog-connector-java
- 使用
- 读取二进制日志文件
- [利用 MySQL 复制流](#利用 MySQL 复制流)
- MariaDB
- 控制事件反序列化
- [通过 JMX 公开 BinaryLogClient](#通过 JMX 公开 BinaryLogClient)
- [Using SSL](#Using SSL)
- 实施说明
- 常见问题解答
- 文档
- BinaryLogFileReader.java
mysql-binlog-connector-java:
使用
java
<dependency>
<groupId>com.zendesk</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.25.0</version>
</dependency>
最初,该项目是作为的分支开始的,但最终却成为了一次完全的重写。主要的不同之处/特点包括:
- 自动的二进制日志文件名/位置 | GTID 解决方案
- 可恢复的断开连接
- 可插拔的故障转移策略
- binlog_checksum=CRC32 支持(适用于 MySQL 5.6.2+ 用户)
- 通过 TLS 进行安全通信
- 适用于 JMX
- 实时统计
- 可在 Maven Central 上获取
- 无第三方依赖,测试套件覆盖不同版本的 MySQL 发行版。
读取二进制日志文件
java
File binlogFile = ...
EventDeserializer eventDeserializer = new EventDeserializer();
eventDeserializer.setCompatibilityMode(
EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG,
EventDeserializer.CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY
);
BinaryLogFileReader reader = new BinaryLogFileReader(binlogFile, eventDeserializer);
try {
for (Event event; (event = reader.readEvent()) != null; ) {
...
}
} finally {
reader.close();
}
这段代码用于读取二进制日志文件(binlogFile)中的事件,并对事件进行处理。
- 首先,通过创建BinaryLogFileReader对象,并传入binlogFile和EventDeserializer对象来初始化。BinaryLogFileReader是Debezium库中的类,用于读取二进制日志文件。EventDeserializer是另一个Debezium类,用于反序列化二进制日志中的事件。
- 接下来,通过调用eventDeserializer的setCompatibilityMode方法,设置事件反序列化的兼容模式。在此例中,将日期和时间以长整型形式表示(DATE_AND_TIME_AS_LONG),将字符和二进制数据以字节数组形式表示(CHAR_AND_BINARY_AS_BYTE_ARRAY)。
- 然后,通过使用BinaryLogFileReader的readEvent方法,循环读取日志文件中的事件。每次循环读取到一个事件,就可以对该事件进行自定义的处理。
- 最后,使用finally块确保在读取完毕后关闭BinaryLogFileReader对象,释放资源。
- 这段代码展示了如何使用Debezium库来读取二进制日志文件,并对其中的事件进行处理。可以根据具体需求在for循环中添加自定义的逻辑来处理读取到的每个事件。
利用 MySQL 复制流
先决条件:无论计划使用哪个用户来访问 BinaryLogClient,他都必须拥有 REPLICATION SLAVE 权限。除非自己指定 binlogFilename/binlogPosition(在这种情况下不会启动自动解析),否则还需要授予 REPLICATION CLIENT 权限。
java
BinaryLogClient client = new BinaryLogClient("hostname", 3306, "username", "password");
EventDeserializer eventDeserializer = new EventDeserializer();
eventDeserializer.setCompatibilityMode(
EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG,
EventDeserializer.CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY
);
client.setEventDeserializer(eventDeserializer);
client.registerEventListener(new EventListener() {
@Override
public void onEvent(Event event) {
...
}
});
client.connect();
这段代码用于创建一个BinaryLogClient对象,连接到MySQL数据库,并注册一个事件监听器。
- 首先,通过创建BinaryLogClient对象,并传入MySQL主机名、端口、用户名和密码来初始化。BinaryLogClient是Debezium库中的类,用于连接到MySQL数据库,并读取其二进制日志中的事件。
- 接下来,通过创建EventDeserializer对象,设置事件反序列化的兼容模式,并将其设置为BinaryLogClient的事件反序列化器。由于默认情况下,BinaryLogClient使用的事件反序列化器是LegacyEventDeserializer,因此需要将其替换为自定义的eventDeserializer。
- 然后,通过调用registerEventListener方法,注册一个事件监听器。在此例中,创建了一个匿名内部类实现EventListener接口,并重写了onEvent方法。在onEvent方法中,可以对读取到的事件进行自定义的处理。
- 最后,通过调用client的connect方法,连接到MySQL数据库并开始读取其二进制日志中的事件。连接成功后,BinaryLogClient将持续读取MySQL二进制日志中的事件,并在每个事件上触发注册的事件监听器。
- 这段代码展示了如何使用Debezium库创建一个BinaryLogClient对象,连接到MySQL数据库,并注册一个事件监听器来处理其二进制日志中的事件。可以根据具体需求在事件监听器中添加自定义的逻辑来处理读取到的每个事件。
可以使用 client.registerLifecycleListener(...) 注册 onConnect / onCommunicationFailure / onEventDeserializationFailure / onDisconnect 的侦听器。
默认情况下,BinaryLogClient 从当前(连接时)主 binlog 位置启动。如果您希望从特定文件名或位置启动,请使用 client.setBinlogFilename(filename) + client.setBinlogPosition(position)。
client.connect() 是阻塞的(意味着客户端将在当前线程中侦听事件)。另一方面,client.connect(timeout) 会生成一个单独的线程。
MariaDB
原始的BinaryLogClient可以直接与MariaDB配合使用,但有两个区别:
- 一、MariaDB的GTID(全局事务标识)不同。它们仍然是字符串,但解析方式不同。
- 二、MariaDB可以发送ANNOTATE_ROWS事件,允许恢复用于生成行的SQL语句(在基于行的复制中)。
请参阅官方文档https://mariadb.com/kb/en/annotate_rows_log_event/并使用client.setUseSendAnnotateRowsEvent(true)进行配置。
控制事件反序列化
可能出于多种原因需要进行控制:不想浪费时间反序列化不需要的事件;感兴趣的事件类型没有定义EventDataReader(或者定义了但存在错误);希望以不同的方式反序列化某些类型的事件(例如,*RowsEventData应该包含表名而不是ID);等等。
java
EventDeserializer eventDeserializer = new EventDeserializer();
// do not deserialize EXT_DELETE_ROWS event data, return it as a byte array
eventDeserializer.setEventDataDeserializer(EventType.EXT_DELETE_ROWS,
new ByteArrayEventDataDeserializer());
// skip EXT_WRITE_ROWS event data altogether
eventDeserializer.setEventDataDeserializer(EventType.EXT_WRITE_ROWS,
new NullEventDataDeserializer());
// use custom event data deserializer for EXT_DELETE_ROWS
eventDeserializer.setEventDataDeserializer(EventType.EXT_DELETE_ROWS,
new EventDataDeserializer() {
...
});
BinaryLogClient client = ...
client.setEventDeserializer(eventDeserializer);
这段代码用于配置事件反序列化器(EventDeserializer)以控制事件数据的反序列化方式。
- 首先,通过创建EventDeserializer对象来初始化事件反序列化器。
- 然后,通过调用setEventDataDeserializer方法,为特定类型的事件设置自定义的事件数据反序列化器。在这个例子中,针对EXT_DELETE_ROWS事件类型,使用了一个名为ByteArrayEventDataDeserializer的自定义事件数据反序列化器,它将事件数据返回为字节数组。对于EXT_WRITE_ROWS事件类型,则使用了一个名为NullEventDataDeserializer的自定义事件数据反序列化器,它完全跳过事件数据的反序列化过程,即不进行任何处理。对于EXT_DELETE_ROWS事件类型,使用了一个匿名内部类实现EventDataDeserializer接口的自定义事件数据反序列化器。
- 接下来,创建一个BinaryLogClient对象,并将上述配置的事件反序列化器设置给它。
- 通过这段代码,可以根据具体需求,为不同类型的事件设置不同的事件数据反序列化器,以控制事件数据的反序列化方式。可以根据需要选择使用预定义的反序列化器,也可以创建自定义的反序列化器来处理特定类型的事件数据。
通过 JMX 公开 BinaryLogClient
java
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
BinaryLogClient binaryLogClient = ...
ObjectName objectName = new ObjectName("mysql.binlog:type=BinaryLogClient");
mBeanServer.registerMBean(binaryLogClient, objectName);
// following bean accumulates various BinaryLogClient stats
// (e.g. number of disconnects, skipped events)
BinaryLogClientStatistics stats = new BinaryLogClientStatistics(binaryLogClient);
ObjectName statsObjectName = new ObjectName("mysql.binlog:type=BinaryLogClientStatistics");
mBeanServer.registerMBean(stats, statsObjectName);
这段代码用于在Java管理扩展(JMX)中注册BinaryLogClient和BinaryLogClientStatistics的MBean。
- 首先,通过调用ManagementFactory的getPlatformMBeanServer方法,获取到一个MBeanServer对象。MBeanServer用于注册和管理MBean(管理Bean)。
- 接下来,创建一个BinaryLogClient对象,并将其以ObjectName的形式注册到MBeanServer中。ObjectName用于唯一标识一个MBean。在此例中,将BinaryLogClient注册到名为"mysql.binlog:type=BinaryLogClient"的ObjectName下。
- 然后,创建一个BinaryLogClientStatistics对象,并将其以ObjectName的形式注册到MBeanServer中。BinaryLogClientStatistics是一个用于统计BinaryLogClient各种信息(例如断开连接的次数、跳过的事件数等)的类。在此例中,将BinaryLogClientStatistics注册到名为"mysql.binlog:type=BinaryLogClientStatistics"的ObjectName下。
- 通过这段代码,可以在JMX中注册BinaryLogClient和BinaryLogClientStatistics的MBean,从而可以通过JMX监控和管理BinaryLogClient的运行状态和统计信息。可以使用JMX工具(如JConsole或VisualVM)来查看和操作这些MBean。
Using SSL
TLSv1.1和TLSv1.2需要+。在MySQL 5.7.10之前,MySQL仅支持TLSv1(参见)。要检查MySQL服务器是否支持TLSv1.1和TLSv1.2,可以使用以下命令:mysql -h host -u root -ptypeyourpasswordmaybe -e "show global variables like 'have_%ssl';"("Value"应该是"YES")。可以使用s命令来确定当前会话的状态("SSL"不应为空)。
java
System.setProperty("javax.net.ssl.trustStore", "/path/to/truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword","truststore.password");
System.setProperty("javax.net.ssl.keyStore", "/path/to/keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "keystore.password");
BinaryLogClient client = ...
client.setSSLMode(SSLMode.VERIFY_IDENTITY);
实施说明
- 无论列定义是否包含"unsigned"关键字,数字类型(tinyint 等)的数据始终返回有符号数据(!)。
- var*/*text/blob 类型的数据始终作为字节数组返回(对于 var,从 1.0.0 开始就是如此)。
常见问题解答
问:典型的事务是什么样子的?
答:如果gtid_mode=ON,则为GTID事件 -> 包含"BEGIN"的QUERY事件 -> ... -> XID事件 | 包含"COMMIT"或"ROLLBACK"的QUERY事件。
问:插入/更新/删除行的EventData中没有关于表的信息(除了一些奇怪的id)。我该如何理解它?
答:每个WriteRowsEventData/UpdateRowsEventData/DeleteRowsEventData事件都是由TableMapEventData事件(包含模式和表名)引导的。如果出于某种原因您需要知道列名(类型等)- 最简单的方法是
sql
select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE,
DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE,
CHARACTER_SET_NAME, COLLATION_NAME from INFORMATION_SCHEMA.COLUMNS;
# see https://dev.mysql.com/doc/refman/5.6/en/columns-table.html for more information
这段代码用于查询MySQL数据库中的所有表、列和其相关信息。
- 通过使用SELECT语句查询INFORMATION_SCHEMA.COLUMNS表,可以获取到所有表、列和其相关信息的详细列表。在此例中,查询结果包括TABLE_SCHEMA(表所在的模式)、TABLE_NAME(表名)、COLUMN_NAME(列名)、ORDINAL_POSITION(列在表中的顺序)、COLUMN_DEFAULT(默认值)、IS_NULLABLE(是否可为NULL)、DATA_TYPE(数据类型)、CHARACTER_MAXIMUM_LENGTH(字符型列的最大长度)、CHARACTER_OCTET_LENGTH(二进制型列的最大长度)、NUMERIC_PRECISION(数值型列的精度)、NUMERIC_SCALE(数值型列的小数位数)、CHARACTER_SET_NAME(字符集名称)和COLLATION_NAME(排序规则名称)。
- 同时,注释中提供了一个链接,可以查看更多关于INFORMATION_SCHEMA.COLUMNS表的详细信息。
- 通过这段代码,可以方便地查询MySQL数据库中的所有表、列和其相关信息,并根据需要对其进行进一步处理和分析。
文档
API概述
- 有两个入口点 - BinaryLogClient(用于从MySQL服务器读取二进制日志)和BinaryLogFileReader(用于离线日志处理)。它们都依赖于EventDeserializer来反序列化事件流。每个事件由EventHeader(包含对EventType的引用等信息)和EventData组成。上述的EventDeserializer有一个EventHeaderDeserializer(默认为EventHeaderV4Deserializer)和一组EventDataDeserializer。如果没有为某种特定类型的事件注册EventDataDeserializer,将使用默认的EventDataDeserializer(NullEventDataDeserializer)。
MySQL内部手册
- 要了解MySQL内部的详细信息,请参考这里https://dev.mysql.com/doc/dev/mysql-server/latest/。其中的MySQL Client/Server Protocol和The Binary Log部分对于**.binlog.network和**.binlog.event包的参考文档非常有用。
BinaryLogFileReader.java
java
/*
* Copyright 2013 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog;
import com.github.shyiko.mysql.binlog.event.Event;
import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
* MySQL binary log file reader.
*
* @author <a href="mailto:stanley.shyiko@gmail.com">Stanley Shyiko</a>
*/
public class BinaryLogFileReader implements Closeable {
public static final byte[] MAGIC_HEADER = new byte[]{(byte) 0xfe, (byte) 0x62, (byte) 0x69, (byte) 0x6e};
private final ByteArrayInputStream inputStream;
private final EventDeserializer eventDeserializer;
public BinaryLogFileReader(File file) throws IOException {
this(file, new EventDeserializer());
}
public BinaryLogFileReader(File file, EventDeserializer eventDeserializer) throws IOException {
this(file != null ? new BufferedInputStream(new FileInputStream(file)) : null, eventDeserializer);
}
public BinaryLogFileReader(InputStream inputStream) throws IOException {
this(inputStream, new EventDeserializer());
}
public BinaryLogFileReader(InputStream inputStream, EventDeserializer eventDeserializer) throws IOException {
if (inputStream == null) {
throw new IllegalArgumentException("Input stream cannot be NULL");
}
if (eventDeserializer == null) {
throw new IllegalArgumentException("Event deserializer cannot be NULL");
}
this.inputStream = new ByteArrayInputStream(inputStream);
try {
byte[] magicHeader = this.inputStream.read(MAGIC_HEADER.length);
if (!Arrays.equals(magicHeader, MAGIC_HEADER)) {
throw new IOException("Not a valid binary log");
}
} catch (IOException e) {
try {
this.inputStream.close();
} catch (IOException ex) {
// ignore
}
throw e;
}
this.eventDeserializer = eventDeserializer;
}
/**
* @return deserialized event or null in case of end-of-stream
*/
public Event readEvent() throws IOException {
return eventDeserializer.nextEvent(inputStream);
}
@Override
public void close() throws IOException {
inputStream.close();
}
}
这段代码是一个MySQL二进制日志文件读取器。
- 首先,代码中包含了版权信息和Apache许可证。
- 然后,导入了需要使用的类,包括Event、EventDeserializer、ByteArrayInputStream和Closeable等。
- 接下来,定义了一个名为BinaryLogFileReader的类。该类实现了Closeable接口,表示它可以被关闭。
- 在类中定义了一个名为MAGIC_HEADER的字节数组,用于验证二进制日志文件的有效性。
- 构造函数BinaryLogFileReader接受一个File参数,并创建一个ByteArrayInputStream对象作为输入流。然后,通过读取输入流的前几个字节,验证二进制日志文件的有效性。如果验证失败,抛出IOException异常。否则,将输入流和事件反序列化器传递给BinaryLogFileReader对象。
- readEvent方法用于读取二进制日志文件中的事件,并通过事件反序列化器将其反序列化为Event对象。如果到达文件末尾,返回null。
- 最后,实现了close方法,用于关闭输入流。
- 通过这段代码,可以创建一个BinaryLogFileReader对象,用于读取MySQL二进制日志文件,并逐个解析其中的事件。