深入解析MySQL replication协议
Why
最开始的时候,go-mysql只是简单的抽象mixer的代码,提供一个基本的mysql driver以及proxy framework,但做到后面,笔者突然觉得,既然研究了这么久mysql client/server protocol,干脆顺带把replication protocol也给弄明白算了。现在想想,幸好当初决定实现了replication的支持,不然后续go-mysql-elasticsearch这个自动同步MySQL到Elasticsearch的工具就不可能在短时间完成。
其实MySQL replication protocol很简单,client向server发送一个MySQL binlog dump的命令,server就会源源不断的给client发送一个接一个的binlog event了。
Register
首先,我们需要伪造一个slave,向master注册,这样master才会发送binlog event。注册很简单,就是向master发送COM_REGISTER_SLAVE命令,带上slave相关信息。这里需要注意,因为在MySQL的replication topology中,都需要使用一个唯一的server id来区别标示不同的server实例,所以这里我们伪造的slave也需要一个唯一的server id。
Binlog dump
最开始的时候,MySQL只支持一种binlog dump方式,也就是指定binlog filename + position,向master发送COM_BINLOG_DUMP命令。在发送dump命令的时候,我们可以指定flag为BINLOG_DUMP_NON_BLOCK,这样master在没有可发送的binlog event之后,就会返回一个EOF package。不过通常对于slave来说,一直把连接挂着可能更好,这样能更及时收到新产生的binlog event。
在MySQL 5.6之后,支持了另一种dump方式,也就是GTID dump,通过发送COM_BINLOG_DUMP_GTID命令实现,需要带上的是相应的GTID信息,不过笔者觉得,如果只是单纯的实现一个能同步binlog的工具,使用最原始的binlog filename + position就够了,毕竟我们不是MySQL,解析GTID还是稍显麻烦的。这里,顺带吐槽一下MySQL internal文档,里面关于GTID encode的格式说明竟然是错误的,文档格式如下:
4 n_sids
for n_sids {
string[16] SID
8 n_intervals
for n_intervals {
8 start (signed)
8 end (signed)
}
但实际坑爹的是n_sids的长度是8个字节。这个错误可以算是血的教训,笔者当时debug了很久都没发现为啥GTID dump一直出错,直到笔者查看了MySQL的源码。
MariaDB虽然也引入了GTID,但是并没有提供一个类似MySQL的GTID dump命令,仍是使用的COM_BINLOG_DUMP命令,不过稍微需要额外设置一些session variable,譬如要设置slave_connect_state为当前已经完成的GTID,这样master就能知道下一个event从哪里发送了。
Binlog Event
对于一个binlog event来说,它分为三个部分,header,post-header以及payload。但实际笔者在处理event的时候,把post-header和payload当成了一个整体body。
MySQL的binlog event有很多版本,但这里笔者只关心version 4的,也就是从MySQL 5.1.x之后支持的版本。而且笔者也只支持这个版本的event解析,首先是不想写过多的兼容代码,另一个更主要的原因就在于现在几乎都没有人使用低版本的MySQL了。
Binlog event的header格式如下:
4 timestamp
1 event type
4 server-id
4 event-size
4 log pos
2 flags
header的长度固定为19,event type用来标识这个event的类型,event size则是该event包括header的整体长度,而log pos则是下一个event所在的位置。
在v4版本的binlog文件中,第一个event就是FORMAT_DESCRIPTION_EVENT,格式为:
2 binlog-version
string[50] mysql-server version
4 create timestamp
1 event header length
string[p] event type header lengths
我们需要关注的就是event type header length这个字段,它保存了不同event的post-header长度,通常我们都不需要关注这个值,但是在解析后面非常重要的ROWS_EVENT的时候,就需要它来判断TableID的长度了。这个后续在说明。
而binlog文件的结尾,通常(只要master不当机)就是ROTATE_EVENT或者STOP_EVENT。这里我们重点关注ROTATE_EVENT,格式如下:
Post-header
8 position
Payload
string[p] name of the next binlog
它里面其实就是标明下一个event所在的binlog filename和position。这里需要注意,当slave发送binlog dump之后,master首先会发送一个ROTATE_EVENT,用来告知slave下一个event所在位置,然后才跟着FORMAT_DESCRIPTION_EVENT。
其实我们可以看到,binlog event的格式很简单,文档都有着详细的说明。通常来说,我们仅仅需要关注几种特定类型的event,所以只需要写出这几种event的解析代码就可以了,剩下的完全可以跳过。
Row Based Replication
如果真要说处理binlog event有啥复杂的,那铁定属于row based replication相关的ROWS_EVENT了,对于一个ROWS_EVENT来说,它记录了每一行数据的变化情况,而对于外部来说,是需要准确的知道这一行数据到底如何变化的,所以我们需要获取到该行每一列的值。而如何解析相关的数据,是非常复杂的。笔者也是看了很久MySQL,MariaDB源码,以及mysql-python-replication的实现,才最终搞定了这个个人觉得最困难的部分。
在详细说明ROWS_EVENT之前,我们先来看看TABLE_MAP_EVENT,该event记录的是某个table一些相关信息,格式如下:
post-header:
if post_header_len == 6 {
4 table id
} else {
6 table id
}
2 flags
payload:
1 schema name length
string schema name
1 [00]
1 table name length
string table name
1 [00]
lenenc-int column-count
string.var_len [length=$column-count] column-def
lenenc-str column-meta-def
n NULL-bitmask, length: (column-count + 8) / 7
table id需要根据post_header_len来判断字节长度,而post_header_len就是存放到FORMAT_DESCRIPTION_EVENT里面的。这里需要注意,虽然我们可以用table id来代表一个特定的table,但是因为alter table或者rotate binlog event等原因,master会改变某个table的table id,所以我们在外部不能使用这个table id来索引某个table。
TABLE_MAP_EVENT最需要关注的就是里面的column meta信息,后续我们解析ROWS_EVENT的时候会根据这个来处理不同数据类型的数据。column def则定义了每个列的类型。
ROWS_EVENT包含了insert,update以及delete三种event,并且有v0,v1以及v2三个版本。
ROWS_EVENT的格式很复杂,如下:
header:
if post_header_len == 6 {
4 table id
} else {
6 table id
}
2 flags
if version == 2 {
2 extra-data-length
string.var_len extra-data
}
body:
lenenc_int number of columns
string.var_len columns-present-bitmap1, length: (num of columns+7)/8
if UPDATE_ROWS_EVENTv1 or v2 {
string.var_len columns-present-bitmap2, length: (num of columns+7)/8
}
rows:
string.var_len nul-bitmap, length (bits set in 'columns-present-bitmap1'+7)/8
string.var_len value of each field as defined in table-map
if UPDATE_ROWS_EVENTv1 or v2 {
string.var_len nul-bitmap, length (bits set in 'columns-present-bitmap2'+7)/8
string.var_len value of each field as defined in table-map
}
... repeat rows until event-end
ROWS_EVENT的table id跟TABLE_MAP_EVENT一样,虽然table id可能变化,但是ROWS_EVENT和TABLE_MAP_EVENT的table id是能保证一致的,所以我们也是通过这个来找到对应的TABLE_MAP_EVENT。
为了节省空间,ROWS_EVENT里面对于各列状态都是采用bitmap的方式来处理的。
首先我们需要得到columns present bitmap的数据,这个值用来表示当前列的一些状态,如果没有设置,也就是某列对应的bit为0,表明该ROWS_EVENT里面没有该列的数据,外部直接使用null代替就成了。
然后就是null bitmap,这个用来表明一行实际的数据里面有哪些列是null的,这里最坑爹的是null bitmap的计算方式并不是(num of columns+7)/8
,也就是MySQL计算bitmap最通用的方式,而是通过columns present bitmap的bits set个数来计算的,这个坑真的很大,为啥要这么设计,最主要的原因就在于MySQL 5.6之后binlog row image的格式增加了minimal和noblob,尤其是minimal,update的时候只会记录相应更改字段的数据,譬如我一行有16列,那么用2个byte就能搞定null bitmap了,但是如果这时候只有第一列更新了数据,其实我们只需要使用1个byte就能记录了,因为后面的铁定全为0,就不需要额外空间存放了,不过话说真有必要这么省空间吗?
null bitmap的计算需要通过columns present bitmap的bits set计算,bits set其实也很好理解,就是一个byte按照二进制展示的时候1的个数,譬如1的bits set就是1,而3的bits set就是2,而255的bits set就是8了。
好了,得到了present bitmap以及null bitmap之后,我们就能实际解析这行对应的列数据了,对于每一列,首先判断是否present bitmap标记了,如果为0,则跳过用null表示,然后在看是否在null bitmap里面标记了,如果为1,表明值为null,最后我们就开始解析真有有数据的列了。
但是,因为我们得到的是一行数据的二进制流,我们怎么知道一列数据如何解析?这里,就要靠TABLE_MAP_EVENT里面的column def以及meta了。
column def定义了该列的数据类型,对于一些特定的类型,譬如MYSQL_TYPE_LONG, MYSQL_TYPE_TINY等,长度都是固定的,所以我们可以直接读取对应的长度数据得到实际的值。但是对于一些类型,则没有这么简单了。这时候就需要通过meta来辅助计算了。
譬如对于MYSQL_TYPE_BLOB类型,meta为1表明是tiny blob,第一个字节就是blob的长度,2表明的是short blob,前两个字节为blob的长度等,而对于MYSQL_TYPE_VARCHAR类型,meta则存储的是string长度。这里,笔者并没有列出MYSQL_TYPE_NEWDECIMAL,MYSQL_TYPE_TIME2等,因为它们的实现实在是过于复杂,笔者几乎对照着MySQL的源码实现的。
搞定了这些,我们终于可以完整的解析一个ROWS_EVENT了,顺带说一下,python-mysql-replication里面minimal/noblob row image的支持,也是笔者提交的pull request,貌似是笔者第一次给其他开源项目做贡献。
总结
实现MySQL replication protocol的解析真心是一件很有挑战的事情,虽然辛苦,但是让笔者更加深入的学习了MySQL的源码,为后续笔者改进LedisDB的replication以及更深入的了解MySQL的replication打下了坚实的基础。
话说,现在成果已经显现,不然go-mysql-elasticsearch不可能如此快速实现,后续笔者准备基于此做一个更新cache的服务,这样我们的代码里面就不会到处出现更新cache的代码了。
深入解析MySQL replication协议的更多相关文章
- mysql交互协议解析——mysql包基础数据、mysql包基本格式
mysql交互协议是开发mysql周边组件常用的协议,如JDBC,libmysql等等. 在此我们要认识到mysql交互协议其实是半双工的交互协议,至于为什么,这里就先挖个小坑,以后再填. 在探讨my ...
- MySQL Replication 主从复制全方位解决方案
1.1 主从复制基础概念 在了解主从复制之前必须要了解的就是数据库的二进制日志(binlog),主从复制架构大多基于二进制日志进行,二进制日志相关信息参考:http://www.cnblogs.com ...
- MySql Replication基本原理
Replication的思想是将数据在集群的多个节点同步.备份,以提高集群数据的可用性(HA):Mysql使用Replication架构来实现上述目的,同时可以提升了集群整体的并发能力.5.6版本作为 ...
- 深度解析mysql登录原理
使用mysql数据库的第一步必然是建立连接登录,然后在上面执行SQL命令.无论是通过mysql的客户端,还是通过C-API,JDBC标准接口连接数据库,这个过程一定少不了.今天我们聊一聊mysql登陆 ...
- MySQL Replication浅析
MySQL Replication是MySQL非常出色的一个功能,该功能将一个MySQL实例中的数据复制到另一个MySQL实例中.整个过程是异步进行的,但由于其高效的性能设计,复制的延时非常小.MyS ...
- 浅谈MySQL Replication(复制)基本原理
1.MySQL Replication复制进程MySQL的复制(replication)是一个异步的复制,从一个MySQL instace(称之为Master)复制到另一个MySQL instance ...
- 搭建mysql主从复制---Mysql Replication
主从复制原理 Mysql的Replication是一个异步的复制过程,从一个Mysql Instance(master)复制到另一个Mysql Instance(slave).中间需要三个线程slav ...
- 第 13 章 可扩展性设计之 MySQL Replication
前言: MySQL Replication 是 MySQL 非常有特色的一个功能,他能够将一个 MySQL Server 的 Instance 中的数据完整的复制到另外一个 MySQL Server ...
- 腾讯工程师带你深入解析 MySQL binlog
欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 本文由 腾讯云数据库内核团队 发布在云+社区 1.概述 binlog是Mysql sever层维护的一种二进制日志,与innodb引擎中的red ...
随机推荐
- jquery 中ajax的参数
url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如put和 ...
- 浅述Oracle分布式事务概念
着系统的复杂性不断增加,我们所面对的分布式系统渐渐增加.分布式文件系统.分布式消息队列系统等等层出不穷,在一些行业特别是互联网行业应用广泛.分布式数据库也是目前使用比较常用的分布式系统之一. 简单来说 ...
- tomcat 远程 调试 eclipse
windows系统: 修改catalina.bat 端口9000 SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE ...
- zookeeper端口号冲突:8080冲突
端口号冲突,在conf/zoo2181.cfg文件里面添加一个端口号的指定(具体的端口号可以自己定义一个): admin.serverPort=8081
- MS SQL到Oracle的数据迁移笔记
MS SQL到Oracle的数据迁移笔记 一.任务背景 旧系统使用MS SQL Server数据库,新系统使用Oracle数据库,现在需要将旧系统中的数据迁移到新系统中,旧数据按照约定的规则转换后,能 ...
- nyoj组合数
算法:深搜 描述 找出从自然数1.2.... .n(0<n<10)中任取r(0<r<=n)个数的所有组合. 输入输入n.r.输出按特定顺序输出所有组合. 特定顺序:每一个组合中 ...
- KM算法专题
原文:http://972169909-qq-com.iteye.com/blog/1184514 题目地址:这里. 1)求图中所有环的总长度(环的长度不唯一)的最小值.当无法得到完备匹配时说明环不存 ...
- vim+ctags+cscope工具
最近在看一个开源的项目,突然发现在Linux上不知道有什么方便的工具来看开源代码.以前,在Windows上用VS2010集成开发环境,对于任意一个函数直接有快捷键跳转到函数定义处.可现在在Linux上 ...
- sass转换为css
sass安装的方法参考官网:http://www.w3cplus.com/sassguide/ SASS文件转换为CSS文件的方法: 首先输出 F: 代表找到F盘 : 然后输出cd sass 代表找到 ...
- python学习第十三天 -模块和包
模块和包 大家都知道,在计算机程序开发的过程中,随着程序代码越写越多,这样代码就会越不容易维护. 有时候为了好维护代码,把不同功能的函数放到不同的xx.py文件中. 在python中,一个.py文件就 ...