首先,我们知道MySQL本身就带有replication的机制,我们需要伪造一个slave,向master注册,这样的话master才会发送binlog event。注册很简单,通过调用limysql.so中的cli_advanced_command(),指定binlog filename+position,向master发送COM_BINLOG_DUMP命令。在发送dump命令的时候,我们可以指定flagBINLOG_DUMP_NON_BLOCK,这样master在没有可以发送的binlog event之后,就会返回一个EOF的包。数据包的具体格式如下:

wKioL1cV41TyhnZbAABWfNiQraU722.png

例如COM_BINLOG_DUMP类型的数据包payload(数据包体)是这样的:

spacer.gif

spacer.gifwKioL1cV44GRInQ6AABg78He_-I979.png


3.1.2 接收事件

通过调用libmysql.so库中的cli_safe_read()函数,获得master发过来的数据,每次获得一个事件记录的数据,cli_safe_read()的返回值标示了从master发送过来的数据的数据字节数。而发送过来的数据保存在mysql->net->read_pos数组中。

 

3.2 Binlog事件

通过调用libmysql.so库中的cli_safe_read()函数可以获取一次binlog事件。

MySQLBinlog事件类型有27种,在MySQL5.6之后增加到38种,但是我们只介绍与ROW模式相关的事件,所有的event都含有如下通用的事件结构:

+===============================+

|      event header   |

+===============================+

|   event data     |

+===============================+

分别为事件头和事件体组成,而事件的内部结构随MySQL版本的不同而变化着,我们需要用到的版本为v4,用于mysql5.1及以上,其他版本就不做介绍了,想要了解的朋友可以参考官方文档。下图为v4版本的event header格式。

wKioL1cV47CDMJehAABoZu6HEpU994.png

由于各个事件的事件头一致,这里我们就不重复介绍了,后面各个事件我们也将忽略对事件头字段的描述。

 

3.2.1 ROTATE_EVENT

ROTATE_EVENT,   记录了接下来要请求的binlog的信息。格式如下:

wKioL1cV5CjDBTvVAAArbISomds199.png

它里面其实就是标明下一个event所在的binlog filenameposition。这里需要注意的是,当slave发送binlog dump之后,master会首先发送一个ROTATE_EVENT,用来告知slave下一个event所在的位置,然后才跟着其他事件。

3.2.2 QUERY_EVENT

QUERY_EVENT, 存储的是SQL,主要是一些与数据无关的操作,如beginalter tabledrop table,create table等。格式如下

wKiom1cV45LQK7pmAACGJFbkuaM265.png  3.2.3 TABLE_MAP_EVENT

TABLE_MAP_EVENT,记录了某个table所对应的表信息,在其中存储了数据库名和表名等。格式如下:

 wKioL1cV5JTwxRmEAACefFx6jRE931.png

   在这个事件中我们需要注意的是,虽然我们可以用表的id标识符table_id来代表一个特定的表,但是因为alter tablerotate binlog event等原因,master会改变某个tabletable_id,所以我们在外部不能使用这个table_id来索引某个table

还需要关注的就是里面的column meta信息,后续我们解析ROW_EVENT的时候会根据这个来处理不同类型的数据。

3.2.4 ROWS_EVENT   

ROWS_EVENT,包含了insertupdate,以及delete三种event,并且有v0v1v2三个版本。ROWS_EVENT的基本格式如下:

wKiom1cV5AHD5vn0AAA_lMOIIlg430.png

在这里MySQL5.6开始eventtype发生变化了,所以MySQL5.6需要改一下,

所以为了我们软件的版本兼容性,我们需要在软件中支持不同的版本。

 

MySQL5.1~MySQL5.6

·        TABLE_MAP_EVENT:19

·        WRITE_ROWS_EVENT: 23

·        UPDATE_ROWS_EVENT: 24

·        DELETE_ROWS_EVENT:  25

MySQL5.6以上

·        TABLE_MAP_EVENT:19

·        WRITE_ROWS_EVENT: 30

·        UPDATE_ROWS_EVENT: 31

·     DELETE_ROWS_EVENT:  32

    ROWS_EVENTtable_idTABLE_MAP_EVENT一样,虽然table_id可能变化,但是ROWS_EVENTTABLE_MAP_EVENTtable_id是能保证一致的,所以我们也是通过这个来找到TABLE_MAP_EVENT的。

为了节省空间,ROWS_EVENT里面对于各列状态都是采用bitmap的方式来处理的。首先我们需要得到column present bitmap的数据,这个值用来表示当前列的一些状态,如果没有设置,也就是某列对应的bit0,表名该ROWS_EVENT里该列的数据,外部用null代替就可以了。

然后就是每个record里的bitmap,这个是用来表明一行实际的数据里面有哪些列是NULL的,如果该列为NULL,则为1

在得到column_bitmapnull_bitmap后,我们就可以实际解析这行对应的数据了,对于每一列,首先判断是否column_bitmap标记了,如果为0,这跳过用null表示,然后再看是否null_bitmap里面标记了,如果为1,则表明为null

但是,因为我们得到的是一行数据的二进制流,又如何将一列数据解析出来呢,这里就需要靠TABLE_MAP_EVENT里的column_meta了。Column_def中定义了该列的数据类型,对于一些特定的类型,譬如MYSQL_TYPE_LONGMYSQL_TYPE_TINY等,长度都是固定的,所以我们可以直接读取对应长度的数据得到实际的值,但对于一些类型,则没有这么简单。这时候就需要通过meta来辅助计算了。举个例子:

MYSQL_TYPE_BLOB类型,meta1表示的为tinyblob,第一个字节解释blob的长度,2表名的是shortblob,前两个字节为blob的长度等,而对于MYSQL_TYPE_VARCHARmeta存储的则是string的长度,下一小节我们介绍每一种类型的计算方法。

3.2.5 XID_EVENT

XID_EVENT一般出现在一个事务操作之后或者其他语句提交之后。它的主要作用提交事务操作和把事件刷新至binlog文件中。里面存的是8个字节的事务ID号。

 

3.3 不同的数据类型的长度

上一节我们介绍了在不同事件的事件格式以及需要注意的事项,在其中ROWS_EVENT事件中,在recordbit_map之后的列数据中,针对不同的数据类型,可能在record中占用不同的字节,因从需要针对每种数据类型进行处理,

下面列出大部分常用数据类型的字节数和解析方法:

3.3.1 MYSQL_TYPE_INT家族

 wKiom1cV5DPBBIehAACY2RQJBO4189.png

3.3.2 MYSQL_TYPE_DOUBLE家族

wKioL1cV5RXSuO3MAABJls20hYc531.png

MYSQL_TYPE_NEWDECIMAL类型

声明语法为decimal(M,D),decimal参量的取值范围如下:

       M为数字的最大数(精度),其范围为1~65,默认为10

       D是小数点后面的数目(标度)。范围为0~30,但不得超过M

       table_map_event中的metadata2个字节,它会记录该类型的精度和标度,其中第一个字节表示精度,第二个字节表示精度。那么数据的长度是如何计算的呢?

       比如说decimal(15,5)a=1234567890.12345,那么a的精度为15,标度为5,所以a的整数位最多为10mysql中每9个数字用一个324个字节的整形来存储,剩下不够9个字节的按照如下方式:

       intdig2byte[10] = {0,1,1,2,3,3,3,4,4,4};

       该数组表示剩下几个字节的数字用几个字节存储。为什么这样做呢,我们知道一个int型变量能存储的最大整数位2^32,是一个十位数的整数,所以最多可以表示9位数的数字。binlog中的decimal按照小数点前后分别存储,所以a的整数位需要2int型来存储,小数后的计算方式也一样,需要3个字节存储,所以decimal(15,5)binlog中需要11个字节来存储数据

3.3.3 MYSQL_TYPE_STRING

wKioL1cV5UKCxSffAABJOSTdwb4894.png

  处理该类型数据时,需要利用metadata辅助计算,metadata的前一个字节表示类型,后一个字节表示长度,如果类型为MYSQL_TYPE_SETMYSQL_TYPE_EUM时,数据的长度则为后一个字节的大小。如果类型为

MYSQL_TYPE_STRING,第一个字节的数字即为该类型数据的长度。

3.3.4 MYSQL_TYPE_BIT

wKioL1cV5WuTihgbAAArXMwnoSc701.png

   查看mysqlcapture实现代码

3.3.5 MYSQL_TYPE_DATA家族

wKioL1cV5Y7QwjouAACZVIYxBpU565.png

3.3.6 MYSQL_TYPE_BLOB家族

wKioL1cV5bqhnz92AABeceTf2bk219.png

Metadata1,第一个字节表示长度,metadata2,前两个字节表示长度,metadata3,前三个字节表示长度,metadata4,前四个字节表示长度。

3.3.7MYSQL_TYPE_VARCHAR

wKiom1cV5Qbjd7u6AAAnCB8hFMg017.png

如果metadata小于256,则第一个字节表示数据的长度,如果metadata大于256

则前两个字节表示数据的长度。