阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

MySQL主库高可用 — 双主单活故障自动切换方案

390次阅读
没有评论

共计 8934 个字符,预计需要花费 23 分钟才能阅读完成。

前言:(PS:前言是后来修改本文时加的)对于这篇文章,有博友提出了一些疑问和见解,有了博友的关注,也促使我想把这套东西做的更实用、更安全。后来又经过思考,对脚本中一些条件和行为做了些改变。经过几次修改,现在终于敢说让小伙伴本使用这套东西了。

主要目的:

以双主结构配合 keepalived 解决 MySQL 主从结构中主库的单点故障;同时通过具体的查询语句提供更细粒度、更为真实的关于主库可用性的判断。

基本思路:
    将 DB1 和 DB2 做成主动被动模式的双主结构:DB1 主动、DB2 被动,通过 keepalived 的 VIP 对外,将 VIP 设置成原 DB1 的 IP,保证改造过程对代码透明
    三个前提:

            两台 MySQL 的配置文件里需要加上“log_slave_updates = 1”;
            并且“备用机”通过“read_only”参数实现除 root 用户之外的只读特性;
            分别在两个数据库创建 test.test 表,插入几条数据,供检测脚本使用。
    正常时,VIP 在 DB1,通过 keepalived 调用脚本定期检查 mysql 服务可用性(通过一个低权限用户连接 mysql 服务器并执行一个简单查询,根据返回结果来判定 mysql 是否可用)

若无法执行查询:

1. 第一次检测失败后, 检查服务状态,:

  1. 若服务异常,则执行切换:关闭 DB1 的 keepalived,使 VIP 漂移至 DB2,通过 DB2 上 keepalived 的 notify_master 机制,触发脚本将 DB2 的 mysql 从被动状态(只读)切换到主动状态(可读写),并发送通知邮件。

  2. 若服务正常(则可能是一些临时性因素导致的监测失败),等待 30s 做第二次检查,这 30s 是对瞬时 / 短时因素造成检查失败的容忍时间,本着“能不切则不切”的原则。若第二次检查仍然失败

2.  开始执行系列切换动作

  1. 将 DB1 的 MySQL 设置为 read_only 模式(阻止写请继续求进入)

  2. kill 掉当前客户端的线程。原来担心 kill 掉线程会对数据执行造成影响,后来查看了官方文档“mysql shutdown process”,发现 mysql 正常关闭过程也有一步是如此操作,所以这里可以放心了。然后 sleep 2,给 kill 命令一些时间(关于 kill 命令的机制,参考 官方解释

  3. 关闭 DB1 的 keepalived,使 DB2 接管 VIP。通过 DB2 上 keepalived 的 notify_master 机制,触发脚本将 DB2 的

    mysql 从被动状态(只读)切换到主动状态(可读写),并发送通知邮件。

3.  管理员修复 DB1 后,通过脚本“change_to_backup.sh”将主库切换回 DB1。脚本思路如下:

    注:涉及到切换主备,就会有中断时间,所以推荐此步骤在业务低谷期执行

  1. 将 DB2 的 read_only 属性置为 1

  2. kill 掉 DB2 上的 client 线程,并 重启 DB2 的 keepalived 使 VIP 漂移至 DB1

  3. 确定 DB1 跟上了 DB2 的更新 并将 DB1 上的 read_only 属性移除

关于“数据一致性”和“切换时间”:

      连续两次失败以后,通过对主 MySQL 设置 read_only 属性,同时 kill 掉用户线程来保证在 DB2 接管服务之前,DB1 上已经没有写操作,避免主从数据不一致。并且切换时间基本上是可确定的:

      30s(两次检测间隔)+2s(等待 kill 命令时间)+ 约 1s(keepalived 切换 VIP),总时间不会超过 35s。

以上是大致思路,具体实现看过下面的脚本,就会一目了然了。

DB1 上 keepalived 配置

! Configuration File for keepalived
 
vrrp_script chk_mysql {
    script “/etc/keepalived/check_mysql.sh”
    interval 30        #这里我的检查间隔设置的比较长,因为我们数据库前面有 redis 做缓存,数据库一两分钟级别的中断对整体可用性影响不大。这也是我没有采用成熟的方案而自己搞了这一套方案的“定心丸”
}
vrrp_instance VI_1 {
    state BACKUP        #通过下面的 priority 来区分 MASTER 和 BACKUP,也只有如此,底下的 nopreempt 才有效
    interface em2
    virtual_router_id 51
    priority 100
    advert_int 1
    nopreempt          #防止切换到从库后,主 keepalived 恢复后自动切换回主库
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
        chk_mysql
    }
     
    virtual_ipaddress {
        192.168.1.5/24
    }
}

/etc/keepalived/check_mysql.sh 脚本内容如下(主要的判断逻辑都在这里)
#!/bin/sh
 
### 判断如果上次检查的脚本还没执行完,则退出此次执行
if [`ps -ef|grep -w “$0″|grep “/bin/sh*”|grep “?”|grep “?”|grep -v “grep”|wc -l` -gt 2];then  #理论上这里应该是 1,但是实验的结果却是 2
    exit 0
fi
 
alias mysql_con=’mysql -uxxxx -pxxxx’
 
### 定义一个简单判断 mysql 是否可用的函数
function excute_query {
    mysql_con -e “select * from test.test;” 2>>/etc/keepalived/logs/check_mysql.err
}
 
### 定义无法执行查询,且 mysql 服务异常时的处理函数
function service_error {
    echo -e “`date “+%F  %H:%M:%S”`    —–mysql service error,now stop keepalived—–” >> /etc/keepalived/logs/check_mysql.err
    /sbin/service keepalived stop &>> /etc/keepalived/logs/check_mysql.err
    echo -e “\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n” >> /etc/keepalived/logs/check_mysql.err
}
 
### 定义无法执行查询, 但 mysql 服务正常的处理函数
function query_error {
    echo -e “`date “+%F  %H:%M:%S”`    —–query error, but mysql service ok, retry after 30s—–” >> /etc/keepalived/logs/check_mysql.err
    sleep 30
    excute_query
    if [$? -ne 0];then
        echo -e “`date “+%F  %H:%M:%S”`    —–still can’t execute query—–” >> /etc/keepalived/logs/check_mysql.err
 
        ### 对 DB1 设置 read_only 属性
        echo -e “`date “+%F  %H:%M:%S”`    —–set read_only = 1 on DB1—–” >> /etc/keepalived/logs/check_mysql.err
        mysql_con -e “set global read_only = 1;” 2>> /etc/keepalived/logs/check_mysql.err
 
        ###kill 掉当前客户端连接
        echo -e “`date “+%F  %H:%M:%S”`    —–kill current client thread—–” >> /etc/keepalived/logs/check_mysql.err
        rm -f /tmp/kill.sql &>/dev/null
        ### 这里其实是一个批量 kill 线程的小技巧
        mysql_con -e ‘select concat(“kill “,id,”;”) from  information_schema.PROCESSLIST where command=”Query” or command=”Execute” into outfile “/tmp/kill.sql”;’
        mysql_con -e “source /tmp/kill.sql”
        sleep 2    ### 给 kill 一个执行和缓冲时间
        ### 关闭本机 keepalived       
        echo -e “`date “+%F  %H:%M:%S”`    —–stop keepalived—–” >> /etc/keepalived/logs/check_mysql.err
        /sbin/service keepalived stop &>> /etc/keepalived/logs/check_mysql.err
        echo -e “\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n” >> /etc/keepalived/logs/check_mysql.err
    else
        echo -e “`date “+%F  %H:%M:%S”`    —–query ok after 30s—–” >> /etc/keepalived/logs/check_mysql.err
        echo -e “\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n” >> /etc/keepalived/logs/check_mysql.err
    fi
}
 
### 检查开始: 执行查询
excute_query
if [$? -ne 0];then
    /sbin/service mysql status &>/dev/null
    if [$? -ne 0];then
        service_error
    else
        query_error
    fi
fi

DB2 上 keepalived 配置:
! Configuration File for keepalived
 
vrrp_instance VI_1 {
    state BACKUP
    interface em2
    virtual_router_id 51
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    notify_master /etc/keepalived/notify_master_mysql.sh    #此条指令告诉 keepalived 发现自己转为 MASTER 后执行的脚本
    virtual_ipaddress {
        192.168.1.5/24
    }
}

/etc/keepalived/notify_master_mysql.sh 脚本内容:
#!/bin/bash
### 当 keepalived 监测到本机转为 MASTER 状态时,执行该脚本
 
change_log=/etc/keepalived/logs/state_change.log
alias mysql_con=’mysql -uroot -pxxxx -e “show slave status\G;” 2>/dev/null’
 
echo -e “`date “+%F  %H:%M:%S”`  —–keepalived change to MASTER—–” >> $change_log
 
slave_info() {
    ### 统一定义一个函数取得 slave 的 position、running、和 log_file 等信息
    ### 根据函数后面所跟参数来决定取得哪些数据
    if [$1 = slave_status];then
        slave_stat=`mysql_con|egrep -w “Slave_IO_Running|Slave_SQL_Running”`
        Slave_IO_Running=`echo $slave_stat|awk ‘{print $2}’`
        Slave_SQL_Running=`echo $slave_stat|awk ‘{print $4}’`
    elif [$1 = log_file -a $2 = pos];then
        log_file_pos=`mysql_con|egrep -w “Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos”`
        Master_Log_File=`echo $log_file_pos|awk ‘{print $2}’`
        Read_Master_Log_Pos=`echo $log_file_pos|awk ‘{print $4}’`
        Exec_Master_Log_Pos=`echo $log_file_pos|awk ‘{print $6}’`
    fi
}
 
action() {
    ### 经判断 ’ 应该 & 可以 ’ 切换时执行的动作
    echo -e “`date “+%F  %H:%M:%S”`    —–set read_only = 0 on DB2—–” >> $change_log
 
    ### 解除 read_only 属性
    mysql_con -e “set global read_only = 0;” 2>> $change_log
 
    echo “DB2 keepalived 转为 MASTER 状态,线上数据库切换至 DB2″|/bin/mailx -s “DB2 keepalived change to MASTER”\
    lijiankai@dm.com 2>> $change_log
 
    echo -e “@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n” >> $change_log
}
 
slave_info slave_status
if [$Slave_IO_Running = Yes -a $Slave_SQL_Running = Yes];then
    i=0    #一个计数器
    slave_info log_file pos
        ### 判断从 master 接收到的 binlog 是否全部在本地执行(这样仍无法完全确定从库已追上主库,因为无法完全保证 io_thread 没有延时(由网络传输问题导致的从库落后的概率很小)
    until [$Read_Master_Log_Pos = $Exec_Master_Log_Pos]
    do
        if [$i -lt 10];then    #将等待 exec_pos 追上 read_pos 的时间限制为 10s
            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_Lo
g_Pos($Read_Master_Log_Pos), wait……” >> $change_log    #输出消息到日志,等待 exec_pos=read_pos
            i=$(($i+1))
            sleep 1
            slave_info log_file pos
        else
            echo -e “The waits time is more than 10s,now force change. Master_Log_File=$Master_Log_File Read_Master_Log_Pos=$Read_Master_Log_Pos Exec_Ma
ster_Log_Pos=$Exec_Master_Log_Pos” >> $change_log
            action
            exit 0
        fi
    done
    action 
 
else
    slave_info log_file pos
    echo -e “DB2’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_Po
s=$Exec_Master_Log_Pos” >> $change_log
    action
fi

DB2 上手动切换回 DB1 的脚本 change_to_backup.sh:
#!/bin/sh
### 手动执行将主库切换回 DB1 的操作
 
alias mysql_con=’mysql -uxxxx -pxxxx’
 
echo -e “`date “+%F  %H:%M:%S”`    —–change to BACKUP manually—–” >> /etc/keepalived/logs/state_change.log
echo -e “`date “+%F  %H:%M:%S”`    —–set read_only = 1 on DB2—–” >> /etc/keepalived/logs/state_change.log
mysql_con -e “set global read_only = 1;” 2>> /etc/keepalived/logs/state_change.log
 
###kill 掉当前客户端连接
echo -e “`date “+%F  %H:%M:%S”`    —–kill current client thread—–” >> /etc/keepalived/logs/state_change.log
rm -f /tmp/kill.sql &>/dev/null
### 这里其实是一个批量 kill 线程的小技巧
mysql_con -e ‘select concat(“kill “,id,”;”) from  information_schema.PROCESSLIST where command=”Query” or command=”Execute” into outfile “/tmp/kill.sql”;’
mysql_con -e “source /tmp/kill.sql” 2>> /etc/keepalived/logs/state_change.log
sleep 2    ### 给 kill 一个执行和缓冲时间
 
### 确保 DB1 已经追上了, 下面的 repl 为复制所用的账户,- h 后跟 DB1 的内网 IP
pos=`mysql -urepl -pxxxx -h192.168.1.x -e “show slave status\G;”|grep “Master_Log_Pos”|awk ‘{printf (“%s”,$NF “\t”)}’`
read_pos=`echo $pos|awk ‘{print $1}’`
exec_pos=`echo $pos|awk ‘{print $2}’`
until [$read_pos = $exec_pos]
do
    echo -e “`date “+%F  %H:%M:%S”`    —–DB1 Exec_Master_Log_Pos($exec_pos) is behind Read_Master_Log_Pos($read_pos), wait……” >> /etc/keepalived/logs/state_change.log
    sleep 1
done
 
### 然后解除 DB1 的 read_only 属性
echo -e “`date “+%F  %H:%M:%S”`    —–set read_only = 0 on DB1—–” >> /etc/keepalived/logs/state_change.log
ssh 192.168.1.x ‘mysql -uxxxx -pxxxx -e “set global read_only = 0;” && /etc/init.d/keepalived start’ 2>> /etc/keepalived/logs/state_change.log
 
### 重启 DB2 的 keepalived 使 VIP 漂移到 DB1
echo -e “`date “+%F  %H:%M:%S”`    —–make VIP move to DB1—–” >> /etc/keepalived/logs/state_change.log
/sbin/service keepalived restart &>> /etc/keepalived/logs/state_change.log
 
echo “DB2 keepalived 转为 BACKUP 状态,线上数据库切换至 DB1″|/bin/mailx -s “DB2 keepalived change to BACKUP” xxx@xxxx.com 2>> /etc/keepalived/logs/state_change.log
 
echo -e “@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n” >> /etc/keepalived/logs/state_change.log

日志截图:
DB1 mysql 服务故障:

MySQL 主库高可用 -- 双主单活故障自动切换方案

DB1 mysql 服务正常,查询失败:

MySQL 主库高可用 -- 双主单活故障自动切换方案

DB2 一次切换过程:

MySQL 主库高可用 -- 双主单活故障自动切换方案

DB2 执行脚本手动切回 DB1:

MySQL 主库高可用 -- 双主单活故障自动切换方案

总结:此方相比 MHA 或者 MMM 之类技术,特点在于简单,降低实施和维护复杂度;同时也安全的解决了主从中 master 节点的单点问题;在此基础上,亦可以再增加从库实现读写分离等架构;不足之处是双主仍是单活,DB2 只是作为热备。

本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-06/132702.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计8934字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19348
评论数
4
阅读量
7802057
文章搜索
热门文章
开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南 大家好,我是星哥。之前介绍了腾讯云的 Code...
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板 1Panel:小白轻松构建 Web 服务与面板加固指南 哈喽,我是星哥,经常有人问我不...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
150元打造低成本NAS小钢炮,捡一块3865U工控板

150元打造低成本NAS小钢炮,捡一块3865U工控板

150 元打造低成本 NAS 小钢炮,捡一块 3865U 工控板 一块二手的熊猫 B3 工控板 3865U,搭...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
Prometheus:监控系统的部署与指标收集

Prometheus:监控系统的部署与指标收集

Prometheus:监控系统的部署与指标收集 在云原生体系中,Prometheus 已成为最主流的监控与报警...
在Windows系统中通过VMware安装苹果macOS15

在Windows系统中通过VMware安装苹果macOS15

在 Windows 系统中通过 VMware 安装苹果 macOS15 许多开发者和爱好者希望在 Window...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
颠覆 AI 开发效率!开源工具一站式管控 30+大模型ApiKey,秘钥付费+负载均衡全搞定

颠覆 AI 开发效率!开源工具一站式管控 30+大模型ApiKey,秘钥付费+负载均衡全搞定

  颠覆 AI 开发效率!开源工具一站式管控 30+ 大模型 ApiKey,秘钥付费 + 负载均衡全...
240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

  240 元左右!五盘位 NAS 主机,7 代 U 硬解 4K 稳如狗,拓展性碾压同价位 在 NA...
星哥带你玩飞牛NAS-16:不再错过公众号更新,飞牛NAS搭建RSS

星哥带你玩飞牛NAS-16:不再错过公众号更新,飞牛NAS搭建RSS

  星哥带你玩飞牛 NAS-16:不再错过公众号更新,飞牛 NAS 搭建 RSS 对于经常关注多个微...
告别Notion焦虑!这款全平台开源加密笔记神器,让你的隐私真正“上锁”

告别Notion焦虑!这款全平台开源加密笔记神器,让你的隐私真正“上锁”

  告别 Notion 焦虑!这款全平台开源加密笔记神器,让你的隐私真正“上锁” 引言 在数字笔记工...
每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年 0.99 刀,拿下你的第一个顶级域名,详细注册使用 前言 作为长期折腾云服务、域名建站的老玩家,星哥一直...