Go-伪装成MySQL Slave同步主库binlog日志实现主从复制

· 2251字 · 5分钟 · 阅读量

准备:

本文主要介绍MySQL主从复制中从库与主库的流程交互逻辑实现。

(一)MySQL主从复制流程 🔗

MySQL复制流程概述 🔗

MySQL 主从复制

  1. 第一步:MySQL从库与MySQL主库进行TCP三次握手,建立起稳定的连接。
  2. 第二步:主从用户认证,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
  3. 第三步:register_slave注册MySQL从库,MySQL从库通过register_slave将自己注册到MySQL主库上,以便master在show slave host时可以查看连接的MySQL从库情况。
  4. 第四步:request_dump请求binlog,MySQL从库通过request_dump,请求从主库指定的二进制日志和文件偏移量开始获取所有的二进制日志。
  5. 第五步:master发送Binlog Event,Master启动binlog dump线程,从指定的二进制日志的对应文件偏移量开始向MySQL从库的IO线程发送二进制日志Event。
  6. 第六步:备库接收存储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详细步骤 🔗

Img

通过Wireshark抓包可以看到当MySQL从库与MySQL主库完成Handshake Packet、Auth Packet认证后的步骤:

  1. 设置主从心跳
  2. 设置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
  3. 设置MySQL从库的slave_uuid,在MySQL主库上SHOW SLAVE HOSTS;可以查看连接的MySQL从库信息。
  4. 注册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。

request_dump请求binlog

当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;

Img