mysql 协议分析

MYSQL Binlog协议分析

 

此处不讨论建立连接,验证和handshake的交互协议

Binlog协议

 

 

一个MYSQL 通信包由包头包体组成

 

包体根据具体的交互协议有自身的组成结构, 在binlog消息体组成结构如下

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

| event  | timestamp         0 : 4    |

| header +----------------------------+

|        | type_code         4 : 1    |

|        +----------------------------+

|        | server_id         5 : 4    |

|        +----------------------------+

|        | event_length      9 : 4    |

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

| event  | fixed part       13 : y    |

| data   +----------------------------+

|        | variable part              |

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

 

注意: 消息体里组成是包括2部分的,第一个字节是master发送给slave的errorcode, 第二个字节开始才是具体消息, 所以一个完整的MYSQL binlog通信包组成如下

 

 

 
   

 

 

 

 

 

 

一次binlog复制通信包含若干个binlog通信包

 

我们通过抓包工具抓取一次binlog复制来分析

 

 

 

我们在master里更新了一条数据, 产生了 41 这条通信数据, 里面包含了4个mysql binlog通信包

第一个binlog事件消息

头3个字节 45 00 00就是消息长度, 注意,在mysql协议里,数值类型是用小序列来传输数据的(小序列: 低位先传), 这条消息里实际消息长度内容是 00 00 45 , 也就是69(45是16进制, 69是10进制)

第4个字节2c是master传过来的序号

第5个字节00时errorcode, 0表示没有错误, 其他是错误

第6~9  4个字节是timestamp

第10 个字节02 是event type, 比如query, insert, update,delete等的类型码

第11-14个字节是serverid, 这里是02 00 00 00, 也是小序列

第15~19个字节是eventlengthoffset, 这里是44 00 00 00, 也是小序列

第二个binlog事件消息

 

 

结构和第一个基本一样, 只是消息长度,序号,eventtype等内容不一样

 

以上是原生mysql复制binlog的交互协议情况, 如果开启了semi半同步, 协议就发生变化, tcp通信次数, 包体结构等都和原来的有差异

抓包分析:

 

 

我们在master里更新了一条数据, 产生了 5 这条通信数据, 里面包含了5个mysql binlog通信包, 比原生的多了1条, 而且包结构也变成了以下结构

 

 

 
   

 

 

 

 

 

 

 

第一个binlog事件消息是新增的事件

 

 

第6个字节是新增的字节码, ef是固定的值,表示这是个semi的消息

第7个字节是新增的字节码, 0或者1, 0表示不需要回一个ack给master, 1表示需要, 一次复制通信(包含5个binlog事件)只有最后一个binlog事件的值为1

第8个字节开始就是具体消息, 和原生的一样

 

最后一个binlog事件消息

 

第7个字节值是1 ,  表示要返回一个ack给master

 

以下就是ack给master的内容

 

 

Slave ack给master 的消息是7这条通信数据

第1-3个字节师消息长度,这里的内容是12 00 00, 十进制内容是18

第4个字节是序号 , 这里是0, (注意: 每次tcp交互里这个序号都被重至为0)

第5个字节是semi标示 ,固定为ef

第6~14个字节,一共8个字节表示位置offset, ef 0a 00 00 00 00 00 00 同样是小序列

第15个字节开始是binlog的文件名 每个字节存储的是ascii码, 如这里的4f=O, 4e=N,2e=.,30=0,36=6, 就是ON.000006

 

 

如果安装了semi插件后,并不启用semi同步,就不会发送ack给master, 就是上面7这个数据包不会发送

 

 

Master要发送semi字节给slave前提是slave连接上master后必须发送SET @rpl_semi_sync_slave= 1 指令给master, 看下面抓包效果

 

 

1c 00 00 00 是包头

0x03 是指令码       COM_QUERY

53开始后面的字节都是具体字符串的ascii码

 

 

完整事件流

Slave连接上master后, master 首先会发ROTATE_EVENT和FORMAT_DESCRIPTION_EVENT 2个事件给slave, ROTATE_EVENT事件告诉slave下一个要读取的binlog(可以理解成初始化要读取的binlog)

 

Master发生一个插入sql, 如insert into test1 values(15), 分别发送给slave的事件是ANONYMOUS_GTID_LOG_EVENT-->QUERY_EVENT-->TABLE_MAP_EVENT-->WRITE_ROWS_EVENT-->XID_EVENT

 

(1)

ANONYMOUS_GTID_LOG_EVENT

(2)

QUERY_EVENT

header {

  version: 1

  logfileName: "20170105-162017-bin.000001"

  logfileOffset: 1920

  serverId: 1

  serverenCode: "UTF-8"

  executeTime: 1508809530000

  sourceType: MYSQL

  schemaName: ""

  tableName: ""

  eventLength: 72

}

entryType: TRANSACTIONBEGIN

storeValue: " \354\001"

 

(3)

TABLE_MAP_EVENT

 

(4)

WRITE_ROWS_EVENT

header {

  version: 1

  logfileName: "20170105-162017-bin.000001"

  logfileOffset: 2040

  serverId: 1

  serverenCode: "UTF-8"

  executeTime: 1508809530000

  sourceType: MYSQL

  schemaName: "test"

  tableName: "test1"

  eventLength: 40

  eventType: INSERT

}

entryType: ROWDATA

storeValue: "\b\333\001\020\001P\000b\035\022\033\b\000\020\004\032\002id \000(\0010\000B\00215R\aint(11)"

 

(5)

XID_EVENT

header {

  version: 1

  logfileName: "20170105-162017-bin.000001"

  logfileOffset: 2080

  serverId: 1

  serverenCode: "UTF-8"

  executeTime: 1508809530000

  sourceType: MYSQL

  schemaName: ""

  tableName: ""

  eventLength: 31

}

entryType: TRANSACTIONEND

storeValue: "\022\003184"

 

 

 

 

PS:

如果本地没有存储position,在slave启动的时候就会调用show master status 获取master 最新的position更新到本地

 

在实验中发现 master 的timeout时间设置很长, 当新日志送到canal, canal关闭,master由于收不到ack,一直hold着,再次启动canal, master hold住的事务能够进行下去,数据也落盘到master硬盘里,但再次启动的canal就没有收到之前中断掉的binlog(开始position是master最后的position)

 

posted on 2017-12-14 20:25  devilwind  阅读(2330)  评论(0编辑  收藏  举报