前言:
以 Mysql 为例,数据库为了主从复制结构和容灾,都会有一份提交日志,通过解析这份日志,理论上说可以获取到每次数据库的数据更新操作。获取这份日志有两种方式:
1、在 MySQL server 上通过外部程序监听磁盘上的 binlog 日志文件
2、借助于 MySQL 的 Master-Slave 结构,使用程序伪装成一个单独的 Slave,通过网络获取到 MySQL 的binlog 日志流
这里有一个注意的点: MySQL 的 binlog 支持三种格式:Statement 、 Row 和 Mixed 格式:
Statement:每一条会修改数据的sql都会记录在binlog中。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。(相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。)
缺点:由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同 的结果。另外mysql 的复制,像一些特定函数功能,slave可与master上要保持一致会有很多相关问题(如sleep()函数, last_insert_id(),以及user-defined functions(udf)会出现问题).
Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改。
优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题
缺点:所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如一条update语句,修改多条记录,则binlog中每一条修改都会有记录,这样造成binlog日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。
Mixedlevel: 是以上两种level的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种.新版本的MySQL中队row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录。至于update或者delete等修改数据的语句,还是会记录所有行的变更。
由于伪装成 Slave 的解析程序很难像 MySQL slave 一样通过 Master 执行的 SQL 来获取数据更新,因此要将 MySQL Master 的 binlog 格式调整成 Row 格式才方便实现数据更新获取服务。
在 MySQL 中, Master-slave 之间只用标识:
- serverId:master一般设置为1, 各个 server 之间必须不同
- binlog 文件名称:当前读取到了哪一个 binlog 文件
- binlog position:当前读取的 binlog 文件的位置
由于同步服务会重启,因此必须自行维护 binlog 的状态。一般存储到 MySQL 或者 Zookeeper 中。当服务重启后,自动根据存储的 binlog 位置,继续同步数据。
binlog 解析原理
mysql 主备复制原理
- MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
- MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
mysql-binlog-connector-java (以下简称mbcj)和 canal 工作原理
- mbcj 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 mbcj)
- mbcj 解析 binary log 对象(原始为 byte 流)
流程
1、client 和 DB 握手认证
2、握手成功后,client 对 DB 发送 show master status
命令,此命令将返回当前最新 binlog存储在那个文件以及对应的偏移量。如果想从当前开始接收binglog,则在后面发送binlog dump命令的时候用这两个值就好。
3、发送show global variables like 'binlog_checksum
命令,这是由于binlog event发送回来的时候需要,在最后获取event内容的时候,会增加4个额外字节做校验用。mysql5.6.5以后的版本中binlog_checksum=crc32,而低版本都是binlog_checksum=none。如果不想校验,可以使用set命令设置set binlog_checksum=none
4、发送 Dump 命令
Dump命令包图如下所示:
如上图所示,在报文中塞入binlogPosition和binlogFileName即可让master从相应的位置发送binlog event
MySql-Binlog-Event
一但发送了BinlogDump命令,master就会在数据库有变化的源源不断的推送binlog event到client。
Packet
在mysql中,如果client或server要发送数据,它需要将数据按照(2 * 24 - 1)拆分成packet,给每一个packet添加header,然后再以此发送。
对于一个packet,格式如下:
3 payload length
1 sequence id
string[len] payload
前面3个字节表明的是该packet的长度,每个packet最大不超过16MB。第4个字节表明的是该packet的序列号,从0开始,对于多个packet依次递增,等到下一个新的命令发送数据的时候才重置为0。前面4个字节组成了一个packet的header,后面就是该packet实际的数据。
因为一个packet最大能发送的数据位16MB,所以如果需要发送大于16MB的数据,就需要拆分成多个packet进行发送。
通常,server 会回给 client 三种类型的 packet
- OK Packet
- ERROR Packet
- result set Packets 头包+字段包+EOF包+行包+EOF包
数据类型
mysql协议只有两种基本的数据类型,integer和string。
integer
integer包括fixed length integer和length encoded integer两种,对于length encoded integer,用的地方比较多,这里详细说明一下。
对于一个integer,我们按照如下的方式将其转成length encoded integer:
如果value < 251,使用1 byte
如果value >= 251 同时 value < 2 ** 16,使用fc + 2 byte
如果value >= 2 ** 16 同时 value < 2 ** 24,使用fd + 3 byte
如果value >= 2 ** 24 同时 value < 2 ** 64,使用fe + 8 byte
相应的,对于一个length encoded integer,我们可以通过判断第一个byte的值来转成相应的integer。
string
string包括:
fixed length string,固定长度string
null terminated string,以null结尾的string
variable length string,通过另一个值决定长度的string
length encoded string,通过起始length encoded integer决定长度的string
rest of packet string,从当前位置到包结尾的string
下面再借用一图说明整个交互过程:
mysql 报文外层结构:
- 前面3字节描述了整个报文中Body的长度,为了解决粘包问题。
- 第4个字节比较特殊,是为了防止串包用。机制是每收到一个报文都在其sequenceId上加1,并随着需要返回的信息返回回去。如果DB检测到sequenceId连续,则表明没有串包。如果不连续,则串包,DB会直接丢弃这个连接。
- 补充一点包的最大大小是16M,超过16M,会将剩余的有效负载一起发送额外的数据包,直到数据包的有效负载小于16M
客户端向MySQL发起握手后,MySQL会向客户端发送packet,包括协议版本、MySQL版本、连接id、盐值、身份验证器、MySQL编码index、MySQL能力等,抓包数据如下:
客户端向MySQL发送对应的AuthPacket认证身份主要包括用户、密码(根据盐值和身份验证器加密)主要分41(SHA-1)和32(SHA-256)两个版本、数据库、编码索引等,抓包数据如下:
至此就可以进行后续获取 binlog 日志:
1、show global variables like ‘gtid_purged’
2、show master status (返回最新的 binlog 文件名字和位置)
3、建立连接
(1)show global variables like ‘binlog_checksum’ 数据库循环冗余校验
(2) select @@server_id
(3)启用心跳 set @master_heartbeat_period=" + heartbeatInterval * 1000000
4、发送 dump 包