MySQL Binlog(三)——MySQL 复制流程详解
前言
上一篇文章讲了MySQL协议的基础内容,在本文中我们通过分析MySQL复制流程看看MySQL协议在复制中的使用。
MySQL复制流程概述
按照复制原理中描述的,在备库上提交change master to请求主备数据连接以后
- TCP连接阶段:备库和主库之间会建立TCP链接(TCP/IP的三次握手建立连接过程不在本文讨论范畴,这里不详细描述)
- 用户认证阶段:主库会对备库的用户名密码进行认证,进行MySQL应用层的三次握手
- register_slave注册slave阶段:备库通过register_slave将自己注册到主库上(该步骤可忽略),以便master在show slave host时可以查看到哪些slave连接上来了
- request_dump请求binlog阶段:备库通过request_dump,请求从主库指定的二进制日志和文件偏移量开始获取所有的二进制日志。
- master发送Binlog Event阶段:Master启动binlog dump线程,从指定的二进制日志的对应文件偏移量开始向备库的IO线程发送二进制日志Event。(该步骤在主库上就是读取文件和网络发送的过程,本文档中不详细描述)
- 备库接收存储Relaylog阶段:备库的IO线程持续接收Event并将Event存储relay log中。具体Event的存储格式以及各种类型的字段在Binlog Event的存储格式请参考后续文章。
接下来的几节将详细对以上2,3,4步的网络协议字节格式、及可能取值等方面进行详细描述
用户认证过程详解
用户认证时序图
1 | Master->Slave:Server Greeting 开始MySQL握手协议 |
如图所示,
- master先发送了greeting网络包;
- slave接收到该网络包以后提交帐号密码进行验证;
- master接收到用户名密码,验证通过后会通过Response OK 返回表示认证通过。
普通的客户端连接MySQL同样需要经历这三次握手协议。
异常情况
- 第1步如果slave没有接收到greeting网络包,在超时等待后会报错退出
- 第2步,如果master没有接收到slave提交的帐号密码,此时,可以在master数据库中看到类似于如下信息:
可以看到当前数据库中会有一个连接连上来,但是在User字段显示是的unauthenticated user,这就是表示连接已经建立,但是用户认证还没有完成。该线程的状态是Receiving from client,表示正在等待client发送用户认证的数据信息。超时等待后,master也会报错并中止对应的线程.
- 第3步,如果master密码认证错误,会直接发送一个错误包,slave和Master都需要中止这个连接。
接下来几节将详细描述用户认证时序图中1,2,3三个网络包的具体字节分段含义。
Server Greeting步骤
相关代码位置
1 | sql/rpl_slave.cc文件中的handle_slave_io() |
Greeting数据包格式
类型 | 名字 | 描述 |
---|---|---|
int<3>3> | payload_length | 数据包中有效信息的长度,不包括数据包头部4个字节的长度 |
int<1>1> | sequence_id | 数据包序列号 |
int<1>1> | 协议版本号 | 表示当前连接使用的协议版本 |
string< null-terminated > | 服务器版本信息 | 表示当前server端服务器版本 |
int<4>4> | thread_id | 表示当前连接,在server上的线程ID |
string< null-terminated > | scramble data part1 | 用于密码校验的随机数部分一 |
int<2>2> | 无意义 | |
int<1>1> | server_charset | 服务器的字符集 |
int<2>2> | server_status | 服务器状态 |
int<2>2> | server_capability | 服务器权能标志 |
int<1>1> | pkt_scramble_length | |
string[10] | 无意义 | |
string< null-terminated > | scramble data part2 | 用于密码校验的随机数部分二 |
string< null-terminated > | scramble plugin name | 用于密码校验的插件名称 |
数据包解析示例
数据包示例:
1 | 4e 00 00 数据包长度 //88 |
各分段含义解释:
- 数据包长度:MySQL协议中固定格式,前三个字节表示数据包长度,这个长度不包括固定格式的4个字节
- 数据包序号:MySQL协议中固定格式,用一个字节表示数据包的序号
- 协议版本号:MySQL协议的版本序号
- 服务器版本信息:服务器的版本,这边为5.7.19-log
- 线程ID:表示master分配给该连接的线程的ID号,与show processlist中的thread id对应
- scramble data part1:master 生成的随机数,用于加密密码
- 服务器字符集:表示master使用的字符集编码
- 服务器状态:表示服务器状态
在/include/mysql_com.h定义了服务器的状态
1 | #define SERVER_STATUS_IN_TRANS 1 |
- 服务器权能标志:用于与客户端协商协议方式
在/include/mysql_com.h定义
1 | #define CLIENT_LONG_PASSWORD 1 /* new more secure passwords */ |
- scramble data part2:与scramble data part1组成为随机数,用于加密密码
- scramble插件名字:指定用于scramble data加密方式的插件,这边为native_mysql_password
Login Request步骤
本节主要描述slave发送的用户认证的数据包(slave->master)过程
相关代码位置
1 | sql/rpl_slave.cc文件中的handle_slave_io() |
数据包格式
类型 | 名字 | 描述 |
---|---|---|
int<3>3> | payload_length | 数据包中有效信息的长度,不包括数据包头部4个字节的长度 |
int<1>1> | sequence_id | 数据包序列号 |
int<4>4> | client_flag | client的标记 |
int<4>4> | max_packet_size | 允许发送的最大数据包的大小 |
int<1>1> | client_charset | client使用的字符集 |
string[23] | 无用 | |
string< null-terminated > | user_name | 登陆的用户名 |
int<1>1> | client_capability | |
string[20] | 加密后的scramble_data | |
string< null-terminated > | database_name | 要连接的数据库的名称 |
string< null-terminated > | scramble_plugin_name | 用于密码校验的插件名称 |
数据包解析
数据包示例
1 | 58 00 00 数据包长度 //98 |
各分段数据含义:
- 数据包长度:MySQL协议中固定格式,前三个字节表示数据包长度,这个长度不包括固定格式的4个字节
- 数据包序号:MySQL协议中固定格式,用一个字节表示数据包的序号
- client flag :指定客户端发送数据的一些规范,
- max_packet_size:指定客户端发送数据包的最大值,
- client使用的字符集编码:client所使用的字符集编码
- 用户名:用于用户登陆的用户名
- client权能标识:client capability
- 加密之后的scramble data:将用于登陆的密码加密之后得到的结果
参考淘宝内核月报关于native_mysql_password插件加密方式:
client:
hash_stage1 = sha1(password) hash_stage2 = sha1(hash_stage1) reply = sha1(scramble, hash_stage2) ^ hash_stage1
server: (逻辑位于sql/password.c:check_scramble_sha1中, 下文亦有提及)
// mysql.user表中, 对应user的passwd实际上是hash_stage2 res1 = sha1(scramble, hash_stage2) hash_stage1 = reply ^ res1 hash_stage2_reassured = sha1(hash_stage1) 再根据hash_stage2_reassured == hash_stage2(from mysql.user)是否一致来判定是否合法
- 数据库名称:指定要连接的schema的名称
- 加密插件的名称:进行加密的插件的名称,这边为native_mysql_password
异常情况
在用户认证过程中,master发送给slave一个Greeting包之后,slave发送一个auth包给master,因为我们是模拟IO线程的过程,所以这个auth包的内容是我们自己组织的,在发送将auth包发送给master进行用户认证的过程中,可能会出现一些异常情况,这样会导致master返回一个非OK包,这边列举可能出现的异常情况。
slave发送的auth包中,用户名和密码错误
master会返回一个ERR包,ERR包中会包含‘ACCESS DENIED’的信息 1045号错误。slave发送的auth包中,数据组织格式有问题
master会返回一个EOF包,并中断连接。
response ok步骤详解
认证没有问题master将直接返回一个OK包。
register_slave注册slave过程过程详解
register slave 认证时序图
1 | Slave->Master:Request Query 设置master_binlog_checksum(必须) |
如图所示,注册slave的过程如下
- slave客户端直接SET @master_binlog_checksum= @@global.binlog_checksum
- 设置正确,返回Ok Packet
- 设置其他相关配置,比如slave_uuid等
- 设置正确,返回Ok Packet
- slave发送register_slave 注册slave请求
- 设置正确,返回Ok Packet
注册slave步骤主要是为了方便主库能获得slave的IP/Port等信息,第5步可忽略
异常情况
- 第1步,第3步如果设置的命令有误,Master将返回Error Packet。
- 第5步,如果填充的register_slave Packet包错误,Master同样将返回Error Packet
接下来几节将详细描述上述第5步网络包的具体字节分段含义。
Register Slave 步骤
相关代码位置
1 | sql/rpl_slave.cc文件中的handle_slave_io()函数 |
数据包格式
类型 | 名字 | 描述 |
---|---|---|
int<3>3> | payload_length | 数据包中有效信息的长度,不包括数据包头部4个字节的长度 |
int<1>1> | sequence_id | 数据包序列号 |
int<1>1> | command_type | 发送的请求的类型 |
int<4>4> | server_id | slave的server_id |
string< null-terminated > | report_host | |
string< null-terminated > | report_user | |
string< null-terninated > | report_password | |
int<2>2> | port | 端口号 |
string[8] | 无用 |
数据包解析
数据包示例:
1 | 12 00 00 数据包长度 |
各分段数据含义:
- 数据包长度:MySQL协议中固定格式,前三个字节表示数据包长度,这个长度不包括固定格式的4个字节
- 数据包序号:MySQL协议中固定格式,用一个字节表示数据包的序号
- COMMAND类型:表示发送的请求的类型
在my_command.h文件中定义了command的类型:
1 | enum enum_server_command |
- report host:指定report的IP地址
- report user:指定report的user
- report password:指定report的账户的密码
- 端口号:指定连接的端口号,等于change master to语句中的port选项
异常情况
如果填充的register_slave Packet包错误,Master同样将返回Error Packet
request dump请求binlog过程详解
request dump 请求时序图
1 | Slave->Master:Request Send Binlog 请求发送binlog |
如图所示,请求binlog的过程如下
- slave客户端发送request dump请求,请求中包含binlog dump开始发送二进制文件名称和偏移量
- master接收到请求,位置正确的话,会先发送Rotate Event表示接下来的二进制日志将从指定的二进制文件和偏移量开始发送
- master紧接着会从指定的二进制文件头读取Format Description Event 并发送给slave,用于slave 确认各个event的header长度等信息。
- master从二进制文件和偏移量开始读取二进制数据并通过网络发给Slave
异常情况下:
- 第1步,如果slave请求的二进制文件或者偏移量不正确,master将返回ERR Packet
Request dump 步骤
相关代码位置
1 | sql/rpl_slave.cc文件中的handle_slave_io()函数 |
数据包格式
类型 | 名字 | 描述 |
---|---|---|
int<3>3> | payload_length | 数据包中有效信息的长度,不包括数据包头部4个字节的长度 |
int<1>1> | sequence_id | 数据包序列号 |
int<1>1> | command_type | 发送的请求的类型 |
int<4>4> | binlog_pos | 请求的binlog位置点 |
int<2>2> | binlog_flag | |
int<4>4> | server_id | slave的server_id |
string< reset-of_packet > | binlog_file_name | 请求的binlog文件名 |
数据包解析
1 | 1b 00 00 数据包长度 |
- 数据包长度:MySQL协议中固定格式,前三个字节表示数据包长度,这个长度不包括固定格式的4个字节
- 数据包序号:MySQL协议中固定格式,用一个字节表示数据包的序号
- COMMAND类型:表示发送的请求的类型
- binlog位置点:表示请求的binlog位置
- binlog flag:
- slave_server_id:表示slave的server_id
- binlog文件名:表示请求的binlog的文件名
异常情况
- binlog 请求的位置点大于对应binlog_file中最大的位置点
Master返回一个ERR数据包,Err Packet格式参见“3.3.2 ”
示例数据包:
1 | 4f 00 00 数据包长度 |
各分段数据包含义:
- 错误号为:1236
- 错误信息为:Client requested master to start replication from position>filesize
- binlog 请求的位置点不是event起始位置点
Master返回一个ERR数据包,Err Packet格式参见上一篇文章中ERR_Packet部分内容。
数据包示例:
1 | e1 00 00 数据包长度 |
各分段含义:
- 错误号为:1236
- 错误信息为:bogusdatain log event;the first event’mysql-bin.000001’at160,the last event read from’/opt/mysql/data/binlog/mysql-bin.000001’at123,the last byte read from’/opt/mysql/data/binlog/mysql-bin.000001’at179.
- binlog 请求的binlog filename 不存在
Master返回一个ERR数据包
数据包示例:
1 | 44 00 00 数据包长度 |
各分段数据含义:
- 错误号为:1236
- 错误信息为:Could not find first log filename in binary log index file
整体流程回顾
最后我们再整体来看一下MySQL复制链接在建立连接的完整过程
- Master与Slave建立TCP/IP连接
- Master发送第一个MySQL协议的Greeting包给Slave
- Slave根据发送的数据包,加密自己的账户及密码等信息,发送给Master
- Master根据Slave发送的auth数据包进行认证,认证成功,则发送一个Response OK数据包给Slave,Slave完成用户登录
- Slave发送一个Query的请求,设置当前连接的master_binlog_checksum为@@global.binlog_checksum这一步是必须的,这是设置校验码
- Master处理完Slave的Query请求之后,发送一个Response OK数据包给Slave
- Slave发送一个Query请求,设置当前连接的slave_uuid为从库的uuid,这一步非必须
- Master处理完Slave的Query请求之后,发送一个Response OK数据包给Slave
- Slave向Master发送一个Register Slave的数据包,进行slave线程的注册
- Master将slave线程注册完成之后,返回一个Response OK数据包
- Slave在注册成功之后,向Master发送Binlog Dump命令,发送请求的binlog的文件名以及pos点
- Master在收到Slave的Binlog Dump命令之后,首先发送一个Rotate Event的内容给Slave,告诉Slave当前发送的binlog的文件名为什么
- Master之后再发送一个Format Description Event内容给Slave,包括版本信息,位置点等信息
- 接着Master按照Slave的Binlog Dump请求中携带的pos点的信息,从该binlog文件偏移位置为pos的地方将Event内容取出来,发送给Slave