准备:
本文主要介绍MySQL主从复制中从库与主库的流程交互逻辑实现。
(一)MySQL主从复制流程 🔗
MySQL复制流程概述 🔗
- 第一步:MySQL从库与MySQL主库进行TCP三次握手,建立起稳定的连接。
- 第二步:主从用户认证,MySQL主库向MySQL从库发送
Server Greeting proto=10 version=5.7.26
的Handshake Packet。MySQL从库解析Handshake包进行逻辑处理,Login Request user=root
是MySQL从库发送的Auth Packet。MySQL主库会对MySQL从库的用户名密码进行认证,进行MySQL应用层的三次握手。可参考之前的文章(二)Go-编写MySQL数据库驱动之MySQL协议解析Handshake Packet(三)Go-编写MySQL数据库驱动之MySQL协议解析Auth Packet - 第三步:register_slave注册MySQL从库,MySQL从库通过register_slave将自己注册到MySQL主库上,以便master在
show slave host
时可以查看连接的MySQL从库情况。 - 第四步:request_dump请求binlog,MySQL从库通过request_dump,请求从主库指定的二进制日志和文件偏移量开始获取所有的二进制日志。
- 第五步:master发送Binlog Event,Master启动binlog dump线程,从指定的二进制日志的对应文件偏移量开始向MySQL从库的IO线程发送二进制日志Event。
- 第六步:备库接收存储Relaylog,MySQL从库的IO线程持续接收Event并将Event存储relay log中。
(二)COM_REGISTER_SLAVE请求报文 🔗
这是上面第三步内容,MySQL从库将自己注册到MySQL主库上。首先前面的四个字节是包头,其中前三位代表packet数据长度,第四位是包序列号。以下是客户端命令请求报文的四字节后的内容。文档地址
Type | Name | Description |
---|---|---|
int<1> | 执行命令[15]COM_REGISTER_SLAVE | 获取二进制日志信息,固定十六进制0x15 |
int<4> | Slave server_id | 从服务器id值(小字节序) |
int<1> | Slave hostname length | 从服务器地址长度 |
int<n> | Hostname | 从服务器地址 |
int<1> | Slave username len | 从服务器用户名长度 |
int<n> | Username | 从服务器用户名 |
int<1> | Slave password len | 从服务器密码长度 |
int<n> | Slave password | 从服务器密码 |
int<2> | Slave connection port | 从服务器连接端口 |
int<4> | Replication rank | 复制排名 |
int<4> | Master server id | 主服务器id值 |
注: 从服务器地址、从服务器用户名、从服务器密码、复制排名、主服务器id值通常不需要设置
Register Slave详细步骤 🔗
通过Wireshark抓包可以看到当MySQL从库与MySQL主库完成Handshake Packet、Auth Packet认证后的步骤:
- 设置主从心跳
- 设置master_binlog_checksum,MySQL在5.6.5版本之后为binlog引入了checksum机制,从库需要与主库相关参数保持一致。在接收到binlog event发送内容进行解码会增加4个额外字节做校验用。mysql5.6.5 以后的版本中 binlog_checksum=crc32,而低版本都是 binlog_checksum=none。如果不想校验,可以使用set命令设置
set binlog_checksum=none
。 - 设置MySQL从库的slave_uuid,在MySQL主库上
SHOW SLAVE HOSTS;
可以查看连接的MySQL从库信息。 - 注册MySQL从库。
SET @master_heartbeat_period= 30000001024 #主从复制心跳
SET @master_binlog_checksum= @@global.binlog_checksum #设置master_binlog_checksum
SET @slave_uuid= '62c16f3c-41ab-11ed-8d8e-0242ac110002' #设置slave_uuid
Request Register Slave #注册MySQL从库
Go代码实现:
设置master_binlog_checksum
func (conn *Mysql) SetChecksum() {
var bytes = []byte{0x03}
conn.Write(append(bytes, []byte("set @master_binlog_checksum= @@global.binlog_checksum;")...), 0)
}
设置slave_uuid
func (conn *Mysql) SetSlaveUuid() {
var bytes = []byte{0x03}
uuid := uuid.New().String()
conn.Write(append(bytes, []byte(fmt.Sprintf("SET @slave_uuid= '%s';", uuid))...), 0)
}
注册MySQL从库
func ComRegisterSlave() []byte {
var bytes = []byte{0x15} //command
var serverId = make([]byte, 4)
binary.LittleEndian.PutUint32(serverId, uint32(4)) //server-id
bytes = append(bytes, serverId...)
bytes = append(bytes, []byte{0x00, 0x00, 0x00}...) //Hostname Username password可不设置
SlaveConnectionPort := []byte{0x00, 0x00}
binary.LittleEndian.PutUint16(SlaveConnectionPort, uint16(3306)) //Slave connection port
bytes = append(bytes, SlaveConnectionPort...)
bytes = append(bytes, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) //Replication rank Master server id可不设置
return bytes
}
(三)COM_BINLOG_DUMP请求报文 🔗
这是上面第四步内容,MySQL从库向MySQL主库请求binlog。首先前面的四个字节是包头,其中前三位代表packet数据长度,第四位是包序列号。以下是客户端命令请求报文的四字节后的内容。文档地址
Type | Name | Description |
---|---|---|
int<1> | 执行命令[12]COM_BINLOG_DUMP | 获取二进制日志信息,固定十六进制0x12 |
int<4> | binlog-pos | 二进制日志数据的起始位置(小字节序) |
int<2> | flags | 二进制日志数据标志位 |
int<4> | server-id | 从服务器的服务器ID值(小字节序) |
int<n> | binlog-filename | 二进制binlog日志的文件名称 |
flags注: 0x00 MySQL binlog非阻塞模式下运行 0x02 MySQL binlog阻塞模式下运行
在发送dump命令的时候,可以指定flag为BINLOG_DUMP_NON_BLOCK,这样master在没有可发送的binlog event之后,就会返回一个EOF package。不过通常对于slave来说,一直把连接挂着可能更好,这样能更及时收到新产生的binlog event。
当MySQL从库成功发送request_dump请求binlog后,MySQL主库就会源源不断的发送binlog日志了。如下图中,Request Send Binlog
就是发送request_dump命令,166 5.720479 192.168.0.107 192.168.0.105 MySQL 230 Response OK Response OK
就是MySQL主库发送binlog二进制文件了。
Go代码实现:
func Binlog() []byte {
var b = make([]byte, 4)
binary.LittleEndian.PutUint32(b, uint32(2344)) //binlog-pos
var bytes = []byte{0x12}
bytes = append(bytes, b...) //COM_BINLOG_DUMP
bytes = append(bytes, 0x00, 0x00)
var serverId = make([]byte, 4)
binary.LittleEndian.PutUint32(serverId, uint32(4)) //server-id
bytes = append(bytes, serverId...)
bytes = append(bytes, []byte("mysql-bin.000001")...) //binlog-filename
return bytes
}
(四)查看MySQL从库连接信息 🔗
show processlist;
SHOW SLAVE HOSTS;