目录
[1. 现状](#1. 现状)
[2. 目标](#2. 目标)
[3. 方案](#3. 方案)
[4. 流程](#4. 流程)
[5. 实现](#5. 实现)
[1. 配置 Orchestrator 钩子](#1. 配置 Orchestrator 钩子)
[2. 准备环境](#2. 准备环境)
[3. 编写 VIP 漂移脚本](#3. 编写 VIP 漂移脚本)
1. 现状
MySQL 实例
主:172.21.1.11:18251
从1:172.21.1.228:18251
从2:172.21.1.229:18251
其中主和从1承载业务,任何一个挂掉都必须手工处理才能恢复,业务长时间中断。
2. 目标
主、从1任何一个挂掉,业务秒级报错后自动恢复,全程不用人工干预。
3. 方案
vip + orchestrator + 自定义脚本
需要两个vip:m_vip、s_vip,初始 m_vip 在主库上,s_vip 在从1上。主挂了,m_vip 漂移到其中一个从上;从1挂了,s_vip 漂移到从2上。
4. 流程
主库挂掉:
(1)orchestrator 5秒发现
(2)将其中最新一个从库提升为主库
(3)将其他剩余从库指向新主库
(4)钩子执行自定义脚本漂移 m_vip。
(5)业务恢复
全程自动,只需要编写 m_vip 漂移脚本。
从1挂掉:
(1)发现库挂了
(2)漂移 s_vip
(3)业务恢复
全程自动,但需要编写脚本检测 mysql 服务并触发 s_vip 漂移,并自行调度执行。orchestrator 不会进行失败切换,也没有相应的钩子。
5. 实现
(1)主库高可用
Orchestrator 3.2.6 通过 PostMasterFailoverProcesses 调用钩子脚本实现 VIP 漂移,核心思路是:在配置文件里定义好钩子脚本和传递给它的参数,然后这个脚本会通过 SSH 远程操作新旧主库,在新主库上添加 VIP,并从旧主库上移除 VIP。
整个配置流程可以分为三个主要步骤。orchestrator 配置了三台的 raft 集群,因此以下配置需要在三台上都执行。
1. 配置 Orchestrator 钩子
在 Orchestrator 的配置文件(/home/mysql/orchestrator/orchestrator.conf.json)中,设置 PostMasterFailoverProcesses 参数。这个参数定义了一个命令列表,它们会在主库故障转移流程成功完成后被执行。
{
// ... 其他配置 ...
"PostMasterFailoverProcesses": [
"/home/mysql/orchestrator/vip_failover.sh {failureType} {failureClusterAlias} {failedHost} {successorHost}"
]
// ... 其他配置 ...
}
Orchestrator 会用实际的值替换掉花括号里的变量,然后作为位置参数传递给脚本。修改配置文件后重启 orchestrator 服务以生效。
三台依次执行:
systemctl daemon-reload
systemctl reset-failed orchestrator
systemctl stop orchestrator
systemctl start orchestrator
sleep 10
systemctl status orchestrator
Orchestrator Raft 集群部署参见:https://blog.csdn.net/wzy0623/article/details/162634623#t11
2. 准备环境
在钩子脚本能正常工作之前,必须完成两项关键的基础环境配置。
(1)建立 SSH 互信:Orchestrator 服务器需要能够使用 mysql 用户免密码 SSH 登录到集群中的每一台 MySQL 服务器(包括旧主和新主)。这是脚本能够远程执行 VIP 管理命令的前提。
# 在三台机器用 mysql 用户依次执行
ssh-keygen
ssh-copy-id 172.21.1.11
ssh-copy-id 172.21.1.228
ssh-copy-id 172.21.1.229
(2)把 mysql 用户的公钥放到每个机器的 root
# 在三台机器用 mysql 用户依次执行
ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.21.1.11
ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.21.1.228
ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.21.1.229
3. 编写 VIP 漂移脚本
以下是一个可用的脚本,它根据 Orchestrator 传递的参数执行 VIP 的删除和添加操作。
脚本:/home/mysql/orchestrator/vip_failover.sh
#!/bin/bash
# ==============================================================================
# 脚本名称: vip_failover.sh
# 适用场景: GitHub Orchestrator 主从切换(Failover)时的高可用 VIP 漂移脚本
# ==============================================================================
# --- 参数接收 (由 Orchestrator 自动传入) ---
FAILURE_TYPE="$1"
CLUSTER_ALIAS="$2"
OLD_MASTER_IP="${3%%:*}" # 自动剥离可能存在的端口号 (如 192.168.1.10:3306 -> 192.168.1.10)
NEW_MASTER_IP="${4%%:*}"
# --- 基础配置 (请根据实际环境修改) ---
VIP="172.21.1.9"
NETMASK="24"
INTERFACE="eth0"
LOG_FILE="/home/mysql/orchestrator/vip_failover.log"
# --- 权限与参数校验 ---
if [[ -z "$FAILURE_TYPE" || -z "$NEW_MASTER_IP" ]]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] FAILURE_TYPE or NEW_MASTER_IP is empty. Skip VIP action." >> "$LOG_FILE"
exit 0
fi
# 日志函数
log() {
local level="$1"
local msg="$2"
echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $msg" >> "$LOG_FILE"
}
# 核心:广播 ARP 刷新网络拓扑
arp_refresh() {
local host="$1"
log "INFO" "Sending gratuitous ARP from $host..."
# 使用 arping 向局域网广播最新的 MAC-IP 映射关系 (发送3次,间隔0.5秒)
ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@"$host" \
"arping -U -c 3 -I $INTERFACE $VIP" >> "$LOG_FILE" 2>&1 &
}
# --- 主逻辑开始 ---
# 仅针对主库级别的故障进行 VIP 漂移
if [[ "$FAILURE_TYPE" =~ ^(DeadMaster|MasterFailover|ForceMasterFailover)$ ]]; then
log "INFO" "==================== Starting VIP Failover ===================="
log "INFO" "Reason: $FAILURE_TYPE | Cluster: $CLUSTER_ALIAS"
log "INFO" "Path: $OLD_MASTER_IP ===> $NEW_MASTER_IP"
# 1. 尝试从旧主库卸载 VIP
if [[ -n "$OLD_MASTER_IP" ]]; then
log "INFO" "Step 1: Removing VIP from Old Master ($OLD_MASTER_IP)..."
# 设置更短的超时时间(3秒)。即使旧主死机卡住,也不影响新主接管。
ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@"$OLD_MASTER_IP" \
"ip addr del $VIP/$NETMASK dev $INTERFACE" >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
log "INFO" "Successfully removed VIP from Old Master."
else
log "WARN" "Failed to remove VIP from Old Master (This is normal if the host is dead)."
fi
fi
# 2. 在新主库上绑定 VIP
log "INFO" "Step 2: Binding VIP to New Master ($NEW_MASTER_IP)..."
# 先尝试清理新主库上可能残留的相同 VIP,防止 IP 冲突报错
ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@"$NEW_MASTER_IP" \
"ip addr del $VIP/$NETMASK dev $INTERFACE 2>/dev/null; ip addr add $VIP/$NETMASK dev $INTERFACE" >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
log "INFO" "Successfully bound VIP to New Master."
# 3. 刷新 ARP 缓存(最关键的一步)
arp_refresh "$NEW_MASTER_IP"
else
log "FATAL" "CRITICAL: Failed to bind VIP to New Master!"
exit 1
fi
log "INFO" "==================== VIP Failover Finished ===================="
else
log "INFO" "Ignore failure type '$FAILURE_TYPE'. No VIP action required."
fi
将此脚本保存后,别忘了赋予它执行权限 chmod +x /home/mysql/orchestrator/vip_failover.sh。
(2)从库高可用
orchestrator 帮不上忙,只能自己编写脚本检测 mysql 服务并触发 s_vip 漂移,并自行调度执行。/home/mysql/orchestrator/slave_vip_monitor.sh 内容如下:
#!/bin/bash
source /home/mysql/.bash_profile
VIP="172.21.1.8"
NETMASK="24"
INTERFACE="eth0"
OTHER_IP="172.21.1.229" # 在229上改成 172.21.1.228
LOG_FILE="/home/mysql/orchestrator/slave_vip_monitor.log"
MYSQL_SOCKET="/data/18251/mysqldata/mysql.sock"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [$1] $2" >> "$LOG_FILE"; }
check_mysql() { mysqladmin -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" -S "$MYSQL_SOCKET" ping 2>/dev/null | grep -q "mysqld is alive"; }
check_vip_local() { ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@"127.0.0.1" \
"ip addr show $INTERFACE 2>/dev/null | grep -q '$VIP/$NETMASK'" 2>/dev/null; }
log "INFO" "Started, VIP: $VIP"
# VIP不在本机直接退出
if ! check_vip_local; then
log "INFO" "VIP not local, exit"
exit 0
fi
# 循环3次检查MySQL,每次间隔5秒
FAIL_COUNT=0
for i in 1 2 3; do
if check_mysql; then
log "INFO" "MySQL alive"
exit 0
else
((FAIL_COUNT++))
log "WARN" "MySQL dead ($FAIL_COUNT/3)"
fi
if [ $i -lt 3 ]; then
sleep 5
fi
done
log "WARN" "Failover to $OTHER_IP"
# 和vip_failover.sh一样,通过SSH执行删除
ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@"127.0.0.1" \
"ip addr del $VIP/$NETMASK dev $INTERFACE" >> "$LOG_FILE" 2>&1
# 远程添加
ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@"$OTHER_IP" \
"ip addr del $VIP/$NETMASK dev $INTERFACE 2>/dev/null; ip addr add $VIP/$NETMASK dev $INTERFACE" >> "$LOG_FILE" 2>&1
ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@"$OTHER_IP" \
"arping -U -c 3 -I $INTERFACE $VIP" >> "$LOG_FILE" 2>&1 &
log "INFO" "Failover done, exit"
exit 0
调度执行:
* * * * * /home/mysql/orchestrator/slave_vip_monitor.sh
* * * * * sleep 20 && /home/mysql/orchestrator/slave_vip_monitor.sh
* * * * * sleep 40 && /home/mysql/orchestrator/slave_vip_monitor.sh
两台从库机器都要部署。
正常检测周期
- Cron触发频率:每20秒一次(0秒、20秒、40秒各执行一次)
- 每次执行的最大耗时:约15秒(3次检查 × 5秒间隔)
切换触发条件
当MySQL故障时,需要满足:
- VIP在本机(立即满足)
- 连续3次检查MySQL失败(每次间隔5秒)
- 执行切换操作(SSH命令约1-3秒)
时间范围估算
最快切换时间:
- 场景:cron刚好在执行,第一次检查就发现MySQL故障
- 时间:0 + 5 + 5 + 2 ≈ 12秒
最慢切换时间:
- 场景:MySQL刚故障时cron刚执行完,需要等下一个周期
- 时间:20秒(等待cron) + 15秒(3次检查) + 2秒(切换) ≈ 37秒
具体时间轴示例
假设MySQL在 10:00:00 故障:
10:00:00 - MySQL故障
10:00:00 - cron刚执行完(最坏情况)
10:00:20 - cron执行第1次检查 → 失败
10:00:25 - 第2次检查 → 失败
10:00:30 - 第3次检查 → 失败
10:00:32 - 切换完成
总耗时:32秒