1. 配置主从关系
- 在从库执行
change master
命令 - 从库收到
SQLCOM_CHANGE_MASTER
最后执行主要函数change_master()
- 主要是配置主从信息
Master_info
(IO线程相关信息),Relay_log_info
(SQL线程相关信息) - 配置
slave_master_info,auto_position,relay_log_purge
等
2. 建立连接
从库执行start slave
,然后从库会开启两个线程,io线程与sql线程;io线程负责拉取binlog存储到本地relaylog中,sql则负责把拉取过来的relay log应用到从库中
mysql_execute_command()
|start_slave_cmd()
|start_slave()
|start_slave_threads()
|start_slave_thread(handle_slave_io) // io线程
|start_slave_thread(handle_slave_sql) // sql线程
3. IO线程:
- 入口函数
handle_slave_io
:
传入的参数为配置主从关系中的Master_info(mi)
,保存主库相关信息,如host,user,passwd,uuid等
在io线程中会创建一个thd,以及一个MYSQL
, 用于连接到主库
- 创建连接
通过调用函数safe_connect(thd, mysql, mi)
,连接到主库
- 获取主库信息
get_master_version_and_clock
,获取主库版本,serverid(确保主从的不一致),时区等get_master_uuid
:获取主库uuidio_thread_init_commands
:初始化一些(会话)变量,如slave_uuid
(在主库的连接中),用于后面的连接
- 注册从库
register_slave_on_master
:主要是发送一个COM_REGISTER_SLAVE
命令给主库,以及包含从库信息的包
- 注册完成后,
通过request_dump
函数向主库发送COM_BINLOG_DUMP
命令,后续主库会创建dump线程用来发送binlog
- 随后从库开始通过
read_event()
读取binlog,并写入到relaylog文件中
4. SQL线程:
- 入口函数
handle_slave_sql
传入的参数为配置主从关系中的Master_info(mi)
,但是用的是Relay_log_info(mi->rli),主要包含relay日志相关的信息,如offset,master log name等
- 大致逻辑如下:
handle_slave_sql()
|slave_start_workers() // 开启工作线程,用于多线程处理
|init_relay_log_pos() // 打开并初始化relaylog
while (!sql_slave_killed(thd,rli)) { // 进入循环
|exec_relay_log_event() // 执行并处理relaylog
|next_event() // 获取下一个事件
|apply_event_and_update_pos()
|ev->apply_event() // 应用事件
|do_apply_event() // apply
|ev->update_pos() // 更新日志位置
}
5. 主库dump线程:
- 注册从库
在收到从库发来的COM_REGISTER_SLAVE
后,会调用register_slave
来注册从库,主要把从主库发来的数据放到
(SLAVE_INFO)si
中,并把si
添加到slave_list
中;slave_list
是一个全局变量, 是一个HASH表,用来存放从库的信息
这里在添加si
到slave_list
前会先做一次unregister_replica
, 把存在于slave_list
中server_id相同的从库移除
typedef struct st_slave_info
{
uint32 server_id;
uint32 rpl_recovery_rank, master_id;
char host[HOSTNAME_LENGTH+1];
char user[USERNAME_LENGTH+1];
char password[MAX_PASSWORD_LENGTH+1];
uint16 port;
THD* thd;
} SLAVE_INFO;
- 开始发送binlog
主库收到COM_BINLOG_DUMP_GTID
命令后,与注册的是同一个连接(thd),会调用com_binlog_dump_gtid
函数处理从库拉取binlog的请求, 主要逻辑如下:
- 读取从库的信息,如serverid,binlog位置等
kill_zombie_dump_threads()
:kill掉僵尸线程,指的是uuid与当前从库相同的dump线程。为什么要清理?
- 因为如果主库空闲的话,dump线程会等待binlog更新,如果在这期间,io线程重连,那么就会存在旧的dump线程 。
- 如果主库长时间空闲,此时从库又触发重连,那么会出现两个dump线程,直到主库有binlog写入
- 发送binlog, 这里调用
mysql_binlog_send
来执行
sender.run()
while {
file= open_binlog_file // 打开binlog文件
send_binlog()
while {
|get_binlog_end_pos() // 获取binlog pos
| while {
| mysql_bin_log.get_binlog_end_pos() // 从binlog中获取
| wait_new_events() // 未获取到,等待新binlog
| while {
| // 与heartbeat_period参数有关,
| wait_with_heartbeat() // 每heartbeat_period则给从库发送一次心跳
| wait_without_heartbeat() // timeout = 0
| mysql_bin_log.wait_for_update_bin_log(thd, timeout)
| // 等待binlog,直到超时 这里是通过update_cond事件来实现的
| }
| }
|send_events()
| while {
| read_event() // 读取event
| // 会先判断是否event已经在从库中应用
| send_packet() // 发送数据
| }
}
}
- dump线程退出
这里可能是从库执行stop slave
,或者是Binlog_sender
内部出错
然后会调用unregister_slave()
函数注销从库,主要是把si
从slave_list
中移除
如果是通过stop slave
退出,在执行完stop slave
后,dump线程不会立马退出,而是在给从库发送binlog或者心跳包时,超时后才会退出循环; 所以在stop s