MHA笔记

简介

MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到在0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用。

该软件由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。

在MHA自动故障切换过程中,MHA试图从宕机的主服务器上保存二进制日志,最大程度的保证数据的不丢失,但这并不总是可行的。例如,如果主服务器硬件故障或无法通过ssh访问,MHA没法保存二进制日志,只进行故障转移而丢失了最新的数据。使用MySQL 5.5的半同步复制,可以大大降低数据丢失的风险。MHA可以与半同步复制结合起来。如果只有一个slave已经收到了最新的二进制日志,MHA可以将最新的二进制日志应用于其他所有的slave服务器上,因此可以保证所有节点的数据一致性。

目前MHA主要支持一主多从的架构,要搭建MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当master,一台充当备用master,另外一台充当从库,因为至少需要三台服务器,出于机器成本的考虑,淘宝也在该基础上进行了改造,目前淘宝TMHA已经支持一主一从。

工作原理

MHA工作原理如下:

  1. 从宕机崩溃的master保存二进制日志事件(binlog events);
  2. 识别含有最新更新的slave;
  3. 应用差异的中继日志(relay log)到其他的slave;
  4. 应用从master保存的二进制日志事件(binlog events);
  5. 提升一个slave为新的master;
  6. 使其他的slave连接新的master进行复制;

MHA软件由两部分组成,Manager工具包和Node工具包,具体的说明如下。
Manager工具包主要包括以下几个工具:

masterha_check_ssh              检查MHA的SSH配置状况
masterha_check_repl             检查MySQL复制状况
masterha_manger                 启动MHA
masterha_check_status           检测当前MHA运行状态
masterha_master_monitor         检测master是否宕机
masterha_master_switch          控制故障转移(自动或者手动)
masterha_conf_host              添加或删除配置的server信息

Node工具包(这些工具通常由MHA Manager的脚本触发,无需人为操作)主要包括以下几个工具:

save_binary_logs                保存和复制master的二进制日志
apply_diff_relay_logs           识别差异的中继日志事件并将其差异的事件应用于其他的slave
filter_mysqlbinlog              去除不必要的ROLLBACK事件(MHA已不再使用这个工具)
purge_relay_logs                清除中继日志(不会阻塞SQL线程)

安装部署

具体的搭建环境如下:

角色                    ip地址          主机名          server_id                   类型
Monitor host            192.168.0.20    server01            -                      监控复制组
Master                  192.168.0.50    server02            1                      写入
Candicate master        192.168.0.60    server03            2                      读
Slave                   192.168.0.70    server04            3                      读

创建用户:

chattr -i /etc/group
chattr -i /etc/gshadow
chattr -i /etc/passwd
chattr -i /etc/shadow

useradd -m -d /home/mha mha
passwd mha

chattr +i /etc/passwd
chattr +i /etc/shadow
chattr +i /etc/group
chattr +i /etc/gshadow

修改权限:

#visudo
末尾添加:
mha ALL=(ALL) NOPASSWD: ALL

加入root组:
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
mha     ALL=(ALL)       ALL

创建、下发公钥,已MHA Server为例:

ssh-keygen -t rsa
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.0.50
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.0.60
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.0.70

修改mha用户环境变量,添加perl、mha、mysqlbinlog:

#vim /home/mha/.bashrc
export PTAH=$PATH:/usr/share/perl5:/opt/soft/mha/bin/:/opt/soft/mysql57/bin/
export LC_ALL=C

修改对应目录权限:

chown -R mha.mha /opt/soft/mha/*
chown -R mha.mha /usr/share/perl5/vendor_perl/MHA/*

安装MHA:

#mha server
rpm -ivh mha4mysql-node-0.56-0.el6.noarch.rpm
rpm -ivh mha4mysql-manager-0.56-0.el6.noarch.rpm

#mha node
rpm -ivh mha4mysql-node-0.56-0.el6.noarch.rpm

rpm包是重新打包的,安装后环境如下:

#mha server
#tree /opt/soft/mha/
/opt/soft/mha/
|-- bin
|   |-- apply_diff_relay_logs
|   |-- filter_mysqlbinlog
|   |-- masterha_check_repl
|   |-- masterha_check_ssh
|   |-- masterha_check_status
|   |-- masterha_conf_host
|   |-- masterha_manager
|   |-- masterha_master_monitor
|   |-- masterha_master_switch
|   |-- masterha_secondary_check
|   |-- masterha_stop
|   |-- purge_relay_logs
|   `-- save_binary_logs
|-- binlog
|-- conf
|   |-- app1.cnf
|   `-- masterha_default.cnf
|-- logs
|   `-- log
`-- scripts

#mha node
#tree /opt/soft/mha/
/opt/soft/mha/
└── bin
    ├── apply_diff_relay_logs
    ├── filter_mysqlbinlog
    ├── purge_relay_logs
    └── save_binary_logs

添加配置文件:

#masterha_default.cnf
[server default]
user=mha
password=mha
ssh_user=mha

repl_user=repl
repl_password=repl

manager_workdir=/opt/soft/mha
manager_log=/opt/soft/mha/logs/log
master_binlog_dir=/work/mysql6666/var/
remote_workdir=/work/dba/
secondary_check_script=/opt/soft/mha/bin/masterha_secondary_check -s 192.168.0.60 -s 192.168.0.70 --user=mha --master_ip=192.168.0.50 --master_port=6666

# master_ip_failover_script=/opt/soft/mha/scripts/master_ip_failover
# master_ip_online_change_script=/opt/soft/mha/scripts/master_ip_online_change
# report_script=/opt/soft/mha/scripts/send_report
# shutdown_script=/opt/soft/mha/scripts/power_manager

ping_interval=1

#app.conf
[server1]
hostname=192.168.0.50
port=6666

[server2]
hostname=192.168.0.60
port=6666
candidate_master=1
check_repl_delay=0

[server3]
hostname=192.168.0.70
port=6666
no_master

检查SSH配置:

/opt/soft/mha/bin/masterha_check_ssh --global_conf=/opt/soft/mha/conf/masterha_default.cnf --conf=/opt/soft/mha/conf/app.cnf

检查复制环境:

/opt/soft/mha/bin/masterha_check_status --global_conf=/opt/soft/mha/conf/masterha_default.cnf --conf=/opt/soft/mha/conf/app.cnf

开启mha监控

/opt/soft/mha/bin/masterha_manager --global_conf=/opt/soft/mha/conf/masterha_default.cnf --conf=/opt/soft/mha/conf/app.cnf --ignore_last_failover

脚本含义

Manager

  • masterha_check_ssh:检查mha的ssh配置情况;
  • masterha_check_repl:检查MySQL复制情况;
  • masterha_manager:启动mha;
  • masterha_check_status:检查当前mha运行状态;
  • masterha_master_monitor:检测master是否宕机;
  • masterha_check_switch:控制故障转移(自动或手动);
  • masteha_conf_host:添加或删除配置的server信息;

Node

  • save_binary_logs:保存和复制master的二进制文件;
  • apply_diff_relay_logs:识别差异的relay log并将其差异event应用于其他slave;
  • filter_mysqlbinlog:去除不必要的rollback event;
  • purge_relay_logs:清除relay log;

工作流程

masterha_manager

mha启动脚本为masterha_manager,可选参数为remove_dead_master_conf、manger_log、ignore_last_failover。
masterha_manager主要流程为:

  1. 调用MasterMonitor,监控MySQL master状态;
  2. 发现master状态异常后,调用MasterFailover进行切换;

具体细节如下:

my ( $exit_code, $dead_master, $ssh_reachable ) =
  MHA::MasterMonitor::main( "--interactive=0", @ARGV );

manager通过monitor监测master状态,一旦获得返回值,则表明monitor状态异常。通过判断exit_code确定是否应切换。

if ( $exit_code && $exit_code != $MHA::ManagerConst::MASTER_DEAD_RC ) {
  exit $exit_code;
}
if ( !$dead_master->{hostname}
  || !$dead_master->{ip}
  || !$dead_master->{port}
  || !defined($ssh_reachable) )
{
  exit 1;
}

检测通过后,调用MasterFailover进执行切换操作。

$exit_code = MHA::MasterFailover::main(
  "--master_state=dead",
  "--interactive=0",
  "--dead_master_host=$dead_master->{hostname}",
  "--dead_master_ip=$dead_master->{ip}",
  "--dead_master_port=$dead_master->{port}",
  "--ssh_reachable=$ssh_reachable",
  @ARGV
);
MasterHA_Manager.png

MasterMonitor

MasterHA_Manager调用MasterMonitor的main方法对MySQL进行监控。流程图如下:


MasterMonitor.png

核心方法是一个死循环,不断调用wait_until_master_is_dead方法监测主库状态,部分代码如下:

while (1) {
  my ( $exit_code, $dead_master, $ssh_reachable ) =
    wait_until_master_is_dead();
  my $msg = sprintf( "Got exit code %d (%s).",
    $exit_code,
    $exit_code == $MHA::ManagerConst::MASTER_DEAD_RC
    ? "Master dead"
    : "Not master dead" );
  $log->info($msg) if ($log);
  if ($g_check_only) {
    finalize();
    return $exit_code;
  }
  if ( $exit_code && $exit_code == $RETRY ) {
    prepare_for_retry();
  }
  else {
    if ( $exit_code && $exit_code != $MHA::ManagerConst::MASTER_DEAD_RC ) {
      finalize_on_error();
    }
    elsif ($g_monitor_only) {
      finalize();
    }
    return ( $exit_code, $dead_master, $ssh_reachable );
  }
}

wait_until_master_is_dead方法的返回值中,exit_code有的值有四种,分别是0、1、20、retry。其中只有当exit_code=MHA::ManagerConst::MASTER_DEAD_RC,也就是20时,后续才会调用failover方法。

wait_until_master_is_dead方法中,核心方法是调用wait_until_master_is_unreachable方法并处理其返回值。逻辑关系如下:


wait_until_master_is_dead.png

拿到wait_until_master_is_unreachable的返回值后,会再次根据配置文件探活,确认主库连接失败后,根据配置文件检测slave状态和数量,有合适新主库后,exit_code返回20,否则返回0或者1;

再次检测代码存活的代码如下:

$_server_manager->connect_all_and_read_server_status(
  $dead_master->{hostname},
  $dead_master->{ip}, $dead_master->{port} );
my @dead_servers  = $_server_manager->get_dead_servers();
my @alive_servers = $_server_manager->get_alive_servers();
$log->info("Dead Servers:");
$_server_manager->print_dead_servers();
$log->info("Alive Servers:");
$_server_manager->print_alive_servers();
$log->info("Alive Slaves:");
$_server_manager->print_alive_slaves();
$_server_manager->print_failed_slaves_if();
$_server_manager->print_unmanaged_slaves_if();

wait_until_master_is_unreachable方法的返回值有三个,分别是ret、dead_master和ssh_reachable。该方法的逻辑如下:


wait_until_master_is_unreachable.png

wait_until_master_is_unreachable调用MHA::ServerManager对主库进行实时检测,包括deadservers、aliveservers、aliveslaves等。

$_server_manager = new MHA::ServerManager( servers => \@servers_config );
$_server_manager->set_logger($log);$_server_manager->connect_all_and_read_server_status();
@dead_servers  = $_server_manager->get_dead_servers();
@alive_servers = $_server_manager->get_alive_servers();
@alive_slaves  = $_server_manager->get_alive_slaves();
$log->info("Dead Servers:");
$_server_manager->print_dead_servers();
$log->info("Alive Servers:");
$_server_manager->print_alive_servers();
$log->info("Alive Slaves:");
$_server_manager->print_alive_slaves();
$_server_manager->print_failed_slaves_if();
$_server_manager->print_unmanaged_slaves_if();
$current_master = $_server_manager->get_current_alive_master();

如果启用GTID,则检查binlog server,否则进行ssh和slave版本检测。

    if ( !$_server_manager->is_gtid_auto_pos_enabled() ) {
      $log->info("GTID (with auto-pos) is not supported");
      MHA::SSHCheck::do_ssh_connection_check( \@alive_servers, $log,
        $servers_config[0]->{log_level}, $g_workdir )
        unless ($g_skip_ssh_check);
      $log->info("Checking MHA Node version..");
      foreach my $slave (@alive_slaves) {
        MHA::ManagerUtil::check_node_version(
          $log,             $slave->{ssh_user}, $slave->{ssh_host},
          $slave->{ssh_ip}, $slave->{ssh_port}
        );
      }
      $log->info(" Version check ok.");
    }
    else {
      $log->info(
"GTID (with auto-pos) is supported. Skipping all SSH and Node package checking."
      );
      check_binlog_servers( $binlog_server_ref, $log );
    }

后续使用MHA::HealthCheck对主库进行ping检查,如果定义了secondary_check_script,则运行该脚本。检查确认主库的确不可达后,返回func_rc, current_master, ssh_reachable。

$master_ping = new MHA::HealthCheck(
  user                   => $current_master->{user},
  password               => $current_master->{password},
  ip                     => $current_master->{ip},
  hostname               => $current_master->{hostname},
  port                   => $current_master->{port},
  interval               => $current_master->{ping_interval},
  ssh_user               => $current_master->{ssh_user},
  ssh_host               => $current_master->{ssh_host},
  ssh_ip                 => $current_master->{ssh_ip},
  ssh_port               => $current_master->{ssh_port},
  ssh_connection_timeout => $current_master->{ssh_connection_timeout},
  ssh_check_command      => $ssh_check_command,
  status_handler         => $_status_handler,
  logger                 => $log,
  logfile                => $g_logfile,
  workdir                => $g_workdir,
  ping_type              => $current_master->{ping_type},
);
$log->info(
  sprintf( "Set master ping interval %d seconds.",
    $master_ping->get_ping_interval() )
);
if ( $current_master->{secondary_check_script} ) {
  $master_ping->set_secondary_check_script(
    $current_master->{secondary_check_script} );
  $log->info(
    sprintf( "Set secondary check script: %s",
      $master_ping->get_secondary_check_script() )
  );
}
else {
  $log->warning(
"secondary_check_scriptis not defined. It is highly recommended setting it to check master reachability from two or more routes."
  );
}

添加vip检测功能

config.pm负责mha的参数处理,如果想添加vip检测参数,则可在my @PARAM_ARRAY 后添加master_vip和master_vip_port。并在parse_server函数中添加解析:

$value{master_vip} = $param_arg->{master_vip};
$value{master_vip_port} = $param_arg->{master_vip_port};

检查vip状态函数可放在MasterMonitor.pm中:

sub check_vip_status {
  my @servers_config;
  my ( $sc_ref, $binlog_server_ref ) = new MHA::Config(
    logger     => $log,
    globalfile => $g_global_config_file,
    file       => $g_config_file
  )->read_config();
  @servers_config = @$sc_ref;
  my $master_vip = $servers_config[0]->{master_vip};
  my $master_vip_port = $servers_config[0]->{master_vip_port};
  my $user = $servers_config[0]->{user};
  my $password = $servers_config[0]->{password};
  my $logfile = $servers_config[0]->{manager_log};
  $log = MHA::ManagerUtil::init_log($logfile);
  if ( !defined($master_vip) || !defined($master_vip_port))
  {
    $log->info("no master_vip or master_vip_port in config file,skip VIP check.") if ($log);
    return 2;
  }
  eval {
    my $dbhelper = DBI->connect("DBI:mysql:;host=$master_vip;" . "port=$master_vip_port;",$user,$password,{ PrintError => 0, RaiseError => 1 });
    my $sth = $dbhelper->prepare("SELECT 1 As Value");
    $sth->execute();
    my $href = $sth->fetchrow_hashref;
  };
  if ($@) {
    $log->info($@) if ($log);
    $log->info("connect VIP error...") if ($log);
    return 0;
  }
  else {
    $log->info("check VIP config success.") if ($log);
    return 1;
  }
}

对主库进行状态检查时,可以先检测vip状态,若vip存活,则进行下一轮检测。该逻辑可放置在MasterMonitor的main函数中。

while (1) {
    my $VIPStatus =  check_vip_status();
    if ($VIPStatus ne 0)
    {
        my ( $exit_code, $dead_master, $ssh_reachable ) = wait_until_master_is_dead();
        ......
    }
}

项目地址
未完待续……

哨兵实现多路探测
ETCD构建MHA Cluster
主库恢复后自动挂载
中途失败完全回滚

你可能感兴趣的:(MHA笔记)