首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MHA 源码阅读 第02期:MasterFailover

MHA 源码阅读 第02期:MasterFailover

作者头像
数据库交流
发布于 2022-04-25 08:34:51
发布于 2022-04-25 08:34:51
8110
举报
文章被收录于专栏:悦专栏悦专栏

作者简介

无为,前饿了么 MySQL DBA,现就职于某知名互联网公司,对 MySQL、 Redis、PostgrepSQL 等主流数据库有一定了解,拥有丰富的一线运维经验。

上期我们已经详细了解了 MHA manager 的 monitor 原理,这一期我们继续探索 MHA 核心功能之一的 failover

1 非 GTID 模式

1.1 调用链路
代码语言:javascript
复制
MasterFailover::main --> do_master_failover 
        --> init_config(): 初始化配置
        --> init_binlog_server(): 初始化binlog server
        --> check_settings()  
        --> force_shutdown() 
        --> check_set_latest_slaves()
        --> save_master_binlog() 
        --> find_latest_base_slave() 
        --> select_new_master() 
        --> recover_master() 
        --> recover_slaves()
        --> reset_slave_on_new_master()
1.2 流程图解
1.3 代码详解
代码语言:javascript
复制
MHA::ServerManager::init_binlog_server( $binlog_server_ref, $log ); <!--binlog server 初始化-->
  • MHA::HealthCheck::ssh_check_simple() <!--binlog server ssh 连通性检查-->
  • MHA::ManagerUtil::get_node_version() <!--binlog server 客户端版本检查 -->
代码语言:javascript
复制
MHA::MasterFailover::check_settings($servers_config_ref);  <!--配置初始化-->
  • check_node_version(): <!--查看MHA的版本-->
  • connect_all_and_read_server_status(): <!--检测确认各个Node节点MySQL是否可以连接-->
  • get_dead_servers(),get_alive_servers(),get_alive_slaves():<!--再次检测一次node节点的状态-->
  • print_dead_servers(): <!--是否挂掉的master是否是当前的master-->
  • MHA::DBHelper::check_connection_fast_util : <!--快速判断dead server,是否真的挂了,如果ping_type=insert,不会double check-->
  • MHA::NodeUtil::drop_file_if(failover_error_file|failover_complete_file): <!--检测上次的failover文件-->
  • <!--如果上次failover的时间在8小时以内,那么这次就不会failover,除非配置了额外的参数-->
  • start_sql_threads_if(): <!--查看所有slave的Slave_SQL_Running是否为Yes,若不是则启动SQL thread-->
  • is_gtid_auto_pos_enabled(): <!--判断是否是GTID模式-->
代码语言:javascript
复制
force_shutdown($dead_master);
  • stop_io_thread(): <!--stop所有slave的IO_thread-->
  • force_shutdown_internal($dead_master): <!--根据shutdown_script脚本是否存在执行不同的逻辑--> master_ip_failover_script: <!--如果有这个脚本,则执行里面的逻辑(比如:切换vip)--> shutdown_script:<!--如果有这个脚本,则执行里面的逻辑(比如:Power off 服务器)-->
代码语言:javascript
复制
check_set_latest_slaves
  • read_slave_status(): <!--获取所有show slave status 信息-->
  • identify_latest_slaves(): <!--找到最新的slave是哪个-->
  • identify_oldest_slaves(): <!--找到最老的slave是哪个-->
代码语言:javascript
复制
save_master_binlog

dead master ssh 可达

  • save_master_binlog_internal <!--用node节点save_binary_logs脚本拷贝相应binlog到manager-->
  • diff_binary_log <!--生产差异binlog日志-->
  • file_copy <!--将差异binlog拷贝到manager节点的 manager_workdir目录下-->

dead master ssh 不可达

  • 差异日志丢失
代码语言:javascript
复制
find_latest_base_slave($dead_master);
  • find_latest_base_slave_internal <!--寻找拥有所有relay-log的最新slave,如果没有,则failover失败-->
代码语言:javascript
复制
<!--# We pick relay logs here. This should not reconfigure slave settings until other slaves connect to new master--> 
  • find_slave_with_all_relay_logs: <!--apply_diff_relay_logs--查看最新的slave是否有其他slave缺失的relay-log-->
代码语言:javascript
复制
select_new_master( $dead_master, $latest_base_slave );

<!--如果指定了首选节点,则其中一个活动的首选节点将是新的主节点。-->

<!--如果最新的服务器落后太多(即停止sql线程进行在线备份),我们不应该将它作为新的主服务器使用,但我们应该在那里获取中继日志-->

<!--即使配置了首选主节点,但如果它远远落后,它也不会成为主节点-->

  • get_candidate_masters() <!--获取配置中候选节点-->
  • get_bad_candidate_masters() <!--获取bad候选节点-->

以下条件不能成为候选 master

代码语言:javascript
复制
dead server          
 no_master >= 1        
 log_bin=0          
 oldest_major_version=0*          

check_slave_delay <!--检查是否延迟很高,两种情况认为延迟很高:1、latest 的 Master_Log_File 大于 这个节点的 Relay_Master_Log_File;2、latest 的 Read_Master_Log_Pos 大于这个节点的 Exec_Master_Log_Pos + 100000000(可以通过设置no_check_delay忽略)-->

也就是三种情况不能选择为主:

  • 参数加了 no_master=1
  • 没有开启 binlog
  • 延迟太大

选举流程:先看 candidate_master,然后找 latest slave, 然后再随机挑选

代码语言:javascript
复制
recover_master( $dead_master, $new_master, $latest_base_slave,$binlog_server_ref )
  • recover_master_internal recover_relay_logs: <!--判断new master是否为最新的slave,如果不是,则生产差异relay logs,并发送给新master--> recover_master_internal: <!--将之前生产的dead master上的binlog传送给new master-->
  • apply_diff
    1. wait_until_relay_log_applied <!--直到new master完成所有relay log,否则一直等待-->
    2. 判断Exec_Master_Log_Pos == Read_Master_Log_Pos <!--如果不等,那么生产差异日志 :save_binary_logs --command=save-->
    3. apply_diff_relay_logs <!----command=apply:对new master进行恢复--> exec_diff <!--Exec_Master_Log_Pos和Read_Master_Log_Pos的差异日志--> read_diff <!--new master与lastest slave的relay log的差异日志--> binlog_diff <!--lastest slave与daed master之间的binlog差异日志-->
  • 如果设置了 master_ip_failover_script 脚本,那么会执行这里面的脚本(一般用来漂移 vip)
  • disable_read_only() <!--允许new master可写-->
代码语言:javascript
复制
$error_code = recover_slaves($dead_master,$new_master,$latest_base_slave,$master_log_file, $master_log_pos, $exec_gtid_set);
  • recover_all_slaves_relay_logs <!--生成Slave与New Slave之间的差异日志,并将该日志拷贝到各Slave的工作目录下-->
  • recover_slave <!--对每个slave进行恢复-->
  • change_master_and_start_slave <!--重新指向到new master,并且start slave-->
代码语言:javascript
复制
reset_slave_on_new_master

在 new master 上执行 reset slave all;

2 GTID 模式

2.1 调用链路
代码语言:javascript
复制
MasterFailover::main --> do_master_failover 
        --> init_config(): 初始化配置
        --> init_binlog_server(): 初始化binlog server
        --> check_settings()  
        --> force_shutdown() 
        --> check_set_latest_slaves()
        --> save_master_binlog() 
        --> get_most_advanced_latest_slave() 
        --> select_new_master() 
        --> recover_master() 
        --> recover_slaves() 
        --> reset_slave_on_new_master()
2.2 流程图解
2.3 代码详解
代码语言:javascript
复制
MHA::ServerManager::init_binlog_server( $binlog_server_ref, $log ); <!--binlog server 初始化-->
  • MHA::HealthCheck::ssh_check_simple() <!--binlog server ssh 连通性检查-->
  • MHA::ManagerUtil::get_node_version() <!--binlog server 客户端版本检查 -->
代码语言:javascript
复制
MHA::MasterFailover::check_settings($servers_config_ref);  <!--配置初始化-->
  • check_node_version() <!--查看MHA的版本-->
  • connect_all_and_read_server_status() <!--检测确认各个Node节点MySQL是否可以连接-->
  • get_dead_servers(),get_alive_servers(),get_alive_slaves() <!--再次检测一次node节点的状态-->
  • print_dead_servers() <!--是否挂掉的master是否是当前的master-->
  • MHA::DBHelper::check_connection_fast_util <!--快速判断dead server,是否真的挂了,如果ping_type=insert,不会double check-->
  • MHA::NodeUtil::drop_file_if(failover_error_file|failover_complete_file) <!--检测上次的failover文件--><!--如果上次failover的时间在8小时以内,那么这次就不会failover,除非配置了额外的参数-->
  • start_sql_threads_if() <!--查看所有slave的Slave_SQL_Running是否为Yes,若不是则启动SQL thread-->
  • is_gtid_auto_pos_enabled() <!--判断是否是GTID模式-->
代码语言:javascript
复制
force_shutdown($dead_master);
  • stop_io_thread() <!--stop所有slave的IO_thread-->
  • force_shutdown_internal($dead_master) <!--根据shutdown_script脚本是否存在执行不同的逻辑--> master_ip_failover_script <!--如果有这个脚本,则执行里面的逻辑(比如:切换vip)--> shutdown_script <!--如果有这个脚本,则执行里面的逻辑(比如:Power off 服务器)-->
代码语言:javascript
复制
check_set_latest_slaves
  • read_slave_status() <!--获取所有show slave status 信息-->
  • identify_latest_slaves() <!--找到最新的slave是哪个-->
  • identify_oldest_slaves() <!--找到最老的slave是哪个-->
代码语言:javascript
复制
save_master_binlog

gtid 模式下无此步骤

代码语言:javascript
复制
get_most_advanced_latest_slave

获取最新的 slave

代码语言:javascript
复制
select_new_master

<!--如果指定了首选节点,则其中一个活动的首选节点将是新的主节点。-->

<!--如果最新的服务器落后太多(即停止sql线程进行在线备份),我们不应该将它作为新的主服务器使用,但我们应该在那里获取中继日志-->

<!--即使配置了首选主节点,但如果它远远落后,它也不会成为主节点-->

  • get_candidate_masters() <!--获取配置中候选节点-->
  • get_bad_candidate_masters() <!--获取bad候选节点-->

以下条件不能成为候选 master

代码语言:javascript
复制
 dead server          
 no_master >= 1        
 log_bin=0          
 oldest_major_version=0*          

check_slave_delay <!--检查是否延迟很高,两种情况认为延迟很高:1、latest 的 Master_Log_File 大于 这个节点的 Relay_Master_Log_File;2、latest 的 Read_Master_Log_Pos 大于这个节点的 Exec_Master_Log_Pos + 100000000(可以通过设置no_check_delay忽略)-->

选举流程:先看 candidate_master,然后找 latest slave, 然后再随机挑选

代码语言:javascript
复制
recover_master( $dead_master, $new_master, $latest_base_slave,$binlog_server_ref )    
  • wait_until_relay_log_applied <!--候选master等待所有relay-log都应用完-->
  • 如果候选 master 不是最新的 slave: latest_slave->wait_until_relay_log_applied($log) <!--最新的slave应用完所有的relay-log--> change_master_and_start_slave <!--让候选master同步到latest master,追上latest slave-->
  • 获取候选 master 此时此刻的日志信息,以便后面切换
  • 如果候选 master 是最新的 slave: 获取候选 master 此时此刻的日志信息,以便后面切换
  • save_from_binlog_server: 如果配置了 binlog server,那么在 binlogsever 能连的情况下,将 binlog 拷贝到 Manager,并生成差异日志 diff_binlog(save_binary_logs --command=save)
  • apply_binlog_to_master: Applying differential binlog:应用差异的 binlog 到 new master
代码语言:javascript
复制
recover_slaves_gtid_internal
  • change_master_and_start_slave <!--因为master已经恢复,那么slave直接change master auto_pos=1 的模式就可以恢复 gtid_wait:用此等待同步全部追上-->
代码语言:javascript
复制
reset_slave_on_new_master

在 new master 上执行 reset slave all;

3 总结

通过源码阅读我们发现在 failover 阶段 MHA 在 GTID 模式和非 GTID 模式下是不一样的,主要差异下:

  1. GTID 模式下不执行 save_master_binlog,也就是说即使主库 ssh 可达也不会保存 binlog 到 manager;
  2. GTID 模式下在 recover_master 阶段(binlog server 可达)会从 binlog server补齐差异日志;

所以,在 GTID 模式下需要使用 binlog server 来解决丢失事物的风险。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 1 非 GTID 模式
    • 1.1 调用链路
    • 1.2 流程图解
    • 1.3 代码详解
  • 2 GTID 模式
    • 2.1 调用链路
    • 2.2 流程图解
    • 2.3 代码详解
  • 3 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档