百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

MySQL + Keepalived 双主热备搭建

toyiye 2024-06-21 12:08 9 浏览 0 评论

什么是双主复制

在传统的主从复制架构中,从库仅仅是作为主库数据的备份,当主库发生故障时,数据库将停止对外提供服务,并且主库故障后手动进行主从切换的过程也较为繁琐。为了解决这个问题,可以采用 MySQL 双主模式,其中一台主库提供服务,另一台作为热备。结合 keepalived 使用虚拟 IP 对外提供服务,一旦主库发生故障,备库可以在很短的时间内接管服务。


机器规划

搭建 MySQL 双主同步

准备工作

创建相关目录

#创建用户
userdel -r mysql
groupadd mysql 
useradd -r -g mysql -s /bin/false mysql 




#创建目录
# /mysql/app/                                   MySQL 数据库软件根目录
# /mysql/data/3306/data/                        MySQL 数据文件目录
# /mysql/log/3306/binlog                        MySQL 二进制日志目录
# /mysql/log/3306/relaylog                      MySQL 中继日志目录
# /mysql/backup/3306/xtrabackup/target_dir      MySQL xtrabackup 物理备份目录
# /mysql/backup/3306/mysqldump                  MySQL mysqldump 逻辑备份目录
# /mysql/script                                 MySQL 常用脚本存放目录


mkdir -p /mysql/app/            
mkdir -p /mysql/data/3308/data/                                 
mkdir -p /mysql/log/3308/binlog                                 
mkdir -p /mysql/log/3308/relaylog                               
mkdir -p /mysql/backup/3308/xtrabackup/target_dir               
mkdir -p /mysql/backup/3308/mysqldump    
mkdir -p /mysql/script                                                                      


#给目录授权
chown -R mysql:mysql /mysql

下载并解压 MySQL 安装包

MySQL 压缩包下载地址:https://dev.mysql.com/downloads/mysql/5.7.html


#解压压缩包
tar zxvf mysql-5.7.29-linux-glibc2.12-x86_64.tar.gz -C /mysql/app
mv /mysql/app/mysql-5.7.29-linux-glibc2.12-x86_64 /mysql/app/mysql
chown -R mysql:mysql /mysql  

配置环境变量

##将MySQL目录添加环境变量##
cat >> ~/.bash_profile <<-EOF
export PATH=$PATH:/mysql/app/mysql/bin
EOF


source ~/.bash_profile

初始化主库 A

主库 A 重要配置如下:


  • 开启 binlog:log_bin=binlog 目录
  • 设置 server_id:server_id = 1,主库 A 的 server_id 和主库 B 要不一样。
  • 针对 GTIP 的方式同步有两个参数必须设置:
  • gtid_mode=on
  • enforce_gtid_consistency=on
  • 防止主键冲突:
  • 设置自增主键步长,通常有几个主库 就写几,避免主键冲突:auto_increment_increment=2
  • 设置自增主键起始值,第一个主库为 1,第二个主库为 2,以此类推:auto_increment_offset=1


关于防止主键冲突的两个参数的详解可以查看 这篇博客。


#主机名和端口号作为目录名的一部分
HostName=`hostname`
MySql_Port=3308
#IP地址
Ip=192.168.1.36
#master server_id 要和 slave 不一样
Server_Id=1


cat > /mysql/data/$MySql_Port/my.cnf <<-EOF
#------------------------------------ 
#客户端设置
#------------------------------------
[client]
port=$MySql_Port
socket =/mysql/data/$MySql_Port/mysql.sock
default-character-set=utf8 




#------------------------------------ 
#mysql连接工具设置
#------------------------------------
[mysql]
prompt="\\u@\\h \\d \\r:\\m:\\s>" #登录时显示登录的用户名、服务器地址、默认数据库名、当前时间
auto-rehash #读取表信息和列信息,可以在连上终端后开启tab补齐功能。
default-character-set=utf8 #默认字符集


#------------------------------------ 
#基本设置
#------------------------------------
[mysqld]
bind_address=0.0.0.0  #监听本地所有地址
port=$MySql_Port  #端口号
user=mysql  #用户
basedir=/mysql/app/mysql  #安装路径
datadir=/mysql/data/$MySql_Port/data  #MySQL数据目录
socket=/mysql/data/$MySql_Port/mysql.sock #用于本地连接的socket文件目录
pid-file=/mysql/data/$MySql_Port/mysql.pid #进程ID文件的目录。
character-set-server=utf8 #默认字符集


#------------------------------------ 
#log setting 日志设置
#------------------------------------
long_query_time=10 #慢查询时间,超过 10 秒则认为是慢查询
slow_query_log=ON #启用慢查询日志
slow_query_log_file=/mysql/log/$MySql_Port/${HostName}-query.log #慢查询日志目录
log_queries_not_using_indexes=1 #记录未使用索引的语句
log_slow_admin_statements=1 #慢查询也记录那些慢的optimize table,analyze table和alter table语句
log-error=/mysql/log/$MySql_Port/${HostName}-error.log #错误日志目录




#------------------------------------ 
#master modify parameter 主库A复制更改参数
#------------------------------------
server_id=$Server_Id #master和slave server_id 需要不同


#------------------------------------ 
#slave parameter  主库B参数
#------------------------------------
relay_log=/mysql/log/$MySql_Port/relaylog/${HostName}-relaylog #中继日志目录
relay-log-index=/mysql/log/$MySql_Port/relaylog/${HostName}-relay.index  #中继日志索引目录
log_slave_updates=1 #主库B从主库A复制的数据会写入主库B binlog 日志文件里,默认是不写入
read_only=0  #主库B读写权限
relay_log_purge=1 #自动清空不再需要中继日志




#二进制日志参数配置
log_bin=/mysql/log/$MySql_Port/binlog/${HostName}-binlog  #binlog目录
log_bin_index=/mysql/log/$MySql_Port/binlog/${HostName}-binlog.index  #指定索引文件的位置
binlog_format=row #行模式复制,默认是 row
binlog_rows_query_log_events=on #在 row 模式下,开启该参数,可以将把 sql 语句打印到 binlog 日志里面,方便查看
binlog_cache_size=1M #事务能够使用的最大 binlog 缓存空间。
max_binlog_size=2048M #binlog 文件最大空间,达到该大小时切分文件
expire_logs_days=7 #设置自动删除 binlog 文件的天数。
sync_binlog=1 #表示每次事务的 binlog 都会fsync持久化到磁盘,MySQL 5.7.7 之后默认为1,之前的版本默认为0
innodb_flush_log_at_trx_commit=1 #表示每次事务的 redo log 都直接持久化到磁盘,默认值为1


#------------------------------------ 
#GTID Settings GTID 同步复制设置
#------------------------------------
gtid_mode=on  #开启GTID同步
enforce_gtid_consistency=on #强制事务一致,确保 GTID 的安全,在事务中就不能创建和删除临时表
binlog_gtid_simple_recovery=1 #这个变量用于在 MySQL 重启或启动的时候寻找 GTIDs 过程中,控制 binlog 如何遍历的算法




#------------------------------------ 
#避免主键冲突设置
#------------------------------------
auto_increment_increment=2  #自增主键步长,通常有几个主库A就写几,避免主键冲突
auto_increment_offset=1  #设置自增主键起始值,第一个主库A为1,第二个主库A为2,以此类推
EOF


初始化主库 A:


mysqld \
--defaults-file=/mysql/data/3308/my.cnf \
--initialize --user=mysql \
--basedir=/mysql/app/mysql \
--datadir=/mysql/data/3308/data


配置 MySQL 启动脚本:


cp /mysql/app/mysql/support-files/mysql.server /etc/init.d/mysql_3308
ln -sf /etc/init.d/mysql_3308 /usr/lib/systemd/system/mysql_3308


#修改启动脚本##
vim /etc/init.d/mysql_3308


basedir=/mysql/app/mysql
datadir=/mysql/data/3308/data
mysqld_pid_file_path=/mysql/data/3308/mysql.pid


#在$bindir/mysqld_safe 后面添加,注意 --defaults-file 要放在第一个
--defaults-file="/mysql/data/3308/my.cnf" 


systemctl daemon-reload



启动 MySQL,修改密码,运行远程登录:


#启动、MySQL服务
systemctl start mysql_3308


#获取MySQL临时密码
Passwd=`cat /mysql/log/3308/*-error.log |grep "root@localhost:"|awk -F ' ' '{print $11}'`
echo $Passwd


#通过本地 socket 登录、修改密码
mysql -uroot -p$Passwd -S /mysql/data/3308/mysql.sock
alter user 'root'@'localhost' identified by  "123456";


#允许远程登录
grant all privileges on *.* to root@'%' identified by '123456';


#刷新权限
flush privileges;

初始化主库 B

主库 B 配置文件,主要是 ip 地址,server_id 以及 auto_increment_offset 的配置和主库 A 不一样,其余配置和主库 A 一样。


#主机名和端口号作为目录名的一部分
HostName=`hostname`
MySql_Port=3308
#IP地址
Ip=192.168.1.37
#master server_id 要和 slave 不一样
Server_Id=2


cat > /mysql/data/$MySql_Port/my.cnf <<-EOF
#------------------------------------ 
#客户端设置
#------------------------------------
[client]
port=$MySql_Port
socket =/mysql/data/$MySql_Port/mysql.sock
default-character-set=utf8 




#------------------------------------ 
#mysql连接工具设置
#------------------------------------
[mysql]
prompt="\\u@\\h : \\d\\r:\\m:\\s>" #登录时显示登录的用户名、服务器地址、默认数据库名、当前时间
auto-rehash #读取表信息和列信息,可以在连上终端后开启tab补齐功能。
default-character-set=utf8 #默认字符集


#------------------------------------ 
#基本设置
#------------------------------------
[mysqld]
bind_address=0.0.0.0  #监听本地所有地址
port=$MySql_Port  #端口号
user=mysql  #用户
basedir=/mysql/app/mysql  #安装路径
datadir=/mysql/data/$MySql_Port/data  #MySQL数据目录
socket=/mysql/data/$MySql_Port/mysql.sock #用于本地连接的socket文件目录
pid-file=/mysql/data/$MySql_Port/mysql.pid #进程ID文件的目录。
character-set-server=utf8 #默认字符集


#------------------------------------ 
#log setting 日志设置
#------------------------------------
long_query_time=10 #慢查询时间,超过 10 秒则认为是慢查询
slow_query_log=ON #启用慢查询日志
slow_query_log_file=/mysql/log/$MySql_Port/${HostName}-query.log #慢查询日志目录
log_queries_not_using_indexes=1 #记录未使用索引的语句
log_slow_admin_statements=1 #慢查询也记录那些慢的optimize table,analyze table和alter table语句
log-error=/mysql/log/$MySql_Port/${HostName}-error.log #错误日志目录




#------------------------------------ 
#master modify parameter 主库A复制更改参数
#------------------------------------
server_id=$Server_Id #master和slave server_id 需要不同




#二进制日志参数配置
log_bin=/mysql/log/$MySql_Port/binlog/${HostName}-binlog  #binlog目录
log_bin_index=/mysql/log/$MySql_Port/binlog/${HostName}-binlog.index  #指定索引文件的位置
binlog_format=row #行模式复制,默认是 row
binlog_rows_query_log_events=on #在 row 模式下,开启该参数,可以将把 sql 语句打印到 binlog 日志里面,方便查看
binlog_cache_size=1M #事务能够使用的最大 binlog 缓存空间。
max_binlog_size=2048M #binlog 文件最大空间,达到该大小时切分文件
expire_logs_days=7 #设置自动删除 binlog 文件的天数。
sync_binlog=1 #表示每次事务的 binlog 都会fsync持久化到磁盘,MySQL 5.7.7 之后默认为1,之前的版本默认为0
innodb_flush_log_at_trx_commit=1 #表示每次事务的 redo log 都直接持久化到磁盘,默认值为1


#------------------------------------ 
#slave parameter  主库B参数
#------------------------------------
relay_log=/mysql/log/$MySql_Port/relaylog/${HostName}-relaylog #中继日志目录
relay-log-index=/mysql/log/$MySql_Port/relaylog/${HostName}-relay.index  #中继日志索引目录
log_slave_updates=1 #主库B从主库A复制的数据会写入主库B binlog 日志文件里,默认是不写入
read_only=0  #主库B读写权限
relay_log_purge=1 #自动清空不再需要中继日志


# 并行复制参数
#主库A上面怎么并行,主库B上面就怎么回放,基于逻辑时钟的概念
#binlog 会记录组提交的信息,从回放的时候就可以知道哪些事务是一组里面的,
#一组里面的就丢到不同线程去回放,不是一组里的就等待,以此来提升并行度
slave-parallel-type=LOGICAL_CLOCK
#多线程复制
slave-parallel-workers=4
#slave 上commit 的顺序保持一致,否则可能会有间隙锁产生
slave-preserve-commit_order=1


master_info_repository=TABLE #默认每接收到10000个事件,写一次master-info,默认是写在文件中的
#修改 relay_log_info_repository 的好处
#1.relay.info 明文存储不安全,把 relay.info 中的信息记录在 table 中相对安全。
#2.可以避免 relay.info 更新不及时,slave 重启后导致的主从复制出错。
relay_log_info_repository=TABLE #将回放信息记录在 slave_relay_log_info 表中,默认是记录在 relay-info.log 文件中
relay_log_recovery=1  #当slave重启时,将所有 relay log 删除,通过 sql 线程重放的位置点去重新拉日志




#------------------------------------ 
#Replication Filter 主库B复制过滤参数
#------------------------------------
#(过滤某个数据库、数据库.表)
#replicate_do_db=yzjtestdb
#replicate_wild_do_table=yzjtestdb.%


#replicate_do_table=yzjtestdb.yzjtest_yg
#replicate_wild_do_table=yzjtestdb.yzjtest_yg


#------------------------------------ 
#GTID Settings GTID 同步复制设置
#------------------------------------
gtid_mode=on  #开启GTID同步
enforce_gtid_consistency=on #强制事务一致,确保 GTID 的安全,在事务中就不能创建和删除临时表
binlog_gtid_simple_recovery=1 #这个变量用于在 MySQL 重启或启动的时候寻找 GTIDs 过程中,控制 binlog 如何遍历的算法


#------------------------------------ 
#避免主键冲突设置
#------------------------------------
auto_increment_increment=2  #自增主键步长,通常有几个主库A就写几,避免主键冲突
auto_increment_offset=2  #设置自增主键起始值,第一个主库A为1,第二个主库A为2,以此类推
EOF


初始化主库 B:


mysqld \
--defaults-file=/mysql/data/3308/my.cnf \
--initialize --user=mysql \
--basedir=/mysql/app/mysql \
--datadir=/mysql/data/3308/data


配置 MySQL 启动脚本:


cp /mysql/app/mysql/support-files/mysql.server /etc/init.d/mysql_3308
ln -sf /etc/init.d/mysql_3308 /usr/lib/systemd/system/mysql_3308


#修改启动脚本##
vi /etc/init.d/mysql_3308


basedir=/mysql/app/mysql
datadir=/mysql/data/3308/data
mysqld_pid_file_path=/mysql/data/3308/mysql.pid


#在$bindir/mysqld_safe 后面添加,注意 --defaults-file 要放在第一个
--defaults-file="/mysql/data/3308/my.cnf" 


systemctl daemon-reload


启动 MySQL,修改密码,运行远程登录:


#启动、MySQL服务
systemctl start mysql_3308


#获取MySQL临时密码
Passwd=`cat /mysql/log/3308/*-error.log |grep "root@localhost:"|awk -F ' ' '{print $11}'`
echo $Passwd


#通过本地 socket 登录、修改密码
mysql -uroot -p$Passwd -S /mysql/data/3308/mysql.sock
alter user 'root'@'localhost' identified by  "123456";


#允许远程登录
grant all privileges on *.* to root@'%' identified by '123456';


#刷新权限
flush privileges;

创建复制用户

分别在主库 A 和主库 B 上创建一个用于数据复制的用户。


grant replication slave on *.* 
to 'repuser'@'%' identified by 'repuser123';

建立主从关系

主库 A 和主库 B 都先清除下 binlog。


reset master;


主库 A 配置主从,指向主库 B。


stop slave;
change master to
    master_host='192.168.1.36',
    master_port=3308,
    master_user='repuser',
    master_password='repuser123',
    master_auto_position=1;
start slave;


主库 B 配置主从,指向主库 A。


stop slave;
change master to
    master_host='192.168.1.37',
    master_port=3308,
    master_user='repuser',
    master_password='repuser123',
    master_auto_position=1;
start slave;


使用 show slave status\G 命令查看主从同步状态,IO 线程和 SQL 线程都为 YES 表示同步正常,主库 A 和主库 B 互为主从。


部署 Keepalived

下载并解压安装包

wget https://www.keepalived.org/software/keepalived-2.2.4.tar.gz
tar -xzvf keepalived-2.2.4.tar.gz

安装相关依赖

yum install kernel-devel openssl-devel popt-devel -y

安装 keepalived,设置开机自动启动

mkdir /software/keepalived
cd keepalived-2.2.4
./configure --prefix=/software/keepalived
make && make install
systemctl enable keepalived
mkdir /etc/keepalived

配置 Keepalived

主库 A 配置 Keepalived

主库 A keepalived 配置文件,编辑 /etc/keepalived/keepalived.conf 文件:


global_defs { 
  router_id keep_mysql_repl_g1          # 负载均衡标识,在局域网内应该是唯一的
 
}


# vrrp_script 级别和 vrrp_instance 一样
vrrp_script chk_mysql {               # 配置虚拟脚本 chk_mysql
  script "/etc/keepalived/check_mysql.sh"     # 执行脚本,检查 mysql 服务是否存活
  interval 3                    # 脚本执行间隔:秒
}


# vrrp_instance  
vrrp_instance v_mysql_1 { 
  state BACKUP                   # 指定该 keepalived 节点的初始状态(MASTER|BACKUP)
  interface ens192                 # VRRP 实例绑定的网口,用于发送 VRRP 包
  virtual_router_id 200               # 路由 ID,范围是 0-255,主备都一样
  priority 100                   # 指定优先级,优先级高的将成为 MASTER
  advert_int 1                   # 指定发送 VRRP 广播的间隔。单位是秒
  nopreempt                     # 设置为不抢占。默认是抢占的
                  
authentication {                   # 身份验证
  auth_type PASS                                  # 指定认证方式
  auth_pass mysql                                 # 指定认证所使用的密码 mysql ,主备都一样
}


track_script {                     # 调用"vrrp_script"的脚本
  chk_mysql                     # 增加一个跟踪脚本到网口上
}


virtual_ipaddress {                 # 虚拟 IP
  192.168.1.38/24 
  } 
}


主库 A 检查脚本,编辑 /etc/keepalived/check_mysql.sh 文件:


#!/bin/bash
#/etc/keepalived/check_mysql.sh
#chmod u+x /etc/keepalived/check_mysql.sh
#Linux 7 使用,如果是配置Linux 6 需要修改脚本






# MySQL账号密码
mysql_user="root"
mysql_pass="123456"


# MySQL错误日志输出
mysql_err="/mysql/log/3308/check_mysql_err.log"


# MySQL杀进程脚本
mysql_kill_session="/tmp/kill.sql"


# MySQL连接字符串
mysql_con="mysql -u${mysql_user} -p${mysql_pass} -S /mysql/data/3308/mysql.sock"








source ~/.bash_profile
if [ `ps -ef|grep -w "$0"|grep "/bin/sh*"|grep "?"|grep "?"|grep -v "grep"|wc -l` -gt 2 ];then  #
    exit 0
fi




function excute_query {
    $mysql_con -e "select 1 from dual;" 2>> $mysql_err
}


function service_error {
    echo -e "`date "+%F  %H:%M:%S"`    -----mysql service error,now stop keepalived-----" >> $mysql_err
    echo -e "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $mysql_err
}


function query_error {
    echo -e "`date "+%F  %H:%M:%S"`    -----query error, but mysql service ok, retry after 30s-----" >> $mysql_err
    sleep 30
    excute_query
    if [ $? -ne 0 ];then
        echo -e "`date "+%F  %H:%M:%S"`    -----still can't execute query-----" >> $mysql_err


        echo -e "`date "+%F  %H:%M:%S"`    -----set read_only = 1 on DB1-----" >> $mysql_err
        $mysql_con -e "set global read_only = 1;" 2>> $mysql_err


        echo -e "`date "+%F  %H:%M:%S"`    -----kill current client thread-----" >> $mysql_err
        rm -f $mysql_kill_session &>/dev/null


        $mysql_con -NB -e 'select concat("kill ",id,";") from  information_schema.PROCESSLIST where command="Query" or command="Execute"' > $mysql_kill_session
        $mysql_con -e "source $mysql_kill_session"
        sleep 2 


        echo -e "`date "+%F  %H:%M:%S"`    -----stop keepalived-----" >> $mysql_err
        systemctl stop keepalived &>> $mysql_err
        echo -e "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $mysql_err
    else
        echo -e "`date "+%F  %H:%M:%S"`    -----query ok after 30s-----" >> $mysql_err
        echo -e "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $mysql_err
    fi
}


excute_query
if [ $? -ne 0 ];then
    systemctl status mysql &>/dev/null
    if [ $? -ne 0 ];then
        service_error
    else
        query_error
    fi
fi


给检查脚本赋与执行权限:


chmod u+x /etc/keepalived/check_mysql.sh

主库 B 配置 Keepalived

主库 B keepalived 配置文件,编辑 /etc/keepalived/keepalived.conf 文件:


global_defs { 
  router_id keep_mysql_repl_g1               # 负载均衡标识,在局域网内应该是唯一的
 
}


# vrrp_instance  
vrrp_instance v_mysql_1 {                   
  state BACKUP                       # 指定该 keepalived 节点的初始状态(MASTER|BACKUP)      
  interface ens192                                         # VRRP 实例绑定的网口,用于发送 VRRP 包
  virtual_router_id 200                                   # 路由ID,范围是0-255,主备都一样
  priority 90                                             # 指定优先级,优先级高的将成为 MASTER
  advert_int 1                                            # 指定发送VRRP广播的间隔。单位是秒
  nopreempt                                               # 设置为不抢占。默认是抢占的
  
authentication {                       # 身份验证
  auth_type PASS                                          # 指定认证方式
  auth_pass mysql                                         # 指定认证所使用的密码 mysql ,主备都一样
}


notify_master /etc/keepalived/notify_master_mysql.sh    # 转换成 master 时,执行的脚本
virtual_ipaddress { 
  192.168.1.38/24 
  } 
}


主库 B 脚本,当发生主从切换时,会执行该脚本。编辑 /etc/keepalived/notify_master_mysql.sh 文件:


#!/bin/bash
#/etc/keepalived/notify_master_mysql.sh
#chmod u+x /etc/keepalived/notify_master_mysql.sh


# MySQL账号密码
mysql_user="root"
mysql_pass="123456"


# 配置更变日志
change_log="/mysql/log/3308/state_change.log"


# 主库B状态日志
slave_status_log="/mysql/log/3308/slave_status_log.log"


# MySQL连接字符串
mysql_conn="mysql -u${mysql_user} -p${mysql_pass} -S /mysql/data/3308/mysql.sock"




source ~/.bash_profile
echo -e "`date "+%F  %H:%M:%S"`   -----keepalived change to MASTER-----" >> $change_log
echo -e "`date "+%F  %H:%M:%S"`   ----------" >> $slave_status_log
$mysql_conn -e "show slave status\G;" >> $slave_status_log


Slave_IO_Running=`$mysql_conn -e "show slave status\G;"|egrep -w "Slave_IO_Running|Slave_SQL_Running"|awk 'NR==1{print}' | awk '{print $2}'`
Slave_SQL_Running=`$mysql_conn -e "show slave status\G;"|egrep -w "Slave_IO_Running|Slave_SQL_Running"|awk 'NR==2{print}' | awk '{print $2}'`
Master_Log_File=`$mysql_conn -e "show slave status\G;" |egrep -w "Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos"|awk 'NR==1{print}' | awk '{print $2}'`
Read_Master_Log_Pos=`$mysql_conn -e "show slave status\G;" |egrep -w "Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos"|awk 'NR==2{print}' | awk '{print $2}'`
Exec_Master_Log_Pos=`$mysql_conn -e "show slave status\G;" |egrep -w "Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos"|awk 'NR==3{print}' | awk '{print $2}'`




action() {
    echo -e "`date "+%F  %H:%M:%S"`    -----set read_only = 0 on `hostname`-slave-----" >> $change_log


    $mysql_conn -e "set global read_only = 0;" 2>> $change_log
        $mysql_conn -e "stop slave;" 2>> $change_log
    echo "`hostname`-slave keepalived 转为 MASTER 状态,线上数据库切换至`hostname`-slave" >> $change_log


    echo -e "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $change_log
}


if [ "$Slave_IO_Running" = "Yes" -a "$Slave_SQL_Running" = "Yes" ];then
        if [ $Read_Master_Log_Pos = $Exec_Master_Log_Pos ];then
            echo -e "`date "+%F  %H:%M:%S"`    -----Master_Log_File=$Master_Log_File . Exec_Master_Log_Pos($Exec_Master_Log_Pos) is equal Read_Master_Log_Pos($Read_Master_Log_Pos)" >> $change_log
                        action
                        $mysql_conn -e "reset slave all;" 2>> $change_log
                        else
                    echo -e "`date "+%F  %H:%M:%S"`    -----Master_Log_File=$Master_Log_File . Exec_Master_Log_Pos($Exec_Master_Log_Pos) is behind Read_Master_Log_Pos($Read_Master_Log_Pos), The waits time is more than 10s,now force change." >> $change_log
                    sleep 10
                        action
                        $mysql_conn -e "reset slave all;" 2>> $change_log
            exit 0
        fi
action 


else
    echo -e "`hostname`-slave's slave status is wrong,now force change. Master_Log_File=$Master_Log_File Read_Master_Log_Pos=$Read_Master_Log_Pos  Exec_Master_Log_Pos=$Exec_Master_Log_Pos" >> $change_log
  action
fi


给脚本赋与执行权限:


chmod u+x /etc/keepalived/notify_master_mysql.sh 

启动 Keepalived

在主库 A 和主库 B 上分别启动 keepalived。


systemctl start keepalived


查看 keepalived 状态。


[root@mysql-master keepalived-2.2.4]# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)
   Active: active (running) since 五 2021-09-10 21:13:37 CST; 18s ago
     Docs: man:keepalived(8)
           man:keepalived.conf(5)
           man:genhash(1)
           https://keepalived.org
  Process: 8184 ExecStart=/software/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 8186 (keepalived)
   Memory: 680.0K
   CGroup: /system.slice/keepalived.service
           ├─8186 /software/keepalived/sbin/keepalived -D
           └─8187 /software/keepalived/sbin/keepalived -D


9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: (v_mysql_1) Sending/queueing gratuitous ARPs on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38


查看网卡地址,此时虚拟 IP 在主库 A 上。


[root@mysql-master keepalived-2.2.4]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 00:50:56:8b:1b:ca brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.36/24 brd 192.168.1.255 scope global ens192
       valid_lft forever preferred_lft forever
    #虚拟 IP
    inet 192.168.1.38/24 scope global secondary ens192
       valid_lft forever preferred_lft forever
    inet6 fe80::2556:f369:b4e7:fb64/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::6b8d:29f7:a5fe:dbee/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::f387:57a3:4975:d8f2/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever

验证高可用

客户端通过虚拟 IP 192.168.1.38 连接数据库,通过 select @@hostname 命令可以看到当前连接的为主库 A。


? mysql -uroot -h 192.168.1.38 -P 3308  -p123456;
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 268
Server version: 5.7.29-log MySQL Community Server (GPL)


Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.


Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.


Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


root@192.168.1.38 (none) 09:11:13>select @@hostname;
+--------------+
| @@hostname   |
+--------------+
| mysql-master |
+--------------+
1 row in set (0.01 sec)


root@192.168.1.38 (none) 09:11:26>


客户端创建表并插入数据。


create database testdb;
create table testdb.data01( 
id int not null primary key auto_increment, 
name varchar(60), 
age int); 


insert into testdb.data01 (name,age) values
('tom',18),
('jack',17),
('rock',16),
('james',15),
('cris',20);


此时分别登录主库 A 和主库 B 查看 testdb.data01 表中的数据,可以确定主库 A 和主库 B 目前数据是同步的。并且查看表中的内容可以发现主键是以 2 为间隔递增的,这是为了防止主从切换时插入数据产生主键冲突。主库 A 的主键会以 1,3,5,7,9 的序号递增。假如在序号为 9 时发生主从切换,新的主库(主库 A)的主键会以 10,12,14,16,18 的序号递增。


停止主库 A,模拟故障切换

[root@mysql-master ~]# systemctl stop mysql_3308.service 


在主库 A 的机器上查看 keepalived 状态,可以看到 keepalived 的优先级被设置为 0,此时虚拟 IP 将会飘到主库 B 的机器上。


[root@mysql-master tmp]# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)
   Active: active (running) since 五 2021-09-10 21:42:11 CST; 3min 59s ago
     Docs: man:keepalived(8)
           man:keepalived.conf(5)
           man:genhash(1)
           https://keepalived.org
  Process: 13365 ExecStart=/software/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 13367 (keepalived)
   Memory: 1.0M
   CGroup: /system.slice/keepalived.service
           ├─13367 /software/keepalived/sbin/keepalived -D
           ├─13368 /software/keepalived/sbin/keepalived -D
           ├─14761 /bin/bash /etc/keepalived/check_mysql.sh
           └─14778 sleep 30


9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: Track script chk_mysql is already running, expect idle - skipping run
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: VRRP_Script(chk_mysql) timed_out
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: (v_mysql_1) Entering FAULT STATE
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: (v_mysql_1) sent 0 priority
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: (v_mysql_1) removing VIPs.


查看主库 B 机器网卡的地址,发现虚拟 IP 已经切换到主库 B 上了。当发生主从切换时,主库 B 的脚本会执行 reset slave all,停止向主库 A 的同步,防止原主库 A 恢复后数据意外同步。


[root@mysql-slave keepalived]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 00:50:56:8b:71:df brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.37/24 brd 192.168.1.255 scope global ens192
    #虚拟 IP
       valid_lft forever preferred_lft forever
    inet 192.168.1.38/24 scope global secondary ens192
       valid_lft forever preferred_lft forever
    inet6 fe80::2556:f369:b4e7:fb64/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::6b8d:29f7:a5fe:dbee/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::f387:57a3:4975:d8f2/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever


客户端发生了重连,通过 select @@hostname 查看可以看到此时连接的是主库 B。


root@192.168.1.38 (none) 09:42:14>select @@hostname;
#重连
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    19
Current database: *** NONE ***


+-------------+
| @@hostname  |
+-------------+
| mysql-slave |
+-------------+
1 row in set (0.04 sec)


客户端插入几条数据:


insert into testdb.data01 (name,age) values
('peter',28),
('mark',27),
('marry',26),
('hule',25),
('handson',20);


查询数据,可以看到在原主库 B 上插入的数据主键会以 10,12,14,16,18 的序号递增。


root@192.168.1.38 (none) 09:44:19>select * from testdb.data01;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | tom     |   18 |
|  3 | jack    |   17 |
|  5 | rock    |   16 |
|  7 | james   |   15 |
|  9 | cris    |   20 |
| 10 | peter   |   28 |
| 12 | mark    |   27 |
| 14 | marry   |   26 |
| 16 | hule    |   25 |
| 18 | handson |   20 |
+----+---------+------+
10 rows in set (0.01 sec)

重新启动主库 A,观察数据同步

由于我们关闭了抢占模式,当主库 A 重新启动时,主从不会发送切换。


[root@mysql-master]# systemctl start mysql_3308.service


主库 A 的数据可以和主库 B 同步。


原文链接;https://xie.infoq.cn/article/46c19f322be92a42ecbd4d7bb

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码