mysql主从复制通过binlog来同步数据,在从库上执行start slave,会开启两个线程,分别是io线程和sql线程。io线程负责从主库拉取binlog并存储到本地的relay log,sql线程负责把relay log中的事务在从库应用。本文将结合源码分析io线程的具体实现。
MySQL源码版本:5.7.19
1. io线程函数
mysql io线程函数在源码sql/rpl_slave.cc中实现,执行命令start slave或者start slave io_thread,就会创建io线程,io线程函数为:
extern "C" void *handle_slave_io(void *arg)
io线程函数的参数arg,实际上是一个Master_info类型的指针mi,保存了主库相关的一些信息,比如host,user,password,主库uuid,主库binlog文件名,主库binlog文件位置等等。
io线程函数中会创建一个THD的对象,一个THD的对象就相当于一个连接,这个连接是可以在show processlist中看到,实际上io线程也可以理解为一个通向主库的连接。
紧接着io线程函数初始化一个MYSQL结构体的指针,称之为mysql,主要作用是与主库通信,走的标准的MySQL客户端/服务器协议,后面io线程获取主库的信息,注册从库,拉取binlog,都将依赖这个指针。
2. 创建连接
有了mi和mysql,通过调用函数safe_connect(thd, mysql, mi),连接到主库。
3. 交换信息
调用get_master_version_and_clock(mysql, mi),获取主库的版本,时间,server_id,字符集校验规则,时区等等信息,如果发现主库的server_id与从库的server_id相同,则会报错。
调用get_master_uuid(mysql, mi),获取主库的server_uuid,如果主库的server_uuid与从库的server_uuid相同,也会报错。
调用io_thread_init_commands(mysql, mi),把从库的server_uuid作为session变量设置到连接上,发送给主库。
4. 注册从库
调用 register_slave_on_master(mysql, mi, &suppress_warnings),在主库上注册从库,注册过程实际上就是发送一个注册命令COM_REGISTER_SLAVE,以及一个特定结构的网络包到主库。这个包结构如下:
- 从库server_id,4字节
- 从库report_host,如果为空,占用1个字节,值为0
- 从库report_user,如果为空,占用1个字节,值为0
- 从库report_password,如果为空,占用1个字节,值为0
- 从库report_port,2字节
- rpl_recovery_rank,4字节,默认为0,兼容老版本
- master_id,4字节,值为0,由主库来填充
这些结构通过抓包,可以清楚地看到包内容与上述结构完全吻合。
5. 拉取binlog
调用request_dump(thd, mysql, mi, &suppress_warnings),向主库发送binlog拉取的命令。如果开启GTID,命令为COM_BINLOG_DUMP_GTID,如果没有开启GTID,命令为COM_BINLOG_DUMP。
请求拉取binlog的网络包结构为:
- binlog_flags,2字节
- 从库server_id,4字节
- binlog名称的大小,4字节
- binlog名称,占用字节由3决定
- binlog位置,8字节
- binlog数据大小,4字节
- gtid_executed的大小,4字节
- uuid的数量,8字节
- uuid1,16字节
- gtid段数量,8字节
- gtid1 start,8字节
- gtid1 end,8字节
- gtid2 start,8字节(可能没有)
- gtid2 end,8字节(可能没有)
- uuid2....等等(可能没有)
开启GTID之后,binlog文件名和binlog位置这两个参数都是固定值,binlog文件名全为0,binlog位置为4,gtid_executed才是真正起作用的。
6. 读取binlog
请求拉取binlog的命令发送完成之后,io线程在一个while循环中,不断调用read_event(mysql, mi, &suppress_warnings) 来读取主库发送的binlog数据,并写入到relay log文件中。
7. 总结
至此io线程的整体逻辑就完成了,从上述过程来看,io线程连接到主库,与主库交换一些必要的信息,在主库上注册从库,然后请求拉取binlog,最后循环调用read_event,将主库不断发送的binlog信息写入到relay log文件中。整个过程很简单,但是需要考虑的细节有很多。了解io线程的主要流程,我们甚至可以自己写一个程序,模拟io线程,从主库拉取binlog。当然前提是需要了解MySQL的client/server的协议,或者使用现成的mysql开发库。
附录:
- io线程函数handle_slave_io定义:sql/rpl_slave.cc
- Master_info类定义:sql/rpl_mi.h
- MYSQL结构体定义:include/mysql.h