【原创】研发应该懂的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中的事件,即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
查询到,该页有一个表格如下所示,其中我红圈的地方,就是私有事件头的长度,即

总结

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

原文地址:https://www.cnblogs.com/rjzheng/p/9745551.html

时间: 2024-10-26 00:46:48

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

冷静审视人工智能技术的本质 | 一图看懂新一代人工智能知识体系大全

冷静审视人工智能技术的本质 吴妙芸 来源:图灵人工智能(ID:TuringAI01) 人工智能的发展离不开基础支持层和技术层,基础支持层包括大数据.计算力和算法:技术层包括计算机视觉.语音识别和自然语言处理.在过去的2016年人工智能风风火火了一把,到目前为止,还在大力向前发展,研究机构.企业.投资机构和政府都对人工智能投入了很多关注,并陆续出台了一些政策.人工智能的技术本质是什么,本文会详细分析. 总览人工智能技术图谱 基础支撑层的算法创新发生在20世纪80年代末,是大数据和计算力将人工智能推

[转帖]一文读懂分布式架构知识体系(内含超全核心知识大图)

一文读懂分布式架构知识体系(内含超全核心知识大图) https://yq.aliyun.com/articles/721007?spm=a2c4e.11153959.0.0.2f464977X7lSdH 作者 | 晓土  阿里巴巴高级工程师 姊妹篇阅读推荐:<云原生时代,分布式系统设计必备知识图谱(内含22个知识点)> 导读:本文力求从分布式基础理论.架构设计模式.工程应用.部署运维.业界方案这几大方面,介绍基于 MSA(微服务架构)的分布式知识体系大纲,从而对 SOA 到 MSA 进化有着立

开发辅助 | 前端开发工程师必懂的 UI 知识

       移动 UI 设计的世界 ... 1.屏幕尺寸 屏幕大小,指屏幕对角线的长度,而不是屏幕的宽度或高度: 单位为英寸 如 iPhone 7 屏幕尺寸为 4.7 英寸:三星 S6 屏幕尺寸为 5.1 英寸: 2.屏幕像素密度 --- ppi 指每英寸屏幕搜拥有的像素数:由屏幕尺寸和屏幕里所包含的像素数量 计算出来的单位: ppi 指每英寸所拥有的像素数 ,即每英寸像素. 像素密度越高,代表屏幕图像显示越清晰. 同样5英寸的屏幕,像素数多,屏幕像素密度大,屏幕更清晰: 屏幕尺寸 和 像素数

一文读懂分布式架构知识体系(内含超全核心知识大图)

作者 | 晓土  阿里巴巴高级工程师 姊妹篇阅读推荐:<云原生时代,分布式系统设计必备知识图谱(内含22个知识点)> 导读:本文力求从分布式基础理论.架构设计模式.工程应用.部署运维.业界方案这几大方面,介绍基于 MSA(微服务架构)的分布式知识体系大纲,从而对 SOA 到 MSA 进化有着立体的认识:从概念上和工具应用上更近一步了解微服务分布式的本质,身临其境的感受如何搭建全套微服务架构的过程. 关注“阿里巴巴云原生”公众号,回复“分布”,即可下载分布式系统及其知识体系清晰大图! 随着移动互

为何计算机人应该懂一点编译知识?

2009年冬,本科计算机<编译原理>课程结业考试结果公布,虽然取得了不错的考试成绩,可是自己除了熟练掌握了那些艰涩难懂的文法分析算法之外,对编译器的工作原理仍然貌似“一无所知”.这让我下定决心在本科毕业设计时,选定了<自定义语言编译器的设计与实现>这个题目,从此与编译领域结下了不解之缘.2012年读研期间,在之前实现的编译器的基础上继而实现了汇编器和静态链接器,并添加了编译优化器.2013年着手启动<自己动手构造编译系统>书稿的撰写,历经两年终于完稿.2016年8月,&

Android研发安全2-Activity组件安全(下)

这篇文章是Android研发安全之Activity组件安全第二篇,本文将给大家分享Activity界面劫持方面的预防知识. 什么是Activity劫持 简单的说就是APP正常的Activity界面被恶意攻击者替换上仿冒的恶意Activity界面进行攻击和非法用途.界面劫持攻击通常难被识别出来,其造成的后果不仅会给用户带来严重损失,更是移动应用开发者们的恶梦.举个例子来说,当用户打开安卓手机上的某一应用,进入到登陆页面,这时,恶意软件侦测到用户的这一动作,立即弹出一个与该应用界面相同的Activi

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

C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的几乎所以的任务,包括会计应用程序.字处理程序.游戏.操作系统等.它不仅是更高级语言(如C++) 的基础,目前还以Objective C的形式开发手机应用程序.目前,C语言最新版本由ISO/IEC 9899:2011 文档定义.           数据结构+算法=程序 1.1 C程序和程序设计 (1

面向对象(三):常用知识下

十一.抽象类 1.抽象类的特点: 抽象类和抽象方法必须用abstract关键字修饰 抽象类一般会有一个 public 的方法,作为访问的入口. 抽象方法,一般使用 protected 关键字修饰,用于子类重写. 内部使用的方法,为 private 修饰. 抽象类中不一定有抽象方法,但是有抽象方法的类必须定义为抽象类. 抽象类不能实例化 ,但是抽象类有构造方法,作用在于子类访问父类数据的初始化. 抽象的子类 如果不想重写抽象方法,该子类是一个抽象类. 重写所有的抽象方法,这个时候子类是一个具体的类

循环知识 下2

之前我们提到,计算机利用数据有三种方式:1.直接使用数据,2.计算和加工数据,3.用数据做判断. 除了while循环,我之前学过的if...elif...else语句,也涉及到[利用数据用做逻辑判断].当逻辑判断通过才会继续执行: 当然,if和while有个显著的区别.那就是if语句只会执行一次,而while是循环语句,只要条件判断为真,就一直循环执行. 这个“判断”的过程,在计算机的世界里是如何发生的呢? 计算机的逻辑判断,只有两种结果,就是True(英文意思是“真”)和False(英文意思是