ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大规模MySQL/PostgreSQL数据库LVM快照备份与恢复实战

大规模MySQL/PostgreSQL数据库LVM快照备份与恢复实战 生产库一上规模备份方案就没那么浪漫了。我接手过几套每天定时mysqldump的MySQL架构数据量到2TB以后备份已经不是为了恢复而是为了心理安慰。真正到灾难发生时恢复要用的时间、依赖的工具链、日志补齐能力都会决定你能不能把业务从泥潭里拉出来。这篇文章说的LVM快照配合自动备份策略是我在Ubuntu 22.04上给大规模数据库搭过的最可靠方案之一。它能把备份窗口缩到秒级、恢复窗口缩到分钟级而且不依赖昂贵的存储设备。文章会从机制、配置、脚本、恢复演练到问题排查一步步拆开适合正在管MySQL/PostgreSQL这类大库的运维和DBA参考。1. 为什么是LVM快照而不是其他备份方案1.1 快照原理写时复制不是复制很多人一听说LVM快照就以为它是把数据复制一份其实不是。快照的底层机制是CoWCopy-on-Write也就是写时复制。创建快照的瞬间LVM不拷贝整卷数据只是记录“这个时间点的元数据状态”。之后原始卷上只要有数据块发生修改LVM才会在写入新数据之前把原始块复制到快照预留的空间里。所以快照看起来是完整的逻辑卷副本实际上是由“原卷里正在被覆盖的旧数据块”拼出来的一幅静态画面。这个机制决定了两个关键结论。第一创建快照的速度几乎和卷大小无关几TB的卷也能瞬间完成不需要长时间锁定业务。第二快照成长不是无限膨胀的它只记录变更块快照空间大小必须和“快照存活期间的写入量”匹配而不是和数据总量匹配。我见过不少新手把几十GB快照挂在几TB数据库上结果第二天快照100%满自动失效备份计划直接失败这就是没理解写时复制。1.2 大规模数据库场景下的优势大规模数据库备份有几个特点数据量特别大物理备份动辄几百GB甚至上TB备份窗口短业务不允许长时间停写恢复要求快最好能回到某个确定的时间点。传统方案里mysqldump或pg_dump这类逻辑备份虽然灵活但对大库来说备份和恢复都慢得惊人。我实测过1TB以上的PostgreSQLpg_dump的备份时间经常要到小时级恢复时间更长而且中间会产生大量额外空间消耗。物理备份工具比如Percona XtraBackup、pg_basebackup也不错但还是需要先扫描数据文件备份过程对磁盘IO有影响对频繁变更的大库很难做到“瞬间定格”。LVM快照的优势在于它把“备份”拆成了两步先在极短时间内拍下一份逻辑卷快照再用后台进程慢慢把快照内容复制到安全位置。前一秒业务还在正常写下一秒已经有一份一致性数据到手。这种模式非常符合大规模数据库的需求可以说是系统级物理备份的最佳前置手段。做个直观对比备份方案备份耗时恢复耗时对业务影响适用规模mysqldump / pg_dump小时级小时到天级高锁表/IO占用小库、中库XtraBackup / pg_basebackup中等中等中在线但吃IO中库、大库LVM快照物理复制秒级拍快照后台复制分钟级低锁库时间极短大库、超大库1.3 必须先说清楚的三个误区快照不是备份这是最重要的一句话。快照依赖原始卷存在同一块物理磁盘挂了快照和原卷一起消失。它更像是“撤回点”不是“保险柜”。所以自动备份必须把快照里的数据复制到独立存储或另一台机器上快照只是中转区。快照空间是会耗尽的生产资源。如果快照预留空间不足LVM会让快照变成“失效”状态无法读取。配置过大又浪费存储需要在容量和变化率之间平衡。快照不能解决所有恢复场景。比如数据库误删某一行数据快照能让你恢复整个文件系统但不一定能帮你精准找出那一行。这时往往需要结合binlog或WAL归档做时间点恢复PITR。快照和PITR不是替代关系是配合关系。所以说LVM快照的正确用法是作为数据库物理备份的快速固化手段加上恢复时的秒级还原介质再配合日志归档和离线副本才能构成完整可靠的数据安全体系。2. Ubuntu 22.04下的LVM环境准备与存储规划2.1 检查现有LVM结构开始之前先盘点机器现状。我用得最多的命令是查看三层结构物理卷、卷组、逻辑卷。pvs vgs lvs -a -o lv_name,vg_name,lv_size,lv_attr关键要看两件事一是数据库数据目录是不是在LVM逻辑卷上二是卷组里还有多少空闲空间。如果数据库目录在普通分区上不在LVM里就要重新规划或迁移没法直接快照。快照必须创建在同一个卷组VG中因为快照空间和原卷共享同一组物理卷。想看更细的状态还可以用vgdisplay -v vgdata注意看Free PE / Size字段这就是能给快照用的空间。如果这里已经是0后续任何快照操作都没有余地必须先扩容卷组。2.2 把数据库目录迁移到LVM如果数据库已经在普通分区上我建议把数据迁移到一个新逻辑卷。过程不复杂但要注意停机时间和数据一致性。# 在卷组中创建逻辑卷 lvcreate -L 2T -n lv_mysql vgdata # 创建文件系统这里以xfs为例 mkfs.xfs /dev/vgdata/lv_mysql # 挂载并迁移数据 mkdir -p /mnt/newdb mount /dev/vgdata/lv_mysql /mnt/newdb rsync -avH --partial /var/lib/mysql/ /mnt/newdb/迁移完成后修改fstab重启验证。这里有个之前踩过的坑rsync迁移数据库目录后由于属主、权限、稀疏文件处理不当MySQL启动时莫名报错。建议加参数保留属主和硬链接迁移完成后用chown -R mysql:mysql修复。同时要把数据库停干净再拷贝别在运行状态硬迁否则数据文件不一致的问题很快会暴露。2.3 预留多少快照空间才够快照空间估算核心公式是快照空间 ≈ 快照存活时长 × 数据库卷的平均写入速度比如数据库卷平均每秒写入10MB快照计划保留8小时理论上需要10MB/s × 3600s × 8 288GB由于写入速率有波动实际建议再乘以1.5到2的系数预留500GB左右比较稳。注意这里说的是给快照本身预留的独立空间不是备份文件占用的空间。也可以按LVM百分比预留比如快照空间占卷组剩余空间的20%lvcreate -s -l 20%FREE -n snap_db_001 vgdata/lv_mysql用百分比的好处是简单坏处是“FREE”会随着快照创建而变小而且百分比没有考虑未来数据增长后面快照满了会失效。我习惯的做法是平时监控数据库卷的写入IO每周统计变化率快照大小按“高峰变化率×保留时长×2”来定宁可多留一点。2.4 文件系统与工具准备LVM快照对文件系统有一定要求尤其是XFS。XFS不支持在线缩容快照前建议先冻结文件系统xfs_freeze -f /var/lib/mysql lvcreate -s -n snap_xxx -L 200G vgdata/lv_mysql xfs_freeze -u /var/lib/mysqlext4相对简单一般sync后再快照即可但如果有数据库还是建议在数据库层面做一致性处理。Ubuntu 22.04通常自带lvm2但还是要确认一下apt update apt install -y lvm2 xfsprogs rsync如果打算用systemd timer做定时任务无需额外装包如果习惯cron系统也自带。这两个工具在后续自动化章节会分别用到。3. 为数据库一致性而生的快照操作全流程3.1 数据库快照为什么必须讲“一致性”对数据库而言单纯创建一个文件系统快照不等于拿到一份可用的数据库备份。原因是数据库文件内部有多块缓冲区和日志它们在磁盘上可能处于不一致状态数据文件里某些页是旧的预写日志里记录着新修改二者还没合并。如果在这个状态拍快照后续数据库引擎在回放日志时也许能恢复但更多时候你会碰到“文件系统一致但数据库逻辑不一致”的问题。所以最安全的快照时机是数据库中所有事务都处在一个可恢复的静止点。不同数据库引擎做法略有差异下面分别给MySQL和PostgreSQL的操作流程。3.2 MySQL InnoDB加锁快照全流程我推荐使用全局读锁加FLUSH TABLES的经典组合。但这里有一个特别容易被忽略的细节FLUSH TABLES WITH READ LOCK获取的锁是会话级的你用第一条mysql命令加上锁命令一退出锁就自动释放等于白加。正确的做法是在同一个MySQL会话中完成加锁、创建快照、解锁三步。mysql --login-pathbackup SQL FLUSH TABLES WITH READ LOCK; SYSTEM lvcreate -s -n snap_mysql_$(date %F_%H%M) -L 200G vgdata/lv_mysql UNLOCK TABLES; SQLSYSTEM是mysql客户端内置命令可以在不退出当前会话的情况下执行shell命令锁才能一直持有到快照创建完毕。如果数据库负载高FLUSH TABLES WITH READ LOCK可能等待较长时间所以我会在业务低峰期执行并加入超时保护。如果库很大担心锁表时间也可以先用Percona XtraBackup做在线备份再加LVM快照做恢复加速二者搭配更稳。但无论如何不要在脚本里写成“mysql加锁”、“lvcreate”、“mysql解锁”三条独立命令那是一个必踩的坑。3.3 PostgreSQL的备份标记法PostgreSQL在一致快照上有自己的机制。PG15开始函数名从pg_start_backup改成了pg_backup_start旧函数在PG15以后仍然可用一段时间但新环境建议直接用新的。PG15及以上的操作同样要在同一个psql会话中执行sudo -u postgres psql SQL SELECT pg_backup_start(lvm_snapshot_backup); \! lvcreate -s -n snap_pg_$(date %F_%H%M) -L 200G vgdata/lv_pg SELECT pg_backup_stop(); SQLPG15以下的版本对应使用SELECT pg_start_backup(lvm_snapshot_backup); -- shell中执行lvcreate快照 SELECT pg_stop_backup();关键点在于pg_backup_start之后PostgreSQL会开始记录WAL并在数据目录中生成backup_label文件。快照拍完后如果不做pg_backup_stop数据库实例会一直处于备份模式文件持续膨胀甚至影响后续wal归档。所以begin和stop必须成对出现。3.4 快照有效性验证快照创建完成并解锁数据库后不能直接认为万事大吉。我在每次备份后都会做两步验证。第一步查看快照状态lvs -a -o lv_name,lv_size,snap_percent,data_percent,lv_attr确保snap_percent不是100%lv_attr中能看到代表快照的标记。第二把快照挂载到临时目录检查数据目录关键文件是否存在mkdir -p /mnt/snap_check mount -o ro /dev/vgdata/snap_mysql_20240601 /mnt/snap_check ls -l /mnt/snap_check/mysql/没有检查的备份等于没有备份。这句话虽然说起来像口号但我确实见过太多备份任务显示成功、恢复时才发现数据文件缺页的案例。4. 自动化定时快照加自动备份脚本的设计4.1 定时任务用cron还是systemd timerUbuntu 22.04两种都能用。cron简单直接systemd timer更便于管理依赖和日志。我的选择是生产环境影响大的操作优先用systemd timer因为它可以设置随机延时、依赖网络挂载、记录完整日志比cron好排查。如果只是临时内网机器cron一行就能解决30 2 * * * /usr/local/sbin/db_lvm_backup.sh /var/log/db_lvm_backup.log 21建议把脚本日志放到单独文件别直接丢到系统日志里不然排查问题时容易头大。systemd timer的单元文件可以这样写# /etc/systemd/system/db-lvm-backup.service [Unit] DescriptionDatabase LVM snapshot backup service [Service] Typeoneshot ExecStart/usr/local/sbin/db_lvm_backup.sh # /etc/systemd/system/db-lvm-backup.timer [Timer] OnCalendar*-*-* 02:30:00 RandomizedDelaySec300 Persistenttrue [Install] WantedBytimers.target启用命令systemctl daemon-reload systemctl enable --now db-lvm-backup.timer4.2 一套可以直接抄的自动快照备份脚本下面这套脚本目前还在生产环境使用核心逻辑做了注释。脚本以MySQL为例PostgreSQL版只是函数不同结构一致。注意密码不要明文写在命令行里建议先配置MySQL login-pathmysql_config_editor set --login-pathbackup --hostlocalhost --userbackup --password脚本内容#!/bin/bash set -euo pipefail VG_NAMEvgdata LV_NAMElv_mysql SNAP_NAMEsnap_db_$(date %Y%m%d_%H%M%S) SNAP_SIZE200G BACKUP_DIR/backup/mysql_snap KEEP_SNAP3 KEEP_BACKUP7 LOG_FILE/var/log/db_lvm_backup.log log() { echo $(date %Y-%m-%d %H:%M:%S) $* | tee -a $LOG_FILE } if ! mountpoint -q /var/lib/mysql; then log ERROR: /var/lib/mysql 未挂载备份中止 exit 1 fi log 开始数据库一致性快照流程 mysql --login-pathbackup SQL FLUSH TABLES WITH READ LOCK; SYSTEM lvcreate -s -n $SNAP_NAME -L $SNAP_SIZE $VG_NAME/$LV_NAME UNLOCK TABLES; SQL log 快照 $SNAP_NAME 创建成功 SNAP_DEV/dev/$VG_NAME/$SNAP_NAME MOUNT_POINT/mnt/snap_$SNAP_NAME mkdir -p $MOUNT_POINT # 如果文件系统是xfs挂载快照必须加nouuid FS_TYPE$(blkid -s TYPE -o value $SNAP_DEV) if [ $FS_TYPE xfs ]; then mount -o ro,nouuid $SNAP_DEV $MOUNT_POINT else mount -o ro $SNAP_DEV $MOUNT_POINT fi mkdir -p $BACKUP_DIR/$SNAP_NAME ionice -c2 -n7 rsync -aHAX --bwlimit50000 $MOUNT_POINT/ $BACKUP_DIR/$SNAP_NAME/ umount $MOUNT_POINT log 备份复制完成: $BACKUP_DIR/$SNAP_NAME # 清理旧快照保留最近KEEP_SNAP个 mapfile -t old_snaps (lvs --noheadings -o lv_name --select lv_attr ~ ^s | tr -d | grep ^snap_db_ | sort | head -n -$KEEP_SNAP) for snap in ${old_snaps[]}; do lvremove -f $VG_NAME/$snap log 删除旧快照: $snap done # 清理旧备份目录 find $BACKUP_DIR -maxdepth 1 -type d -name snap_db_* -mtime $KEEP_BACKUP -exec rm -rf {} \; log 备份任务完成细节上说几点快照挂载是只读的这样不会污染快照数据。rsync参数中的-X保留扩展属性-A保留ACL-H保留硬链接。数据库目录里硬链接不常见但保留它们总没坏处尤其当目录里有其他应用时。带宽限制和ionice是为了避免备份IO和业务IO互相抢占大规模数据库上格外重要。4.3 保留策略不是越多越好快照和备份目录都不能无限增长。保留策略要控制几个维度快照保留数量通常3到5个覆盖最近一周的手动恢复点。备份目录的保留时间按业务RPO要求一般7到14天。日志归档如果数据库还开了binlog要把binlog保留天数算进去快照加binlog可以恢复到任意时间点这个组合价值最大。清理时必须注意快照删除前确保对应备份已经复制完成否则数据就永久丢了。脚本里是先复制完再删快照顺序反了会前功尽弃。这是踩过的坑现在把它写成硬性逻辑。4.4 离线备份和异地备份为什么不能省略就地备份只能防误删、防软件故障防不了主机硬件故障和机房级灾难。快照恢复再快也只建立在“同一个卷组里的物理盘还活着”的前提下。所以我会额外做离线备份把备份目录用rsync推到独立存储或另一台机器rsync -avH --delete /backup/mysql_snap/ backup-server:/data/backup/mysql_snap/或者用restic、borg这类带压缩去重的备份工具把快照内容加密推到远程对象存储。注意如果数据库目录本身在LVM逻辑卷中而备份目录也放在同一个卷组里那备份目录本身也会被LVM快照包含等于快照套备份备份体积失控。生产环境我建议备份目录放在另一块物理磁盘或外部挂载点上避免这种自我嵌套。4.5 监控快照使用率别等失效才发现LVM快照失效是静默的如果监控不到位你会一直以为备份正常。一定要加入监控项最简单的方式是采集lvs输出lvs -o lv_name,snap_percent,data_percent --noheadings | awk $20 80 {print WARN: snapshot nearly full $0}超过80%就告警留出足够余地给晚上或周末的高峰写入。我还会在脚本里加一个前置检查如果卷组剩余空间不足以创建预期大小的快照直接发告警而不是带病执行。备份的成功定义不是“备份命令返回0”而是“恢复演练能通过”。5. 高效恢复的实践路径5.1 先分清你遇到的是什么恢复场景恢复效率的前提是确定场景误删除大表或数据行从快照中把丢失的数据文件目录拷回原卷逻辑卷彻底损坏挂载最近可用快照完整恢复数据库文件代码升级或版本回滚用快照直接把整个数据目录还原到升级前状态这是最快的方式整机物理故障只能靠离线备份或异地备份LVM快照帮不上忙这也是为什么要做离线备份。确定场景后恢复动作会完全不同。不要在脑子里只有一个“用快照还原”的思路那是恢复中最容易翻车的开始。5.2 快照挂载导出最稳妥的取数方式不要直接在原始卷上做实验先挂载快照取数据mkdir -p /mnt/snap_restore mount -o ro,nouuid /dev/vgdata/snap_db_20240601 /mnt/snap_restore然后按需复制。比如恢复MySQL的某个库rsync -av /mnt/snap_restore/mysql/your_db/ /var/lib/mysql/your_db/ chown -R mysql:mysql /var/lib/mysql复制完成后卸载快照、启动数据库让它自己跑一次崩溃恢复流程。InnoDB的redo日志会保证新合并的数据文件与当前状态一致这个过程通常只有几十秒到几分钟比从mysqldump恢复快几个数量级。5.3 整卷快速回滚lvconvert --merge的用法和风险如果确认要放弃当前所有变更直接把逻辑卷回滚到快照状态LVM提供合并命令# 先卸载原逻辑卷 umount /var/lib/mysql # 合并快照快照会被清空原卷变成快照时的内容 lvconvert --merge /dev/vgdata/snap_db_20240601 # 重新挂载 mount /var/lib/mysql但实际操作中我不建议随便merge。快照合并是单向的合并后原来的“当前状态”就没了如果发现回滚错了又没别的保底备份会非常被动。能用“挂载快照复制文件”解决的就不去merge给自己留条退路。merge只适合数据库已经完全不可用、且你确定快照内容才是你要的那个时间点的场景。5.4 恢复演练才是效率保证没有演练过的恢复方案在真正出故障时大概率会卡在某个命令上。我在团队里推行的做法是每两周做一次恢复演练用一台相同环境测试机从快照恢复到新卷记录全流程耗时创建测试快照模拟数据库崩溃删除数据文件用快照恢复并启动数据库检查表或记录是否完整。几次演练下来恢复时间会稳定下来也会把“忘了chown”“忘了刷新表缓存”“快照名写错”这些小问题都提前暴露掉。运维工作的核心不是拍脑门写脚本而是把恢复动作训练成肌肉记忆。6. 常见问题与排查技巧实录6.1 快照空间被写满导致备份失败现象创建快照后运行一段时间使用快照挂载时出现IO错误lvs显示snap_percent100快照被标记为失效。原因快照空间小于数据变化量写时复制区被写满。处理立即删除或扩展快照优先保证原始卷稳定运行扩展快照空间lvextend -L 100G /dev/vgdata/snap_db_20240601调整快照大小策略增加预留系数。经验上快照最好的状态是创建后一段时间内使用率始终低于50%到快照被清理或复制完成前也不会超过80%。发生过一次快照满以后后面所有备份都会连续失败必须从根上解决空间预算。6.2 卷组空间不足快照创建失败lvcreate时报No space left on device看起来很奇怪因为文件系统明明还有空间。本质是卷组里的物理扩展块PE不够了。可以临时清理旧快照、移除不再使用的LV或者用vgextend把新磁盘加入卷组pvcreate /dev/sdb vgextend vgdata /dev/sdb在规划阶段我会把卷组空闲比例控制在15%到20%以上这既是留给快照的空间也是日常数据增长的安全垫。太紧的卷组任何运维操作都会碰壁。6.3 挂载快照报UUID或日志错误XFS挂载快照时提示需要日志恢复或者出现UUID完全相同导致无法挂载通常是冻结没做好或没有使用nouuid选项。XFS的快照要配合xfs_freezeext4也要先确保sync执行并且如果数据库一直处于活动状态日志信息本来就在变动。解决方法是回退到正确的操作顺序先冻结或锁库再快照最后解冻或解锁。如果检测到文件系统真的异常别在快照上强行修改从最新可用快照重新取数更省事。6.4 数据库实例恢复后报不一致错误快照本身没问题但数据库启动时提示需要做崩溃恢复或者某张表打不开。这类问题多半是快照时刻刚好在事务执行中途数据库引擎的事务日志没到达检查点。MySQL可以通过innodb_force_recovery临时拉起但我建议优先用binlog配合快照做时间点恢复而不是硬着头皮修。PostgreSQL则要依赖WAL如果快照前没有使用pg_backup_start标记或者WAL归档不完整数据可能无法精确恢复到快照点。提前在一致性章节的流程规范里找原因比事后修复更有效。6.5 快照造成性能下降的规避有些团队说“快照一开数据库就慢”这往往是快照空间过小产生的连锁反应。当快照接近满时LVM会阻塞原始卷写入导致应用大面积卡顿。问题现象可能原因排查命令处理建议快照100%失效快照空间不足lvs -o snap_percent删除或lvextend重新规划lvcreate失败VG空间不够vgs / vgdisplay清理或vgextend扩容XFS快照无法挂载未冻结或uuid冲突mount -o ro,nouuid先xfs_freeze再快照数据库启动不一致快照时机不正确检查日志/WAL配合binlog/WAL做PITR数据库变慢快照满或IO抢占iostat, lvs加空间rsync限速ionice规避经验快照大小宁可多留不要小气空间不是最贵的数据安全才是创建快照时错开业务高峰期复制快照数据时控制rsync带宽比如--bwlimit50000用ionice把备份进程设置为后台低优先级。生产环境中数据库性能抖动90%以上都能从IO调度和空间规划找到原因快照本身不是原罪。最后再分享一个实践心得我在生产环境给快照备份加了一个“自动验证恢复”的步骤每周从最新快照中随机抽一个小库恢复到测试实例跑几个查询确认数据正常。这个方案虽然增加了一点时间成本但换来了非常宝贵的安心感。如果你还在用“备份脚本显示成功”来定义安全那我建议你从下一个备份周期开始把恢复测试和时间点恢复一起纳入维护日程。LVM快照是个好工具但只有配合严谨的备份策略和定期的恢复演练它才是真正可靠的数据库“时光机”。
返回列表