zoukankan      html  css  js  c++  java
  • 【原创】研发应该懂的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));
    	}
    }
    

    输出如下

    魔数xfex62x69x6e是否正确:true
    

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

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

    于是,你们可以把Binlog的文件结构像下面这么理解
    image
    说一下这个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
    查询到,该页有一个表格如下所示,其中我红圈的地方,就是私有事件头的长度,即
    image

    总结

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

  • 相关阅读:
    CodeForces 734F Anton and School
    CodeForces 733F Drivers Dissatisfaction
    CodeForces 733C Epidemic in Monstropolis
    ZOJ 3498 Javabeans
    ZOJ 3497 Mistwald
    ZOJ 3495 Lego Bricks
    CodeForces 732F Tourist Reform
    CodeForces 732E Sockets
    CodeForces 731E Funny Game
    CodeForces 731D 80-th Level Archeology
  • 原文地址:https://www.cnblogs.com/rjzheng/p/9745551.html
Copyright © 2011-2022 走看看