供电不稳定、无UPS/无双电源环境下服务器高可用完整方案
目录
- 需求背景与核心目标
- 整体架构设计
- 硬件层方案:实现与验证
- 操作系统层方案:实现与验证
- 应用层方案:实现与验证
- 数据库层方案:实现与验证
- 双系统切换与数据同步机制
- 故障应急响应流程
- 运维巡检规范
- 风险控制与优化建议
一、需求背景与核心目标
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. 落地实现
- 进入 RAID 卡配置界面(LSI/Broadcom 系列按
Ctrl+R,华为/海思系列按Ctrl+A)。 - 创建
LD0:绑定Disk0 + Disk1,RAID 级别选RAID1。 - 创建
LD1:绑定Disk2 + Disk3,RAID 级别选RAID1。 - 控制器全局策略 (Controller Properties):
Write Policy= Write Through(直写,彻底禁用板载 BBU/超级电容缓存)Read Policy= No Read Ahead 或 Direct I/OIO Policy= Direct
- 将
LD0和LD1均标记为 Bootable。 - 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/Ocommit=5:XFS journal 每 5 秒强制提交一次(默认 30 秒,缩短可减少断电丢失窗口)- XFS 的 barrier(写屏障)默认开启,无需额外指定
nofail:备用阵列挂载失败时不阻塞系统启动
ext4 用户 (Ubuntu 如使用 ext4):将 xfs 替换为 ext4,并追加
barrier=1,journal_async_commit,commit=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 上已有的完好数据不被破坏。
三项改进措施:
-
--delay-updates:rsync 传输阶段将所有文件先写入 LD1 上的隐藏暂存目录(.~tmp~/),全部传输完成后再批量 rename 到最终位置。传输阶段 LD1 上原有文件完全不被触碰;若断电发生在传输阶段,LD1 的原有数据完整无损,只丢失本次增量部分。 -
--fsync:每个文件传输完成后立即调用 fsync(),确保落到 LD1 的物理盘,不依赖内核页缓存刷盘时机。 -
同步后 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 无脏数据noautofstab: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 整盘克隆(推荐,一次性完成)
- 使用 Clonezilla Live USB 启动服务器
- 选择
device-to-device,源为 LD0,目标为 LD1 - 克隆完成后,修改 LD1 系统内的
/etc/fstabUUID(用blkid获取 LD1 各分区 UUID 后替换) - 修改 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,但系统正常运行
-
记录故障盘编号(RAID 卡管理界面中标记为 Failed 的 PD)。
-
不要立即关机,先执行一次手动数据同步:
bash/usr/local/bin/sync_data.sh /usr/local/bin/backup_mysql.sh -
在业务低谷期更换故障硬盘。
-
插入新盘后,RAID 卡自动开始重建(Rebuild),观察进度:
bashMegaCli -PDRbld -ShowProg -PhysDrv [E:S] -aALL # 或 storcli /c0/e<E>/s<S> show rebuild -
重建完成,阵列恢复 Optimal,无需其他操作。
场景二:主阵列完全崩溃(系统无法引导)
现象:开机报错,无法进入系统;LD0 显示 Failed/Offline
-
开机进入 RAID 卡配置界面,确认 LD0 状态为 Failed。
-
将 LD1 设置为 Boot Device(第一启动项)。
-
保存退出,服务器从备用系统(RAID1-B)引导启动。
-
登录系统后执行完整性检查:
bashdf -h # 确认挂载正常 ls -la /data/ # 确认数据目录 systemctl status mysqld || systemctl status mysql supervisorctl status tail -50 /var/log/rsync_backup.log # 查看最后一次同步时间 -
手动启动业务(如 Supervisor 未自动拉起):
bashsupervisorctl start all
灾后恢复(回迁主系统)
-
更换 LD0 中的损坏硬盘,在 RAID 卡中重建 LD0。
-
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 -
进入 BIOS,将启动顺序恢复为 LD0 优先。
-
重启服务器,确认主系统正常接管,恢复 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、无双电源的极端供电环境下,实现服务器高可用。核心要点:
- 硬件层:两组独立 RAID1,全局 Write Through,禁用缓存,两套可引导系统
- OS 层 :XFS
commit=5,内核脏页 5 秒过期,udev 规则持久化关闭 HDD 写缓存 - 应用层 :rsync 加互斥锁,
--backup保留被删文件,Supervisor 设置重试上限 - 数据库层 :
innodb_flush_log_at_trx_commit=1+O_DIRECT,免密备份,7 天轮转 - 容灾层:Clonezilla 克隆初始化备系统,BIOS 切换 < 5 分钟,季度演练
最大残余风险为 30 分钟数据同步窗口和 RAID 卡单点故障,已在风险清单中给出缓解措施。