MySql系列:研发应该懂的binlog(下)

导读:本篇文章讲解 MySql系列:研发应该懂的binlog(下),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

研发应该懂的binlog知识(下)


前言

今天博主将为大家分享研发应该懂的binlog(下),不喜勿喷,如有异议欢迎讨论!

在本文,我来阐述一下binlog的结构,以及如何使用 java来解析 binlog。(上一篇:MySql系列:研发应该懂的binlog(上))


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

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

什么,你还没碰到过解析binlog的需求?(博主大概的叙述一个流程)

当有一个业务场景需要我们有:Redis缓存和Mysql集群的双写一致性问题。这时有一定经验的人可能会有一个思路,至于细节就请自行百度一下我只是来说一下我之前看到的一个例子。

  1. 当数据库更新时
  2. 请求Mysql后会生成BinLog记录日志
  3. 这时要去更改Redis缓存信息
  4. 我们可以用Redis的消息对列来实现,他的重点在于MySql的Binlog日志
  5. 通过binlog日志我们可以拿到相应的key-value
  6. 将其放入队列尝试删除
  7. 删除失败则重新返回队列再次重试
  8. 直到删除成功为止

正文

先说一下, binlog的结构。 文件头由一个四字节 MagicNumber构成,其值为1852400382,在内存中就是”0xfe,0x62,0x69,0x6e”。这个 MagicNumber就是来验证这个 binlog文件是否有效 。

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

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

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

package com.test;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

/**
 * 
 * @Description: 订单实体类
 * @ClassName: MagicParser.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:25
 * @Email chen87647213@163.com
 */
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以后的版本中binlogchecksum=crc32,而低版本都是binlogchecksum=none。如果不想校验,可以使用set命令设置set binlog_checksum=none。说得再通俗一点, Checksum要么为4个字节,要么为0个字节。

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

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

package com.test;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

/**
 * 
 * @Description: 订单实体类
 * @ClassName: EventHeader.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:25
 * @Email chen87647213@163.com
 */
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 =newStringBuilder();
		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,代码如下所示

package com.test;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

/**
 * 
 * @Description: 订单实体类
 * @ClassName: EventHeader.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:25
 * @Email chen87647213@163.com
 */
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);

	}
}

输出如下

在这里插入图片描述
注意看,两个参数

eventLength=119
nextPosition=123

下一个事件从123字节开始。这是怎么算的呢,当前事件是119字节,算上最开始4个字节的魔数占位符,那么119+4=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的结构对应如下表所示
在这里插入图片描述ps:这个n其实我们可以推算出,为39。事件长度为119字节,减去事件头19字节,减去末位的4字节(末位四个字节循环校验码),减去2个字节的binlog版本,减去50个字节的服务器版本号,减去4个字节的时间戳,减去1个字节的事件头长度。得到如下算式

在这里插入图片描述

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

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

package com.test;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @Description: 订单实体类
 * @ClassName: FormatDescriptionEventData.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:25
 * @Email chen87647213@163.com
 */
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();

	}

}

那如何解析呢,如下所示

package com.test;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

/**
 * 
 * @Description: 订单实体类
 * @ClassName: HeaderParser.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:25
 * @Email chen87647213@163.com
 */
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的值即可,如下所示

在这里插入图片描述

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

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

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

在这里插入图片描述


到这里MySql系列:研发应该懂的binlog(下),分析分享完毕了,快去试试吧!


最后

  • 更多参考精彩博文请看这里:《陈永佳的博客》

  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!——-


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/97827.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!