跳到主要内容

MySQL MGR 集群

MGR (MySQL Group Replication) 提供了多节点状态同步,支持自动成员管理与故障检测,建议作为生产环境的首选高可用方案。

本指南介绍如何在三个节点上构建基于原生 Group Replication 的单主模式集群,并配置 MySQL Router 实现透明访问。

集群节点规划

服务器IP主机角色部署服务
10.206.0.2Primary (引导节点)MySQL、MySQL Router、MySQL Shell
10.206.0.3SecondaryMySQL、MySQL Router、MySQL Shell
10.206.0.4SecondaryMySQL、MySQL Router、MySQL Shell

MySQL 服务部署

部署说明

本章节涵盖了二进制环境部署 MySQL 的步骤。为确保集群基础一致性,以下步骤需在集群内的 每一个节点 上分别完整执行。

  1. 下载安装包

    wget https://pdpublic.mingdao.com/private-deployment/offline/common/mysql-8.0.45-linux-glibc2.17-x86_64.tar.xz
  2. 解压安装包并移动至安装路径

    tar -xvf mysql-8.0.45-linux-glibc2.17-x86_64.tar.xz
    mv mysql-8.0.45-linux-glibc2.17-x86_64 /usr/local/mysql
  3. 添加 mysql 到 PATH 环境变量

    echo 'export PATH=/usr/local/mysql/bin:$PATH' > /etc/profile.d/mysql.sh
    source /etc/profile.d/mysql.sh
  4. 创建系统用户与相关目录

    useradd -U -r -s /sbin/nologin mysql
    mkdir -p /data/mysql
    mkdir -p /data/logs/mysql
    chown -R mysql:mysql /data/mysql /data/logs/mysql /usr/local/mysql/
  5. 创建 /etc/my.cnf 配置文件

    配置文件修改要点

    在将以下内容写入服务器之前,请务必根据当前节点的实际环境修改以下参数:

    • server-id:每个节点必须唯一,建议按顺序递增(如 1, 2, 3)。
    • report_host:填写当前服务器的真实内网 IP 地址。
    • group_replication_local_address:填写当前服务器的真实内网 IP(端口默认为 33061)。
    • group_replication_group_seeds:填写集群内所有 3 个节点的 IP 及端口列表。
    cat > /etc/my.cnf <<'EOF'
    [mysqld]
    user = mysql
    basedir = /usr/local/mysql
    datadir = /data/mysql
    socket = /usr/local/mysql/mysqld.sock
    pid-file = /usr/local/mysql/mysqld.pid
    log-error = /data/logs/mysql/mysqld.log

    server-id = 1
    bind-address = 0.0.0.0
    port = 3306
    mysqlx_port = 33060
    skip-name-resolve = ON
    max_connections = 5000
    default_storage_engine = InnoDB
    innodb_buffer_pool_size = 2G
    character_set_server = utf8mb4
    collation_server = utf8mb4_0900_ai_ci
    slow_query_log = 1
    slow_query_log_file = /data/logs/mysql/mysql-slow.log
    long_query_time = 1
    log_bin = /data/mysql/mysql-bin
    binlog_format = ROW
    sync_binlog = 1
    binlog_expire_logs_seconds = 2592000
    # --- GTID & Replication ---
    gtid_mode = ON
    enforce_gtid_consistency = ON
    log_slave_updates = ON
    master_info_repository = TABLE
    relay_log_info_repository = TABLE
    transaction_write_set_extraction = XXHASH64
    binlog_transaction_dependency_tracking = WRITESET
    replica_parallel_type = LOGICAL_CLOCK
    replica_parallel_workers = 4
    replica_preserve_commit_order = ON

    # --- Group Replication (MGR) ---
    report_host = 10.206.0.2
    report_port = 3306
    plugin_load_add = 'group_replication.so'
    group_replication_group_name = "c9f6d3f2-7b21-4e5a-9c87-3a0e9f0a43d2"
    group_replication_start_on_boot = OFF
    group_replication_local_address = "10.206.0.2:33061"
    group_replication_group_seeds = "10.206.0.2:33061,10.206.0.3:33061,10.206.0.4:33061"
    group_replication_single_primary_mode = ON
    group_replication_enforce_update_everywhere_checks = OFF
    group_replication_recovery_get_public_key = ON


    [client]
    port = 3306
    socket = /usr/local/mysql/mysqld.sock

    [mysql]
    default-character-set = utf8mb4
    EOF
  6. 配置 systemd 管理文件

    cat > /etc/systemd/system/mysql.service <<'EOF'
    [Unit]
    Description=MySQL Database Server
    Documentation=man:mysqld(8) http://dev.mysql.com/doc/
    After=network.target
    Wants=network-online.target
    After=network-online.target

    [Service]
    Environment=MYSQLD_PARENT_PID=1
    User=mysql
    Group=mysql
    Type=forking
    PIDFile=/usr/local/mysql/mysqld.pid
    ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --daemonize
    ExecStop=/bin/kill -s SIGTERM $MAINPID
    LimitNOFILE=102400
    LimitMEMLOCK=infinity
    Restart=on-failure
    OOMScoreAdjust=-500
    TimeoutSec=0

    [Install]
    WantedBy=multi-user.target
    EOF
  7. 初始化 mysql

    mysqld --no-defaults --initialize --datadir=/data/mysql/ --user=mysql --log-error=/data/logs/mysql/mysqld.log
  8. 启动 mysql

    systemctl daemon-reload
    systemctl enable mysql
    systemctl start mysql
  9. 登录 mysql 修改初始密码

    使用初始化生成的临时密码登录:

    mysql -uroot -p$(grep 'temporary password' /data/logs/mysql/mysqld.log | awk '{print $NF}')

    登录后在 MySQL 内依次执行以下命令:

    -- 修改当前 root 用户密码
    ALTER USER USER() IDENTIFIED BY '123456';
    RESET MASTER;

    -- 创建允许远程连接的 root 用户
    SET SQL_LOG_BIN=0;
    CREATE USER 'root'@'%' IDENTIFIED BY '123456';
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
    SET SQL_LOG_BIN=1;
    • 命令中设置的 root 用户密码为 123456,实际部署必须修改为强口令
    • 若密码中包含特殊字符,仅允许 -_,禁止使用 @ ! # & 等字符,以避免兼容性问题

构建 MGR 集群

部署说明
  • 全节点准备:同步用户创建、通道凭证设置等基础配置需在 所有节点 执行。
  • 引导与成组:初始化集群引导(Bootstrap)仅在 第一个节点 执行,其余节点只需执行加入操作。
  1. 创建主从复制同步用户

    在每个节点上执行以下命令,创建用于组复制同步的账户:

    SET SQL_LOG_BIN=0;
    CREATE USER 'repl'@'%' IDENTIFIED BY '123456';
    GRANT REPLICATION SLAVE, CONNECTION_ADMIN, BACKUP_ADMIN, GROUP_REPLICATION_STREAM ON *.* TO 'repl'@'%';
    SET SQL_LOG_BIN=1;
    • 命令中设置的组复制同步帐号 repl 密码为 123456,实际部署必须修改为强口令
    • 若密码中包含特殊字符,仅允许 -_,禁止使用 @ ! # & 等字符,以避免兼容性问题
  2. 配置组复制恢复通道

    在每个节点上执行以下命令,完成组复制恢复通道凭证配置。注意将密码替换为实际的同步账号 repl 密码:

    CHANGE REPLICATION SOURCE TO SOURCE_USER='repl', SOURCE_PASSWORD='123456' FOR CHANNEL 'group_replication_recovery';
  3. 启动并初始化 MGR 组

    MGR 集群必须由一个节点发起“引导”,其它节点方可加入。请务必按以下顺序执行:

    • A. 第一台节点 (引导初始化) 执行以下命令,开启引导模式并初始化集群:

      SET GLOBAL group_replication_bootstrap_group=ON;
      START GROUP_REPLICATION;
      SET GLOBAL group_replication_bootstrap_group=OFF;
    • B. 其它节点 (加入现有集群) 在第一台节点初始化成功后,在其余节点执行以下命令加入组:

      START GROUP_REPLICATION;
  4. 检查组成员信息

    SELECT * FROM performance_schema.replication_group_members;

    正常状态应看到所有节点 MEMBER_STATE = ONLINE,并且只有一个 PRIMARY 节点,其余为 SECONDARY

  5. 修改 /etc/my.cnf 文件中 group_replication_start_on_boot 参数值为 ON

    在每个节点上执行。初始化阶段为防止引导冲突将该参数设为了 OFF,集群建立成功后需将其开启,以确保节点重启后能自动重新加入 MGR 复制组:

    sed -ri 's|group_replication_start_on_boot.*|group_replication_start_on_boot = ON|g' /etc/my.cnf
    grep group_replication_start_on_boot /etc/my.cnf
  6. 滚动重启并验证节点状态

    在每个节点上依次执行,重启一个节点后务必确认其状态恢复为 ONLINE,再操作下一个节点:

    # 1. 重启服务
    systemctl restart mysql

    # 2. 检查节点状态 (确保当前所有活跃节点 MEMBER_STATE 均为 ONLINE)
    mysql -uroot -p123456 -e 'SELECT * FROM performance_schema.replication_group_members;'

开启 InnoDB Cluster

部署说明
  • 全节点操作:需在所有节点下载并安装 MySQL Shell (mysqlsh) 软件包。

  • 单节点操作:InnoDB Cluster 的初始化配置仅需在 第一个节点 (Primary) 上执行一次即可。

  1. 下载 mysqlsh 安装包

    wget https://pdpublic.mingdao.com/private-deployment/offline/common/mysql-shell-8.0.45-linux-glibc2.17-x86-64bit.tar.gz
  2. 解压安装包并移动至安装路径

    tar -xvf mysql-shell-8.0.45-linux-glibc2.17-x86-64bit.tar.gz
    mv mysql-shell-8.0.45-linux-glibc2.17-x86-64bit /usr/local/mysql-shell
  3. 添加 mysqlsh 到 PATH 环境变量

    echo 'export PATH=/usr/local/mysql-shell/bin:$PATH' > /etc/profile.d/mysql-shell.sh
    source /etc/profile.d/mysql-shell.sh
  4. 连接 PRIMARY 节点 (仅首个节点执行)

    mysqlsh --uri root@10.206.0.2:3306 -p123456
  5. 初始化创建 InnoDB Cluster

    var cluster = dba.createCluster('hap-mysql')
  6. 查看集群状态

    cluster.status()
    • 重新登录后需要先 var cluster = dba.getCluster() 才可以使用 cluster.status()

MySQL Router 部署

部署说明

为了实现接入层的高可用,以下步骤需在集群内的 每一个节点 上分别执行。

  1. 下载安装包

    wget https://pdpublic.mingdao.com/private-deployment/offline/common/mysql-router-8.0.45-linux-glibc2.17-x86_64.tar.xz
  2. 解压安装包并移动至安装路径

    tar -xvf mysql-router-8.0.45-linux-glibc2.17-x86_64.tar.xz
    mv mysql-router-8.0.45-linux-glibc2.17-x86_64 /usr/local/mysql-router
  3. 添加 MySQL 到 PATH 环境变量

    echo 'export PATH=/usr/local/mysql-router/bin:$PATH' > /etc/profile.d/mysql-router.sh
    source /etc/profile.d/mysql-router.sh
  4. 初始化 mysqlrouter

    mysqlrouter --bootstrap root:123456@10.206.0.2:3306 --user=mysql --report-host=10.206.0.2

    注意:

    • --bootstrap 后跟的 IP 为 Primary 节点 IP(用于读取集群拓扑),集群所有节点填写相同的 Primary 地址即可。

    • --report-host 后跟的 IP 为 当前节点本机内网 IP,每个节点需分别修改为自身实际 IP。

  5. 增加 mysqlrouter 最大连接数配置

    sed -i '/^\[DEFAULT\]$/a max_total_connections=5000' /usr/local/mysql-router/mysqlrouter.conf
  6. 配置权限

    chown -R mysql:mysql /usr/local/mysql-router
  7. 配置 systemd

    cat > /etc/systemd/system/mysqlrouter.service <<'EOF'
    [Unit]
    Description=MySQL Router Service
    Documentation=man:mysqlrouter(1) https://dev.mysql.com/doc/mysql-router/en/
    Wants=network-online.target
    After=network-online.target

    [Service]
    Type=simple
    User=mysql
    Group=mysql
    WorkingDirectory=/usr/local/mysql-router
    ExecStart=/usr/local/mysql-router/bin/mysqlrouter -c /usr/local/mysql-router/mysqlrouter.conf
    Restart=on-failure
    RestartSec=2
    LimitNOFILE=102400
    OOMScoreAdjust=-500
    TimeoutSec=0

    [Install]
    WantedBy=multi-user.target
    EOF
  8. 启动 mysqlrouter

    systemctl daemon-reload
    systemctl enable mysqlrouter
    systemctl start mysqlrouter
  9. 连接测试

    通过本节点 MySQL Router 的读写端口 6446 验证连接是否正常:

    mysql -h 127.0.0.1 -P 6446 -u root -p123456

MySQL MGR 自动恢复

配置说明

MGR 集群在运行中若发生 半数以上节点同时故障,为防止数据“脑裂”,集群会自动进入 全量中断(OFFLINE/RECOVERING)状态。此时即便节点服务进程正常,MGR 默认也无法在无人干预的情况下自动重新成组。

配置 “自动恢复脚本” 的核心目的在于通过定时检测,在集群彻底中断时自动根据 GTID 进度选取最新节点引导恢复,实现故障自愈。以下步骤需在 每个节点 上完整执行。

  1. 创建目录并赋予权限

    # 1. 创建日志目录
    mkdir -p /data/logs/mysql

    # 2. 确保目录属主为 mysql 用户(或根据你的实际运行用户调整)
    chown -R mysql:mysql /data/logs/mysql

    # 3. 设置目录访问权限
    chmod 755 /data/logs/mysql
  2. 创建核心检测与恢复脚本 /usr/local/bin/mysql-auto-recover.sh

    注意:请务必核对并修改下方脚本中 核心配置 区域的参数(如密码、节点 IP 列表等),确保其与您的实际部署环境一致。

    cat > /usr/local/bin/mysql-auto-recover.sh <<'EOF'
    #!/bin/bash
    set -euo pipefail # 严格模式

    # ============================ 核心配置 ============================
    MYSQL_CLI="/usr/local/mysql/bin/mysql" # MySQL 客户端的文件路径
    MYSQL_SHELL="/usr/local/mysql-shell/bin/mysqlsh" # MySQL Shell 文件路径
    MYSQL_USER="root" # MySQL 数据库管理员用户名
    MYSQL_PASS="123456" # MySQL 数据库管理员密码
    CLUSTER_NAME="hap-mysql" # MGR (InnoDB 集群) 的名称
    LOG_FILE="/data/logs/mysql/mysql-auto-recover.log" # 自动恢复脚本的日志路径
    LOCK_FILE="/data/logs/mysql/mysql-auto-recover.lock" # 进程排他锁文件路径
    NODES=("10.206.0.2" "10.206.0.3" "10.206.0.4") # MGR 集群中所有节点的 IP 地址列表,空格分隔,需与集群规划保持一致
    LOG_KEEP_DAYS=180 # 本地日志文件保留天数
    TRIES=3 # 故障确认检测的连续重试次数
    SLEEP_SEC=2 # 每次故障检测重试之间的等待/缓冲时间(单位:秒)

    # ============================ 日志函数 ============================
    # 统一日志格式:[YYYY-MM-DD HH:MM:SS] [LEVEL] [PID] message
    # 级别:INFO(正常事件)/ WARN(异常但可自恢复)/ ERROR(恢复失败需关注)
    _log() {
    local level="$1"; shift
    printf '[%s] [%-5s] [pid=%d] %s\n' "$(date '+%F %T')" "$level" "$$" "$*" >> "$LOG_FILE"
    }
    log_info() { _log "INFO" "$@"; }
    log_warn() { _log "WARN" "$@"; }
    log_error() { _log "ERROR" "$@"; }

    # 把外部进程 (如 mysqlsh) 的 stdout/stderr 每一行加上统一前缀后落盘;
    # 同时过滤掉空行和已知无价值的噪音告警,保证日志整洁可读。
    log_pipe() {
    local tag="$1"
    local line
    while IFS= read -r line; do
    [ -z "$line" ] && continue
    case "$line" in
    *"Using a password on the command line"*) continue ;;
    *"Cannot set LC_ALL to locale"*) continue ;;
    esac
    _log "INFO" "[$tag] $line"
    done
    }

    # ============================ 基础函数 ============================
    # 并发控制:确保同一时间只有一个检测进程
    exec_lock() {
    exec 9>"$LOCK_FILE"
    if ! flock -n 9; then
    log_warn "Another instance is already running; exiting."
    exit 0
    fi
    }

    # 日志切割与清理:按日分文件,主日志路径通过符号链接指向当天文件
    rotate_logs() {
    local log_file="$1"
    local keep_days="${2:-180}"
    local log_dir base_name today today_log

    log_dir=$(dirname "$log_file")
    base_name=$(basename "$log_file" .log)
    today=$(date +%F)
    today_log="${log_dir}/${base_name}_${today}.log"

    # 今天的日志文件不存在时,创建并把主日志路径指向它(原子更新软链)
    if [ ! -f "$today_log" ]; then
    : > "$today_log"
    ln -sfn "$today_log" "$log_file"
    fi

    # 若主日志既不是软链也不是今天的文件(例如人为改动),修正软链
    if [ ! -L "$log_file" ] || [ "$(readlink -f "$log_file" 2>/dev/null || true)" != "$today_log" ]; then
    ln -sfn "$today_log" "$log_file"
    fi

    # 清理过期日志(只扫当前目录,避免误删)
    find "$log_dir" -maxdepth 1 -name "${base_name}_*.log" -type f -mtime +"$keep_days" -exec rm -f {} \; 2>/dev/null || true
    }

    check_node_alive() {
    local node="$1"
    if "$MYSQL_CLI" -h "$node" -u"$MYSQL_USER" -p"$MYSQL_PASS" \
    --connect-timeout=3 -e "SELECT 1;" >/dev/null 2>&1; then
    return 0
    else
    log_warn "Node $node is unreachable."
    return 1
    fi
    }

    check_all_nodes_alive() {
    local ok=0
    for node in "${NODES[@]}"; do
    if check_node_alive "$node"; then
    ok=$((ok+1))
    fi
    done
    echo "$ok"
    }

    # 返回值:非负整数=ONLINE 成员数;-1=查询失败(区别于真正的 0)
    get_cluster_online_count() {
    local result
    result=$("$MYSQL_CLI" -u"$MYSQL_USER" -p"$MYSQL_PASS" \
    --connect-timeout=3 -N -e \
    "SELECT COUNT(*) FROM performance_schema.replication_group_members WHERE MEMBER_STATE='ONLINE';" 2>/dev/null) || {
    echo "-1"
    return 0
    }
    if [[ "$result" =~ ^[0-9]+$ ]]; then
    echo "$result"
    else
    echo "-1"
    fi
    }

    # 统计 gtid_executed 中已提交的事务总数,兼容:
    # 1) 单事务:uuid:1000 → 1
    # 2) 区间: uuid:1-1000 → 1000
    # 3) 多区间:uuid:1-100:200-500 → 100 + 301 = 401
    # 4) 多源: uuid1:1-100,uuid2:1-200 → 100 + 200 = 300
    # 关键:按 ":" 切分、跳过 UUID 段,避免把 UUID 中的数字/连字符误算入事务数;
    # 用"事务总数"而非"最大序号"作为选主依据,可正确处理各节点最高区间相同、
    # 低区间大小不同的情形(例如 1-53986:1000005-1000010 vs 1-53988:1000005-1000010)。
    count_gtid_transactions() {
    local gtid="$1"
    [ -z "$gtid" ] && { echo 0; return 0; }
    echo "$gtid" | awk '
    BEGIN { total = 0 }
    {
    # 去掉所有空白字符
    gsub(/[ \t\r\n]/, "", $0)
    # 多源以逗号分隔
    n = split($0, sources, ",")
    for (i = 1; i <= n; i++) {
    # 每个源形如 uuid:range[:range...],第一段是 UUID 必须跳过
    m = split(sources[i], parts, ":")
    for (j = 2; j <= m; j++) {
    seg = parts[j]
    if (seg ~ /^[0-9]+$/) {
    # 单事务
    total += 1
    } else if (seg ~ /^[0-9]+-[0-9]+$/) {
    # 闭区间 [start, end],事务数 = end - start + 1
    k = split(seg, range, "-")
    start = range[1] + 0
    end = range[k] + 0
    if (end >= start) total += (end - start + 1)
    }
    }
    }
    print total
    }'
    }

    find_primary_node() {
    declare -A TX_MAP
    local max_tx=0
    local primary=""
    local node gtid tx

    for node in "${NODES[@]}"; do
    gtid=$("$MYSQL_CLI" -h "$node" -u"$MYSQL_USER" -p"$MYSQL_PASS" \
    --connect-timeout=3 -N -e \
    "SELECT @@GLOBAL.gtid_executed;" 2>/dev/null) || gtid=""
    tx=$(count_gtid_transactions "$gtid")
    TX_MAP[$node]=$tx
    log_info "Node $node GTID: executed=[$gtid] transactions=$tx"
    if [ "$tx" -gt "$max_tx" ]; then
    max_tx=$tx
    primary=$node
    fi
    done

    # 如果所有节点 GTID 都是 0(新建空集群或查询全部失败),默认选第一个节点
    [ -z "$primary" ] && primary="${NODES[0]}"

    log_info "Primary candidate elected: $primary (transactions=$max_tx)"
    echo "$primary"
    }

    # 判断当前主机的 IP 是否命中目标节点(支持多网卡,精确匹配)
    is_local_host() {
    local target="$1"
    local ip
    for ip in $(hostname -I 2>/dev/null); do
    [ "$ip" = "$target" ] && return 0
    done
    return 1
    }

    do_recovery() {
    local my_ip rc
    my_ip=$(hostname -I | awk '{print $1}')
    log_info "This node ($my_ip) is the recovery target; invoking reboot_cluster_from_complete_outage."
    set +e
    # LC_ALL / LANG 预置为 C.UTF-8,避免 mysqlsh 在未安装 en_US.UTF-8 的系统上报 locale 告警;
    # 所有输出重定向到 log_pipe,统一加 [MYSQLSH] 前缀落盘;
    # 用 PIPESTATUS[0] 捕获 mysqlsh 自身退出码(而非 log_pipe 的 0)。
    timeout 60 env LC_ALL=C.UTF-8 LANG=C.UTF-8 \
    "$MYSQL_SHELL" --no-wizard --py \
    --user="$MYSQL_USER" --password="$MYSQL_PASS" \
    --host=127.0.0.1 <<PYEOF 2>&1 | log_pipe "MYSQLSH"
    import sys
    try:
    cluster = dba.reboot_cluster_from_complete_outage('$CLUSTER_NAME', {'force': True})
    print("Cluster rebooted successfully.")
    except Exception as e:
    print("Recovery failed:", e)
    sys.exit(1)
    PYEOF
    rc=${PIPESTATUS[0]}
    set -e
    if [ "$rc" -eq 0 ]; then
    log_info "Cluster reboot completed successfully."
    elif [ "$rc" -eq 124 ]; then
    log_error "Cluster reboot timed out after 60 seconds; will retry on next cycle."
    else
    log_error "Cluster reboot failed (exit_code=$rc); will retry on next cycle."
    fi
    }

    # ============================ 主流程 ============================
    # 先加锁,再轮转日志,避免多实例并发时竞态破坏软链/日志
    exec_lock
    rotate_logs "$LOG_FILE" "$LOG_KEEP_DAYS"

    MY_IP=$(hostname -I | awk '{print $1}')
    log_info "===== MGR health check started (host=$MY_IP) ====="

    # 连续 TRIES 次全部确认为“全节点可达且集群 ONLINE 成员=0”才判定为整集群故障
    is_outage=false
    consecutive=0
    for attempt in $(seq 1 "$TRIES"); do
    reachable=$(check_all_nodes_alive)
    online_count=$(get_cluster_online_count)

    if [ "$reachable" -eq "${#NODES[@]}" ] && [ "$online_count" = "0" ]; then
    consecutive=$((consecutive + 1))
    log_warn "Attempt $attempt/$TRIES: outage signal confirmed ($consecutive/$TRIES) [reachable=$reachable, online=$online_count]"
    if [ "$consecutive" -ge "$TRIES" ]; then
    is_outage=true
    break
    fi
    else
    if [ "$consecutive" -gt 0 ]; then
    log_info "Attempt $attempt/$TRIES: outage signal reset [reachable=$reachable, online=$online_count]"
    fi
    consecutive=0
    fi

    # 最后一次不再 sleep,避免无意义等待
    [ "$attempt" -lt "$TRIES" ] && sleep "$SLEEP_SEC"
    done

    if [ "$is_outage" = true ]; then
    log_warn "Complete cluster outage detected; initiating recovery procedure."
    PRIMARY_NODE=$(find_primary_node)

    if is_local_host "$PRIMARY_NODE"; then
    do_recovery
    else
    log_info "This node ($MY_IP) is not the recovery target ($PRIMARY_NODE); standing by."
    fi
    else
    log_info "Cluster is healthy or precondition not met; no action taken."
    fi

    log_info "===== MGR health check finished ====="
    EOF

    授予执行权限

    chmod +x /usr/local/bin/mysql-auto-recover.sh
  3. 配置 Systemd 服务单元

    创建 Service 文件,定义如何运行上述脚本。

    cat > /etc/systemd/system/mysql-auto-recover.service <<'EOF'
    [Unit]
    Description=MySQL MGR Auto-recovery Service
    After=network.target mysql.service

    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/mysql-auto-recover.sh
    User=root
    TimeoutSec=0
    EOF
  4. 配置 Systemd 定时器

    创建 Timer 文件,设定脚本每分钟执行一次。

    cat > /etc/systemd/system/mysql-auto-recover.timer <<'EOF'
    [Unit]
    Description=Run MySQL MGR Recovery check every minute

    [Timer]
    OnCalendar=*-*-* *:*:00
    Unit=mysql-auto-recover.service
    Persistent=true

    [Install]
    WantedBy=timers.target
    EOF
  5. 加载配置并启动

    # 重载 systemd 配置
    systemctl daemon-reload

    # 启用并立即启动定时器
    systemctl enable --now mysql-auto-recover.timer
  6. 常用命令

    以下是管理自动恢复服务及其定时器的常用指令:

    # 查看定时器下次执行时间
    systemctl list-timers --all | grep mysql-auto-recover

    # 查看实时运行日志
    tail -f /data/logs/mysql/mysql-auto-recover.log

    # 手动立即触发一次恢复检测
    systemctl start mysql-auto-recover.service

    # 查看定时器服务状态
    systemctl status mysql-auto-recover.timer

    # 临时停止自动恢复监控
    systemctl stop mysql-auto-recover.timer