VIP + Orchestrator + 自定义脚本 MySQL 高可用方案

目录

[1. 现状](#1. 现状)

[2. 目标](#2. 目标)

[3. 方案](#3. 方案)

[4. 流程](#4. 流程)

[5. 实现](#5. 实现)

(1)主库高可用

[1. 配置 Orchestrator 钩子](#1. 配置 Orchestrator 钩子)

[2. 准备环境](#2. 准备环境)

[3. 编写 VIP 漂移脚本](#3. 编写 VIP 漂移脚本)

(2)从库高可用


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秒

相关推荐
TPBoreas14 小时前
MySQL性能优化面试全攻略
mysql·面试·性能优化
xlq2232215 小时前
11.表的内外连接,索引
mysql
AllData公司负责人15 小时前
数据库同步平台|AIIData数据中台实现OceanBase、达梦数据库、OpenGauss、人大金仓、Hive、TDengine 一键接入Doris
大数据·数据库·hive·mysql·oceanbase·tdengine
wefg116 小时前
【MySQL】事务
数据库·mysql
黑白极客16 小时前
mysql的高可用性
数据库·mysql
heimeiyingwang19 小时前
【架构实战】MySQL索引优化:从慢查询到毫秒响应
数据库·mysql·架构
渣渣灰飞19 小时前
MySQL 系统学习 第二阶段 第四章:DCL(Data Control Language)第二节:权限管理与角色(Role)
数据库·学习·mysql
宠友信息21 小时前
多端即时通讯源码技术实践,Redis路由、MySQL持久化与Socket接入
spring boot·websocket·mysql·uni-app
渣渣灰飞21 小时前
MySQL 系统学习 第四阶段:MySQL 高级 第一节:索引(Index)
数据库·学习·mysql