供电不稳定、无UPS/无双电源环境下服务器高可用完整方案

供电不稳定、无UPS/无双电源环境下服务器高可用完整方案

目录

  1. 需求背景与核心目标
  2. 整体架构设计
  3. 硬件层方案:实现与验证
  4. 操作系统层方案:实现与验证
  5. 应用层方案:实现与验证
  6. 数据库层方案:实现与验证
  7. 双系统切换与数据同步机制
  8. 故障应急响应流程
  9. 运维巡检规范
  10. 风险控制与优化建议

一、需求背景与核心目标

1. 场景痛点

  • 供电环境恶劣:服务器面临频繁突然断电、电压波动等问题。
  • 硬件限制:现场条件禁止使用 UPS(不间断电源),禁止使用双路冗余电源。
  • 硬件配置:服务器仅配备 4 块硬盘。
  • 业务底线:必须保证服务器不掉盘、不损坏、系统不崩溃、数据零丢失、应用可快速恢复。

2. 核心目标

  • 硬件防护:抵御断电冲击,最大程度降低物理损坏率。
  • 系统韧性:断电后可正常开机引导,不卡死、不报错。
  • 数据安全:确保数据零丢失、文件系统不损坏、具备可恢复能力。
  • 业务连续:支持自动恢复与手动切换,实现故障后的快速自愈。

3. 已知风险说明

RAID 卡单点故障:两组阵列共用同一块 RAID 卡,RAID 卡本身损坏将导致两组阵列同时不可访问。缓解措施:备件库中常备同型号 RAID 卡,并将 RAID 卡配置(LD 信息、策略)截图存档,以便快速重建。


二、整体架构设计

最终架构模型(4盘 = 2组独立 RAID1)

复制代码
┌─────────────────────────────────────────────────┐
│                  硬件 RAID 卡                    │
│   ┌──────────────────┐  ┌──────────────────┐    │
│   │   LD0(主阵列)  │  │   LD1(备阵列)  │    │
│   │  Disk0 + Disk1   │  │  Disk2 + Disk3   │    │
│   │     RAID1        │  │     RAID1        │    │
│   │   可引导(Boot)   │  │   可引导(Boot)   │    │
│   └────────┬─────────┘  └────────┬─────────┘    │
└────────────┼────────────────────┼───────────────┘
             │  主系统运行          │  备用系统
             │  /data 数据区        │  /mnt/backup 同步目标
             │                      │
             └────── rsync 定时单向同步 ──▶
  • 数据流向:主系统(LD0) ➔ 定时单向同步 ➔ 备用系统(LD1)
  • 容灾切换:主阵列故障时,改 BIOS 启动顺序,从 LD1 引导

三、硬件层方案:实现与验证

1. 核心策略

  • 将 4 块盘划分为两组完全独立的 RAID1。
  • 强制全局关闭 RAID 卡写缓存(无UPS环境下的绝对红线,开启写缓存断电必丢数据)。
  • 两组逻辑驱动器(LD)均设置为可引导(Bootable)。
  • 采用企业级 SSD 或带有掉电保护(Power Loss Protection)的硬盘;机房做好接地、防雷及抗浪涌措施。

2. 落地实现

  1. 进入 RAID 卡配置界面(LSI/Broadcom 系列按 Ctrl+R,华为/海思系列按 Ctrl+A)。
  2. 创建 LD0:绑定 Disk0 + Disk1,RAID 级别选 RAID1
  3. 创建 LD1:绑定 Disk2 + Disk3,RAID 级别选 RAID1
  4. 控制器全局策略 (Controller Properties):
    • Write Policy = Write Through(直写,彻底禁用板载 BBU/超级电容缓存)
    • Read Policy = No Read AheadDirect I/O
    • IO Policy = Direct
  5. LD0LD1 均标记为 Bootable
  6. BIOS 启动顺序:LD0 第一,LD1 第二。

3. 效果验证

bash 复制代码
# LSI/Broadcom 卡(MegaCli 或 storcli)
# 查看所有 LD 状态,确认 State: Optimal
MegaCli -LDInfo -Lall -aALL

# 确认写策略为 Write Through(无 WriteBack 字样)
MegaCli -LDGetProp -Cache -Lall -aALL

# storcli(新版工具)等效命令
storcli /c0 /vall show

验收标准:

  • 两组阵列 State 均为 Optimal
  • Current Cache Policy 中 无 WriteBack ,显示 WriteThrough
  • BIOS 启动列表中可见两个独立引导设备

破坏性测试(在初始部署阶段执行一次):

  • 热拔除 Disk0 或 Disk1 中任意一块,LD0 降级(Degraded)但系统不宕机,LD1 状态不变
  • 重新插入硬盘,LD0 自动重建,重建完成后恢复 Optimal

四、操作系统层方案:实现与验证

本章分 CentOS 7.9 和 Ubuntu 24.04 两个分支分别说明,差异主要体现在持久化方式。

1. 核心策略

  • 使用 XFS 文件系统(CentOS 7.9 默认;Ubuntu 24.04 也推荐 XFS,或保留 ext4)。
  • 挂载参数减少无效写、缩短日志提交窗口;XFS barrier 默认已启用,无需显式指定。
  • 内核脏页参数:缩短脏数据在内存中的驻留时间上限(commit=5 对应 XFS journal,dirty_expire_centisecs=500 对应内核页缓存,二者相互配合)。
  • 通过 udev 规则持久化关闭硬盘底层写缓存(hdparm -W 0 仅对 SATA HDD 有效;NVMe/SSD 由控制器保证,无需此步骤;硬件 RAID 后端盘通常不可被 OS 直接寻址,该命令需对 RAID 逻辑设备 /dev/sda 等执行)。

2. 落地实现

2.1 文件系统挂载参数(/etc/fstab

两个系统格式相同,设备名按实际替换(可用 blkid 获取 UUID):

text 复制代码
# 建议使用 UUID 代替设备名,防止设备名漂移
UUID=<主阵列根分区UUID>  /           xfs  defaults,noatime,nodiratime,commit=5  0 1
UUID=<主阵列数据分区UUID> /data       xfs  defaults,noatime,nodiratime,commit=5  0 2
UUID=<备阵列数据分区UUID> /mnt/backup xfs  defaults,noatime,nodiratime,commit=5,nofail  0 2

参数说明:

  • noatime,nodiratime:禁止更新访问时间,减少无效写 I/O
  • commit=5:XFS journal 每 5 秒强制提交一次(默认 30 秒,缩短可减少断电丢失窗口)
  • XFS 的 barrier(写屏障)默认开启,无需额外指定
  • nofail:备用阵列挂载失败时不阻塞系统启动

ext4 用户 (Ubuntu 如使用 ext4):将 xfs 替换为 ext4,并追加 barrier=1,journal_async_commitcommit=5 同样适用。

2.2 内核脏页参数(/etc/sysctl.d/99-disk-safety.conf

两个系统路径相同 ,推荐写入独立文件而非直接修改 /etc/sysctl.conf

ini 复制代码
# 脏数据在内存中驻留的最大时间:5秒(默认 3000 = 30秒)
vm.dirty_expire_centisecs = 500

# 后台刷盘线程唤醒间隔:2秒(默认 500 = 5秒)
vm.dirty_writeback_centisecs = 200

# 脏数据占总内存比超过此值时,进程阻塞强制刷盘
vm.dirty_ratio = 10

# 脏数据占总内存比超过此值时,后台异步开始刷盘
vm.dirty_background_ratio = 5

立即生效:

bash 复制代码
sysctl --system
# 验证
sysctl vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
2.3 关闭硬盘底层写缓存

仅适用于 SATA HDD。若 RAID 卡已接管所有磁盘 I/O(硬件 RAID 模式),OS 看到的是逻辑设备,此命令对逻辑设备执行;若为直通/JBOD 模式,则对各物理设备执行。SSD/NVMe 跳过此步。

CentOS 7.9 ------ 通过 udev 规则持久化:

bash 复制代码
cat > /etc/udev/rules.d/60-disable-write-cache.rules << 'EOF'
# 对所有 SATA 磁盘禁用写缓存
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \
  RUN+="/sbin/hdparm -W 0 /dev/%k"
EOF

udevadm control --reload-rules
udevadm trigger

Ubuntu 24.04 ------ 通过 udev 规则持久化(路径略有不同):

bash 复制代码
cat > /etc/udev/rules.d/60-disable-write-cache.rules << 'EOF'
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \
  RUN+="/usr/sbin/hdparm -W 0 /dev/%k"
EOF

udevadm control --reload-rules
udevadm trigger

立即验证(重启前确认):

bash 复制代码
hdparm -W /dev/sda   # 输出应为 write-caching =  0 (off)
hdparm -W /dev/sdb
2.4 禁用开机 fsck 长时间自检

CentOS 7.9:

bash 复制代码
# XFS 本身无 fsck 强制周期,确认 /etc/fstab 中根分区 pass 值为 1 即可
# 禁用 tune2fs 的定期检查(如有 ext4 分区)
tune2fs -c 0 -i 0 /dev/sdaX

Ubuntu 24.04:

bash 复制代码
# systemd-fsck 默认行为,强制跳过非必要检查
# ext4 分区同上;XFS 无需操作
# 检查是否存在 forcefsck 标记文件
ls /forcefsck 2>/dev/null && rm /forcefsck

3. 效果验证

bash 复制代码
# 1. 确认挂载参数生效
mount | grep -E '/ |/data|/mnt/backup'
# 应看到 noatime,nodiratime,commit=5 等参数

# 2. 确认内核参数
sysctl vm.dirty_expire_centisecs vm.dirty_writeback_centisecs \
       vm.dirty_ratio vm.dirty_background_ratio

# 3. 确认写缓存关闭
hdparm -W /dev/sda
# 期望:write-caching =  0 (off)

# 4. 模拟断电测试(在非生产环境执行)
# 直接拔电源插头,重新上电后确认:
dmesg | grep -iE 'error|xfs|ext4|filesystem'
# 正常情况:XFS 看到 journal replay 完成,无 corruption 报错
# 系统应在 30 秒内完成引导,不进入 emergency mode

五、应用层方案:实现与验证

1. 核心策略

  • 应用写数据必须调用 fsync() 落盘,不依赖 OS 缓存刷盘时机。
  • rsync 同步加并发锁(flock),防止两个任务叠加执行。
  • rsync 使用 --delay-updates + --fsync,保护同步期间断电时 LD1 上已有的完好数据不受影响。
  • 同步完成后立即 umount LD1,确保非同步窗口期间 LD1 处于干净卸载状态。
  • --delete 结合 --backup 保留被删除文件副本,防止误删扩散。
  • Supervisor 设置 startretries 上限,持续崩溃时触发告警而非无限重启掩盖故障。

关于备用盘(LD1)在同步期间断电的风险说明

LD1 挂载到主系统上接受 rsync 写入时,存在两类不同性质的风险:

风险类型 实际情况 原因
文件系统损坏(无法挂载) 概率极低 XFS 日志 + barrier + Write Through RAID,断电后日志重放即可恢复挂载
数据不完整(文件残缺/半截) 必然存在 断电瞬间正在传输的文件处于中间状态

因此问题的本质是:如何缩小"正在被写的文件"的范围,确保 LD1 上已有的完好数据不被破坏

三项改进措施:

  1. --delay-updates :rsync 传输阶段将所有文件先写入 LD1 上的隐藏暂存目录(.~tmp~/),全部传输完成后再批量 rename 到最终位置。传输阶段 LD1 上原有文件完全不被触碰;若断电发生在传输阶段,LD1 的原有数据完整无损,只丢失本次增量部分。

  2. --fsync:每个文件传输完成后立即调用 fsync(),确保落到 LD1 的物理盘,不依赖内核页缓存刷盘时机。

  3. 同步后 umount:umount 会强制刷盘并将文件系统标记为"干净卸载",两次同步之间 LD1 处于未挂载状态------即使主系统断电,LD1 也没有任何脏数据可丢失。

    同步期间断电的影响范围(改进后):
    ─────────────────────────────────────────────────────────
    断电发生在传输阶段 → LD1 原有数据完整,暂存目录有残缺临时文件(下次同步清理)
    断电发生在 rename 阶段 → 部分文件已更新为新版本,部分仍为旧版本(均完整,非半截)
    断电发生在 umount 期间 → XFS 日志重放,文件系统可正常挂载
    两次同步之间断电 → LD1 未挂载,无任何风险

2. 落地实现

2.1 数据同步脚本 /usr/local/bin/sync_data.sh
bash 复制代码
#!/bin/bash
set -euo pipefail

SRC="/data/"
DST="/mnt/backup/data/"
BACKUP_DEVICE="UUID=<备用阵列数据分区UUID>"   # 用 blkid 获取实际 UUID
MOUNT_POINT="/mnt/backup"
LOG_FILE="/var/log/rsync_backup.log"
LOCK_FILE="/var/run/rsync_backup.lock"
BACKUP_DIR="${MOUNT_POINT}/data_deleted/$(date +%F)"

# 获取互斥锁,防止并发执行
exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
    echo "$(date '+%F %T') [WARN] 上一次同步仍在运行,本次跳过" >> "${LOG_FILE}"
    exit 0
fi

# 挂载 LD1(如已挂载则跳过)
if ! mountpoint -q "${MOUNT_POINT}"; then
    mount "${BACKUP_DEVICE}" "${MOUNT_POINT}"
    echo "$(date '+%F %T') [INFO] 已挂载 LD1: ${MOUNT_POINT}" >> "${LOG_FILE}"
fi

echo "$(date '+%F %T') [INFO] 开始同步 ${SRC} => ${DST}" >> "${LOG_FILE}"

# --delay-updates : 先将所有文件传到暂存目录,全部完成后再 rename,保护 LD1 原有数据
# --fsync         : 每个文件写完后立即 fsync,避免数据滞留在页缓存
# --backup        : 被删除的文件移入带日期的目录,而非直接丢弃
rsync -a --delete \
    --delay-updates \
    --fsync \
    --backup --backup-dir="${BACKUP_DIR}" \
    --exclude="*.tmp" \
    --exclude="*.lock" \
    --exclude="*.sock" \
    --log-file="${LOG_FILE}" \
    "${SRC}" "${DST}"

RSYNC_EXIT=$?

# 强制刷新 LD1 的所有页缓存到物理盘
sync

if [ ${RSYNC_EXIT} -eq 0 ]; then
    echo "$(date '+%F %T') [INFO] 同步完成,准备卸载 LD1" >> "${LOG_FILE}"
    # umount 确保 LD1 以干净状态脱离主系统,两次同步之间无脏数据风险
    umount "${MOUNT_POINT}"
    echo "$(date '+%F %T') [INFO] LD1 已卸载,处于安全状态" >> "${LOG_FILE}"
else
    echo "$(date '+%F %T') [ERROR] 同步失败(退出码: ${RSYNC_EXIT}),尝试卸载 LD1" >> "${LOG_FILE}"
    # 即使 rsync 失败也要 umount,避免 LD1 长期处于挂载状态
    umount "${MOUNT_POINT}" 2>> "${LOG_FILE}" || \
        echo "$(date '+%F %T') [WARN] umount 失败,请手动检查" >> "${LOG_FILE}"
fi

exit ${RSYNC_EXIT}
bash 复制代码
chmod +x /usr/local/bin/sync_data.sh

配合修改 /etc/fstab 将 LD1 数据分区的挂载行加上 noauto,防止开机自动挂载(改为由脚本按需挂载):

text 复制代码
# LD1 数据分区改为手动挂载(noauto),由同步脚本控制生命周期
UUID=<备用阵列数据分区UUID>  /mnt/backup  xfs  defaults,noatime,nodiratime,commit=5,nofail,noauto  0 2

脚本关键改进说明:

  • --delay-updates:传输阶段不碰 LD1 原有文件,断电最多丢失本次增量,不破坏已有数据
  • --fsync:每个文件写完即落盘,不依赖内核后台刷盘
  • sync + umount:同步后强制落盘并卸载,非同步窗口 LD1 无脏数据
  • noauto fstab:LD1 不自动挂载,杜绝意外写入
2.2 定时任务

CentOS 7.9 / Ubuntu 24.04 相同:

bash 复制代码
crontab -e
text 复制代码
# 每 30 分钟同步一次(错开整点,避免与其他定时任务撞车)
5,35 * * * * /usr/local/bin/sync_data.sh
2.3 应用守护(Supervisor)

CentOS 7.9 安装:

bash 复制代码
yum install -y epel-release
yum install -y supervisor
systemctl enable supervisord
systemctl start supervisord

Ubuntu 24.04 安装:

bash 复制代码
apt-get install -y supervisor
systemctl enable supervisor
systemctl start supervisor

应用配置 /etc/supervisor/conf.d/myapp.conf

两个系统配置格式相同,conf.d 路径在 Ubuntu 下为 /etc/supervisor/conf.d/,CentOS 下为 /etc/supervisord.d/(注意文件扩展名 .ini)。

ini 复制代码
[program:myapp]
command=/usr/bin/python3 /opt/myapp/app.py
directory=/opt/myapp
autostart=true
autorestart=true
; 连续崩溃超过 5 次停止重试,防止掩盖真实故障
startretries=5
; 进程退出码不为 0 时才算崩溃
exitcodes=0
stdout_logfile=/var/log/myapp.out.log
stderr_logfile=/var/log/myapp.err.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=5
stderr_logfile_maxbytes=50MB
stderr_logfile_backups=5
; 优雅退出等待时间
stopwaitsecs=30

CentOS 7.9 配置路径差异:

bash 复制代码
# CentOS conf.d 路径
cat > /etc/supervisord.d/myapp.ini << 'EOF'
[program:myapp]
... # 内容同上
EOF

supervisorctl reread
supervisorctl update

Ubuntu 24.04:

bash 复制代码
cat > /etc/supervisor/conf.d/myapp.conf << 'EOF'
[program:myapp]
... # 内容同上
EOF

supervisorctl reread
supervisorctl update

3. 效果验证

bash 复制代码
# 1. 手动触发同步,确认日志正常,同步完成后 LD1 已卸载
/usr/local/bin/sync_data.sh
tail -20 /var/log/rsync_backup.log
mountpoint -q /mnt/backup && echo "仍挂载(异常)" || echo "已卸载(正常)"

# 2. 验证 LD1 在同步结束后确实处于卸载状态(非同步窗口无法意外写入)
mount | grep backup   # 不应有输出

# 3. 验证并发锁:再次触发应被跳过
/usr/local/bin/sync_data.sh &
/usr/local/bin/sync_data.sh
# 第二次应输出 "上一次同步仍在运行,本次跳过"

# 4. 验证 --delay-updates 保护效果(模拟同步中断)
# 在另一个终端中:kill $(pgrep rsync)  模拟中断
# 中断后检查 LD1 挂载(此时 LD1 可能仍挂载)
mount /mnt/backup
ls /mnt/backup/data/    # 原有文件应完整,暂存目录 .~tmp~/ 可能有残缺临时文件
ls /mnt/backup/data/.~tmp~/ 2>/dev/null || echo "无残缺临时文件"
umount /mnt/backup      # 手动卸载,下次同步会自动重新挂载并清理

# 5. 在主目录创建测试文件,触发同步后确认备目录出现且 LD1 已卸载
touch /data/test_sync_$(date +%s)
/usr/local/bin/sync_data.sh
mount /mnt/backup && ls /mnt/backup/data/test_sync_* && umount /mnt/backup

# 6. 验证 Supervisor 自愈
supervisorctl status myapp
kill -9 $(supervisorctl pid myapp)
sleep 3
supervisorctl status myapp        # 应已重新 RUNNING

# 7. 断电重启后验证:LD1 未挂载(noauto),应用自动启动
mountpoint -q /mnt/backup && echo "异常:LD1 被自动挂载" || echo "正常:LD1 未挂载"
supervisorctl status

六、数据库层方案:实现与验证

1. 核心策略

  • MySQL InnoDB 配置最高安全级别:每次事务提交同步写 redo log 和 binlog。
  • 密码通过 mysql_config_editor 存储,脚本中不出现明文密码。
  • 备份文件保留近 7 天,超期自动清理,防止磁盘写满。
  • 备份完成后立即触发同步,不依赖 cron 的时间窗口巧合。

2. 落地实现

2.1 MySQL 安全配置(my.cnf / mysqld.conf

CentOS 7.9 配置文件路径:/etc/my.cnf/etc/my.cnf.d/server.cnf

Ubuntu 24.04 配置文件路径:/etc/mysql/mysql.conf.d/mysqld.cnf

ini 复制代码
[mysqld]
# 每次事务提交都将 redo log 刷入磁盘(最安全,性能有损耗)
innodb_flush_log_at_trx_commit = 1

# 每次 binlog 写入都同步到磁盘
sync_binlog = 1

# 绕过 OS 页缓存,直接 DMA 写入磁盘(避免双重缓存)
innodb_flush_method = O_DIRECT

# 缩小 redo log 缓冲,减少断电时内存中未落盘数据量
innodb_log_buffer_size = 8M

# 开启崩溃安全复制(MySQL 5.7+)
relay_log_recovery = ON

应用配置:

bash 复制代码
# CentOS 7.9
systemctl restart mysqld

# Ubuntu 24.04
systemctl restart mysql
2.2 配置免密备份(避免脚本中出现明文密码)
bash 复制代码
# 使用 mysql_config_editor 存储凭据(~/.mylogin.cnf,二进制加密)
mysql_config_editor set --login-path=backup \
    --host=localhost \
    --user=backup_user \
    --password
# 输入密码后回车,不回显

# 创建只读备份账号(最小权限原则)
mysql -uroot -p << 'EOF'
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'your_strong_password';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, RELOAD, REPLICATION CLIENT
    ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;
EOF
2.3 备份脚本 /usr/local/bin/backup_mysql.sh
bash 复制代码
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/data/backup/mysql"
DATE=$(date +%F)
LOG_FILE="/var/log/mysql_backup.log"
RETENTION_DAYS=7
SYNC_SCRIPT="/usr/local/bin/sync_data.sh"

mkdir -p "${BACKUP_DIR}"

echo "$(date '+%F %T') [INFO] 开始 MySQL 备份" >> "${LOG_FILE}"

# 使用 login-path 免密登录;--single-transaction 对 InnoDB 一致性快照
mysqldump --login-path=backup \
    --all-databases \
    --single-transaction \
    --routines \
    --triggers \
    --events \
    --master-data=2 \
    2>> "${LOG_FILE}" \
    | gzip -c > "${BACKUP_DIR}/all_dbs_${DATE}.sql.gz"

echo "$(date '+%F %T') [INFO] 备份完成:${BACKUP_DIR}/all_dbs_${DATE}.sql.gz" >> "${LOG_FILE}"

# 删除超过保留期的旧备份
find "${BACKUP_DIR}" -name "all_dbs_*.sql.gz" -mtime +${RETENTION_DAYS} -delete
echo "$(date '+%F %T') [INFO] 已清理 ${RETENTION_DAYS} 天前的旧备份" >> "${LOG_FILE}"

# 备份完成后立即触发数据同步(将备份文件同步至备用阵列)
if [ -x "${SYNC_SCRIPT}" ]; then
    echo "$(date '+%F %T') [INFO] 触发同步到备用阵列" >> "${LOG_FILE}"
    "${SYNC_SCRIPT}"
fi
bash 复制代码
chmod +x /usr/local/bin/backup_mysql.sh
2.4 定时任务
bash 复制代码
crontab -e
text 复制代码
# 每日凌晨 1:10 执行(错开整点)
10 1 * * * /usr/local/bin/backup_mysql.sh
2.5 binlog 定期清理(防止 binlog 撑满磁盘)

my.cnf / mysqld.cnf 中追加:

ini 复制代码
# binlog 保留 7 天后自动清理(MySQL 8.0 使用 binlog_expire_logs_seconds)
# MySQL 5.7(CentOS 7.9 默认)
expire_logs_days = 7

# MySQL 8.0+(Ubuntu 24.04)
# binlog_expire_logs_seconds = 604800

3. 效果验证

bash 复制代码
# 1. 验证 InnoDB 刷盘参数
mysql --login-path=backup -e "
  SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
  SHOW VARIABLES LIKE 'sync_binlog';
  SHOW VARIABLES LIKE 'innodb_flush_method';
"
# 期望:1 / 1 / O_DIRECT

# 2. 手动执行备份脚本,确认生成压缩文件
/usr/local/bin/backup_mysql.sh
ls -lh /data/backup/mysql/
tail -20 /var/log/mysql_backup.log

# 3. 验证备份文件完整性
zcat /data/backup/mysql/all_dbs_$(date +%F).sql.gz | \
    mysql --login-path=backup --database=test_restore
# (在测试环境执行,确认 SQL 可正常导入)

# 4. 断电模拟后验证 MySQL 自愈
systemctl status mysqld   # CentOS
systemctl status mysql    # Ubuntu
# 确认服务自动启动,无表损坏报错
mysql --login-path=backup -e "SHOW ENGINE INNODB STATUS\G" | grep -A5 "LATEST DETECTED DEADLOCK"

七、双系统切换与数据同步机制

1. 运行机制

项目 主系统 备系统
阵列 RAID1-A (LD0) RAID1-B (LD1)
角色 对外提供服务 热备,保持数据同步
数据目录 /data /mnt/backup/data
同步方向 源端 目标端
BIOS 优先级 第一 第二(平时不启动)

2. 备用系统初始化(首次部署)

备用系统需要与主系统保持完全一致的 OS、应用、配置,推荐通过克隆方式初始化:

方式一:Clonezilla 整盘克隆(推荐,一次性完成)

  1. 使用 Clonezilla Live USB 启动服务器
  2. 选择 device-to-device,源为 LD0,目标为 LD1
  3. 克隆完成后,修改 LD1 系统内的 /etc/fstab UUID(用 blkid 获取 LD1 各分区 UUID 后替换)
  4. 修改 LD1 系统的 hostname(可与主系统保持一致,或加 -backup 后缀)

方式二:手动同步(OS 已分别安装时)

bash 复制代码
# 在主系统上执行,同步 /data 数据区到备用阵列
rsync -aAX --delete \
    --exclude="/data/backup/" \
    /data/ /mnt/backup/data/

# 同步应用程序目录
rsync -aAX --delete /opt/myapp/ /mnt/backup/opt/myapp/

3. 版本一致性维护

每次更新主系统后,手动将变更同步至备系统:

bash 复制代码
# 同步 RPM/DEB 包列表(备用系统下次启动时可参考安装)
# CentOS 7.9
rpm -qa > /data/backup/pkg_list_$(date +%F).txt

# Ubuntu 24.04
dpkg --get-selections > /data/backup/pkg_list_$(date +%F).txt

4. 自动故障切换(免人工干预 BIOS)

不手工修改 BIOS 也能自动切换,共三个层次,覆盖不同的故障场景:

复制代码
故障类型                    自动切换机制
─────────────────────────────────────────────────────
LD0 物理彻底损坏            UEFI 自动 fallback(层次一)
系统引导成功但内核崩溃/反复重启  GRUB boot_counter(层次二)
系统启动但关键服务持续崩溃    应用层 watchdog 触发 grub-reboot(层次三)
层次一:UEFI 自动 Fallback(一次性 BIOS 配置)

当 LD0 对应的物理磁盘完全损坏,RAID 卡不再呈现 LD0 为可引导设备时,UEFI 会依次尝试启动列表中的下一个设备(即 LD1)。这个能力大多数服务器 UEFI 已具备,只需确认以下开关已启用(仅需配置一次):

进入 BIOS → Boot 选项卡,找到并启用以下选项(不同品牌名称略有差异):

品牌 选项名称
Dell Boot Sequence → "Boot Other Device" = Enabled
HP Boot Options → "After Power Loss" + "Try Other Boot Devices" = Enabled
华为/浪潮 Boot Priority → "Attempt Boot from Other Devices" = Enabled
通用 AMI/Award Boot → "Boot Up NumLock Status" 附近的 "Try Next Boot Device on Failure"

验证方法:拔掉 LD0 的两块硬盘,重启,服务器应在不操作 BIOS 的情况下自动从 LD1 引导。

层次二:GRUB boot_counter(引导成功但系统反复崩溃)

原理:GRUB 在每次启动时递减一个计数器;系统成功引导后由 systemd 服务将计数器重置为初始值;若计数器归零仍未被重置,说明主系统连续失败,GRUB 自动切换到 LD1 启动项。

步骤一:在主系统 GRUB 中添加 LD1 启动菜单项,并插入计数器逻辑

CentOS 7.9:

bash 复制代码
# 获取 LD1 根分区 UUID
blkid /dev/sdc1   # 根据实际 LD1 根分区设备名调整
# 假设 UUID=xxxxxxxx-ld1-root

# 编辑自定义 GRUB 脚本
cat > /etc/grub.d/40_custom_failover << 'EOF'
#!/bin/sh
exec tail -n +3 $0

# --- 启动计数器逻辑 ---
# 在 /etc/grub.d/00_header 之后、menuentry 之前执行
# 注意:此段逻辑通过 /boot/grub2/grubenv 持久化

menuentry "Backup System (LD1) - Auto Failover" --id backup_system {
    insmod part_gpt
    insmod xfs
    search --no-floppy --fs-uuid --set=root <LD1根分区UUID>
    linux   /boot/vmlinuz-$(uname -r) root=UUID=<LD1根分区UUID> ro quiet
    initrd  /boot/initramfs-$(uname -r).img
}
EOF
chmod +x /etc/grub.d/40_custom_failover
bash 复制代码
# 在 /etc/grub.d/00_header 末尾插入计数器逻辑(追加到该文件)
cat >> /etc/grub.d/00_header << 'GRUBEOF'

# ===== boot_counter failover =====
if [ -s \$prefix/grubenv ]; then
  load_env
fi

if [ "\${boot_counter}" ]; then
  if [ "\${boot_counter}" -gt 0 ]; then
    set boot_counter=\$(( \${boot_counter} - 1 ))
    save_env boot_counter
    set default=0
  else
    # 计数器耗尽,切换到备用系统
    set default=backup_system
  fi
else
  set boot_counter=3
  save_env boot_counter
fi
# ===== end =====
GRUBEOF

# 重新生成 GRUB 配置
grub2-mkconfig -o /boot/grub2/grub.cfg

# 初始化计数器为 3
grub2-editenv /boot/grub2/grubenv set boot_counter=3

Ubuntu 24.04:

bash 复制代码
# 添加 LD1 启动菜单项
cat > /etc/grub.d/40_custom_failover << 'EOF'
#!/bin/sh
exec tail -n +3 $0

menuentry "Backup System (LD1) - Auto Failover" --id backup_system {
    insmod part_gpt
    insmod xfs
    search --no-floppy --fs-uuid --set=root <LD1根分区UUID>
    linux   /boot/vmlinuz root=UUID=<LD1根分区UUID> ro quiet splash
    initrd  /boot/initrd.img
}
EOF
chmod +x /etc/grub.d/40_custom_failover

# Ubuntu GRUB 配置文件位置不同
cat >> /etc/grub.d/00_header << 'GRUBEOF'

# ===== boot_counter failover =====
if [ -s \$prefix/grubenv ]; then
  load_env
fi

if [ "\${boot_counter}" ]; then
  if [ "\${boot_counter}" -gt 0 ]; then
    set boot_counter=\$(( \${boot_counter} - 1 ))
    save_env boot_counter
    set default=0
  else
    set default=backup_system
  fi
else
  set boot_counter=3
  save_env boot_counter
fi
# ===== end =====
GRUBEOF

# Ubuntu 使用 update-grub
update-grub

# 初始化计数器
grub-editenv /boot/grub/grubenv set boot_counter=3

步骤二:系统成功启动后重置计数器(systemd 服务)

两个系统均创建以下服务文件:

bash 复制代码
cat > /etc/systemd/system/grub-boot-success.service << 'EOF'
[Unit]
Description=Reset GRUB boot_counter on successful boot
After=network.target mysqld.service mysql.service supervisord.service supervisor.service
# 等待关键服务启动成功后再重置,确保"成功"的标准是服务可用

[Service]
Type=oneshot
RemainAfterExit=yes
# CentOS 7.9
ExecStart=/bin/sh -c 'grub2-editenv /boot/grub2/grubenv set boot_counter=3'
# Ubuntu 24.04 请改为:
# ExecStart=/bin/sh -c 'grub-editenv /boot/grub/grubenv set boot_counter=3'

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable grub-boot-success.service
systemctl start grub-boot-success.service

Ubuntu 24.04 将 ExecStart 中的命令替换为 grub-editenv /boot/grub/grubenv set boot_counter=3,其余相同。

工作原理时序:

复制代码
第1次崩溃重启 → GRUB 读取 boot_counter=3 → 递减为 2 → 仍从 LD0 启动
第2次崩溃重启 → GRUB 读取 boot_counter=2 → 递减为 1 → 仍从 LD0 启动
第3次崩溃重启 → GRUB 读取 boot_counter=1 → 递减为 0 → 仍从 LD0 启动
第4次崩溃重启 → GRUB 读取 boot_counter=0 → 切换到 backup_system(LD1)自动引导
正常启动完成  → systemd 服务重置 boot_counter=3 → 恢复初始状态
层次三:应用层 Watchdog(服务可用但业务持续崩溃)

当系统引导正常,但关键业务服务在 Supervisor 重试次数耗尽后进入 FATAL 状态,触发主动切换:

bash 复制代码
cat > /usr/local/bin/app_watchdog.sh << 'EOF'
#!/bin/bash
# 检测业务进程进入 FATAL 状态,触发切换到备用系统
LOG="/var/log/app_watchdog.log"

FATAL_COUNT=$(supervisorctl status 2>/dev/null | grep -c "FATAL" || true)

if [ "${FATAL_COUNT}" -gt 0 ]; then
    echo "$(date '+%F %T') [ALERT] 检测到 ${FATAL_COUNT} 个服务 FATAL,准备切换到备用系统" >> "${LOG}"

    # 触发一次数据同步,尽量减少数据丢失
    /usr/local/bin/sync_data.sh >> "${LOG}" 2>&1 || true

    # 设置下次启动使用备用系统(一次性,不修改默认值)
    # CentOS 7.9:
    grub2-reboot "Backup System (LD1) - Auto Failover"
    # Ubuntu 24.04 改为:
    # grub-reboot "Backup System (LD1) - Auto Failover"

    echo "$(date '+%F %T') [ALERT] 已设置 grub-reboot,10 秒后重启" >> "${LOG}"
    sleep 10
    reboot
fi
EOF
chmod +x /usr/local/bin/app_watchdog.sh
bash 复制代码
# 每 5 分钟检查一次
crontab -e
# 追加:
# */5 * * * * /usr/local/bin/app_watchdog.sh

grub2-reboot(CentOS)/ grub-reboot(Ubuntu)只影响下一次启动,不改变默认启动项。备系统启动后如果业务正常,下次重启仍会尝试 LD0(boot_counter 机制接管)。

三层机制覆盖范围汇总
故障场景 触发层次 切换时间 需人工介入
LD0 两块硬盘全部损坏 层次一(UEFI fallback) ~1 分钟(正常 POST 时间)
系统文件损坏/内核崩溃,连续 4 次失败 层次二(GRUB boot_counter) 4 次重启周期(约 5~10 分钟)
系统正常,业务服务 FATAL 层次三(watchdog) 约 5~15 分钟
以上均未覆盖的复杂故障 人工操作 人工响应时间

5. 演练验证(季度执行一次)

bash 复制代码
# 步骤一:确认当前数据已同步到备用阵列
tail -5 /var/log/rsync_backup.log
diff -rq --exclude="*.log" /data/ /mnt/backup/data/ | head -20

# 步骤二(验证层次二):手动将 boot_counter 置 0,重启验证自动切换
# CentOS 7.9
grub2-editenv /boot/grub2/grubenv set boot_counter=0
# Ubuntu 24.04
grub-editenv /boot/grub/grubenv set boot_counter=0
reboot
# 期望:服务器自动从 LD1 引导,无需任何手工操作

# 步骤三:从备系统启动后,验证
hostname
df -h
systemctl status mysqld || systemctl status mysql
supervisorctl status
curl -s http://localhost:8080/health

# 步骤四:恢复主系统为默认启动(重置计数器)
# CentOS 7.9
grub2-editenv /boot/grub2/grubenv set boot_counter=3
# Ubuntu 24.04
grub-editenv /boot/grub/grubenv set boot_counter=3
reboot  # 重启回主系统

八、故障应急响应流程

场景一:主阵列降级(单盘故障,系统仍在运行)

复制代码
现象:RAID 监控告警 LD0 Degraded,但系统正常运行
  1. 记录故障盘编号(RAID 卡管理界面中标记为 Failed 的 PD)。

  2. 不要立即关机,先执行一次手动数据同步:

    bash 复制代码
    /usr/local/bin/sync_data.sh
    /usr/local/bin/backup_mysql.sh
  3. 在业务低谷期更换故障硬盘。

  4. 插入新盘后,RAID 卡自动开始重建(Rebuild),观察进度:

    bash 复制代码
    MegaCli -PDRbld -ShowProg -PhysDrv [E:S] -aALL
    # 或
    storcli /c0/e<E>/s<S> show rebuild
  5. 重建完成,阵列恢复 Optimal,无需其他操作。

场景二:主阵列完全崩溃(系统无法引导)

复制代码
现象:开机报错,无法进入系统;LD0 显示 Failed/Offline
  1. 开机进入 RAID 卡配置界面,确认 LD0 状态为 Failed。

  2. 将 LD1 设置为 Boot Device(第一启动项)。

  3. 保存退出,服务器从备用系统(RAID1-B)引导启动。

  4. 登录系统后执行完整性检查:

    bash 复制代码
    df -h                         # 确认挂载正常
    ls -la /data/                 # 确认数据目录
    systemctl status mysqld || systemctl status mysql
    supervisorctl status
    tail -50 /var/log/rsync_backup.log  # 查看最后一次同步时间
  5. 手动启动业务(如 Supervisor 未自动拉起):

    bash 复制代码
    supervisorctl start all

灾后恢复(回迁主系统)

  1. 更换 LD0 中的损坏硬盘,在 RAID 卡中重建 LD0。

  2. LD0 重建完成后,在当前备系统上执行反向同步(B → A):

    bash 复制代码
    # 挂载重建后的 LD0 数据分区
    mount /dev/ld0_data /mnt/restore
    
    # 反向同步(注意方向:备系统数据 → 主系统)
    rsync -aAX --delete \
        --exclude="/data/backup/" \
        /data/ /mnt/restore/
    
    # 同步 MySQL 备份
    rsync -aAX /data/backup/mysql/ /mnt/restore/backup/mysql/
    
    umount /mnt/restore
  3. 进入 BIOS,将启动顺序恢复为 LD0 优先。

  4. 重启服务器,确认主系统正常接管,恢复 A → B 自动同步。


九、运维巡检规范

巡检项目一览

频次 巡检项目 预期标准 检查命令
每日 RAID 阵列状态 所有 LD 为 Optimal,无 Degraded MegaCli -LDInfo -Lall -aALL
每日 数据同步日志 无 ERROR,同步耗时在预期范围内 tail -20 /var/log/rsync_backup.log
每日 磁盘使用率 各分区使用率 < 80% df -h
每日 数据库备份 备份文件按时生成,大小合理 ls -lh /data/backup/mysql/
每周 磁盘健康(SMART) 无重分配扇区,无待处理扇区 smartctl -a /dev/sda
每月 系统镜像冷备 对主系统完整镜像,存至离线介质 `dd if=/dev/sda
每季度 主备切换演练 备系统可正常接管业务 参见第七章演练流程
事件驱动 停电前干预 收到预告立即执行优雅关机 shutdown -h now
事件驱动 来电后检查 市电稳定 3 分钟后再手动开机 目视检查电压表

每日巡检脚本 /usr/local/bin/daily_check.sh

bash 复制代码
#!/bin/bash
# 每日巡检脚本,建议配置发送邮件或写入集中日志
LOG="/var/log/daily_check.log"
ALERT=0

log() { echo "$(date '+%F %T') $*" | tee -a "${LOG}"; }
warn() { log "[WARN] $*"; ALERT=1; }
info() { log "[INFO] $*"; }

info "========== 每日巡检开始 =========="

# 1. RAID 状态检查
if command -v MegaCli &>/dev/null; then
    DEGRADED=$(MegaCli -LDInfo -Lall -aALL 2>/dev/null | grep -c "Degraded" || true)
    FAILED=$(MegaCli -LDInfo -Lall -aALL 2>/dev/null | grep -c "Failed" || true)
    [ "${DEGRADED}" -gt 0 ] && warn "RAID 阵列降级!请立即检查(Degraded 数量: ${DEGRADED})"
    [ "${FAILED}" -gt 0 ]   && warn "RAID 阵列故障!请立即处理(Failed 数量: ${FAILED})"
    [ "${DEGRADED}" -eq 0 ] && [ "${FAILED}" -eq 0 ] && info "RAID 状态正常(Optimal)"
elif command -v storcli &>/dev/null; then
    storcli /c0 /vall show 2>/dev/null | grep -iE 'dgrd|fail' && warn "RAID 状态异常" || info "RAID 状态正常"
else
    warn "未找到 MegaCli/storcli,请手动检查 RAID 状态"
fi

# 2. 磁盘使用率检查(阈值 80%)
while IFS= read -r line; do
    USE=$(echo "${line}" | awk '{print $5}' | tr -d '%')
    MNT=$(echo "${line}" | awk '{print $6}')
    [ "${USE}" -ge 80 ] && warn "磁盘 ${MNT} 使用率 ${USE}%,请清理"
done < <(df -h --output=source,size,used,avail,pcent,target | tail -n +2 | grep -vE 'tmpfs|devtmpfs')

# 3. 最近一次 rsync 同步检查
if [ -f /var/log/rsync_backup.log ]; then
    LAST_SYNC=$(grep '\[INFO\] 同步完成' /var/log/rsync_backup.log | tail -1)
    [ -z "${LAST_SYNC}" ] && warn "rsync 同步日志中无成功记录"
    grep -q '\[ERROR\]' <(tail -100 /var/log/rsync_backup.log) && warn "rsync 日志中存在 ERROR"
    info "最后同步:${LAST_SYNC}"
fi

# 4. MySQL 备份检查
BACKUP_FILE="/data/backup/mysql/all_dbs_$(date +%F).sql.gz"
if [ -f "${BACKUP_FILE}" ]; then
    SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1)
    info "今日 MySQL 备份存在,大小:${SIZE}"
else
    warn "今日 MySQL 备份文件不存在:${BACKUP_FILE}"
fi

# 5. 关键服务状态
for SVC in mysqld mysql supervisord supervisor; do
    systemctl is-active --quiet "${SVC}" 2>/dev/null && {
        info "服务 ${SVC}:运行中"
        break
    }
done

info "========== 巡检结束,ALERT=${ALERT} =========="

# 返回非零退出码以便 cron 触发邮件告警
exit ${ALERT}
bash 复制代码
chmod +x /usr/local/bin/daily_check.sh

# 加入 crontab,每天早上 8:05 执行
crontab -e
# 追加:
# 5 8 * * * /usr/local/bin/daily_check.sh

十、风险控制与优化建议

1. 残余风险清单

风险项 影响 缓解措施
RAID 卡单点故障 两组阵列同时不可访问 备件库存放同型号 RAID 卡;定期导出 RAID 配置截图
30 分钟同步窗口内断电 最多丢失 30 分钟数据 减小 cron 间隔(如改为 10 分钟);对关键业务使用数据库层复制替代文件同步
rsync 同步期间断电 备用阵列中文件处于中间状态 --backup 参数保留旧版本;切换到备系统后先校验数据再开服
主备版本不一致 切换后应用无法启动 每次更新主系统后,立即同步 pkg list 并在备系统补装
磁盘缓慢损坏(坏道积累) SMART 告警前数据已损坏 每周 SMART 检查;企业级硬盘 3 年强制更换

2. 性能权衡说明

开启以下安全配置会带来性能损耗,属于在高可靠场景下的合理取舍:

  • innodb_flush_log_at_trx_commit=1:TPS 相比 =2 可能降低 30%~50%,但这是数据零丢失的必要代价
  • commit=5(XFS):写密集型应用会产生更多小 I/O,可根据实际负载适当调大至 commit=10
  • RAID Write Through:写性能低于 Write Back,约 10%~30% 差距,但断电安全性有根本保证

3. 监控集成建议

若条件允许,部署轻量级监控:

bash 复制代码
# 安装 Zabbix Agent(CentOS 7.9)
yum install -y zabbix-agent
# 配置监控项:RAID 状态、磁盘使用率、MySQL 进程存活、同步日志时间戳

# Ubuntu 24.04
apt-get install -y zabbix-agent2

关键监控指标:

  • RAID LD 状态(Optimal / Degraded / Failed)
  • 各分区使用率阈值告警(80% 警告,90% 严重)
  • rsync 最后成功时间戳(超过 1 小时未同步告警)
  • MySQL 进程存活状态

总结

本方案通过硬件RAID冗余 + 操作系统级防护 + 应用层容灾 + 数据库强刷盘的四层架构,在无 UPS、无双电源的极端供电环境下,实现服务器高可用。核心要点:

  1. 硬件层:两组独立 RAID1,全局 Write Through,禁用缓存,两套可引导系统
  2. OS 层 :XFS commit=5,内核脏页 5 秒过期,udev 规则持久化关闭 HDD 写缓存
  3. 应用层 :rsync 加互斥锁,--backup 保留被删文件,Supervisor 设置重试上限
  4. 数据库层innodb_flush_log_at_trx_commit=1 + O_DIRECT,免密备份,7 天轮转
  5. 容灾层:Clonezilla 克隆初始化备系统,BIOS 切换 < 5 分钟,季度演练

最大残余风险为 30 分钟数据同步窗口和 RAID 卡单点故障,已在风险清单中给出缓解措施。

相关推荐
上海云盾安全满满2 小时前
服务器CPU跑满的原因及解决办法
运维·服务器
scan7242 小时前
从runtime获取信息
java·服务器·前端
团象科技2 小时前
从出海业务落地视角观察 海外服务器跑开源软件的实操逻辑演变
运维·服务器·开源软件
加成BUFF2 小时前
第七天 ROS《 参数服务器与Launch文件》
运维·ros·参数服务器
snow@li2 小时前
CI/CD:深入理解 CI/CD(2026版)
运维·ci/cd
java_cj2 小时前
K8s入门第一课:从零理解Kubernetes核心概念与架构设计
运维·云原生·容器·架构·kubernetes
snow@li2 小时前
nginx:详解与速查表 / Nginx = 反向代理 + 负载均衡 + 静态服务器 + HTTP 缓存 / 请求分发、静态加速、上线不中断
linux·服务器·nginx
鼎讯信通2 小时前
一机多能,能源通信运维优选——鼎讯JM-Q150 实测解析
运维·能源·信息与通信·天馈线测试仪
小则又沐风a2 小时前
进程最终篇---进程控制(模拟实现xshell)
java·linux·服务器·前端