【转载】研发应该懂的binlog知识(下)
引言
这篇是《研发应该懂的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
中的事件,即EventHeader
和EventData
。可以理解为下述的对应关系:
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个字节的事件头长度。得到如下算式
不过,我们还是假装不知道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
查询到,该页有一个表格如下所示,其中我红圈的地方,就是私有事件头的长度,即
总结
关于其他事件的结构体,大家可以自行去网站查询,解析原理都是一样的。
作者:孤独烟 出处: http://rjzheng.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。
【转载】研发应该懂的binlog知识(下)的更多相关文章
- 【原创】研发应该懂的binlog知识(下)
引言 这篇是<研发应该懂的binlog知识(上)>的下半部分.在本文,我会阐述一下binlog的结构,以及如何使用java来解析binlog. 不过,话说回来,其实严格意义上来说,研发应该 ...
- 【转载】研发应该懂的binlog知识(上)
---------------------------------------------------------------------------------------------------- ...
- 【原创】研发应该懂的binlog知识(上)
引言 为什么写这篇文章? 大家当年在学MySQL的时候,为了能够迅速就业,一般是学习一下MySQL的基本语法,差不多就出山找工作了.水平稍微好一点的童鞋呢还会懂一点存储过程的编写,又或者是懂一点索引的 ...
- 转载:奇异值分解(SVD) --- 线性变换几何意义(下)
本文转载自他人: PS:一直以来对SVD分解似懂非懂,此文为译文,原文以细致的分析+大量的可视化图形演示了SVD的几何意义.能在有限的篇幅把这个问题讲解的如此清晰,实属不易.原文举了一个简单的图像处理 ...
- 【转载】看懂SqlServer查询计划
看懂SqlServer查询计划 阅读目录 开始 SQL Server 查找记录的方法 SQL Server Join 方式 更具体执行过程 索引统计信息:查询计划的选择依据 优化视图查询 推荐阅读-M ...
- 转载 Deep learning:一(基础知识_1)
前言: 最近打算稍微系统的学习下deep learing的一些理论知识,打算采用Andrew Ng的网页教程UFLDL Tutorial,据说这个教程写得浅显易懂,也不太长.不过在这这之前还是复习下m ...
- 【转载】 Android PullToRefresh (ListView GridView 下拉刷新) 使用详解
Android下拉刷新pullToRefreshListViewGridView 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/3 ...
- 【转载】关于 Ubuntu 的小知识分享
转载自:http://os.51cto.com/art/201307/402197.htm 一.默认开机直接进入到Ubuntu命令行界面 安装Ubuntu后,开机会默认进入到图形界面,如果不喜欢图形界 ...
- C语言程序设计做题笔记之C语言基础知识(下)
C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...
随机推荐
- c库函数 rewind fseek
rewind(3) 将文件内部的位置指针重新指向一个流(数据流/文件)的开头 不是文件指针而是文件内部的位置指针 rewind函数作用等同于 (void)fseek(stream, 0L, SEEK_ ...
- 【leetcode】1129. Shortest Path with Alternating Colors
题目如下: Consider a directed graph, with nodes labelled 0, 1, ..., n-1. In this graph, each edge is ei ...
- 对postcss以及less和sass的研究
1.postcss PostCSS 的主要功能只有两个:第一个就是前面提到的把 CSS 解析成 JavaScript 可以操作的 抽象语法树结构(Abstract Syntax Tree,AST),第 ...
- 如何生成各种mif文件,绝对经典!!!
mif文件生成模板,只需要5步,很简单!!!!! 先说明如何操作,1-2-3-4-5步,后面附上模板!!! 下面以汉字去模演示过程: 1.取模软件设置:注意这里是设置的输出数据的格式!!!!!!!!! ...
- 转:HTML5 History API 详解
从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...
- ASP.NET超大文件上传与下载
总结一下大文件分片上传和断点续传的问题.因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况.http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件 ...
- [HG]钻石游戏diamond 题解
题面 钻石游戏(diamond) 问题描述: 一个\(M\)行\(N\)列的棋盘,里面放了\(M \times N\)个各种颜色的钻石. 每一次你可以选择任意两个相邻的颜色不同的钻石,进行交换.两个格 ...
- sh_04_qq号码
sh_04_qq号码 # 1. 定义一个变量记录 QQ 号码 qq_number = " # 2. 定义一个变量记录 QQ 密码 qq_password = " # 注意:在使用解 ...
- vux组件样式大合集
1.Actionsheet 2.Alert 3.badge 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. ...
- Spring 使用RedisTemplate操作Redis
首先添加依赖: <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> < ...