MHA搭建

一. 介绍

MHA是一套MySQL高可用管理软件,除了检测Master宕机后,提升候选Slave为New Master之外(漂虚拟IP),还会自动让其他Slave与New Master建立复制关系。MHA Manager可以单独部署在一台独立的机器上,并管理多个master-slave集群。

二. 架构

 

1. 上图中存在以下节点:

  • MHA Manager:MHA的管理节点,负责检测MySQL的状态,以及管理MySQL的复制关系
  • Master:MySQL对外提供的服务( 通过 VIP )的主节点
  • Slave-M:MySQL候选主节点,本质上为一个Slave节点,只是在Master宕机后,会被提升为 New Master
  • Slave-1:MySQL从机节点,对外可以提供只读服务

2. 当Master宕机后,MHA Manager会让Slave-M提升为New Master,然后修改Slave-1的复制关系(CHANGE MASTER),指向New Master

  • MHA Manager检测Master是否宕机, 不会简单的只检查自身与Master之间的直连状态
  • MHA Manager会透过其他Slave节点去检测Master,进行二次探测,最大限度的避免脑裂的发生
    • 比如说,检测到Master宕机了,MHA Manager会透过Slave-M检测是否为宕机
    • 如果检测后仍为宕机状态,会继续透过Slave1...SlaveN进行探测,只有在透过所有节点检测到Master宕机了,才真的认为Master宕机了
    • 以上操作步骤的前提是,MHA Manager到所有MySQL节点的SSH免密码登录要打通

3. 最后将提供对外服务的VIP漂移到New Master上,继续对外提供服务

4. 一个MHA Manager可以管理多个MySQL的Master-Slave集群(通过不同的app.conf)

 

三. Failover的过程

1. 从宕机崩溃的Master节点保存二进制日志事件(binlog events);

  • 此种情况为MySQL挂了,但是服务器没挂,还是可以通过SSH连接过去,如果服务器彻底宕机了,该步骤略过;

2. 识别含有最新的更新的Slave节点;

3. 应用差异的中继日志(relay-log)到其他的Slave;

4. 应用从Master保存的二进制日志事件(binlog events);

  • 如果之前Master彻底宕机了,就没有保存的binlog,该步骤略过;

5. 提升一个Slave为新的Master(New);

6. 使其他的Slave连接新的Master(New)进行复制;

从上面的步骤看,MHA无法完全保证数据不会丢;

    1. MySQL 5.7之前数据不丢的前提是Master服务器还可以被MHA Manager进行SSH连接,通过应用保存的binlog的方式来保证。
    2. MySQL 5.7之后通过无损复制,仅仅是减少了丢数据的可能性,假如此时的状态为切成异步的状态,那就和之前一样了(可以设置超时的时间很大);
    3. 当Master恢复的时候,最后一部分数据是否需要Flashback,MHA也是不负责这个事情,需要人工介入。

 

四. MHA安装

MHA由两个部分组成MHA Manager和MHA Node,Manager依赖Node,所以需要先安装Node。

1. 安装MHA Node

所有服务器均要安装 (包括MHA Manager)

  • 安装Perl依赖
yum install perl-DBI perl-DBD-MySQL -y
  •  安装Node
wget https://github.com/yoshinorim/mha4mysql-node/releases/download/v0.58/mha4mysql-node-0.58-0.el7.centos.noarch.rpm
rpm -ivh mha4mysql-node-0.58-0.el7.centos.noarch.rpm

安装成功后在/usr/bin/目录下生成以下文件

apply_diff_relay_logs #识别差异的中继日志事件并将其差异的事件应用于其他的slave
filter_mysqlbinlog #去除不必要的ROLLBACK事件(脚本中指出该脚本已经过时,应该是被废弃了)
purge_relay_logs #清除中继日志(不会阻塞SQL线程)
save_binary_logs #保存和复制master的二进制日志

 

2. MHA Manager的安装,仅仅只需要在MHA Manager服务器上安装
  • 安装Perl依赖
yum install -y perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
  •  安装Master
https://github.com/yoshinorim/mha4mysql-manager/releases/download/v0.58/mha4mysql-manager-0.58-0.el7.centos.noarch.rpm
rpm -ivh mha4mysql-manager-0.58-0.el7.centos.noarch.rpm

安装成功后在/usr/local/bin/目录下生成以下文件

masterha_check_repl # 检查MySQL复制状态
masterha_check_ssh # 检查MHA的SSH状态
masterha_check_status # 检查当前MHA的状态
masterha_conf_host # 添加或删除配置的server信息
masterha_manager # 启动MHA
masterha_master_monitor # 检测master是否宕机
masterha_master_switch # 控制故障转移(手动或者自动)
masterha_secondary_check # 通过slave检测master是否宕机(二次探测)
masterha_stop # MHA停止

 

3. 创建目录存放配置文件
mkdir -p /etc/masterha

 

4. 配置SSH免密登录(root用户)

SSH免密码登录是为了能够让MHA Manager可以远程登录到各个MySQL Server,以及各个MySQL Server之间可以相互远程登录,然后进行相关操作

  • MHA Manager生成Key
ssh-keygen #执行该命令一直按Enter
  • 公钥分发(执行ssh.sh,需要修改里面的ip地址)
[root@CN-MHA-MySQL-M mha]# cat ssh.sh 
#!/bin/bash
UserName=root
IP="xx.xx.xx."
#分发公钥
for i in 43 44 45
do
  ssh-copy-id -i ~/.ssh/id_rsa.pub $UserName@$IP$i
done
    • MHA Manager
      • 将 MHA Manager 服务器的公钥,依次分发给 Master 、 Slave1 和 Slave2
    • MySQL Master
      • 将 Master 的公钥依次分发给 Slave-M 和 Slave-1
    • MySQL Slave-M
      • 将 Slave-M 的公钥依次分发给 Master 和 Slave-1
    • MySQL Slave2-1
      • 将 Slave-1 的公钥依次分发给 Master 和 Slave-M
5. 确认ssh打通

上述操作配置完成以后,在每个服务器上,执行ssh@IP的操作(MySQL Server节点之间相互连接,以及MHA Manager到所有MySQL Server),确认可以免密码登录成功。

 

6. MHA环境说明

Role

IP

Hostname

Server_id

Desc

Master & MHA Manager

10.100.172.45

MHA-MySQL-M

11

MHA 管理服务器,用于监控,MySQL Master服务器,用于read-write

Candicate Master (Slave)

10.100.172.43

MHA-MySQL-S1

44

MySQL Slave服务器,read-only,当Master宕机后,被提升为Master(New)

Slave

10.100.172.44

MHA-MySQL-S2

55

MySQL Slave服务器,read-only

 

7. MySQL建立复制,此处不赘述

 

8. MHA配置

MHA配置文件和说明配置文件统一放在/etc/masterha/中,针对当前这个MySQL复制组,我们定义为 app1.conf,如果还有其他MySQL复制组,可以继续定义

#
# MHA Manager 端 /etc/masterha/app1.conf
#
[server default]
log_level=debug
# 这两个参数需要根据不同的集群进行修改
manager_log=/var/log/masterha/jfrog-cn/manager.log
manager_workdir=/var/log/masterha/app1
# 按照master服务器存放binlog的实际路径进行修改,主要为了让MHA拉取binlog
master_binlog_dir=/data/mysql/
# 设置自动failover的脚本
master_ip_failover_script=/usr/local/bin/master_ip_failover
# 设置手动切换时候的脚本 (供(masterha_master_switch使用)
master_ip_online_change_script=/usr/local/bin/master_ip_online_change
# 告警脚本,可自行修改,这里没有使用
report_script=/opt/mha/failover_alerts
# 设置故障发生后关闭故障主机的脚本(主要作用是关闭主机防止发生脑裂,这里没有使用,类似Fence功能)
#shutdown_script="/usr/local/bin/power_manager --command=stopssh2 --host=test-1 --ssh_user=root"
# 监控主库的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行railover
ping_interval=3
# 检测方式是insert,MHA-0.56开始支持insert
ping_type=INSERT
# 设置远端mysql在发生切换时binlog的保存位置
remote_workdir=/tmp
# 复制用的用户及密码
repl_user=repl
repl_password=*******
# 通过从机进行二次探测的脚本, IP地址按照实际的情况进行修改
secondary_check_script=/usr/bin/masterha_secondary_check -s 10.100.172.43 -s 10.100.172.44 --user=root --master_host=10.100.172.45 --master_port=3306
# 定义ssh的用户
ssh_user=root
# 监控的用户,此处时MySQL用户和password
user=root
password=*******

[server1]
# candidate_master参数的意思为:设置为候选Master,如果发生主从切换,该主机会被提升为Master,即使这个服务器上的数据不是最新的(会用relay-log补全)
candidate_master=1
check_repl_delay=0
# 这个hostname也可以配置成IP地址,同ip参数一样
# 如果这里写名字,需要DNS配合,或者使用/etc/hosts
hostname=10.100.172.45
port=3306

[server2]
candidate_master=1
# check_repl_delay参数的意思为:默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master;
# 因为对于这个slave的恢复需要花费很长时间;
# 通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时;
# 这个参数对于设置了candidate_master=1的主机非常有用,因为这个候选主在切换的过程中一定是新的master;
# 但是用sysbench测试发现如果从库落后主库太多还是不会切换,所以要做好主从延迟监控
check_repl_delay=0
hostname=10.100.172.43
port=3306

[server3]
hostname=10.100.172.44
# no_master表示该主机不会被提升为Master
no_master=1
port=3306

 

9. MHA脚本说明
  • master_ip_failover
#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';

use Getopt::Long;

my (
    $command,          $ssh_user,        $orig_master_host, $orig_master_ip,
    $orig_master_port, $new_master_host, $new_master_ip,    $new_master_port
);

my $vip = '10.20.12.59/24';
my $key = '88';
my $ssh_start_vip = "sudo /sbin/ifconfig ens192:$key $vip";
my $ssh_stop_vip = "sudo /sbin/ifconfig ens192:$key down";

GetOptions(
    'command=s'          => \$command,
    'ssh_user=s'         => \$ssh_user,
    'orig_master_host=s' => \$orig_master_host,
    'orig_master_ip=s'   => \$orig_master_ip,
    'orig_master_port=i' => \$orig_master_port,
    'new_master_host=s'  => \$new_master_host,
    'new_master_ip=s'    => \$new_master_ip,
    'new_master_port=i'  => \$new_master_port,
);

exit &main();

sub main {

    print "\n\nIN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip===\n\n";

    if ( $command eq "stop" || $command eq "stopssh" ) {

        my $exit_code = 1;
        eval {
            print "Disabling the VIP on old master: $orig_master_host \n";
            &stop_vip();
            $exit_code = 0;
        };
        if ($@) {
            warn "Got Error: $@\n";
            exit $exit_code;
        }
        exit $exit_code;
    }
    elsif ( $command eq "start" ) {

        my $exit_code = 10;
        eval {
            print "Enabling the VIP - $vip on the new master - $new_master_host \n";
            &start_vip();
            $exit_code = 0;
        };
        if ($@) {
            warn $@;
            exit $exit_code;
        }
        exit $exit_code;
    }
    elsif ( $command eq "status" ) {
        print "Checking the Status of the script.. OK \n";
        exit 0;
    }
    else {
        &usage();
        exit 1;
    }
}

sub start_vip() {
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
sub stop_vip() {
     return 0  unless  ($ssh_user);
    `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}

sub usage {
    print
    "Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
}

 

  • master_ip_online_change
#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use Getopt::Long;
#my (
# $command, $ssh_user, $orig_master_host, $orig_master_ip,
# $orig_master_port, $new_master_host, $new_master_ip, $new_master_port
#);
my (
 $command, $orig_master_is_new_slave, $orig_master_host,
 $orig_master_ip, $orig_master_port, $orig_master_user,
 $orig_master_password, $orig_master_ssh_user, $new_master_host,
 $new_master_ip, $new_master_port, $new_master_user,
 $new_master_password, $new_master_ssh_user,
);
my $vip = '10.20.12.59/24';
# 注意修改系统对应的接口名称(CentOS比较变态)
my $eth = 'ens192';
my $key = '88';
my $ssh_start_vip = "/sbin/ifconfig $eth:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig $eth:$key down";
my $ssh_user = "root";
GetOptions(
'command=s' => \$command,
#'ssh_user=s' => \$ssh_user,
#'orig_master_host=s' => \$orig_master_host,
#'orig_master_ip=s' => \$orig_master_ip,
#'orig_master_port=i' => \$orig_master_port,
#'new_master_host=s' => \$new_master_host,
#'new_master_ip=s' => \$new_master_ip,
#'new_master_port=i' => \$new_master_port,
'orig_master_is_new_slave' => \$orig_master_is_new_slave,
'orig_master_host=s' => \$orig_master_host,
'orig_master_ip=s' => \$orig_master_ip,
'orig_master_port=i' => \$orig_master_port,
'orig_master_user=s' => \$orig_master_user,
'orig_master_password=s' => \$orig_master_password,
'orig_master_ssh_user=s' => \$orig_master_ssh_user,
'new_master_host=s' => \$new_master_host,
'new_master_ip=s' => \$new_master_ip,
'new_master_port=i' => \$new_master_port,
'new_master_user=s' => \$new_master_user,
'new_master_password=s' => \$new_master_password,
'new_master_ssh_user=s' => \$new_master_ssh_user,
);
exit &main();
sub main {
print "\n\nIN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip===\n\n";
if ( $command eq "stop" || $command eq "stopssh" ) {
my $exit_code = 1;
eval {
print "Disabling the VIP on old master: $orig_master_host \n";
 &stop_vip();
 $exit_code = 0;
 };
if ($@) {
warn "Got Error: $@\n";
exit $exit_code;
 }
exit $exit_code;
 }
elsif ( $command eq "start" ) {
my $exit_code = 10;
eval {
print "Enabling the VIP - $vip on the new master - $new_master_host \n";
 &start_vip();
 $exit_code = 0;
 };
if ($@) {
warn $@;
exit $exit_code;
 }
exit $exit_code;
 }
elsif ( $command eq "status" ) {
print "Checking the Status of the script.. OK \n";
exit 0;
 }
else {
 &usage();
exit 1;
 }
}
sub start_vip() {
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
sub stop_vip() {
return 0 unless ($ssh_user);
`ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}
sub usage {
print
"Usage: master_ip_failover --command=start|stop|stopssh|status --ssh-user=user --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
}
  1. 修改上述两个脚本中的$vip,以符合自己实际的需求

  2. 将上述两个文件复制到MHA Manager节点的 /usr/local/bin下

  3. 将上述两个文件赋予执行权限:

    1. chmod +x master_ip_failover
    2. chmod +x master_ip_online_change

 

  • failover_alerts
#!/bin/bash

function send2im() {
    grep -q 'Finished master recovery successfully' /var/log/masterha/jfrog-cn/manager.log
    if [ $? -eq 0 ]; then
        warnings="MHA Cluster Failover Successfully."
        time=$(grep 'Finished master recovery successfully' /var/log/masterha/jfrog-cn/manager.log | awk -F '-' '{print $1}')
        new_master=$(grep 'Enabling the VIP' /var/log/masterha/jfrog-cn/manager.log | awk -F '-' '{print $3}')
	hostname=$(hostname)
        if [ $hostname == "CN-MHA-MySQL-M" ]; then
          env="CN MySQL Cluster"
        else
          env="INAP MySQL Cluster"
        fi
        curl "https://inbots.zoom.us/incoming/hook/oUGE5R-mM4LCzEa5Uadybue_?format=fields" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer xxxxxxxxxx" \
        -d "{
            \"Warnings\": \"${warnings}\",
            \"New Master\": \"${new_master}\",
            \"Time\": \"${time}\",
            \"Env\": \"${env}\"
        }"
    else
        warnings="MHA Cluster Failover Failed."
        level="Highest"
        curl "https://inbots.zoom.us/incoming/hook/oUGE5R-mM4LCzEa5Uadybue_?format=fields" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer xxxxxxxxxx" \
        -d "{
            \"Warnings\": \"${warnings}\",
            \"Level\": \"${level}\"
        }"
    fi
}

send2im
10. Slave上的relay_log配置

从MHA Failover的过程中可以了解到,MHA Manager在恢复(补齐)其他Slave数据时会用到relay-log,因此这些relay-log需要被保留。而默认情况下,SQL线程在回放完毕后,MySQL会主动删除relay-log,需要禁用该功能,确保relay-log不被自动删除。在所有节点(包括Master)中配置如下参数,然后重启mysql 服务即可。

  • 所有的节点
[mysqld]
# 关闭relay-log主动删除的功能
relay_log_purge = 0

但是这样做了以后又带来另外一个问题,relay-log会大量堆积,导致磁盘空间紧张,所以需要定时清空过时的relay-log。所幸的是MHA帮我们实现了这个功能,MHA Node的安装包中有一个pure_relay_logs工具,提供删除大量relay-log 的功能。在Linux下删除体积较大的文件需要一定的时间,且消耗一定的资源,所以pure_relay_logs在删除relay-log之前,会做一次硬连接,然后删除对应的relay-log(只删除指向relay-log的指针),这样不会造成系统资源消耗,从而影响复制(造成复制延时),最后再删除硬连接的relay-log(_hardlink_)  

  • 定时清理relay-log的脚本,将这个脚本放入所有的Slave节点,并添加到定时任务
#!/bin/bash
# Filename:/opt/mha/cron_purge_relay_logs.sh
user=root
passwd=xxxxxx
# port=3306
host=localhost
socket=/data/mysql/mysql.sock
log_dir='/data/mysql/purge_relay_logs'
work_dir='/data/mysql/relay_log_hardlink'
purge='/usr/bin/purge_relay_logs'
if [[ ! -d ${work_dir} ]];then
 mkdir ${work_dir} -p
fi
if [[ ! -d ${log_dir} ]];then
 mkdir ${log_dir} -p
fi
${purge} --user=${user} --password=${passwd} --host=${host} --socket=${socket} --workdir=${work_dir} --disable_relay_log_purge >> ${log_dir}/purge_relay_logs.log 2>&1

 

11. SSH检测
[root@CN-MHA-MySQL-M masterha]# masterha_check_ssh --conf=jfrog-cn.conf 
Mon Jul 10 21:46:43 2023 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Mon Jul 10 21:46:43 2023 - [info] Reading application default configuration from jfrog-cn.conf..
Mon Jul 10 21:46:43 2023 - [info] Reading server configuration from jfrog-cn.conf..
Mon Jul 10 21:46:43 2023 - [info] Starting SSH connection tests..
Mon Jul 10 21:46:44 2023 - [debug] 
Mon Jul 10 21:46:43 2023 - [debug]  Connecting via SSH from root@10.100.172.45(10.100.172.45:22) to root@10.100.172.43(10.100.172.43:22)..
Mon Jul 10 21:46:44 2023 - [debug]   ok.
Mon Jul 10 21:46:44 2023 - [debug]  Connecting via SSH from root@10.100.172.45(10.100.172.45:22) to root@10.100.172.44(10.100.172.44:22)..
Mon Jul 10 21:46:44 2023 - [debug]   ok.
Mon Jul 10 21:46:45 2023 - [debug] 
Mon Jul 10 21:46:44 2023 - [debug]  Connecting via SSH from root@10.100.172.43(10.100.172.43:22) to root@10.100.172.45(10.100.172.45:22)..
Mon Jul 10 21:46:44 2023 - [debug]   ok.
Mon Jul 10 21:46:44 2023 - [debug]  Connecting via SSH from root@10.100.172.43(10.100.172.43:22) to root@10.100.172.44(10.100.172.44:22)..
Mon Jul 10 21:46:45 2023 - [debug]   ok.
Mon Jul 10 21:46:46 2023 - [debug] 
Mon Jul 10 21:46:44 2023 - [debug]  Connecting via SSH from root@10.100.172.44(10.100.172.44:22) to root@10.100.172.45(10.100.172.45:22)..
Mon Jul 10 21:46:45 2023 - [debug]   ok.
Mon Jul 10 21:46:45 2023 - [debug]  Connecting via SSH from root@10.100.172.44(10.100.172.44:22) to root@10.100.172.43(10.100.172.43:22)..
Mon Jul 10 21:46:45 2023 - [debug]   ok.
Mon Jul 10 21:46:46 2023 - [info] All SSH connection tests passed successfully.
Use of uninitialized value in exit at /bin/masterha_check_ssh line 44.  -- 该报错可能是脚本错误,不影响检测,所以忽略

 

12. 复制关系检测

masterha_check_repl --conf=/etc/masterha/jfrog-cn.conf

[root@CN-MHA-MySQL-M masterha]# masterha_check_repl --conf=jfrog-cn.conf 
Mon Jul 10 21:47:46 2023 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Mon Jul 10 21:47:46 2023 - [info] Reading application default configuration from jfrog-cn.conf..
Mon Jul 10 21:47:46 2023 - [info] Reading server configuration from jfrog-cn.conf..
Mon Jul 10 21:47:46 2023 - [info] MHA::MasterMonitor version 0.58.
Mon Jul 10 21:47:46 2023 - [debug] Connecting to servers..
Mon Jul 10 21:47:47 2023 - [debug]  Connected to: 10.100.172.45(10.100.172.45:3306), user=root
Mon Jul 10 21:47:47 2023 - [debug]  Number of slave worker threads on host 10.100.172.45(10.100.172.45:3306): 16
Mon Jul 10 21:47:47 2023 - [debug]  Connected to: 10.100.172.43(10.100.172.43:3306), user=root
Mon Jul 10 21:47:47 2023 - [debug]  Number of slave worker threads on host 10.100.172.43(10.100.172.43:3306): 16
Mon Jul 10 21:47:47 2023 - [debug]  Connected to: 10.100.172.44(10.100.172.44:3306), user=root
Mon Jul 10 21:47:47 2023 - [debug]  Number of slave worker threads on host 10.100.172.44(10.100.172.44:3306): 16
Mon Jul 10 21:47:47 2023 - [debug]  Comparing MySQL versions..
Mon Jul 10 21:47:47 2023 - [debug]   Comparing MySQL versions done.
Mon Jul 10 21:47:47 2023 - [debug] Connecting to servers done.
Mon Jul 10 21:47:47 2023 - [info] GTID failover mode = 1
Mon Jul 10 21:47:47 2023 - [info] Dead Servers:
Mon Jul 10 21:47:47 2023 - [info] Alive Servers:
Mon Jul 10 21:47:47 2023 - [info]   10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info]   10.100.172.43(10.100.172.43:3306)
Mon Jul 10 21:47:47 2023 - [info]   10.100.172.44(10.100.172.44:3306)
Mon Jul 10 21:47:47 2023 - [info] Alive Slaves:
Mon Jul 10 21:47:47 2023 - [info]   10.100.172.43(10.100.172.43:3306)  Version=8.0.33 (oldest major version between slaves) log-bin:enabled
Mon Jul 10 21:47:47 2023 - [info]     GTID ON
Mon Jul 10 21:47:47 2023 - [debug]    Relay log info repository: TABLE
Mon Jul 10 21:47:47 2023 - [info]     Replicating from 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info]     Primary candidate for the new Master (candidate_master is set)
Mon Jul 10 21:47:47 2023 - [info]   10.100.172.44(10.100.172.44:3306)  Version=8.0.33 (oldest major version between slaves) log-bin:enabled
Mon Jul 10 21:47:47 2023 - [info]     GTID ON
Mon Jul 10 21:47:47 2023 - [debug]    Relay log info repository: TABLE
Mon Jul 10 21:47:47 2023 - [info]     Replicating from 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info]     Not candidate for the new Master (no_master is set)
Mon Jul 10 21:47:47 2023 - [info] Current Alive Master: 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info] Checking slave configurations..
Mon Jul 10 21:47:47 2023 - [info]  read_only=1 is not set on slave 10.100.172.43(10.100.172.43:3306).
Mon Jul 10 21:47:47 2023 - [info]  read_only=1 is not set on slave 10.100.172.44(10.100.172.44:3306).
Mon Jul 10 21:47:47 2023 - [info] Checking replication filtering settings..
Mon Jul 10 21:47:47 2023 - [info]  binlog_do_db= , binlog_ignore_db= 
Mon Jul 10 21:47:47 2023 - [info]  Replication filtering check ok.
Mon Jul 10 21:47:47 2023 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Mon Jul 10 21:47:47 2023 - [info] Checking SSH publickey authentication settings on the current master..
Mon Jul 10 21:47:47 2023 - [debug] SSH connection test to 10.100.172.45, option -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o BatchMode=yes -o ConnectTimeout=5, timeout 5
Mon Jul 10 21:47:47 2023 - [info] HealthCheck: SSH to 10.100.172.45 is reachable.
Mon Jul 10 21:47:47 2023 - [info] 
10.100.172.45(10.100.172.45:3306) (current master)
 +--10.100.172.43(10.100.172.43:3306)
 +--10.100.172.44(10.100.172.44:3306)

Mon Jul 10 21:47:47 2023 - [info] Checking replication health on 10.100.172.43..
Mon Jul 10 21:47:47 2023 - [info]  ok.
Mon Jul 10 21:47:47 2023 - [info] Checking replication health on 10.100.172.44..
Mon Jul 10 21:47:47 2023 - [info]  ok.
Mon Jul 10 21:47:47 2023 - [info] Checking master_ip_failover_script status:
Mon Jul 10 21:47:47 2023 - [info]   /usr/local/bin/master_ip_failover --command=status --ssh_user=root --orig_master_host=10.100.172.45 --orig_master_ip=10.100.172.45 --orig_master_port=3306 


IN SCRIPT TEST====/sbin/ifconfig ens192:88 down==/sbin/ifconfig ens192:88 10.100.172.47/24===

Checking the Status of the script.. OK 
Mon Jul 10 21:47:47 2023 - [info]  OK.
Mon Jul 10 21:47:47 2023 - [warning] shutdown_script is not defined.
Mon Jul 10 21:47:47 2023 - [debug]  Disconnected from 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [debug]  Disconnected from 10.100.172.43(10.100.172.43:3306)
Mon Jul 10 21:47:47 2023 - [debug]  Disconnected from 10.100.172.44(10.100.172.44:3306)
Mon Jul 10 21:47:47 2023 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.  -- 复制关系正常

 

13. 启动MHA

(1). 添加VIP

由于MHA的主要目标是为了维护MySQL的复制关系,提供高可靠性的MySQL集群;至于Apps想如何访问这个集群,MHA其实没有规定的,留给我们自己决定,目前大概有如下几种访问方式:

  • 虚拟IP
    • 方式一:Keepalived:Keepalived是Linux下的一个高可用组件,可以让两台或多台Linux主机组成一个高可用集群,对外提供一个虚拟IP进行访问(也意味着同一时刻,只有一台主机能够被访问)。
      • 我们这里可以在 Master 和 Slave1 ( Candicate Master )上安装keepalived,然后 检测mysql状态 (keepalived中可以定义检测脚本);
      • 如果Master没有宕机,MySQL挂了,则Master上的Keepalived检测到MySQL挂了, 降低自己的优先级(低于Slave1) ,然后keepalived使用的 VRRP 协议(心跳)通过协商后,认为 Slave1的优先级更高 ,进而 VIP 转移到 Slave1 ;
      • 如果Master直接宕机了,则 Slave1检测不到Master的存在 ,进而 VIP 也转移到 Slave1 ;
      • keepalived会有可能产生脑裂,导致VIP在两个服务器上(有两台服务器宣称自己的IP地址是VIP),这样就会导致有的写操作落在Master上,而有的写操作落在Slave1上,从而到时整个集群的数据不一致;
      • 产生脑裂的原因有多种,大部分情况是网络问题;又或者是服务器太卡,导致心跳包没有及时处理;
    • 方式二:使用脚本 masterha_ip_failover ;由于这个脚本的start/stop操作只有在切换(Failover)期间才会执行,所以 即便你设置了该脚本,MHA也不会在一开始( masterha_manager启动 )的时候,帮你在Master上设置VIP,初次在Master上设置VIP需要人工操作,后期如果有Failover操作,MHA会执行脚本,帮你切换。
  • 智能DNS

此种模式下,MySQL集群本身只做高可用,维持复制关系,Apps通过内网DNS来访问MySQL。此种情况下,需要DNS端可以检测MySQL服务是否可用,从而决定推送Master还是Slave1的IP地址给Apps,类似Smart Client的效果;

我们这里推荐使用虚拟IP--方式二

##
## Master 端
##
# 在我的虚拟机中,网卡名称为ens3
Shell> ifconfig ens192:88 10.100.172.47/24

(2). 启动masterha_manager

Shell> nohup masterha_manager --conf=/etc/masterha/app1.conf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/masterha/app1/manager.log 2>&1 &
Shell> masterha_check_status --conf=/etc/masterha/app1.conf
app1 (pid:18702) is running(0:PING_OK), master:Master
# 状态正常,且当前主机是 Master
 
14. 自动Failover测试
  • 测试步骤如下:
    • 停掉 Slave的IO线程 ,模拟复制延时(Slave1保持不变);
    • 使用sysbench对Master进行测试,生成测试数据(可以 产生大量的binlog );
    • 等待步骤2完成 后, 开启Slave上的IO线程 ,去追Master的binlog,同时立即操作第四步;
    • 关闭Master上的MySQL ,让MHA 产生 Failover 操作;
    • 观察最终状态;

  注意:步骤3 和 步骤4 不要调换,我多次测试发现会有问题; 

  • Failover过程,大致包含如下步骤: 
    1. 多次探测( 包括透过Slave进行二次探测 )Master,确认是否宕机;
    2. 检查配置文件阶段, 包括MySQL实例的当前的配置 ;
    3. 处理Master,前提是还能ssh到Master,否则跳过该步骤;
      包括删除虚拟IP,还有关机操作(防止脑裂,但是关机配置我们注释了);
    4. 如果还能SSH到Master上,则复制binlog到MHA Manager上,否则跳过;
      这一步在源码和之前网上别人的日志中是能看到 scp 信息的,但是我这里没出现scp信息,不知道是否是MHA0.58的问题,但是最终数据是能一致(表明确实是获取binlog了)
      从这里看出,单纯的靠MHA是不能完全保证数据不丢的;
    5. 确认包含最新更新的Slave ;
    6. 应用从master保存的binlog ;
    7. 将配置中配置为 candidate_master=1 的Slave,提升为 New Master ;
    8. 配置其他的Slave连接( CHANGE MASTER TO ) New Master; 

 

15. 手动Failover测试

注意事项:
在使用手动Failover的时候,masterha_manager(在线Failover功能)需要关闭 (自动切换还是手工切换自己选择),这个也是MHA留给我们自己决定的地方,线上使用可以选择使用自动切换,也可以通过报警后,处理问题,然后决定是否手工切换。

手工切换命令如下:

##
## MHA Manager 端
##
Shell> masterha_master_switch --master_state=dead --conf=/etc/masterha/app1.conf --dead_master_host=Master --dead_master_port=3306 --new_master_host=Slave1 --new_master_port=3306 --ignore_last_failover

注意事项:

  1. 如果你的 app1.conf 中配置的 hostname 为 Master、Slave1 等,则上述命令中的 --dead_master_host 和 --new_master_host 也需要写成 Master、Slave1 ,而 不能写成IP地址
  2. 如果你的 app1.conf 中配置的 hostname 为IP地址,则 --dead_master_host 和 --new_master_host 也要配置成IP地址
  3. 在进行手动Failover的时候,请确保Master上的MySQL挂了,否则会报如下错误:
    None of server is dead. Stop failover. # 提示我们没有服务器挂

 

16. MHA总结

(1). 部署操作过程

  1. 配置好MySQL复制关系,至少三个节点,且确保rpl用户传递到Slave上;
  2. 在 MHA Manager 上安装 Manager 组件;
  3. 在所有节点上安装 Node 组件;
  4. 配置好/etc/masterha/app1.conf ;
  5. 启动masterha_manager(supervisord托管);
  6. 确认masterha_check_status状态;

(2). Old Master恢复

  • 日志处理

当切换完成后,假如Old Master修复完成,这时需要对比一下数据是否一致;如果主从不一致,即Old Master 宕机时刻的binlog没有传到Slave,且MHA也无法获取到这部分binlog,需要通过Flashback工具将这部分数据切除 ;

    • Old Master 的binlog信息可以通过 show master status\G 看到,或者通过 mysqlbinlog 进行查看;
    • New Master 上查看执行到的 Old Master 复制过来的binlog信息可以通过 show global variables like "%gtid%"; 来进行查看(mysql会保留之前执行过的GTID信息);
      • 在没有使用MHA,或者使用MHA手动Failover的时候,可以通过在Slave上执行 show slave status\G 通过 Exec_Master_Log_Pos 观察到执行到的位置(等待回放完毕)
      • 而使用MHA切换后, show slave status\G 的信息会被MHA给reset掉(除非自己给MHA打补丁,将信息记录下来);

通过上述方法,大致可以判断是否需要Flashback或者哪些数据需要Flashback;

  • 角色处理

  当Old Master恢复后且日志处理完成,建议将该服务器作为New Master的Slave节点,通过CHANGE MASTER(MASTER_AUTO_POSITION=1)可以很方便的建立主从关系。

--
-- Old Master 端
--
mysql> change master to master_host='10.100.172.43', master_port=3306, master_auto_position=1, master_user='rpl', master_password='123';
mysql> start slave;


如果中间产生问题,和之前处理GTID复制出错一样, 跳过执行的部分 即可:

mysql> stop slave;
mysql> reset master;
mysql> set @@global.gtid_purged='2d408b04-0154-11e6-83b6-5254f035dabc:1-4389';
mysql> change master to master_host='10.100.172.43', master_port=3306, master_auto_position=1, master_user='rpl', master_password='123';
mysql> start slave;

 

备注:

1. 用普通用户配置MHA步骤(mysql为例)
  • 配置mysql用户在服务器之间免密登陆
  • 配置mysql用户免密执行sudo命令(为了在failover脚本中执行sudo命令)
  • 在master_ip_failover和master_ip_online_change中的/sbin/ifconfig改为sudo /sbin/ifconfig
  • 确保mysql用户具有以下权限:
    • mysql用户可以启动数据库
    • 数据目录属主和属组为mysql
    • MHA脚本mysql用户可以执行
2. 将主从架构切换成MHA架构
  • 将MHA搭建完成并测试
  • 将Master-1的数据导出,导出时需要添加--set-gtid-purged=OFF,此时导出的sql文件(以下简称sql文件)中没有SET @@GLOBAL.GTID_PURGED命令,但是导出的结果会开启binlog
  • 将Master-1的数据同步到Master-2,因为sql文件中开启了binlog,所以Master-2上产生了binlog,这样就会自动同步到Slave-M和Slave-1
  • 因为sql文件中没有set purge gtid命令,所以如果直接执行change master命令会报错,所以先需要在Master-2上执行SET @@GLOBAL.GTID_PURGED。gtid的值获取方式如下:
[root@CN-MHA-MySQL-M mha]# head -n 30 test.sql 
-- MySQL dump 10.13  Distrib 8.0.33, for Linux (x86_64)
--
-- Host: localhost    Database: artdb
-- ------------------------------------------------------
-- Server version    8.0.33

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Position to start replication or point-in-time recovery from
--

-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000040', MASTER_LOG_POS=237;  -- 备份时刷新了binlog,这是最新的binlog文件及position


root@mysqldb 20:17:  [(none)]> show binlog events in 'binlog.000040' limit 10; -- Previous_gtids即为已经purged gtid
+---------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------+
| Log_name      | Pos  | Event_type     | Server_id | End_log_pos | Info                                                                                                      |
+---------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------+
| binlog.000040 |    4 | Format_desc    |        11 |         126 | Server ver: 8.0.33, Binlog ver: 4                                                                         |
| binlog.000040 |  126 | Previous_gtids |        11 |         237 | a0dae802-0b4c-11ee-a4f1-005056a4d832:1353002-8081776,
a5cd0d9c-0b4c-11ee-9992-005056a4da31:923814-930072  |
| binlog.000040 |  237 | Gtid           |        11 |         316 | SET @@SESSION.GTID_NEXT= 'a0dae802-0b4c-11ee-a4f1-005056a4d832:8081777'                                   |
| binlog.000040 |  316 | Query          |        11 |         401 | BEGIN                                                                                                     |
| binlog.000040 |  401 | Rows_query     |        11 |         528 | # UPDATE access_topology SET state = 'HEALTHY', last_updated = 1689596066619 WHERE endpoint_id = 30830002 |
| binlog.000040 |  528 | Table_map      |        11 |         616 | table_id: 238 (artdb.access_topology)                                                                     |
| binlog.000040 |  616 | Update_rows    |        11 |         948 | table_id: 238 flags: STMT_END_F                                                                           |
| binlog.000040 |  948 | Rows_query     |        11 |        1075 | # UPDATE access_topology SET state = 'HEALTHY', last_updated = 1689596066619 WHERE endpoint_id = 30830004 |
| binlog.000040 | 1075 | Table_map      |        11 |        1163 | table_id: 238 (artdb.access_topology)                                                                     |
| binlog.000040 | 1163 | Update_rows    |        11 |        1471 | table_id: 238 flags: STMT_END_F                                                                           |
+---------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------+
10 rows in set (0.00 sec)
# Previous_gtids是1353002-8081776,这是因为之前迁移过一次,正常情况应该是1-8081776,第二个GTID同理
# 1353002-8081776表示1353002-8081776都被purge了,而1-1353002也被purge了,所以真正被purge的应该是1-8081776,同理另外一个被purge的应该是1-930072
  • 在Master-2上执行
SET @@GLOBAL.GTID_PURGED='a0dae802-0b4c-11ee-a4f1-005056a4d832:1-8081776,
a5cd0d9c-0b4c-11ee-9992-005056a4da31:1-930072'

 

posted @   BinBin-HF  阅读(74)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
点击右上角即可分享
微信分享提示