引言

这篇是《研发应该懂的binlog知识(上)》的下半部分。在本文,我会阐述一下binlog的结构,以及如何使用java来解析binlog

不过,话说回来,其实严格意义上来说,研发应该还需要懂如何监听binlog的变化。我本来也想写这块的知识,但是后来发现,这块讲起来篇幅过长,需要从mysql的通讯协议开始讲起,实在是不适合放在这篇文章讲,所以改天抽时间再写一篇监听binlog变化的文章。

说到这里,大家可能有一个疑问:

研发为什么要懂得如何解析binlog?

说句实在话,如果在实际项目中遇到,我确实推荐使用现成的jar包来解析,比如mysql-binlog-connector-java或者open-replicator等。但是呢,这类jar包解析binlog的原理都是差不多的。因为我有一个怪癖,我用一个jar包,都会去溜几眼,看一下大致原理,所以想在这个部分把如何解析binlog的实质性原理讲出来,希望大家有所收获。大家懂一个大概的原理即可,不需要自己再去造轮子。另外,注意了,本文教你的是解析binlog的方法,不可能每一个事件带你解析一遍。能达到举一反三的效果,就是本文的目的。

什么,你还没碰到过解析binlog的需求?没事,那先看着,就当学习一下,将来一定会遇到。

正文

先说一下,binlog的结构。

文件头由一个四字节Magic Number构成,其值为1852400382,在内存中就是"0xfe,0x62,0x69,0x6e"。这个Magic Number就是来验证这个binlog文件是否有效 。

引一个题外话

java里头的class文件,头四个字节的Magic Number是多少?

回答:"0xCAFEBABE。"这个数字可能比较难记,记(咖啡宝贝)就好。

下面写个程序,读一份binlog文件,给大家binlog看看头四个字节是否为"0xfe,0x62,0x69,0x6e",代码如下

public class MagicParser {

	public static final byte[] MAGIC_HEADER = new byte[]{(byte) 0xfe, (byte) 0x62, (byte) 0x69, (byte) 0x6e};

	public static void main(String[] args)throws Exception {
String filePath = "D:\\mysql-bin.000001";
File binlogFile = new File(filePath);
ByteArrayInputStream inputStream = null;
inputStream = new ByteArrayInputStream(new FileInputStream(binlogFile));
byte[] magicHeader = inputStream.read(4);
System.out.println("魔数\\xfe\\x62\\x69\\x6e是否正确:"+Arrays.equals(MAGIC_HEADER, magicHeader));
}
}

输出如下

魔数\xfe\x62\x69\x6e是否正确:true

在文件头之后,跟随的是一个一个事件依次排列。在《binlog二进制文件解析》一文中,将其分为三个部分:通用事件头(common-header)、私有事件头(post-header)和事件体(event-body)。本文修改了一下,只用两个Java类来修饰binlog中的事件,即EventHeaderEventData。可以理解为下述的对应关系:

EventHeader --> 通用事件头(common-header)
EventData ---> 私有事件头(post-header)和事件体(event-body)

于是,你们可以把Binlog的文件结构像下面这么理解



说一下这个Checksum,在获取event内容的时候,会增加4个额外字节做校验用。mysql5.6.5以后的版本中binlog_checksum=crc32,而低版本都是binlog_checksum=none。如果不想校验,可以使用set命令设置set binlog_checksum=none。说得再通俗一点,Checksum要么为4个字节,要么为0个字节。

下面说一下通用事件头的结构,如下所示

属性 字节数 含义
timestamp 4 包含了该事件的开始执行时间
eventType 1 事件类型
serverId 4 标识产生该事件的MySQL服务器的server-id
eventLength 4 该事件的长度(Header+Data+CheckSum)
nextPosition 4 下一个事件在binlog文件中的位置
flags 2 标识产生该事件的MySQL服务器的server-id。

从上表可以看出,EventHeader固定为19个字节,为此我们构造下面的类,来解析这个通用事件头

public class EventHeader {
private long timestamp;
private int eventType;
private long serverId;
private long eventLength;
private long nextPosition;
private int flags;
//省略setter和getter方法
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("EventHeader");
sb.append("{timestamp=").append(timestamp);
sb.append(", eventType=").append(eventType);
sb.append(", serverId=").append(serverId);
sb.append(", eventLength=").append(eventLength);
sb.append(", nextPosition=").append(nextPosition);
sb.append(", flags=").append(flags);
sb.append('}');
return sb.toString();
}
}

OK,接下来,我们来一段代码试着解析一下第一个事件的EventHeader,代码如下所示

public class HeaderParser {

	public static final byte[] MAGIC_HEADER = new byte[]{(byte) 0xfe, (byte) 0x62, (byte) 0x69, (byte) 0x6e};

	public static void main(String[] args)throws Exception {
String filePath = "D:\\mysql-bin.000001";
File binlogFile = new File(filePath);
ByteArrayInputStream inputStream = null;
inputStream = new ByteArrayInputStream(new FileInputStream(binlogFile));
byte[] magicHeader = inputStream.read(4);
if(!Arrays.equals(MAGIC_HEADER, magicHeader)){
throw new RuntimeException("binlog文件格式不对");
}
EventHeader eventHeader = new EventHeader();
eventHeader.setTimestamp(inputStream.readLong(4) * 1000L);
eventHeader.setEventType(inputStream.readInteger(1));
eventHeader.setServerId(inputStream.readLong(4));
eventHeader.setEventLength(inputStream.readLong(4));
eventHeader.setNextPosition(inputStream.readLong(4));
eventHeader.setFlags(inputStream.readInteger(2));
System.out.println(eventHeader); }
}

输出如下

EventHeader{timestamp=1536487335000, eventType=15, serverId=1, eventLength=119, nextPosition=123, flags=1}

注意看,两个参数

eventLength=119
nextPosition=123

下一个事件从123字节开始。这是怎么算的呢,当前事件长度是是119字节,算上最开始4个字节的魔数占位符,那么下一个事件自然是,119+4=123,从123字节开始。再强调一次,这个119字节,是包含EventHeader,EventData,Checksum,三个部分的长度为119。

最重要的一个参数

eventType=15

我们去下面的地址

https://dev.mysql.com/doc/internals/en/binlog-event-type.html

查询一下,15对应的事件类型为FORMAT_DESCRIPTION_EVENT。我们接下来,需要知道FORMAT_DESCRIPTION_EVENT所对应EventData的结构。在下面的地址

https://dev.mysql.com/doc/internals/en/format-description-event.html

查询得到EventData的结构对应如下表所示

属性 字节数 含义
binlogVersion 2 binlog版本
serverVersion 50 服务器版本
timestamp 4 该字段指明该binlog文件的创建时间。
headerLength 1 事件头长度,为19
headerArrays n 一个数组,标识所有事件的私有事件头的长度

ps:这个n其实我们可以推算出,为39。事件长度为119字节,减去事件头19字节,减去末位的4字节(末位四个字节循环校验码),减去2个字节的binlog版本,减去50个字节的服务器版本号,减去4个字节的时间戳,减去1个字节的事件头长度。得到如下算式

\[119-19-4-2-50-4-1=39
\]

不过,我们还是假装不知道n是多少吧。

根据上表结构 ,我们给出一个JAVA类如下所示

public class FormatDescriptionEventData {
private int binlogVersion;
private String serverVersion;
private long timestamp;
private int headerLength;
private List headerArrays = new ArrayList<Integer>();
//省略setter和getter方法
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("FormatDescriptionEventData");
sb.append("{binlogVersion=").append(binlogVersion);
sb.append(", serverVersion=").append(serverVersion);
sb.append(", timestamp=").append(timestamp);
sb.append(", headerLength=").append(headerLength);
sb.append(", headerArrays=").append(headerArrays);
sb.append('}');
return sb.toString();
}
}

那如何解析呢,如下所示

public class HeaderParser {

	public static final byte[] MAGIC_HEADER = new byte[] { (byte) 0xfe,
(byte) 0x62, (byte) 0x69, (byte) 0x6e }; public static void main(String[] args) throws Exception {
String filePath = "D:\\mysql-bin.000001";
File binlogFile = new File(filePath);
ByteArrayInputStream inputStream = null;
inputStream = new ByteArrayInputStream(new FileInputStream(binlogFile));
byte[] magicHeader = inputStream.read(4);
if (!Arrays.equals(MAGIC_HEADER, magicHeader)) {
throw new RuntimeException("binlog文件格式不对");
}
EventHeader eventHeader = new EventHeader();
eventHeader.setTimestamp(inputStream.readLong(4) * 1000L);
eventHeader.setEventType(inputStream.readInteger(1));
eventHeader.setServerId(inputStream.readLong(4));
eventHeader.setEventLength(inputStream.readLong(4));
eventHeader.setNextPosition(inputStream.readLong(4));
eventHeader.setFlags(inputStream.readInteger(2));
System.out.println(eventHeader);
inputStream.enterBlock((int) (eventHeader.getEventLength() - 19 - 4));
FormatDescriptionEventData descriptionEventData = new FormatDescriptionEventData();
descriptionEventData.setBinlogVersion(inputStream.readInteger(2));
descriptionEventData.setServerVersion(inputStream.readString(50).trim());
descriptionEventData.setTimestamp(inputStream.readLong(4) * 1000L);
descriptionEventData.setHeaderLength(inputStream.readInteger(1));
int sums = inputStream.available();
for (int i = 0; i < sums; i++) {
descriptionEventData.getHeaderArrays().add(inputStream.readInteger(1));
}
System.out.println(descriptionEventData);
}
}

至于输出,就不给大家看了,没啥意思。大家看headerArrays的值即可,如下所示

headerArrays=[56, 13, 0, 8, 0, 18, 0, 4, 4, 4, 4, 18, 0, 0, 95, 0, 4, 26, 8, 0, 0, 0, 8, 8, 8, 2, 0, 0, 0, 10, 10, 10, 42, 42, 0, 18, 52, 0, 1]

其实他所输出的值,可以在地址

https://dev.mysql.com/doc/internals/en/format-description-event.html

查询到,该页有一个表格如下所示,其中我红圈的地方,就是私有事件头的长度,即

总结

关于其他事件的结构体,大家可以自行去网站查询,解析原理都是一样的。

【原创】研发应该懂的binlog知识(下)的更多相关文章

  1. 【转载】研发应该懂的binlog知识(下)

    引言 这篇是<研发应该懂的binlog知识(上)>的下半部分.在本文,我会阐述一下binlog的结构,以及如何使用java来解析binlog.不过,话说回来,其实严格意义上来说,研发应该还 ...

  2. 【原创】研发应该懂的binlog知识(上)

    引言 为什么写这篇文章? 大家当年在学MySQL的时候,为了能够迅速就业,一般是学习一下MySQL的基本语法,差不多就出山找工作了.水平稍微好一点的童鞋呢还会懂一点存储过程的编写,又或者是懂一点索引的 ...

  3. 【转载】研发应该懂的binlog知识(上)

    ---------------------------------------------------------------------------------------------------- ...

  4. C语言程序设计做题笔记之C语言基础知识(下)

    C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...

  5. [持续交付实践] 研发协作平台:DevOps背景下的组织结构

    前言 今年以来做的事情越来越杂,负责的技术方向越来越广,精力越来越分散(创业公司的典型特点),编码的时间越来越少,有时候也会觉得很疲惫没办法专注一个事情. 除了技术方向上的实践,组织上如何组建一个最优 ...

  6. 一看就懂的Ubuntu系统下samba服务器安装配置教程

    文章目录 前言 环境搭建 安装 配置 Examples 1 创建共享(任何人都可以访问) 2 单用户权限(需要密码访问) 添加samba用户 配置参数 3 支持游客访问(单用户拥有管理员权限) 前言 ...

  7. 【原创】Python 懂车帝口碑爬虫

    本文所有教程及源码.软件仅为技术研究.不涉及计算机信息系统功能的删除.修改.增加.干扰,更不会影响计算机信息系统的正常运行.不得将代码用于非法用途,如侵立删! 懂车帝综合口碑 需求 操作环境 win1 ...

  8. 在开启bin-log日志下Mysql报错

    This function has none of DETERMINISTIC, NO SQL解决办法 创建存储过程时 出错信息: ERROR 1418 (HY000): This function ...

  9. 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(下)

    上一篇文章:https://www.cnblogs.com/cgzl/p/9734083.html 处理数据 嵌套字段 看例子: 我想查看viewer下的repositories.注意里面的edges ...

随机推荐

  1. Android/IOS手机使用Fiddler抓包

    对于Android和IOS开发及测试的同事来说抓包是一个很重要的事,有利于排查问题所在,快速定位问题.但长期以来一直没有一款可以快速抓包的工具,直到有了Fiddler2. 使用步骤: 1.  Fidd ...

  2. Struts2.5学习笔记----org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter报错

    Struts2.3升级到struts2.5后报错 <filter> <filter-name>struts2</filter-name> <filter-cl ...

  3. SQL 删除外键列

    一 SQL删除列的语句是: alter table tableName drop column columnName --(其中,tableName为表名,columnName为列名) 但是,如果某列 ...

  4. kali系统固化到固态硬盘小记(赠送给广大折腾党的笔记)

    1.首先你需要一个移动硬盘和一个移动硬盘盒子(一根数据转换线,一般买盒子商家会赠送的) SSD硬盘要事先格式化一下格式,不然识别不出来 2.准备好Kali镜像,传送门在这里https://www.ka ...

  5. Informix数据库配置与连接

    1.环境 数据库版本:12.1 操作系统:Windows Server 2008 客户端:IBM Data Studio 4.1.3 2.配置 数据库安装后默认是无法远程访问的,需要修改sqlhost ...

  6. jQuery -- 光阴似箭(五):AJAX 方法

    jQuery -- 知识点回顾篇(五):AJAX 方法 1. $.ajax 方法:用于执行 AJAX(异步 HTTP)请求. <!DOCTYPE html> <html> &l ...

  7. Ubuntu下使用QQ/Wechat

    实验环境:Ubuntu 16.04桌面版root用户下 安装Docker 配置Docker的apt源 $ sudo apt-get install apt-transport-https ca-cer ...

  8. LeetCode算法题-Merge Sorted Array(Java实现)

    这是悦乐书的第161次更新,第163篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第20题(顺位题号是88).给定两个排序的整数数组nums1和nums2,将nums2中 ...

  9. June 11. 2018 Week 24th, Monday

    Love is the beauty of the soul. 爱是灵魂之美. From Saint Augustine. The complete version of this quote goe ...

  10. Unity 琐碎5 : 利用反射设置编辑器参数

    问题 最近处理unity资源打包问题时候经常遇到的一个问题就是平台切换和Bundle编译.一般情况下,平台转换我需要依赖Cache Serbver加快转换速度,但是在Build Bundle的时候我又 ...