问题描述:
结构的1主2从,机器名分别为M1、S1、S2。使用keepalived+haproxy,将M1、S1做心跳,在M1正常的情况下,S1只负责读,如果M1挂了,则让S1变成读+写。
问题如下:在M1挂掉,S1升级成新主机的情况下,S2如何能够自动将主机变更为S1,并且知道S1接手以后,新的日志文件的名称和position。
最佳回答:
备库如何发起DUMP请求
引入GTID,最大的好处当然是我们可以随心所欲的切换主备拓扑结构了。在一个正常运行的复制结构中,我们可以在备库简单的执行如下SQL:
CHANGE MASTER TO MASTER_USER=’$USERNAME’, MASTER_HOST=’ ‘, MASTER_PORT=’ ‘, MASTER_AUTO_POSITION=1;
打开GTID后,我们就无需指定binlog文件或者位置,MySQL会自动为我们做这些事情。这里的关键就是MASTER_AUTO_POSITION。IO线程连接主库,可以大概分为以下几步:
1.IO线程在和主库建立TCP链接后,会去获取主库的uuid(get_master_uuid),然后在主库上设置一个用户变量@slave_uuid(io_thread_init_commands)
2.之后,在主库上注册SLAVE(register_slave_on_master)
在主库上调用register_slave来注册备库,将备库的host,user,password,port,server_id等信息记录到slave_list哈希中。
3.调用request_dump,开始向主库请求数据,这里分两种情况:
MASTER_AUTO_POSITION=0时,向主库发送命令的类型为COM_BINLOG_DUMP,这是传统的请求BINLOG的模式
MASTER_AUTO_POSITION=1时,命令类型为COM_BINLOG_DUMP_GTID,这是新的方式。
这里我们只讨论第二种。第二种情况下,会先去读取备库已经执行的gtid集合
quoted code in rpl_slave.cc :
if (command == COM_BINLOG_DUMP_GTID)
{
// get set of GTIDs
Sid_map sid_map(NULL/*no lock needed*/);
Gtid_set gtid_executed(&sid_map);
global_sid_lock->wrlock();
gtid_state->dbug_print();
if (gtid_executed.add_gtid_set(mi->rli->get_gtid_set()) != RETURN_STATUS_OK ||
gtid_executed.add_gtid_set(gtid_state->get_logged_gtids()) !=
RETURN_STATUS_OK)
构建完成发送包后,发送给主库。
在主库上接受到命令后,调用入口函数com_binlog_dump_gtid,流程如下:
1.slave_gtid_executed.add_gtid_encoding(packet_position, data_size) ;读取备库传来的GTID SET
2.读取备库的uuid(get_slave_uuid),被根据uuid来kill僵尸线程(kill_zombie_dump_threads)
这也是之前SLAVE IO线程执行SET @SLAVE_UUID的用处。
3.进入mysql_binlog_send函数:
|?>调用MYSQL_BIN_LOG::find_first_log_not_in_gtid_set,从最后一个Binlog开始扫描,获取文件头部的PREVIOUS_GTIDS_LOG_EVENT,如果它是slave_gtid_executed的子集,保存当前binlog文件名,否则继续向前扫描。
这一步的目的就是为了找出备库执行到的最后一个Binlog文件。
|?>从这个文件头部开始扫描,遇到GTID_EVENT时,会去判断该GTID是否包含在slave_gtid_executed中:
Gtid_log_event gtid_ev(packet->ptr() + ev_offset,
packet->length() ? checksum_size,
p_fdle);
skip_group= slave_gtid_executed->contains_gtid(gtid_ev.get_sidno(sid_map),
gtid_ev.get_gno());
主库通过GTID决定是否可以忽略事务,从而决定执行开始的位置
注意,在使用MASTER_LOG_POSITION后,就不要指定binlog的位置,否则会报错。