MySQL 之 MHA + ProxySQL + keepalived 实现读写分离,高可用(一)

准备服务器:

docker network create --subnet=192.168.0.0/16 staticnetdocker run -d --privileged -v `pwd`/mysql_data:/data -p 3001:3306 --name mysql5-master --hostname mysql5-master --net staticnet --ip 192.168.0.101 eiki/mysql:5.7.23 /usr/sbin/init
docker run -d --privileged -v `pwd`/mysql_data:/data -p 3002:3306 --name mysql5-slave --hostname mysql5-slave --net staticnet --ip 192.168.0.102 eiki/mysql:5.7.23 /usr/sbin/init
docker run -d --privileged -v `pwd`/mysql_data:/data -p 3003:3306 --name mysql5-s2 --hostname mysql5-s2 --net staticnet --ip 192.168.0.103 eiki/mysql:5.7.23 /usr/sbin/init

docker run -d --privileged -v `pwd`/mysql_data:/data -p 7032:6032 -p 6080:6080 --name proxysql2 --hostname proxysql2 --net staticnet --ip 192.168.0.202 eiki/proxysql:latest /usr/sbin/init
docker run -d --privileged -v `pwd`/mysql_data:/data -p 6032:6032 -p 7080:6080 --name proxysql --hostname proxysql --net staticnet --ip 192.168.0.201 eiki/proxysql:latest /usr/sbin/init
其中6032是管理端口,6033是程序端口,6080是http端口

主从搭建(一主两从)

MySQL安装过程略

主库/etc/my.cnf

[mysql]
prompt = [\\[email protected]\\h][\\d]>\\_
port                    = 3306
socket                  = /usr/local/mysql/mysql.sock

[mysqld]
user                    = mysql
port                    = 3306
server-id               = 1
pid-file                = /usr/local/mysql/mysqld.pid
socket                  = /usr/local/mysql/mysql.sock
basedir                 = /usr/local/mysql
datadir                 = /usr/local/mysql/data
log-bin                 = master-bin
log-bin-index           = master-bin.index
relay_log_purge         = 0

#以下两个参数不加,从执行change不可以指定channel(ERROR 3077 (HY000))
master_info_repository  =table
relay_log_info_repository =table

从库/etc/my.cnf

[mysql]
prompt = [\\[email protected]\\h][\\d]>\\_
port                    = 3306
socket                  = /usr/local/mysql/mysql.sock

[mysqld]
user                    = mysql
port                    = 3306
server-id               = 101
pid-file                = /usr/local/mysql/mysqld.pid
socket                  = /usr/local/mysql/mysql.sock
basedir                 = /usr/local/mysql
datadir                 = /usr/local/mysql/data
relay-log-index         = slave-relay-bin.index
relay-log               = slave-relay-bin
relay_log_purge         = 0
#以下两个参数不加,从执行change不可以指定channel(ERROR 3077 (HY000))
master_info_repository  =table
relay_log_info_repository =table 

主库上创建复制账号

create user [email protected]‘192.168.0.%‘ identified by ‘repl‘;
grant replication slave on *.* to [email protected]‘%‘;
flush privileges;

主库上执行备份

mysqldump --master-data=2 --single-transaction -R --triggers -A > all.sql其中--master-data=2代表备份时刻记录master的Binlog位置和Position,--single-transaction意思是获取一致性快照,-R意思是备份存储过程和函数,--triggres的意思是备份触发器,-A代表备份所有的库。更多信息请自行mysqldump --help查看。

查看主库备份时的binlog名称和位置,MASTER_LOG_FILE和MASTER_LOG_POS

[[email protected] ~]# head -n 30 all.sql | grep ‘CHANGE MASTER TO‘
-- CHANGE MASTER TO MASTER_LOG_FILE=‘mysql-bin.000010‘, MASTER_LOG_POS=112;

把备份复制到s1和s2,也就是192.168.0.102和192.168.0.103

scp all.sql [email protected]:/data/
scp all.sql [email protected]:/data/

导入备份到s1,s2,执行复制相关命令

mysql -uroot -p < /data/all.sql
CHANGE MASTER TO MASTER_HOST=‘192.168.0.101‘,MASTER_USER=‘repl‘,MASTER_PASSWORD=‘repl‘,MASTER_LOG_FILE=‘master-bin.000007‘,MASTER_LOG_POS=739 for channel ‘s1‘;start slave for channel ‘s1‘;show slave status\G;

查看复制状态(可以看见复制成功):

[[email protected] ~]# mysql -e ‘show slave status\G‘ | egrep ‘Slave_IO|Slave_SQL‘
               Slave_IO_State: Waiting for master to send event
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes

另外一个从节点搭建复制环境,操作和上面一样

两台slave服务器设置read_only(从库对外提供读服务,只所以没有写进配置文件,是因为随时slave会提升为master

[[email protected] ~]# mysql -e ‘set global read_only=1‘
[[email protected] ~]#
[[email protected] ~]# mysql -e ‘set global read_only=1‘
[[email protected] ~]#

创建管理账号(主库执行)

grant replication slave,reload,create user,super on *.* to [email protected]‘%‘ identified by ‘mats‘ with grant option;

至此一主两从架构搭建完毕

配置互信

确保所有节点都安装openssh并启动

openssh的安装步骤:
yum -y install openssh*
systemctl start sshd


ssh-keygen -t rsa
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]

ssh-keygen -t rsa
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]

ssh-keygen -t rsa
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]

ssh-keygen -t rsa
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]

ssh-keygen -t rsa
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]

  

部署MHA

角色                    ip地址             主机名             server_id                  类型
Monitor host            192.168.0.201    ProxySQL            -                      监控复制组
Master                  192.168.0.101    master              1                        写入
Candicate master        192.168.0.102    s1                  101                      读
Slave                   192.168.0.103    s2                  102                      读

其中master对外提供写服务,备选master(实际的slave,主机名s1)提供读服务,slave也提供相关的读服务,一旦master宕机,将会把备选master提升为新的master,slave指向新的master

官方网址
Manager:https://github.com/yoshinorim/mha4mysql-manager
Node: https://github.com/yoshinorim/mha4mysql-node
https://github.com/yoshinorim/mha4mysql-manager/wiki

参考来源:https://www.cnblogs.com/gomysql/p/3675429.html

使用git clone从github拷贝项目

git clone https://github.com/yoshinorim/mha4mysql-manager
git clone https://github.com/yoshinorim/mha4mysql-node

安装依赖包(在所有节点安装MHA node所需的perl模块)

yum install glib2-devel libpcap-devel libnet-devel cmake gc++ gcc gcc-c++ make git
yum install gcc gcc-c++ kernel-devel perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes perl-Module-Install -y

在所有的节点安装mha node

cd mha4mysql-node-0.58
perl Makefile.PL
make && make install

安装完成后会在/usr/local/bin目录下生成以下脚本文件:

[[email protected] src]# ll /usr/local/bin
total 80
-r-xr-xr-x. 1 root root 17639 Nov 14 15:46 apply_diff_relay_logs
-r-xr-xr-x. 1 root root  7138 Nov 14 15:43 config_data
-r-xr-xr-x. 1 root root  4807 Nov 14 15:46 filter_mysqlbinlog
-r-xr-xr-x. 1 root root  4371 Nov 14 15:43 json_pp
lrwxrwxrwx. 1 root root    26 Nov 14 17:02 mysql -> /usr/local/mysql/bin/mysql
lrwxrwxrwx. 1 root root    32 Nov 14 17:02 mysqlbinlog -> /usr/local/mysql/bin/mysqlbinlog
-r-xr-xr-x. 1 root root 13649 Nov 14 15:43 prove
-r-xr-xr-x. 1 root root  8337 Nov 14 15:46 purge_relay_logs
-r-xr-xr-x. 1 root root  7525 Nov 14 15:46 save_binary_logs

安装mha manager

cd mha4mysql-node-0.58
perl Makefile.PL
make && make install

安装完成后会在/usr/local/bin目录下面生成以下脚本文件

[[email protected] 3306]# ll /usr/local/bin
total 92
-r-xr-xr-x. 1 root root 17639 Nov 14 15:52 apply_diff_relay_logs
-r-xr-xr-x. 1 root root  4807 Nov 14 15:52 filter_mysqlbinlog
-rw-r--r--. 1 root root    74 Nov 14 16:36 load_cnf
-r-xr-xr-x. 1 root root  1995 Nov 14 15:54 masterha_check_repl
-r-xr-xr-x. 1 root root  1779 Nov 14 15:54 masterha_check_ssh
-r-xr-xr-x. 1 root root  1865 Nov 14 15:54 masterha_check_status
-r-xr-xr-x. 1 root root  3201 Nov 14 15:54 masterha_conf_host
-r-xr-xr-x. 1 root root  2517 Nov 14 15:54 masterha_manager
-r-xr-xr-x. 1 root root  2165 Nov 14 15:54 masterha_master_monitor
-r-xr-xr-x. 1 root root  2373 Nov 14 15:54 masterha_master_switch
-r-xr-xr-x. 1 root root  5172 Nov 14 15:54 masterha_secondary_check
-r-xr-xr-x. 1 root root  1739 Nov 14 15:54 masterha_stop
-r-xr-xr-x. 1 root root  8337 Nov 14 15:52 purge_relay_logs
-r-xr-xr-x. 1 root root  7525 Nov 14 15:52 save_binary_logs

复制相关脚本到/etc/mha/scripts目录(软件包解压缩后就有了,不是必须,因为这些脚本不完整,需要自己修改,这是软件开发着留给我们自己发挥的,如果开启下面的任何一个脚本对应的参数,而对应这里的脚本又没有修改,则会抛错,自己被坑的很惨)

mkdir -p /data/mha/3306/log
mkdir -p /etc/mha/scripts
cp /data/mha4mysql-manager-master/samples/scripts/* /etc/mha/scripts
[[email protected] 3306]# ll /etc/mha/scripts
total 36
-rwxr-xr-x. 1 root root   150 Nov 14 17:53 manager.sh
-rwxr-xr-x. 1 root root  2169 Nov 14 17:27 master_ip_failover #自动切换时vip管理的脚本,不是必须,如果我们使用keepalived的,我们可以自己编写脚本完成对vip的管理,比如监控mysql,如果mysql异常,我们停止keepalived就行,这样vip就会自动漂移
-rwxr-xr-x. 1 root root 10369 Nov 14 17:38 master_ip_online_change #在线切换时vip的管理,不是必须,同样可以可以自行编写简单的shell完成
-rwxr-xr-x. 1 root root 11867 Nov 14 16:41 power_manager           #故障发生后关闭主机的脚本,不是必须
-rwxr-xr-x. 1 root root  1360 Nov 14 16:41 send_report             #因故障切换后发送报警的脚本,不是必须,可自行编写简单的shell完成。

配置/etc/masterha_default.cnf

cat >> /etc/masterha_default.cnf << EOF
[server default]
user=root
password=abc123
ssh_user=root
repl_user=repl
repl_password=repl
ping_interval=1
#master_binlog_dir= /var/lib/mysql,/var/log/mysql
secondary_check_script=masterha_secondary_check -s 192.168.0.101 -s 192.168.0.102 -s 192.168.0.103
master_ip_failover_script="/etc/mha/scripts/master_ip_failover"
master_ip_online_change_script="/etc/mha/scripts/master_ip_online_change"
report_script="/etc/mha/scripts/send_report"
EOF

配置/data/mha/3306/mha.cnf

cat >>/data/mha/3306/mha.cnf << EOF
[server default]
manager_workdir=/data/mha/3306
manager_log=/data/mha/3306/manager.log

[server1]
hostname=192.168.0.101
candidate_master=1
master_binlog_dir="/usr/local/mysql/data"

[server2]
hostname=192.168.0.102
candidate_master=1
master_binlog_dir="/usr/local/mysql/data"

[server3]
hostname=192.168.0.103
master_binlog_dir="/usr/local/mysql/data"
#表示没有机会成为master
no_master=1
EOF
ln -s /usr/local/mysql/bin/mysqlbinlog /usr/local/bin/mysqlbinlog
ln -s /usr/local/mysql/bin/mysql /usr/local/bin/mysql

配置/etc/mha/scripts/master_ip_failover

cat >> /etc/mha/scripts/master_ip_failover << EOF
#!/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 = ‘192.168.0.88/24‘;   #设置VIP
my $key = ‘1‘;
my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip";  #注意网卡,根据实际情况改写
my $ssh_stop_vip = "/sbin/ifconfig eth0:$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 ([email protected]) {
            warn "Got Error: [email protected]\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 ([email protected]) {
            warn [email protected];
            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";
}
EOF

配置/etc/mha/scripts/master_ip_online_change

cat >> /etc/mha/scripts/master_ip_online_change << EOF
#!/usr/bin/env perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.

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

use Getopt::Long;
use MHA::DBHelper;
use MHA::NodeUtil;
use Time::HiRes qw( sleep gettimeofday tv_interval );
use Data::Dumper;

my $_tstart;
my $_running_interval = 0.1;
my (
  $command,          $orig_master_host, $orig_master_ip,
  $orig_master_port, $orig_master_user,
  $new_master_host,  $new_master_ip,    $new_master_port,
  $new_master_user,
);

my $vip = ‘192.168.0.88/24‘;  # Virtual IP
my $key = "1";
my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig eth0:$key down";
my $ssh_user = "root";
my $new_master_password=‘abc123‘;
my $orig_master_password=‘abc123‘;
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,
  ‘orig_master_user=s‘     => \$orig_master_user,
  #‘orig_master_password=s‘ => \$orig_master_password,
  ‘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,
);

exit &main();

sub current_time_us {
  my ( $sec, $microsec ) = gettimeofday();
  my $curdate = localtime($sec);
  return $curdate . " " . sprintf( "%06d", $microsec );
}

sub sleep_until {
  my $elapsed = tv_interval($_tstart);
  if ( $_running_interval > $elapsed ) {
    sleep( $_running_interval - $elapsed );
  }
}

sub get_threads_util {
  my $dbh                    = shift;
  my $my_connection_id       = shift;
  my $running_time_threshold = shift;
  my $type                   = shift;
  $running_time_threshold = 0 unless ($running_time_threshold);
  $type                   = 0 unless ($type);
  my @threads;

  my $sth = $dbh->prepare("SHOW PROCESSLIST");
  $sth->execute();

  while ( my $ref = $sth->fetchrow_hashref() ) {
    my $id         = $ref->{Id};
    my $user       = $ref->{User};
    my $host       = $ref->{Host};
    my $command    = $ref->{Command};
    my $state      = $ref->{State};
    my $query_time = $ref->{Time};
    my $info       = $ref->{Info};
    $info =~ s/^\s*(.*?)\s*$/$1/ if defined($info);
    next if ( $my_connection_id == $id );
    next if ( defined($query_time) && $query_time < $running_time_threshold );
    next if ( defined($command)    && $command eq "Binlog Dump" );
    next if ( defined($user)       && $user eq "system user" );
    next
      if ( defined($command)
      && $command eq "Sleep"
      && defined($query_time)
      && $query_time >= 1 );

    if ( $type >= 1 ) {
      next if ( defined($command) && $command eq "Sleep" );
      next if ( defined($command) && $command eq "Connect" );
    }

    if ( $type >= 2 ) {
      next if ( defined($info) && $info =~ m/^select/i );
      next if ( defined($info) && $info =~ m/^show/i );
    }

    push @threads, $ref;
  }
  return @threads;
}

sub main {
  if ( $command eq "stop" ) {
    ## Gracefully killing connections on the current master
    # 1. Set read_only= 1 on the new master
    # 2. DROP USER so that no app user can establish new connections
    # 3. Set read_only= 1 on the current master
    # 4. Kill current queries
    # * Any database access failure will result in script die.
    my $exit_code = 1;
    eval {
      ## Setting read_only=1 on the new master (to avoid accident)
      my $new_master_handler = new MHA::DBHelper();

      # args: hostname, port, user, password, raise_error(die_on_error)_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, 1 );
      print current_time_us() . " Set read_only on the new master.. ";
      $new_master_handler->enable_read_only();
      if ( $new_master_handler->is_read_only() ) {
        print "ok.\n";
      }
      else {
        die "Failed!\n";
      }
      $new_master_handler->disconnect();

      # Connecting to the orig master, die if any database error happens
      my $orig_master_handler = new MHA::DBHelper();
      $orig_master_handler->connect( $orig_master_ip, $orig_master_port,
        $orig_master_user, $orig_master_password, 1 );

      ## Drop application user so that nobody can connect. Disabling per-session binlog beforehand
      #$orig_master_handler->disable_log_bin_local();
      #print current_time_us() . " Drpping app user on the orig master..\n";
      #FIXME_xxx_drop_app_user($orig_master_handler);

      ## Waiting for N * 100 milliseconds so that current connections can exit
      my $time_until_read_only = 15;
      $_tstart = [gettimeofday];
      my @threads = get_threads_util( $orig_master_handler->{dbh},
        $orig_master_handler->{connection_id} );
      while ( $time_until_read_only > 0 && $#threads >= 0 ) {
        if ( $time_until_read_only % 5 == 0 ) {
          printf
"%s Waiting all running %d threads are disconnected.. (max %d milliseconds)\n",
            current_time_us(), $#threads + 1, $time_until_read_only * 100;
          if ( $#threads < 5 ) {
            print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . "\n"
              foreach (@threads);
          }
        }
        sleep_until();
        $_tstart = [gettimeofday];
        $time_until_read_only--;
        @threads = get_threads_util( $orig_master_handler->{dbh},
          $orig_master_handler->{connection_id} );
      }

      ## Setting read_only=1 on the current master so that nobody(except SUPER) can write
      print current_time_us() . " Set read_only=1 on the orig master.. ";
      $orig_master_handler->enable_read_only();
      if ( $orig_master_handler->is_read_only() ) {
        print "ok.\n";
      }
      else {
        die "Failed!\n";
      }

      ## Waiting for M * 100 milliseconds so that current update queries can complete
      my $time_until_kill_threads = 5;
      @threads = get_threads_util( $orig_master_handler->{dbh},
        $orig_master_handler->{connection_id} );
      while ( $time_until_kill_threads > 0 && $#threads >= 0 ) {
        if ( $time_until_kill_threads % 5 == 0 ) {
          printf
"%s Waiting all running %d queries are disconnected.. (max %d milliseconds)\n",
            current_time_us(), $#threads + 1, $time_until_kill_threads * 100;
          if ( $#threads < 5 ) {
            print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . "\n"
              foreach (@threads);
          }
        }
        sleep_until();
        $_tstart = [gettimeofday];
        $time_until_kill_threads--;
        @threads = get_threads_util( $orig_master_handler->{dbh},
          $orig_master_handler->{connection_id} );
      }

                print "Disabling the VIP on old master: $orig_master_host \n";
                &stop_vip();     

      ## Terminating all threads
      print current_time_us() . " Killing all application threads..\n";
      $orig_master_handler->kill_threads(@threads) if ( $#threads >= 0 );
      print current_time_us() . " done.\n";
      #$orig_master_handler->enable_log_bin_local();
      $orig_master_handler->disconnect();

      ## After finishing the script, MHA executes FLUSH TABLES WITH READ LOCK
      $exit_code = 0;
    };
    if ([email protected]) {
      warn "Got Error: [email protected]\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "start" ) {
    ## Activating master ip on the new master
    # 1. Create app user with write privileges
    # 2. Moving backup script if needed
    # 3. Register new master‘s ip to the catalog database

# We don‘t return error even though activating updatable accounts/ip failed so that we don‘t interrupt slaves‘ recovery.
# If exit code is 0 or 10, MHA does not abort
    my $exit_code = 10;
    eval {
      my $new_master_handler = new MHA::DBHelper();

      # args: hostname, port, user, password, raise_error_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, 1 );

      ## Set read_only=0 on the new master
      #$new_master_handler->disable_log_bin_local();
      print current_time_us() . " Set read_only=0 on the new master.\n";
      $new_master_handler->disable_read_only();

      ## Creating an app user on the new master
      #print current_time_us() . " Creating app user on the new master..\n";
      #FIXME_xxx_create_app_user($new_master_handler);
      #$new_master_handler->enable_log_bin_local();
      $new_master_handler->disconnect();

      ## Update master ip on the catalog database, etc
                print "Enabling the VIP - $vip on the new master - $new_master_host \n";
                &start_vip();
                $exit_code = 0;
    };
    if ([email protected]) {
      warn "Got Error: [email protected]\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "status" ) {

    # do nothing
    exit 0;
  }
  else {
    &usage();
    exit 1;
  }
}

# A simple system call that enable the VIP on the new master
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() {
    `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}

sub usage {
  print
"Usage: master_ip_online_change --command=start|stop|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";
  die;
}
EOF

配置/etc/mha/scripts/send_report

cat >> /etc/mha/scripts/send_report << EOF
#!/usr/bin/perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.

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

use Getopt::Long;

#new_master_host and new_slave_hosts are set only when recovering master succeeded
my ( $dead_master_host, $new_master_host, $new_slave_hosts, $subject, $body );
GetOptions(
  ‘orig_master_host=s‘ => \$dead_master_host,
  ‘new_master_host=s‘  => \$new_master_host,
  ‘new_slave_hosts=s‘  => \$new_slave_hosts,
  ‘subject=s‘          => \$subject,
  ‘body=s‘             => \$body,
);

# Do whatever you want here

exit 0;

EOF

设置relay log的清除方式(在每个slave节点上):

[[email protected] ~]# mysql -e ‘set global relay_log_purge=0‘
[[email protected] ~]# mysql -e ‘set global relay_log_purge=0‘

注意:

MHA在发生切换的过程中,从库的恢复过程中依赖于relay log的相关信息,所以这里要将relay log的自动清除设置为OFF,采用手动清除relay log的方式。在默认情况下,从服务器上的中继日志会在SQL线程执行完毕后被自动删除。但是在MHA环境中,这些中继日志在恢复其他从服务器时可能会被用到,因此需要禁用中继日志的自动删除功能。定期清除中继日志需要考虑到复制延时的问题。在ext3的文件系统下,删除大的文件需要一定的时间,会导致严重的复制延时。为了避免复制延时,需要暂时为中继日志创建硬链接,因为在linux系统中通过硬链接删除大文件速度会很快。(在mysql数据库中,删除大表时,通常也采用建立硬链接的方式)

MHA节点中包含了pure_relay_logs命令工具,它可以为中继日志创建硬链接,执行SET GLOBAL relay_log_purge=1,等待几秒钟以便SQL线程切换到新的中继日志,再执行SET GLOBAL relay_log_purge=0。

pure_relay_logs脚本参数如下所示:

--user mysql                      用户名
--password mysql                  密码
--port                            端口号
--workdir                         指定创建relay log的硬链接的位置,默认是/var/tmp,由于系统不同分区创建硬链接文件会失败,故需要执行硬链接具体位置,成功执行脚本后,硬链接的中继日志文件被删除
--disable_relay_log_purge         默认情况下,如果relay_log_purge=1,脚本会什么都不清理,自动退出,通过设定这个参数,当relay_log_purge=1的情况下会将relay_log_purge设置为0。清理relay log之后,最后将参数设置为OFF。

设置定期清理relay脚本(两台slave服务器)

cat >> purge_relay_log.sh  << EOF
#!/bin/bash
user=root
passwd=123456
port=3306
log_dir=‘/data/masterha/log‘
work_dir=‘/data‘
purge=‘/usr/local/bin/purge_relay_logs‘

if [ ! -d $log_dir ]
then
   mkdir $log_dir -p
fi

$purge --user=$user --password=$passwd --disable_relay_log_purge --port=$port --workdir=$work_dir >> $log_dir/purge_relay_logs.log 2>&1
EOF

添加到crontab定期执行

[[email protected] ~]# crontab -l
0 4 * * * /bin/bash /root/purge_relay_log.sh

purge_relay_logs脚本删除中继日志不会阻塞SQL线程。下面我们手动执行看看什么情况。

[[email protected] ~]# purge_relay_logs --user=root --password=123456 --port=3306 -disable_relay_log_purge --workdir=/data/
2014-04-20 15:47:24: purge_relay_logs script started.
 Found relay_log.info: /data/mysql/relay-log.info
 Removing hard linked relay log files server03-relay-bin* under /data/.. done.
 Current relay log file: /data/mysql/server03-relay-bin.000002
 Archiving unused relay log files (up to /data/mysql/server03-relay-bin.000001) ...
 Creating hard link for /data/mysql/server03-relay-bin.000001 under /data//server03-relay-bin.000001 .. ok.
 Creating hard links for unused relay log files completed.
 Executing SET GLOBAL relay_log_purge=1; FLUSH LOGS; sleeping a few seconds so that SQL thread can delete older relay log files (if it keeps up); SET GLOBAL relay_log_purge=0; .. ok.
 Removing hard linked relay log files server03-relay-bin* under /data/.. done.
2014-04-20 15:47:27: All relay log purging operations succeeded.

检查SSH配置

检查MHA Manger到所有MHA Node的SSH连接状态

[[email protected] 3306]# masterha_check_ssh --conf=/data/mha/3306/mha.cnf
Fri Nov 16 11:26:15 2018 - [info] Reading default configuration from /etc/masterha_default.cnf..
Fri Nov 16 11:26:15 2018 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Fri Nov 16 11:26:15 2018 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Fri Nov 16 11:26:15 2018 - [info] Starting SSH connection tests..
Fri Nov 16 11:26:16 2018 - [debug]
Fri Nov 16 11:26:15 2018 - [debug]  Connecting via SSH from [email protected](192.168.0.101:22) to [email protected](192.168.0.102:22)..
Fri Nov 16 11:26:16 2018 - [debug]   ok.
Fri Nov 16 11:26:16 2018 - [debug]  Connecting via SSH from [email protected](192.168.0.101:22) to [email protected](192.168.0.103:22)..
Fri Nov 16 11:26:16 2018 - [debug]   ok.
Fri Nov 16 11:26:17 2018 - [debug]
Fri Nov 16 11:26:16 2018 - [debug]  Connecting via SSH from [email protected](192.168.0.102:22) to [email protected](192.168.0.101:22)..
Fri Nov 16 11:26:16 2018 - [debug]   ok.
Fri Nov 16 11:26:16 2018 - [debug]  Connecting via SSH from [email protected](192.168.0.102:22) to [email protected](192.168.0.103:22)..
Fri Nov 16 11:26:16 2018 - [debug]   ok.
Fri Nov 16 11:26:18 2018 - [debug]
Fri Nov 16 11:26:16 2018 - [debug]  Connecting via SSH from [email protected](192.168.0.103:22) to [email protected](192.168.0.101:22)..
Fri Nov 16 11:26:17 2018 - [debug]   ok.
Fri Nov 16 11:26:17 2018 - [debug]  Connecting via SSH from [email protected](192.168.0.103:22) to [email protected](192.168.0.102:22)..
Fri Nov 16 11:26:17 2018 - [debug]   ok.
Fri Nov 16 11:26:18 2018 - [info] All SSH connection tests passed successfully.

检查整个复制环境状况

通过masterha_check_repl脚本查看整个集群的状态

[[email protected] 3306]# masterha_check_repl --conf=/data/mha/3306/mha.cnf
Fri Nov 16 11:27:25 2018 - [info] Reading default configuration from /etc/masterha_default.cnf..
Fri Nov 16 11:27:25 2018 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Fri Nov 16 11:27:25 2018 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Fri Nov 16 11:27:25 2018 - [info] MHA::MasterMonitor version 0.58.
Fri Nov 16 11:27:26 2018 - [info] GTID failover mode = 0
Fri Nov 16 11:27:26 2018 - [info] Dead Servers:
Fri Nov 16 11:27:26 2018 - [info] Alive Servers:
Fri Nov 16 11:27:26 2018 - [info]   192.168.0.101(192.168.0.101:3306)
Fri Nov 16 11:27:26 2018 - [info]   192.168.0.102(192.168.0.102:3306)
Fri Nov 16 11:27:26 2018 - [info]   192.168.0.103(192.168.0.103:3306)
Fri Nov 16 11:27:26 2018 - [info] Alive Slaves:
Fri Nov 16 11:27:26 2018 - [info]   192.168.0.102(192.168.0.102:3306)  Version=5.7.23-log (oldest major version between slaves) log-bin:enabled
Fri Nov 16 11:27:26 2018 - [info]     Replicating from 192.168.0.101(192.168.0.101:3306)
Fri Nov 16 11:27:26 2018 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Nov 16 11:27:26 2018 - [info]   192.168.0.103(192.168.0.103:3306)  Version=5.7.23-log (oldest major version between slaves) log-bin:enabled
Fri Nov 16 11:27:26 2018 - [info]     Replicating from 192.168.0.101(192.168.0.101:3306)
Fri Nov 16 11:27:26 2018 - [info]     Not candidate for the new Master (no_master is set)
Fri Nov 16 11:27:26 2018 - [info] Current Alive Master: 192.168.0.101(192.168.0.101:3306)
Fri Nov 16 11:27:26 2018 - [info] Checking slave configurations..
Fri Nov 16 11:27:26 2018 - [info] Checking replication filtering settings..
Fri Nov 16 11:27:26 2018 - [info]  binlog_do_db= , binlog_ignore_db=
Fri Nov 16 11:27:26 2018 - [info]  Replication filtering check ok.
Fri Nov 16 11:27:26 2018 - [info] GTID (with auto-pos) is not supported
Fri Nov 16 11:27:26 2018 - [info] Starting SSH connection tests..
Fri Nov 16 11:27:29 2018 - [info] All SSH connection tests passed successfully.
Fri Nov 16 11:27:29 2018 - [info] Checking MHA Node version..
Fri Nov 16 11:27:29 2018 - [info]  Version check ok.
Fri Nov 16 11:27:29 2018 - [info] Checking SSH publickey authentication settings on the current master..
Fri Nov 16 11:27:29 2018 - [info] HealthCheck: SSH to 192.168.0.101 is reachable.
Fri Nov 16 11:27:30 2018 - [info] Master MHA Node version is 0.58.
Fri Nov 16 11:27:30 2018 - [info] Checking recovery script configurations on 192.168.0.101(192.168.0.101:3306)..
Fri Nov 16 11:27:30 2018 - [info]   Executing command: save_binary_logs --command=test --start_pos=4 --binlog_dir=/usr/local/mysql/data --output_file=/var/tmp/save_binary_logs_test --manager_version=0.58 --start_file=master-bin.000003
Fri Nov 16 11:27:30 2018 - [info]   Connecting to [email protected](192.168.0.101:22)..
  Creating /var/tmp if not exists..    ok.
  Checking output directory is accessible or not..
   ok.
  Binlog found at /usr/local/mysql/data, up to master-bin.000003
Fri Nov 16 11:27:30 2018 - [info] Binlog setting check done.
Fri Nov 16 11:27:30 2018 - [info] Checking SSH publickey authentication and checking recovery script configurations on all alive slave servers..
Fri Nov 16 11:27:30 2018 - [info]   Executing command : apply_diff_relay_logs --command=test --slave_user=‘root‘ --slave_host=192.168.0.102 --slave_ip=192.168.0.102 --slave_port=3306 --workdir=/var/tmp --target_version=5.7.23-log --manager_version=0.58 --relay_log_info=/usr/local/mysql/data/relay-log.info  --relay_dir=/usr/local/mysql/data/  --slave_pass=xxx
Fri Nov 16 11:27:30 2018 - [info]   Connecting to [email protected](192.168.0.102:22)..
  Checking slave recovery environment settings..
    Opening /usr/local/mysql/data/relay-log.info ... ok.
    Relay log found at /usr/local/mysql/data, up to slave-relay-bin.000002
    Temporary relay log file is /usr/local/mysql/data/slave-relay-bin.000002
    Checking if super_read_only is defined and turned on.. not present or turned off, ignoring.
    Testing mysql connection and privileges..
mysql: [Warning] Using a password on the command line interface can be insecure.
 done.
    Testing mysqlbinlog output.. done.
    Cleaning up test file(s).. done.
Fri Nov 16 11:27:30 2018 - [info]   Executing command : apply_diff_relay_logs --command=test --slave_user=‘root‘ --slave_host=192.168.0.103 --slave_ip=192.168.0.103 --slave_port=3306 --workdir=/var/tmp --target_version=5.7.23-log --manager_version=0.58 --relay_log_info=/usr/local/mysql/data/relay-log.info  --relay_dir=/usr/local/mysql/data/  --slave_pass=xxx
Fri Nov 16 11:27:30 2018 - [info]   Connecting to [email protected](192.168.0.103:22)..
  Checking slave recovery environment settings..
    Opening /usr/local/mysql/data/relay-log.info ... ok.
    Relay log found at /usr/local/mysql/data, up to slave-relay-bin.000002
    Temporary relay log file is /usr/local/mysql/data/slave-relay-bin.000002
    Checking if super_read_only is defined and turned on.. not present or turned off, ignoring.
    Testing mysql connection and privileges..
mysql: [Warning] Using a password on the command line interface can be insecure.
 done.
    Testing mysqlbinlog output.. done.
    Cleaning up test file(s).. done.
Fri Nov 16 11:27:30 2018 - [info] Slaves settings check done.
Fri Nov 16 11:27:30 2018 - [info]
192.168.0.101(192.168.0.101:3306) (current master)
 +--192.168.0.102(192.168.0.102:3306)
 +--192.168.0.103(192.168.0.103:3306)

Fri Nov 16 11:27:30 2018 - [info] Checking replication health on 192.168.0.102..
Fri Nov 16 11:27:30 2018 - [info]  ok.
Fri Nov 16 11:27:30 2018 - [info] Checking replication health on 192.168.0.103..
Fri Nov 16 11:27:30 2018 - [info]  ok.
Fri Nov 16 11:27:30 2018 - [info] Checking master_ip_failover_script status:
Fri Nov 16 11:27:30 2018 - [info]   /etc/mha/scripts/master_ip_failover --command=status --ssh_user=root --orig_master_host=192.168.0.101 --orig_master_ip=192.168.0.101 --orig_master_port=3306 

IN SCRIPT TEST====/sbin/ifconfig eth0:1 down==/sbin/ifconfig eth0:1 192.168.0.88/24===

Checking the Status of the script.. OK
Fri Nov 16 11:27:30 2018 - [info]  OK.
Fri Nov 16 11:27:30 2018 - [warning] shutdown_script is not defined.
Fri Nov 16 11:27:30 2018 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.
[[email protected] 3306]#

检查MHA Manager的状态

[[email protected] 3306]# masterha_check_status  --conf=/data/mha/3306/mha.cnf
mha is stopped(2:NOT_RUNNING).

开启MHA Manager监控

vi manager.sh
nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /data/mha/3306/manager.log 2>&1 &

./manager.sh

启动参数介绍:

--remove_dead_master_conf 该参数代表当发生主从切换后,老的主库的ip将会从配置文件中移除。

--manger_log 日志存放位置

--ignore_last_failover 在缺省情况下,如果MHA检测到连续发生宕机,且两次宕机间隔不足8小时的话,则不会进行Failover,之所以这样限制是为了避免ping-pong效应。该参数代表忽略上次MHA触发切换产生的文件,默认情况下,MHA发生切换后会在日志目录,也就是上面我设置的/data产生app1.failover.complete文件,下次再次切换的时候如果发现该目录下存在该文件将不允许触发切换,除非在第一次切换后收到删除该文件,为了方便,这里设置为--ignore_last_failover。

配置VIP

vip配置可以采用两种方式,一种通过keepalived的方式管理虚拟ip的浮动;另外一种通过脚本方式启动虚拟ip的方式(即不需要keepalived或者heartbeat类似的软件)。

(一)使用脚本管理VIP 的方式, 修改master_ip_failover 脚本,使用脚本管理VIP:

通过配置/etc/mha/scripts/master_ip_failover实现

(二)keepalived方式管理虚拟ip

下载软件进行并进行安装(两台master,准确的说一台是master,另外一台是备选master,在没有切换以前是slave):

[[email protected] ~]# wget http://www.keepalived.org/software/keepalived-1.2.12.tar.gz

tar xf keepalived-1.2.12.tar.gz
cd keepalived-1.2.12
./configure --prefix=/usr/local/keepalived
make &&  make install
cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/
cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
mkdir /etc/keepalived
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
cp /usr/local/keepalived/sbin/keepalived /usr/sbin/

(2)配置keepalived的配置文件,在master上配置(192.168.0.101)

[[email protected] ~]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
     notification_email {
     [email protected]
   }
   notification_email_from [email protected]
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id MySQL-HA
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth1
    virtual_router_id 51
    priority 150
    advert_int 1
    nopreempt

    authentication {
    auth_type PASS
    auth_pass 1111
    }

    virtual_ipaddress {
        192.168.0.88
    }
}

[[email protected] ~]# 

其中router_id MySQL HA表示设定keepalived组的名称,将192.168.0.88这个虚拟ip绑定到该主机的eth1网卡上,并且设置了状态为backup模式,将keepalived的模式设置为非抢占模式(nopreempt),priority 150表示设置的优先级为150。下面的配置略有不同,但是都是一个意思。
在候选master上配置(192.168.0.60)

[[email protected] ~]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
     notification_email {
     [email protected]
   }
   notification_email_from [email protected]
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id MySQL-HA
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth1
    virtual_router_id 51
    priority 120
    advert_int 1
    nopreempt

    authentication {
    auth_type PASS
    auth_pass 1111
    }

    virtual_ipaddress {
        192.168.0.88
    }
}

[[email protected] ~]# 

(3)启动keepalived服务,在master上启动并查看日志

[[email protected] ~]# /etc/init.d/keepalived start
Starting keepalived:                                       [  OK  ]
[[email protected] ~]# tail -f /var/log/messages
Apr 20 20:22:16 192 Keepalived_healthcheckers[15334]: Opening file ‘/etc/keepalived/keepalived.conf‘.
Apr 20 20:22:16 192 Keepalived_healthcheckers[15334]: Configuration is using : 7231 Bytes
Apr 20 20:22:16 192 kernel: IPVS: Connection hash table configured (size=4096, memory=64Kbytes)
Apr 20 20:22:16 192 kernel: IPVS: ipvs loaded.
Apr 20 20:22:16 192 Keepalived_healthcheckers[15334]: Using LinkWatch kernel netlink reflector...
Apr 20 20:22:19 192 Keepalived_vrrp[15335]: VRRP_Instance(VI_1) Transition to MASTER STATE
Apr 20 20:22:20 192 Keepalived_vrrp[15335]: VRRP_Instance(VI_1) Entering MASTER STATE
Apr 20 20:22:20 192 Keepalived_vrrp[15335]: VRRP_Instance(VI_1) setting protocol VIPs.
Apr 20 20:22:20 192 Keepalived_vrrp[15335]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth1 for 192.168.0.88
Apr 20 20:22:20 192 Keepalived_healthcheckers[15334]: Netlink reflector reports IP 192.168.0.88 added
Apr 20 20:22:25 192 Keepalived_vrrp[15335]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth1 for 192.168.0.88

发现已经将虚拟ip 192.168.0.88绑定了网卡eth1上。
(4)查看绑定情况

[[email protected] ~]# ip addr | grep eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 192.168.0.50/24 brd 192.168.0.255 scope global eth1
    inet 192.168.0.88/32 scope global eth1
[[email protected] ~]# 

在另外一台服务器,候选master上启动keepalived服务,并观察

[[email protected] ~]# /etc/init.d/keepalived start ; tail -f /var/log/messages
Starting keepalived:                                       [  OK  ]
Apr 20 20:26:18 192 Keepalived_vrrp[9472]: Registering gratuitous ARP shared channel
Apr 20 20:26:18 192 Keepalived_vrrp[9472]: Opening file ‘/etc/keepalived/keepalived.conf‘.
Apr 20 20:26:18 192 Keepalived_vrrp[9472]: Configuration is using : 62976 Bytes
Apr 20 20:26:18 192 Keepalived_vrrp[9472]: Using LinkWatch kernel netlink reflector...
Apr 20 20:26:18 192 Keepalived_vrrp[9472]: VRRP_Instance(VI_1) Entering BACKUP STATE
Apr 20 20:26:18 192 Keepalived_vrrp[9472]: VRRP sockpool: [ifindex(3), proto(112), unicast(0), fd(10,11)]
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Netlink reflector reports IP 192.168.80.138 added
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Netlink reflector reports IP 192.168.0.60 added
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Netlink reflector reports IP fe80::20c:29ff:fe9d:6a9e added
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Netlink reflector reports IP fe80::20c:29ff:fe9d:6aa8 added
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Registering Kernel netlink reflector
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Registering Kernel netlink command channel
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Opening file ‘/etc/keepalived/keepalived.conf‘.
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Configuration is using : 7231 Bytes
Apr 20 20:26:18 192 kernel: IPVS: Registered protocols (TCP, UDP, AH, ESP)
Apr 20 20:26:18 192 kernel: IPVS: Connection hash table configured (size=4096, memory=64Kbytes)
Apr 20 20:26:18 192 kernel: IPVS: ipvs loaded.
Apr 20 20:26:18 192 Keepalived_healthcheckers[9471]: Using LinkWatch kernel netlink reflector...

从上面的信息可以看到keepalived已经配置成功。
注意:

上面两台服务器的keepalived都设置为了BACKUP模式,在keepalived中2种模式,分别是master->backup模式和backup->backup模式。这两种模式有很大区别。在master->backup模式下,一旦主库宕机,虚拟ip会自动漂移到从库,当主库修复后,keepalived启动后,还会把虚拟ip抢占过来,即使设置了非抢占模式(nopreempt)抢占ip的动作也会发生。在backup->backup模式下,当主库宕机后虚拟ip会自动漂移到从库上,当原主库恢复和keepalived服务启动后,并不会抢占新主的虚拟ip,即使是优先级高于从库的优先级别,也不会发生抢占。为了减少ip漂移次数,通常是把修复好的主库当做新的备库。

(5)MHA引入keepalived(MySQL服务进程挂掉时通过MHA 停止keepalived):

要想把keepalived服务引入MHA,我们只需要修改切换是触发的脚本文件master_ip_failover即可,在该脚本中添加在master发生宕机时对keepalived的处理。

编辑脚本/usr/local/bin/master_ip_failover,修改后如下,我对perl不熟悉,所以我这里完整贴出该脚本(主库上操作,192.168.0.50)。

在MHA Manager修改脚本修改后的内容如下(参考资料比较少):

#!/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 = ‘192.168.0.88‘;
my $ssh_start_vip = "/etc/init.d/keepalived start";
my $ssh_stop_vip = "/etc/init.d/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 ([email protected]) {
            warn "Got Error: [email protected]\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 ([email protected]) {
            warn [email protected];
            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 master
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";
}

现在已经修改这个脚本了,我们现在打开在上面提到过的参数,再检查集群状态,看是否会报错。

[[email protected] ~]# grep ‘master_ip_failover_script‘ /etc/masterha/app1.cnf
master_ip_failover_script= /usr/local/bin/master_ip_failover
[[email protected] ~]# 

[[email protected] ~]# masterha_check_repl --conf=/etc/masterha/app1.cnf
Sun Apr 20 23:10:01 2014 - [info] Slaves settings check done.
Sun Apr 20 23:10:01 2014 - [info]
192.168.0.50 (current master)
 +--192.168.0.60
 +--192.168.0.70

Sun Apr 20 23:10:01 2014 - [info] Checking replication health on 192.168.0.60..
Sun Apr 20 23:10:01 2014 - [info]  ok.
Sun Apr 20 23:10:01 2014 - [info] Checking replication health on 192.168.0.70..
Sun Apr 20 23:10:01 2014 - [info]  ok.
Sun Apr 20 23:10:01 2014 - [info] Checking master_ip_failover_script status:
Sun Apr 20 23:10:01 2014 - [info]   /usr/local/bin/master_ip_failover --command=status --ssh_user=root --orig_master_host=192.168.0.50 --orig_master_ip=192.168.0.50 --orig_master_port=3306
Sun Apr 20 23:10:01 2014 - [info]  OK.
Sun Apr 20 23:10:01 2014 - [warning] shutdown_script is not defined.
Sun Apr 20 23:10:01 2014 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.

可以看见已经没有报错了。哈哈
 /usr/local/bin/master_ip_failover添加或者修改的内容意思是当主库数据库发生故障时,会触发MHA切换,MHA Manager会停掉主库上的keepalived服务,触发虚拟ip漂移到备选从库,从而完成切换。当然可以在keepalived里面引入脚本,这个脚本监控mysql是否正常运行,如果不正常,则调用该脚本杀掉keepalived进程。

常用命令

检查整个复制环境状况
masterha_check_repl --conf=/data/mha/3306/mha.cnf

检查SSH配置
masterha_check_ssh --conf=/data/mha/3306/mha.cnf

检测当前MHA运行状态
masterha_check_status  --conf=/data/mha/3306/mha.cnf

启动MHA
vi manager.sh
nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /data/mha/3306/manager.log 2>&1 & 

停止MHA
masterha_stop  --conf=/data/mha/3306/mha.cnf

以上就是MHA 安装配置的全过程,以下进行简单的测试。

(1)failover 测试

手动kill 了master 上面的mysqld 进程,查看切换状态

日志待补全

以上是切换的全日志过程,我们可以看到MHA 切换主要经历以下步骤:

1.配置文件检查阶段,这个阶段会检查整个集群配置文件配置

2.宕机的master处理,这个阶段包括虚拟ip摘除操作,主机关机操作(这个我这里还没有实现,需要研究)

3.复制dead maste和最新slave相差的relay log,并保存到MHA Manger具体的目录下

4.识别含有最新更新的slave

5.应用从master保存的二进制日志事件(binlog events)

6.提升一个slave为新的master进行复制

7.使其他的slave连接新的master进行复制

(2)手动switch

在许多情况下, 需要将现有的主服务器迁移到另外一台服务器上。 比如主服务器硬件故障,RAID 控制卡需要重建,将主服务器移到性能更好的服务器上等等。维护主服务器引起性能下降, 导致停机时间至少无法写入数据。 另外, 阻塞或杀掉当前运行的会话会导致主主之间数据不一致的问题发生。 MHA 提供快速切换和优雅的阻塞写入,这个切换过程只需要 0.5-2s 的时间,这段时间内数据是无法写入的。在很多情况下,0.5-2s 的阻塞写入是可以接受的。因此切换主服务器不需要计划分配维护时间窗口。

MHA在线切换的大概过程:
1.检测复制设置和确定当前主服务器
2.确定新的主服务器
3.阻塞写入到当前主服务器
4.等待所有从服务器赶上复制
5.授予写入到新的主服务器
6.重新设置从服务器

注意,在线切换的时候应用架构需要考虑以下两个问题:

1.自动识别master和slave的问题(master的机器可能会切换),如果采用了vip的方式,基本可以解决这个问题。

2.负载均衡的问题(可以定义大概的读写比例,每台机器可承担的负载比例,当有机器离开集群时,需要考虑这个问题)

为了保证数据完全一致性,在最快的时间内完成切换,MHA的在线切换必须满足以下条件才会切换成功,否则会切换失败。

1.所有slave的IO线程都在运行

2.所有slave的SQL线程都在运行

3.所有的show slave status的输出中Seconds_Behind_Master参数小于或者等于running_updates_limit秒,如果在切换过程中不指定running_updates_limit,那么默认情况下running_updates_limit为1秒。

4.在master端,通过show processlist输出,没有一个更新花费的时间大于running_updates_limit秒。

执行切换

[[email protected] tmp]# masterha_master_switch --conf=/data/mha/3306/mha.cnf --master_state=alive --new_master_host=192.168.0.102 --new_master_port=3306 --orig_master_is_new_slave --running_updates_limit=10000

其中参数的意思:

--orig_master_is_new_slave 切换时加上此参数是将原 master 变为 slave 节点,如果不加此参数,原来的 master 将不启动

--running_updates_limit=10000,故障切换时,候选master 如果有延迟的话, mha 切换不能成功,加上此参数表示延迟在此时间范围内都可切换(单位为s),但是切换的时间长短是由recover 时relay 日志的大小决定

参考来源:

http://www.cnblogs.com/gomysql/p/3675429.html

http://www.178linux.com/61111

https://www.cnblogs.com/rayment/p/7355093.html

原文地址:https://www.cnblogs.com/EikiXu/p/9968454.html

时间: 2024-09-30 10:56:23

MySQL 之 MHA + ProxySQL + keepalived 实现读写分离,高可用(一)的相关文章

MHA+ProxySQL实现读写分离高可用

最近在研究ProxySQL,觉得还挺不错的,所以就简单的折腾了一下,ProxySQL目前也是Percona在推荐的一个读写分离的中间件.关于详细的介绍可以参考官方文档.https://github.com/sysown/proxysql/wiki 本文主要介绍的是MHA+ProxySQL读写分离以及高可用,ProxySQL的细节请参考文档,目前已经有人写发非常详细了,文章最后会给出链接.当然和Group Replication,PXC搭配那是更完美的.关于MHA的配置参考我前面的文章,本文就不再

mysql的MHA之Atlas 配置, 读写分离实现

1.安装 Atlas #在主库安装,进入安装包目录 [[email protected]-db01 ~]# cd /home/oldboy/tools/ #下载Atlas [[email protected]-db01 tools]# wget httpss://github.com/Qihoo360/Atlas/releases/download/2.2.1/Atlas-2.2.1.el6.x86_64.rpm #安装 [[email protected]-db01 tools]# rpm -

Atlas读写分离[高可用]

Atlas下载地址: https://github.com/Qihoo360/Atlas/releases Atlas是出于360的, 比mysql-proxy更稳定, 部署起来更方便. 环境: proxy:192.168.99.60 master:192.168.99.61 slave:192.168.99.62 1.安装 由于我使用的是rpm包, 直接安装 rpm -ivh Atlas-2.2.el6.x86_64.rpm 就是这么简单, 安装完成. 目录默认在: /usr/local/my

MySQL深入10-利用Ameoba实现读写分离

本篇博文主要介绍利用Ameoba实现MySQL的读写分离功能,而MySQL官方的mysql-proxy需要自己编写复杂的lua脚本才能实现同样的功能,故个人还是倾向使用amoeba哦: =================================================================== 1 简介 2 准备 2.1 时间同步 2.2 配置MySQL主从复制架构 3 ameoba安装配置 3.1 安装配置JDK 3.2 安装ameoba 3.3 配置ameoba 3

MySQL主从复制(Master-Slave)与读写分离(MySQL-Proxy)实践 转载

http://heylinux.com/archives/1004.html MySQL主从复制(Master-Slave)与读写分离(MySQL-Proxy)实践 Mysql作为目前世界上使用最广泛的免费数据库,相信所有从事系统运维的工程师都一定接触过.但在实际的生产环境中,由单台Mysql作为独立的数据库是完全不能满足实际需求的,无论是在安全性,高可用性以及高并发等各个方面. 因此,一般来说都是通过 主从复制(Master-Slave)的方式来同步数据,再通过读写分离(MySQL-Proxy

Mysql一主多从和读写分离配置简记

Mysql一主多从和读写分离配置简记 标签: mysql数据库服务器class数据库servermanager 2012-05-30 16:44 14981人阅读 评论(1) 收藏 举报  分类: 数据库 版权声明:本文为博主原创文章,未经博主允许不得转载. 近期开发的系统中使用MySql作为数据库,由于数据涉及到Money,所以不得不慎重.同时,用户对最大访问量也提出了要求.为了避免Mysql成为性能瓶颈并具备很好的容错能力,特此实现主从热备和读写分离.在此简做纪要,以备日后所用! 一.配置主

linux下mysql基于mycat做主从复制和读写分离之基础篇

Linux下mysql基于mycat实现主从复制和读写分离1.基础设施 两台虚拟机:172.20.79.232(主) 172.20.79.233(从) 1.1软件设施 mysql5.6.39 , mycat1.6-RELEASE jdk1.7及其以上版本2.实现步骤一(mycat实现读写分离) 1.首先在两台服务器安装mysql 1.下载mysql的repo源 $ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rp

docker部署django项目、mysql主从搭建、django实现读写分离

目录 docker部署django项目 1.1 基于python基础镜像 将本地django项目打包并发到远程服务器上 将服务器上的.zip django项目解压 部署的具体流程 1.2 基于dockerfile 2 mysql主从搭建 3 django实现读写分离 我们使用python镜像源构建的出的容器,是一个纯净的debian系统下的python环境,在容器内部要想安装vim.nginx等,就不能使用yum install ...了,需要使用apt-get源.首先更新apt-get源,使用

Keepalived+Haproxy双主高可用负载均衡web和mysql综合实验

日期及版本:2014.5.4v1.0 架构图 实验目的: 1.Haproxy+Keepalived双主双机高可用模型,keepalived为Haproxy主从提供高可用保证haproxy-master若挂掉haproxy-backup能无缝接管,haproxy为后端Web提供负载均衡,缓解并发压力,实现WEB站点负载均衡+高可用性: 2. Haproxy反代web做动静分离: 3. Haproxy反代mysql 算法leastconn和roundrobin的不同效果: 系统环境: OS:cent