共计 6786 个字符,预计需要花费 17 分钟才能阅读完成。
解读 MySQL Client/Server Protocol: Connection & Replication
MySQL 客户端与服务器之间的通信基于特定的 TCP 协定,本文将会详解其中的 Connection 和 Replication 局部,这两个局部别离对应的是客户端与服务器建设连贯、实现认证鉴权,以及客户端注册成为一个 slave 并获取 master 的 binlog 日志。
Connetcion Phase
MySQL 客户端想要与服务器进行通信,第一步就是须要胜利建设连贯,整个过程如下图所示:
- client 发动一个 TCP 连贯。
- server 响应一个
Initial Handshake Packet
(初始化握手包),内容会蕴含一个默认的认证形式。 - 这一步是可选的,单方建设 SSL 加密连贯。
- client 回应
Handshake Response Packet
,内容须要包含用户名和依照指定形式进行加密后的明码数据。 - server 响应
OK_Packet
确认认证胜利,或者ERR_Packet
示意认证失败并敞开连贯。
Packet
一个 Packet 其实就是一个 TCP 包,所有包都有一个最根本的构造:
如上图所示,所有包都能够看作由 header 和 body 两局部形成:第一局部 header 总共有 4 个字节,3 个字节用来标识 body 即 payload 的大小,1 个字节记录 sequence ID;第二局部 body 就是 payload 理论的负载数据。
因为 payload length 只有 3 个字节来记录,所以一个 packet 的 payload 的大小不能超过 2^24 = 16 MB,示例:
Packet :
- 当数据不超过 16 MB 时,精确来说是 payload 的大小不超过 2^24-1 Byte(三个字节所能示意的最大整数 0xFFFFFF),发送一个 packet 就够了。
- 当数据大小超过了 16 MB 时,就须要把数据切分成多个 packet 传输。
- 当数据 payload 的刚好是 2^24-1 Byte 时,一个包尽管足够了,然而为了示意数据传输结束,依然会多传一个 payload 为空的 packet。
Sequence ID:包的序列号,从 0 开始递增。在一个残缺的会话过程中,每个包的序列号顺次加一,当开始一个新的会话时,序列号从新从 0 开始。例如:在建设连贯的阶段,server 发送 Initial Handshake Packet(Sequence ID 为 0),client 回应 Handshake Response Packet(Sequence ID 为 1),server 再响应 OK_Packet 或者 ERR_Packet(Sequence ID 为 2),而后建设连贯的阶段就完结了,再有后续的命令数据,包的 Sequence ID 就从新从 0 开始;在命令阶段(client 向 server 发送增删改查这些都属于命令阶段),一个命令的申请和响应就能够看作一个残缺的会话过程,比方 client 先向 server 发送了一个查问申请,而后 server 对这个查问申请进行了响应,那么这一次会话就完结了,下一个命令就是新的会话,Sequence ID 也就从新从 0 开始递增。
Initial Handshake Packet <span id=”Initial_Handshake_Packet”></span>
建设连贯时,当客户端发动一个 TCP 连贯后,MySQL 服务端就会回应一个 Initial Handshake Packet
,这个初始化握手包的数据格式如下图所示:
这个图从上往下顺次是:
- 1 个字节的整数,示意 handshake protocol 的版本,当初都是 10。
- 以 NUL(即一个字节 0x00)结尾的字符串,示意 MySQL 服务器的版本,例如
5.7.18-log
。 - 4 个字节的整数,示意线程 id,也是这个连贯的 id。
- 8 个字节的字符串,
auth-plugin-data-part-1
后续明码加密须要用到的随机数的前 8 位。 - 1 个字节的填充位。
- 2 个字节的整数,
capability_flags_1
即Capabilities Flags
的低位 2 位字节。 - 1 个字节的整数,示意服务器默认的字符编码格局,比方
utf8_general_ci
。 - 2 个字节的整数,服务器的状态标识。
- 2 个字节的整数,
capability_flags_2
即Capabilities Flags
的高位 2 位字节。 - 1 个字节的整数,如果服务器具备 CLIENT_PLUGIN_AUTH 的能力(其实就是可能进行客户端身份验证,根本都反对),那么传递的是
auth_plugin_data_len
即加密随机数的长度,否则传递的是 0x00。 - 10 个字节的填充位,全副是 0x00。
- 由
auth_plugin_data_len
指定长度的字符串,auth-plugin-data-part-2
加密随机数的后 13 位。 - 如果服务器具备 CLIENT_PLUGIN_AUTH 的能力(其实就是可能进行客户端身份验证,根本都反对),那么传递的是
auth_plugin_name
即用户认证形式的名称。
对于 MySQL 5.x 版本,默认的用户身份认证形式叫做 mysql_native_password
(对应下面的 auth_plugin_name
),这种认证形式的算法是:
SHA1(password) XOR SHA1("20-bytes random data from server" <concat> SHA1( SHA1( password) ) )
其中加密所需的 20 个字节的随机数就是 auth-plugin-data-part-1
(8 位数)和 auth-plugin-data-part-2
(13 位中的前 12 位数)组成。
留神:MySQL 应用的小端字节序。
看到这,你可能还对 Capabilities Flags
感到很困惑。
Capabilities Flags
Capabilities Flags
其实就是一个性能标记,用来表明服务端和客户端反对并心愿应用哪些性能。为什么须要这个性能标记?因为首先 MySQL 有泛滥版本,每个版本可能反对的性能有区别,所以服务端须要表明它反对哪些性能;其次,对服务端来说,连贯它的客户端能够是各种各样的,这些客户端心愿应用哪些性能也是须要表明的。
Capabilities Flags
个别是 4 个字节的整数:
如上图所示,每个性能都独占一个 bit 位。
Capabilities Flags
通常都是多个性能的组合示意,例如要示意 CLIENT_PROTOCOL_41
、CLIENT_PLUGIN_AUTH
、CLIENT_SECURE_CONNECTION
这三个性能,那么就把他们对应的 0x00000200
、0x00080000
、0x00008000
进行比特位或运算就能失去最终的值 0x00088200
也就是最终的 Capabilities Flags
。
依据 Capabilities Flags
判断是否反对某个性能,例如 Capabilities Flags
的值是 0x00088200
,要判断它是否反对 CLIENT_SECURE_CONNECTION
的性能,则间接进行比特位与运算即可,即 Capabilities Flags
& CLIENT_SECURE_CONNECTION
== CLIENT_SECURE_CONNECTION
。
Handshake Response Packet <span id=”Handshake_Response_Packet”></span>
建设连贯的过程中,当客户端收到了服务端的 Initial Handshake Packet
后,须要向服务端回应一个 Handshake Response Packet
,包的数据格式如下图所示:
顺次是:
- 4 个字节的整数,
Capabilities Flags
,肯定要设置CLIENT_PROTOCOL_41
,对于 MySQL 5.x 版本,应用默认的身份认证形式,还须要对应的设置CLIENT_PLUGIN_AUTH
和CLIENT_SECURE_CONNECTION
。 - 4 个字节的整数,包大小的最大值,这里指的是命令包的大小,比方一条 SQL 最多能多大。
- 1 个字节的整数,字符编码方式。
- 23 个字节的填充位,全是 0x00。
- 以 NUL(0x00)结尾的字符串,登录的用户名。
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
个别不应用。- 1 个字节的整数,
auth_response_length
,明码加密后的长度。 auth_response_length
指定长度的字符串,明码与随机数加密后的数据。- 如果
CLIENT_CONNECT_WITH_DB
间接指定了连贯的数据库,则须要传递以 NUL(0x00)结尾的字符串,内容是数据库名。 CLIENT_PLUGIN_AUTH
个别都须要,默认形式须要传递的值就是mysql_native_password
。
能够看到,Handshake Response Packet
与 Initial Handshake Packet
其实是绝对应的。
OK_Packet
& ERR_Packet
<span id=”OK_ERR”></span>
OK_Packet
和 ERR_Packet
是 MySQL 服务端通用的响应包。
MySQL 5.7.5 版本当前,OK_Packet
还蕴含了 EOF_Packet
(用来显示正告和状态信息)。辨别 OK_Packet
和 EOF_Packet
:
- OK: header = 0x00 and length of packet > 7
- EOF: header = 0xfe and length of packet < 9
MySQL 5.7.5 版本之前,EOF_Packet
是一个独自格局的包:
如果身份认证通过、连贯建设胜利,返回的 OK_Packet
就会是:
0x07 0x00 0x00 0x02 0x00 0x00 0x00 0x02 0x00 0x00 0x00
如果连贯失败,或者呈现谬误则会返回 ERR_Packet
格局的包:
Replication
想要获取到 master 的 binlog 吗?只有你对接实现 replication 协定即可。
- client 与 server 之间胜利建设连贯、实现身份认证,这个过程就是上文所述的 connection phase。
- client 向 server 发送
COM_REGISTER_SLAVE
包,表明要注册成为一个 slave,server 响应OK_Packet
或者ERR_Packet
,只有胜利能力进行后续步骤。 - client 向 server 发送
COM_BINLOG_DUMP
包,表明要开始获取 binlog 的内容。 -
server 响应数据,可能是:
binlog network stream
(binlog 网络流)。ERR_Packet
,示意有谬误产生。EOF_Packet
,如果COM_BINLOG_DUMP
中的 flags 设置为了 0x01,则在 binlog 没有更多新事件时发送EOF_Packet
,而不是阻塞连贯持续期待后续 binlog event。
COM_REGISTER_SLAVE
客户端向 MySQL 发送 COM_REGISTER_SLAVE
,表明它要注册成为一个 slave,包格局如下图:
除了 1 个字节的固定内容 0x15 和 4 个字节的 server-id,其余内容通常都是空或者疏忽,须要留神的是这里的 user 和 password 并不是登录 MySQL 的用户名和明码,只是 slave 的一种标识而已。
COM_BINLOG_DUMP
注册成为 slave 之后,发送 COM_BINLOG_DUMP
就能够开始承受 binlog event 了。
- 1 个字节的整数,固定内容 0x12。
- 4 个字节的整数,
binlog-pos
即 binlog 文件开始的地位。 - 2 个字节的整数,
flags
,个别状况下 slave 会始终放弃连贯期待承受 binlog event,然而当 flags 设置为了 0x01 时,如果以后 binlog 全副接管完了,则服务端会发送EOF_Packet
而后完结整个过程,而不是放弃连贯持续期待后续 binlog event。 - 4 个字节的整数,
server-id
,slave 的身份标识,MySQL 能够同时存在多个 slave,每个 slave 必须领有不同的server-id
。 - 不定长字符串,
binlog-filename
,开始的 binlog 文件名。查看以后的 binlog 文件名和 pos 地位,能够执行 SQL 语句show master status
,查看所有的 binlog 文件,能够执行 SQL 语句show binary logs
。
Binlog Event
客户端注册 slave 胜利,并且发送 COM_BINLOG_DUMP
正确,那么 MySQL 就会向客户端发送 binlog network stream 即 binlog 网络流,所谓的 binlog 网络流其实就是源源不断的 binlog event 包(对 MySQL 进行的操作,例如 inset、update、delete 等,在 binlog 中是以一个或多个 binlog event 的模式存在的)。
Replication 的两种形式:
- 异步,默认形式,master 一直地向 slave 发送 binlog event,无需 slave 进行 ack 确认。
- 半同步,master 向 slave 每发送一个 binlog event 都须要期待 ack 确认回复。
Binlog 有三种模式:
statement
,binlog 存储的是原始 SQL 语句。row
,binlog 存储的是每行的理论前后变动。mixed
,混合模式,binlog 存储的一部分是 SQL 语句,一部分是每行变动。
Binlog Event 的包格局如下图:
每个 Binlog Event 包都有一个确定的 event header,依据 event 类型的不同,可能还会有 post header 以及 payload。
Binlog Event 的类型十分多:
-
Binlog Management:
START_EVENT_V3
FORMAT_DESCRIPTION_EVENT
: MySQL 5.x 及以上版本 binlog 文件中的第一个 event,内容是 binlog 的根本形容信息。STOP_EVENT
ROTATE_EVENT
: binlog 文件产生了切换,binlog 文件中的最初一个 event。SLAVE_EVENT
INCIDENT_EVENT
HEARTBEAT_EVENT
: 心跳信息,表明 slave 落后了 master 多少秒(执行 SQL 语句SHOW SLAVE STATUS
输入的Seconds_Behind_Master
字段)。
-
Statement Based Replication Events(binlog 为 statement 模式时相干的事件):
QUERY_EVENT
: 原始 SQL 语句,例如 insert、update …。INTVAR_EVENT
: 基于会话变量的整数,例如把主键设置为了 auto_increment 自增整数,那么进行插入时,这个字段理论写入的值就记录在这个事件中。RAND_EVENT
: 外部 RAND() 函数的状态。USER_VAR_EVENT
: 用户变量事件。XID_EVENT
: 记录事务 ID,事务 commit 提交了才会写入。
-
Row Based Replication Events(binlog 为 row 模式时相干的事件):
TABLE_MAP_EVENT
: 记录了后续事件波及到的表构造的映射关系。- v0 事件对应 MySQL 5.1.0 to 5.1.15 版本
DELETE_ROWS_EVENTv0
: 记录了行数据的删除。UPDATE_ROWS_EVENTv0
: 记录了行数据的更新。WRITE_ROWS_EVENTv0
: 记录了行数据的新增。- v1 事件对应 MySQL 5.1.15 to 5.6.x 版本
DELETE_ROWS_EVENTv1
: 记录了行数据的删除。UPDATE_ROWS_EVENTv1
: 记录了行数据的更新。WRITE_ROWS_EVENTv1
: 记录了行数据的新增。- v2 事件对应 MySQL 5.6.x 及其以上版本
DELETE_ROWS_EVENTv2
: 记录了行数据的删除。UPDATE_ROWS_EVENTv2
: 记录了行数据的更新。WRITE_ROWS_EVENTv2
: 记录了行数据的新增。
-
LOAD INFILE replication(加载文件的非凡场景,本文不做介绍):
LOAD_EVENT
CREATE_FILE_EVENT
APPEND_BLOCK_EVENT
EXEC_LOAD_EVENT
DELETE_FILE_EVENT
NEW_LOAD_EVENT
BEGIN_LOAD_QUERY_EVENT
EXECUTE_LOAD_QUERY_EVENT
想要解析具体某个 binlog event 的内容,只有对照官网文档数据包的格局即可。
结语
MySQL Client/Server Protocol 协定其实很简略,就是相互之间依照约定的格局发包,而了解了协定,置信你本人就能够实现一个 lib 去注册成为一个 slave 而后解析 binlog。