今天有朋友咨询我MySQL高可用方案MHA的事情,恰好之前在公司改造过MHA,看过MHA的代码,解答了朋友的问题,在这里也分享一下经验。
MHA使用Perl语言编写,本文尽量不涉及语法,直观讲解。
MHA作为业界内火了十几年的MySQL高可用方案,必然有他的过人之处。在传统的MySQL复制模式下(文件 + 点位),MHA可以追补relay log,可以重放差异的binlog,还有丰富的工具支持,在当时看来,基本上无懈可击。
但是随着MySQL版本的迭代,GTID横空出世。虽然MHA也可在启用GTID的MySQL集群中使用,但是第一个坑就出现了。
直接上代码:
$log->info("* Phase 3: Master Recovery Phase..\n");
$log->info();
$log->info("* Phase 3.1: Getting Latest Slaves Phase..\n");
$log->info();
check_set_latest_slaves();
if ( !$_server_manager->is_gtid_auto_pos_enabled() ) {
$log->info();
$log->info("* Phase 3.2: Saving Dead Master's Binlog Phase..\n");
$log->info();
save_master_binlog($dead_master);
}
$log->info();
$log->info("* Phase 3.3: Determining New Master Phase..\n");
$log->info();
my $latest_base_slave;
if ( $_server_manager->is_gtid_auto_pos_enabled() ) {
$latest_base_slave = $_server_manager->get_most_advanced_latest_slave();
}
else {
$latest_base_slave = find_latest_base_slave($dead_master);
}
$new_master = select_new_master( $dead_master, $latest_base_slave );
my $start_failover_subject = "Start Failover";
send_report($dead_master, $new_master, $start_failover_subject);
my ( $master_log_file, $master_log_pos, $exec_gtid_set ) =
recover_master( $dead_master, $new_master, $latest_base_slave,
$binlog_server_ref );
$new_master->{activated} = 1;
$log->info("* Phase 3: Master Recovery Phase completed.\n");
从上述代码中可以看出,如果没有启用GTID,也就是!$_server_manager->is_gtid_auto_pos_enabled()
,会保存原主库的binlog,也就是save_master_binlog($dead_master)
函数。如果启用了GTID呢,就进入了选主阶段,并没有保存原主库的binlog。
那么问题来了,假设一套启用了GTID的MySQL集群,所有从库都有延时,这时候主库宕机,就出现了我朋友的问题,切换过程中会丢失数据。
如果丢失数据,那MHA岂不是不可靠了吗?
其实不然,上述问题可解。在上面代码的最后,是在补新主库($new_master
)和原主库($dead_master
)的差异数据。调用的函数为recover_master( $dead_master, $new_master, $latest_base_slave,$binlog_server_ref )
。在这个函数中,穿入的参数$binlog_server_ref
,就是上面问题的解,下面上论据。
我们先看一下recover_master
这个函数
sub recover_master($$$$) {
...
my ( $master_log_file, $master_log_pos, $exec_gtid_set );
if ( $_server_manager->is_gtid_auto_pos_enabled() ) {
( $master_log_file, $master_log_pos, $exec_gtid_set ) =
recover_master_gtid_internal( $new_master, $latest_base_slave,
$binlog_server_ref );
}
else {
( $master_log_file, $master_log_pos ) =
recover_master_internal( $new_master, $latest_base_slave );
}
...
return ( $master_log_file, $master_log_pos, $exec_gtid_set );
}
这里只提取了恢复新主库和原主库差异binlog相关的部分。可以看到,如果启用了GTID($_server_manager->is_gtid_auto_pos_enabled()
),那么调用recover_master_gtid_internal( $new_master, $latest_base_slave,$binlog_server_ref );
;否则会调用recover_master_internal( $new_master, $latest_base_slave )
。从这里可以看出,如果启用GTID,调用的函数中的参数有$binlog_server_ref
,也就是binlog server的相关配置。
我们继续看被调用的函数recover_master_gtid_internal
。
sub recover_master_gtid_internal($$$) {
...
if (
save_from_binlog_server(
$relay_master_log_file, $exec_master_log_pos, $binlog_server_ref
)
)
{
apply_binlog_to_master($target);
}
return $_server_manager->get_new_master_binlog_position($target);
}
上面代码中,略去了补relay log和change master部分。可以看出,如果启用了$binlog_server_ref
,则会调用apply_binlog_to_master
来补binlog。apply_binlog_to_master
这个函数就不展开了,大致过程就是调用save_binary_logs
把binlog server的binlog拉取到本地,然后在新主库上回放。
综上,在启用GTID的MySQL集群上部署MHA,必须额外增加binlog server,来解决延时带来的数据丢失隐患。
注意,binlog server的原理也是采用mysqlbinlog模拟一个从库来保存binlog日志,因此binlog server的配置要>= slaves。
聊到这里,朋友的问题已经解答完毕,至于如何在MHA中配置binlog server,我让他自行百度去了。
后面,我们又聊了一下MySQL 8.x中能否使用MHA?答案是可以的。(这里不做MGR和MHA的比较,只讨论可行性)。MySQL 8.x在使用MHA时有哪些问题,参考这里。