概述

MySQL对于一个业务系统中太重要了,基本上挂了,整个业务也就挂了。系统架构里面也要尽量避免MySQL单点。所以,MySQL高可用是一个很重要的问题。现在的方案也很多,例如,MM,MHA等。

MHA是MySQL数据库高可用方案中比较成熟和流行的一种。比较常见的做法是一主两从,一个主库提供读写服务,一个从服务器作为备选主库,一个纯从库。当MHA管理节点监测到主库挂了的时候,他会将备选库升级成主库,这样就保证了数据库服务的高可用。具体的原理参见文档。

实践的大致架构如图。没有足够的服务器,所以将从库服务器作为管理节点。

mha

配置这个方案的步骤大致如下

  • 初始化服务器环境
  • 安装数据库,配置用户,配置主从
  • 安装MHA节点和管理软件
  • 测试MHA切换
  • 配置keepalived
  • 测试MHA自动切换+虚拟IP切换

MHA搭建

初始化环境

首先,安装3个Cent OS 7的虚拟机,初始化如下的服务器环境

CT_1  # master 节点

172.16.71.128
mysql slave 123456@slave

CT_2 # candidate master 节点

172.16.71.129
mysql root 123456@ct2

CT_3 # slave + mha-manage 节点

172.16.71.130
mysql root 123456@ct3

配置3个服务器之间两两免密码SSH登录。因为mha-manager节点在slave上,slave还需要配置自己ssh自己,不然后续配置检查会报错。

#/etc/hosts
172.16.71.128 ct1
172.16.71.129 ct2
172.16.71.130 ct3

ssh-keygen -t rsa

# ct1
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct2
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct3

# ct2
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct1
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct3

# ct3
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct1
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct2
ssh-copy-id -i /root/.ssh/id_rsa.pub root@ct3

安装MySQL

在三台服务器上,安装MySQL

# 移除旧的源
rpm -qa | grep mariadb 
rpm -e mariadb-libs-5.5.56-2.el7.x86_64 --nodeps

# 安装mysql的源
wget http://dev.mysql.com/get/mysql57-community-release-el7-7.noarch.rpm

# 安装
yum install mysql57-community-release-el7-7.noarch.rpm
yum install mysql-community-server

# 配置
grep 'password' /var/log/mysqld.log
set global validate_password_policy=0;
uninstall plugin validate_password

配置MySQL主从

编辑MySQL配置,启动服务

# vim /etc/my.cnf

略。

MHA需要用到两个MySQL用户,一个是用于主从同步的账户,另一个是MHA自身用于控制主从切换进行管理的用户。

首先在master上创建用户从库同步用户,配置同步。

# master

create user 'slave'@'%' identified by 'password';
grant replication slave, replication client on *.* to 'slave'@'%' identified by 'password';
flush privileges;

# slave_1, slave_2

change master to 
MASTER_HOST='ct1',
MASTER_USER='slave',
MASTER_PASSWORD='password';

start slave;
show slave status \G;

然后,配置MHA用于管理的用户,在master上创建,会自动同步至其他的库

# master

create user 'mha'@'%' identified by 'password';
grant all privileges on *.* to 'mha'@'%' identified by 'password';
flush privileges;

配置MHA

在三台服务器上安装mha-node,然后在ct3上安装mha-master。

mha-master的配置如下。这里,主要是配置ct1作为主数据库,ct2作为备选数据库,ct3作为纯从库。

# /etc/masterha/app1.cnf

[server default]
# mysql管理用户
user=mha
password=123456@mha

# ssh用户
ssh_user=root

# working directory on the manager
manager_workdir=/var/log/masterha/app1.log

# working directory on MySQL servers
remote_workdir=/var/log/mysql

# failover script
master_ip_failover_script=/etc/masterha/scripts/master_ip_failover.sh

[server1]
hostname=ct1

[server2]
hostname=ct2
candidate_master=1 # 作为备选主库

[server3]
hostname=ct3
no_master=1 # 纯从库

测试

在ct3上执行如下检查,确认配置没有问题

# 检查ssh配置
masterha_check_ssh --conf=/etc/masterha/app1.cnf

# 检查集群配置
masterha_check_repl --conf=/etc/masterha/app1.cnf

这里就安装配置好了mha环境,可以进行初步的测试了。

启动mha-manage,关闭ct1上的mysql实例

masterha_manage --conf=/etc/masterha/app1.cnf

# 在ct1上关闭数据库
systemctl stop mysqld

可以看到,mha-manager切换了主从库,将ct2设置为了主库,ct3的复制源变成了ct2。证明mha整体运行正确。

当这样切换之后,如果恢复了ct1,重新运行,这时候因为ct2变成了主库,所以,之前的app1.cnf配置就不行了。需要重新调整下配置了。更改mha的配置,设置ct1为备选主库,然后重新启动mha-manage。这样,下次就可以再继续切换了。

keepalived

上面初步的搭好了mha的高可用环境。但是有很多不够完善的地方。

例如,mha故障自动切换了主库之后,主库变成了另一个实例,业务端需要对应的修改连接的数据库服务器地址才能正常运行。这是不合适的。因此,我们希望对外提供一个固定的IP地址,当后端切换,依然能够通过这个IP访问到。要实现这个效果,需要用到keepalived的虚拟IP。

keepalived是一个基于VRRP协议的高可用软件,可以用来避免集群单点故障。

keepalived初始化时绑定虚拟IP到master上,master会发送组播消息给其他的节点。当backup收不到消息时,会认为master挂了,然后根据配置权重推举一个新的机器接管IP提供服务。

配置keepalived

在master(ct1)上配置keepalived并启动

global_defs {
    router_id mha_1
}

vrrp_instance MYSQL_MASTER {
    state master
    interface ens33
    virtual_router_id 10
    priority 100
    advert_int 1
    nopreempt

    authentication {
        auth_type PASS
        auth_pass 121212
    }

    virtual_ipaddress {
        172.16.71.120/24
    }
}

在backup(ct2)上如下配置并启动

global_defs {
    router_id mha_2
}

vrrp_instance MYSQL_BACKUP {
    state BACKUP
    interface ens33
    virtual_router_id 10
    priority 90
    advert_int 1
    nopreempt

    authentication {
        auth_type PASS
        auth_pass 121212
    }

    virtual_ipaddress {
        172.16.71.120/24
    }
}

启动之后,查看master的IP,可以看到虚拟IP绑定到了master上

ip a | grep ens33

ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 172.16.71.129/24 brd 172.16.71.255 scope global noprefixroute dynamic ens33
    inet 172.16.71.120/24 scope global secondary ens33

测试关闭master上的keepalived实例,再次查看IP,会发现虚拟IP绑定到了backup节点上了。这样,就保证了当master挂了,这个虚拟IP依然是可用的。

配置MHA+keepalived

上面单独配置好了MHA+keepalived,现在离最后的效果只差最后一步了。最终的效果是,当MHA切换备选服务器时,虚拟IP绑定到备选服务器上,通过虚拟IP能够访问到备选服务器的MySQL。

实现方法是在MHA切换备选服务器时,关闭主服务器上的keepalived服务。当备选服务器检测到主服务器的keepalived挂了,就自动接管虚拟IP了。

MHA提供了一个配置,可以在切换时执行一个脚本,也就是上面的master_ip_failover_script选项。切换虚拟IP的动作就在这个脚本里。

脚本代码如下

#!/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 = '172.16.71.120/24';
my $ssh_start_vip = "service keepalived start";
my $ssh_stop_vip = "service keepalived stop";
 
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";
        #`ssh $ssh_user\@cluster1 \" $ssh_start_vip \"`;
        exit 0;
    }
    else {
        &usage();
        exit 1;
    }
}
 
# A simple system call that enable the VIP on the new mastersub start_vip() {
sub start_vip() {
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
# A simple system call that disable the VIP on the old_master
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";
}

如果一切顺利,到这一步就配置好了所有的组件,就可以做最后的测试了。

首先测试通过虚拟IP连接数据库。在php项目里面配置虚拟IP连接数据库,进行读写操作,在主服务器上看有正确的插入,并同步到了从库。

然后,停止主库上的MySQL。可以看到,MHA和上面一样,切换到了备选库。然后紧接着就执行上面的failover脚本,脚本成功关闭了主服务器上的keepalived服务。

最后再使用虚拟IP连接数据库进行读写。可以看到,这时候读写在备选服务器上了。

证明整个系统运行OK。

最后

进行了整个高可用实践,对MySQL高可用架构有了更熟悉的认识。mha和keepalived是做什么的,怎么使用,两者是如何搭配做到高可用的。

但是,还有一些地方得进一步实践,例如,挂了的master如何迅速恢复到这个架构里面,最佳实践是如何的。另外,整个架构还有一个环节没有涉及,就是读写分离。配置了一主两从,可以使用读写分离,降低主库的压力,增加主库的容错性。