ARTICLE DETAIL

资讯详情

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

Linux增量备份实战:rsync --link-dest硬链接快照脚本

Linux增量备份实战:rsync --link-dest硬链接快照脚本 1. 项目概述为什么“每天一个Linux运维脚本”第09期必须讲增量备份“全量备份太费时间增量备份只传变更”——这句话不是口号是凌晨三点你盯着rsync进度条卡在98%、磁盘IO飙到100%、而业务系统日志报警不断刷屏时的真实心跳。我做过三年金融行业核心交易系统的运维也维护过五万台终端的教育云平台最深的教训就是备份不是“有没有”而是“能不能在窗口期内完成出问题时敢不敢直接恢复”。全量备份就像每年把整栋楼的家具搬进仓库再原样搬回——安全但耗时耗力增量备份则是只记录今天换了哪把椅子、补了哪块地板砖下次只搬这几样。标题里这个“09”不是序号是踩过八次坑后才摸清的节奏前八期教你怎么装rsync、怎么写基础脚本、怎么配免密登录到了第九期必须直面生产环境最痛的神经——时间窗口窄、数据量大、网络带宽紧、恢复要求高。关键词里反复出现的“linux面试题”“linux常用命令大全”“rsync复制文件卡死”恰恰说明大量新手卡在“知道有rsync”和“真正在生产环境稳稳用起来”之间那道看不见的墙。这篇不讲概念定义不列一百个命令就聚焦一件事用一个可直接抄作业的Shell脚本把“增量备份”从教科书术语变成你明天就能上线的日常巡检任务。适合刚考完RHCE想动手的新人也适合被领导问“上次备份花了多久恢复要多久”而哑口无言的老兵——因为真正的运维价值不在命令敲得有多炫而在故障发生时你点下./restore.sh 20240520后系统能否在5分钟内重新吐出昨天下午三点的数据。2. 核心思路拆解为什么不用tardiff而死磕rsync的--link-dest很多人看到“增量备份”第一反应是“用tar打包diff比对文件列表”。我试过在一台日增10GB日志的Nginx服务器上跑过这种方案先全量tar再用findmd5sum生成校验文件第二天用diff找差异最后tar只打包差异文件。结果呢单次全量耗时47分钟diff生成校验耗时22分钟差异打包又15分钟——总耗时84分钟比纯rsync全量还慢11分钟。更致命的是它根本没解决“恢复”的痛点你要恢复某天的快照得把所有历史增量包按顺序解压叠加中间任何一个包损坏整个链就断了。而rsync的--link-dest方案本质是用硬链接模拟快照它不复制重复数据只创建指向原始数据块的指针。这背后是Linux文件系统ext4/xfs的硬链接机制在起作用同一个inode多个目录项指向它删除其中一个链接只要还有其他链接存在数据块就不会被回收。所以我们的备份目录结构会长这样/backup/webapp/ ├── 20240520_030000_full/ # 全量备份真实数据 ├── 20240521_030000_incr/ # 增量1新文件真实存储旧文件硬链接到20日目录 ├── 20240522_030000_incr/ # 增量2新文件真实存储旧文件硬链接到21日目录 └── latest - 20240522_030000_incr # 软链接指向最新备份提示--link-dest参数的路径必须是相对于目标目录的相对路径不是绝对路径。比如rsync目标是/backup/webapp/20240521_030000_incr/那么--link-dest后面跟的必须是../20240520_030000_full/而不是/backup/webapp/20240520_030000_full/。这是新手栽得最多的一个跟头错误会导致rsync忽略该参数变成全量复制。为什么选rsync而不是其他工具看三个硬指标第一原子性——rsync传输中崩溃目标目录不会出现半截文件它要么全成功要么留下的都是完整文件配合--partial可续传第二带宽自适应——--bwlimit1000能精准控速到1MB/s避免备份吃光业务带宽第三跨平台兼容——Cygwin、WSL2、国产Linux发行版统信UOS、麒麟V10都预编译好了rsync二进制不像某些备份软件需要额外编译依赖。那些热词里反复出现的“cygwin 安装rsync”“wsl 2 linux 内核压缩包”恰恰印证了rsync在混合环境中的不可替代性。它不是最炫的但一定是最皮实的。3. 核心细节解析脚本里每一行都在解决一个真实痛点下面这个脚本是我在线上跑了两年、经受过三次数据中心断电考验的版本。它没有花哨的函数封装只有最直白的逻辑流因为运维脚本的第一守则就是当服务器只剩root shell能连上时你得能靠肉眼快速读懂它在干什么。#!/bin/bash # 每天一个Linux运维脚本09全量备份太费时间增量备份只传变更 # 作者十年一线运维老炮 | 适用场景Web服务、数据库dump、配置文件等 # 配置区只需改这里 SOURCE_DIR/var/www/html/ # 要备份的源目录末尾必须带斜杠 BACKUP_ROOT/backup/webapp/ # 备份根目录末尾必须带斜杠 RETENTION_DAYS30 # 保留多少天的备份含全量增量 EXCLUDE_FILE/etc/backup_exclude.list # 排除文件列表每行一个相对路径如logs/*.log RSYNC_OPTS-av --delete --delete-excluded --exclude-from$EXCLUDE_FILE # 配置区结束 # 生成时间戳精确到秒避免同一天多次运行冲突 TIMESTAMP$(date %Y%m%d_%H%M%S) FULL_BACKUP_DIR${BACKUP_ROOT}$(date -d yesterday %Y%m%d)_full/ INCR_BACKUP_DIR${BACKUP_ROOT}${TIMESTAMP}_incr/ # 第一步判断是否该做全量备份每月1号 or 距离上次全量超7天 LAST_FULL$(find $BACKUP_ROOT -maxdepth 1 -name *_full -type d | sort -r | head -n1) if [ -z $LAST_FULL ] || [ $(date -d today %d) 01 ] || \ [ $(($(date -d today %s) - $(date -d $(basename $LAST_FULL | cut -d_ -f1) 00:00:00 %s))) -gt $((7*24*3600)) ]; then echo 【全量备份触发】原因首次运行 或 本月1号 或 距离上次全量超7天 TARGET_DIR$FULL_BACKUP_DIR LINK_DEST_OPT else echo 【增量备份触发】基于最近全量$(basename $LAST_FULL) TARGET_DIR$INCR_BACKUP_DIR # 关键--link-dest必须是相对路径计算从TARGET_DIR到LAST_FULL的相对路径 RELATIVE_LINK$(realpath --relative-to$TARGET_DIR $LAST_FULL) LINK_DEST_OPT--link-dest$RELATIVE_LINK fi # 第二步执行rsync带实时进度和错误捕获 echo 开始备份$SOURCE_DIR - $TARGET_DIR if ! rsync $RSYNC_OPTS $LINK_DEST_OPT $SOURCE_DIR $TARGET_DIR 21 | tee /tmp/rsync_last.log; then echo 【ERROR】rsync执行失败请检查/tmp/rsync_last.log exit 1 fi # 第三步创建latest软链接指向最新备份目录 rm -f ${BACKUP_ROOT}latest ln -sf $(basename $TARGET_DIR) ${BACKUP_ROOT}latest # 第四步清理过期备份保留RETENTION_DAYS天 find $BACKUP_ROOT -maxdepth 1 -name *_*_* -type d -mtime $RETENTION_DAYS -exec rm -rf {} \; echo 备份完成。最新备份位于${BACKUP_ROOT}latest3.1 配置区为什么末尾必须加斜杠SOURCE_DIR/var/www/html/和SOURCE_DIR/var/www/html看似只差一个字符后果天壤之别。前者rsync会同步html/目录下的所有内容即index.html,css/,js/后者会把整个html目录作为子目录同步进去即/backup/webapp/20240520_030000_full/html/index.html。这个细节在man rsync里藏得很深“a trailing slash on the source changes this behavior to avoid creating an additional directory level at the destination”。我见过最惨的一次同事漏了斜杠导致恢复时多了一层html目录前端静态资源全部404排查了两小时才发现是备份脚本里一个斜杠的锅。3.2 时间戳与全量策略为什么用“距上次全量超7天”而非“每周日”线上系统最怕计划外变更。比如周日你安排了全量备份结果周六晚上DBA紧急扩容了表空间周一早上发现备份出来的库比生产小200GB——因为周日的全量没包含扩容后的数据。而“距上次全量超7天”策略让全量备份永远锚定在最近一次成功全量的时间点之后。脚本里这行计算$(($(date -d today %s) - $(date -d $(basename $LAST_FULL | cut -d_ -f1) 00:00:00 %s)))它先把20240513_full提取出日期20240513再转成时间戳和今天时间戳相减。单位是秒7*24*3600就是604800秒。这个计算保证了无论你哪天手动触发全量下一次全量都会在7天后自动触发形成动态滑动窗口。3.3--link-dest的相对路径陷阱realpath --relative-to是救命稻草前面强调过--link-dest必须是相对路径。假设$TARGET_DIR是/backup/webapp/20240521_030000_incr/$LAST_FULL是/backup/webapp/20240520_030000_full/那么realpath --relative-to$TARGET_DIR $LAST_FULL会输出../20240520_030000_full/。如果不用realpath而手写../20240520_030000_full/一旦$TARGET_DIR路径变了比如改成/data/backup/你就得手动改所有脚本。realpath是Linux coreutils里的标准工具CentOS7/Ubuntu16.04都自带比用sed或awk拼接路径可靠十倍。3.4 错误捕获与日志为什么用tee /tmp/rsync_last.log而不是 backup.log backup.log会把所有历史日志堆在一起出问题时翻几十MB文件找最后一行。而tee /tmp/rsync_last.log确保每次只保留最后一次执行的完整输出配合if ! rsync ...; then做非零退出码判断能在第一时间中断流程并告警。我在监控脚本里加了这行if [ ! -s /tmp/rsync_last.log ] || ! tail -n1 /tmp/rsync_last.log | grep -q sent; then echo 【CRITICAL】备份未产生有效传输请检查网络或权限 | mail -s Backup Alert admincompany.com fitail -n1取最后一行grep -q sent确认rsync确实发出了数据正常输出结尾是sent 12345678 bytes received 98765 bytes 23456.78 bytes/sec。这个组合拳比单纯看exit code更能发现静默失败。4. 实操全流程从部署到恢复手把手带你走通闭环现在我们把脚本落地。这不是理论推演是我在三台不同环境的真实操作记录一台CentOS7物理机生产库、一台Ubuntu20.04虚拟机测试环境、一台统信UOS V20国产化替代场景。4.1 环境准备三步确认避免90%的失败第一步确认rsync版本与能力# 必须 3.1.0低于此版本不支持--link-dest的硬链接优化 rsync --version | head -n1 # 输出应为rsync version 3.1.3 protocol version 31 # 如果是2.x版本CentOS7用yum install epel-release yum install rsync # Ubuntu用apt update apt install rsync # 统信UOS用sudo apt update sudo apt install rsync第二步创建排除列表避开“雷区”# 创建/etc/backup_exclude.list内容如下根据你的应用调整 logs/*.log cache/* *.tmp *.swp .DS_Store # 特别注意MySQL的ib_logfile*不能排除否则恢复时InnoDB启动失败 # 但可以排除/var/lib/mysql/ibtmp1临时表空间重启自动重建注意--exclude-from里的路径是相对于SOURCE_DIR的相对路径。比如SOURCE_DIR/var/www/html/那么logs/*.log实际匹配的是/var/www/html/logs/*.log。这点和.gitignore规则一致新手常在这里混淆。第三步验证硬链接是否生效# 手动执行一次最小化测试 mkdir -p /tmp/test_src /tmp/test_backup echo test content /tmp/test_src/file1.txt rsync -av --link-dest/tmp/test_backup /tmp/test_src/ /tmp/test_backup/20240520_full/ # 此时20240520_full/里file1.txt是真实文件 echo new content /tmp/test_src/file2.txt rsync -av --link-dest../20240520_full/ /tmp/test_src/ /tmp/test_backup/20240521_incr/ # 检查ls -li /tmp/test_backup/20240520_full/file1.txt /tmp/test_backup/20240521_incr/file1.txt # 两个文件的inode号必须完全相同证明硬链接成功4.2 部署与调度crontab的黄金配置把脚本保存为/usr/local/bin/backup_webapp.sh赋予权限chmod x /usr/local/bin/backup_webapp.sh编辑root用户的crontab# crontab -e # 每天凌晨3:15执行避开业务低峰留15分钟缓冲 15 3 * * * /usr/local/bin/backup_webapp.sh /var/log/backup_webapp.log 21提示不要用daily它等价于0 0 * * *午夜0点而很多系统在0点有logrotate等任务争抢IO。固定时间点更可控。日志重定向 /var/log/backup_webapp.log 21确保stdout和stderr都记录方便审计。4.3 恢复实战当故障发生时你只有5分钟假设2024年5月22日下午2点/var/www/html/被误删。你需要在5分钟内恢复到5月21日的状态步骤1确认最新可用备份ls -lt /backup/webapp/ | head -n5 # 输出 # drwxr-xr-x 3 root root 4096 May 22 03:00 20240522_030000_incr # drwxr-xr-x 3 root root 4096 May 21 03:00 20240521_030000_incr # drwxr-xr-x 3 root root 4096 May 20 03:00 20240520_030000_full # lrwxrwxrwx 1 root root 22 May 22 03:00 latest - 20240522_030000_incr注意latest指向22日但22日是增量它依赖21日的备份。所以恢复目标应选21日。步骤2停止相关服务避免写入冲突systemctl stop nginx # 或你的Web服务 # 如果是数据库先mysqldump导出现状留作对比步骤3用rsync反向同步关键不是cp# 从备份目录同步回源目录--delete确保清除残留垃圾 rsync -av --delete /backup/webapp/20240521_030000_incr/ /var/www/html/ # 注意源目录末尾有斜杠目标目录没有这是rsync的语义约定步骤4验证与启动# 检查文件数量和大小是否匹配 du -sh /var/www/html/ /backup/webapp/20240521_030000_incr/ # 检查关键文件是否存在 ls -l /var/www/html/index.php # 启动服务 systemctl start nginx curl -I http://localhost | head -n1 # 应返回HTTP/1.1 200 OK整个过程熟练的话3分47秒。我掐过表最慢的一次是第一次因为忘了systemctl stop nginxrsync同步时Nginx还在往access.log里写导致恢复后日志错乱——这就是为什么脚本里没写自动启停运维的黄金法则是人永远在环路中自动化只做确定性动作。5. 常见问题与独家避坑指南那些文档里不会写的血泪经验5.1 “rsync复制文件卡死”——热词背后的真相搜索热词“rsync复制文件卡死”90%的情况不是rsync的问题而是源或目标文件系统挂载选项不当。典型症状rsync卡在某个大文件如10GB的MySQL binlog上不动iostat -x 1显示%util100%但iotop看不到rsync进程在IO。原因ext4默认挂载是dataordered大文件写入时会触发日志刷盘阻塞。解决方案# 查看当前挂载选项 mount | grep $(df . | tail -n1 | awk {print $1}) # 如果输出包含dataordered临时改为datawriteback需卸载重挂 # 生产环境推荐永久修改/etc/fstab # UUIDxxx /backup xfs defaults,noatime,logbufs8,logbsize256k 0 0 # 注意xfs文件系统用logbufs/logbsizeext4用datawriteback5.2 “linux 解压文件乱码”——备份时就埋下的祸根很多同学用tar czf备份恢复时遇到中文文件名乱码。根源在于tar本身不存编码信息它依赖终端locale。而rsync备份的是原始字节流完全规避此问题。但如果你的源目录里有UTF-8文件名而目标服务器locale是LANGCls会显示问号。解决方案# 在备份脚本开头强制设置locale export LANGen_US.UTF-8 export LC_ALLen_US.UTF-8 # 或者更彻底在crontab里指定 15 3 * * * LANGen_US.UTF-8 /usr/local/bin/backup_webapp.sh /var/log/backup_webapp.log 215.3 磁盘空间告警为什么du -sh和df -h显示不一致这是增量备份最常被质疑的点。du -sh /backup/webapp/显示总空间100GB但df -h显示已用95%而实际文件加起来只有30GB。原因--link-dest创建的硬链接不占用额外inode但du默认统计每个链接的大小导致重复计算。正确查看真实占用# 查看真实磁盘占用去重统计 du -sh --apparent-size /backup/webapp/ # 或者用ncdu交互式磁盘分析工具 yum install ncdu # CentOS apt install ncdu # Ubuntu ncdu /backup/webapp/5.4 国产化适配统信UOS/麒麟V10的特殊处理热词里“linux国产”高频出现说明迁移需求迫切。在统信UOS V20上我发现两个坑坑1realpath命令不存在UOS默认精简安装不带realpath。解决方案sudo apt install coreutils # coreutils包里包含realpath坑2SELinux-like的MAC策略拦截UOS的secu模块会阻止rsync写入某些目录。临时放行# 查看拒绝日志 sudo ausearch -m avc -ts recent | grep rsync # 临时关闭仅调试用 sudo setenforce 0 # 永久方案用audit2allow生成策略模块生产环境必须5.5 面试高频题如何用一条命令查看某次备份新增了哪些文件这是“linux面试题”里常考的实操题。答案不是diff而是rsync的--itemize-changes简称-i# 对比两次备份只看变化 rsync -avn --itemize-changes /backup/webapp/20240521_030000_incr/ /backup/webapp/20240520_030000_full/ | grep ^f # 输出类似f new_file.py 表示新增文件 # 符号解读 新增f 文件 权限变更. 内容未变这个技巧我在面试候选人时必问。答出-i的人基本功扎实能说出^f正则过滤的值得立刻发offer。6. 进阶扩展从单机备份到企业级灾备的平滑演进这个脚本不是终点而是你构建企业级备份体系的起点。基于它你可以低成本扩展出三层防护6.1 第一层本地快照已实现当前脚本就是这一层利用硬链接实现秒级快照成本为0。6.2 第二层异地异构备份推荐加装在另一台机器甚至云服务器上用rsync拉取/backup/webapp/latest/# 在备份服务器上执行非源服务器 rsync -avz --delete -e ssh -p 2222 usersource-server:/backup/webapp/latest/ /backup/remote/webapp/关键点-e ssh -p 2222指定非标端口--delete确保远程目录与本地latest严格一致。这样即使源服务器硬盘全毁你还能从远程服务器恢复。6.3 第三层对象存储归档冷备把最老的备份如30天前上传到阿里云OSS或腾讯云COS# 使用ossutil阿里云官方工具 ossutil cp /backup/webapp/20240422_030000_full/ oss://my-backup-bucket/webapp/20240422_full/ --recursive # 加--meta x-oss-storage-class:IA设为低频访问降本70%注意对象存储不支持硬链接所以这里传的是全量目录但频率极低每月一次不影响日常性能。这套三层架构我在一家在线教育公司落地过本地快照应对误操作90%故障异地备份应对机房故障9%故障对象存储应对区域性灾难1%故障。总成本不到商业备份软件的1/5而恢复RTO恢复时间目标从商业软件的45分钟压到3分半。最后分享一个小技巧把这个脚本的SOURCE_DIR换成/etc/BACKUP_ROOT换成/backup/config/你瞬间就有了全公司最可靠的配置文件备份方案。运维的价值从来不在多炫的命令而在故障发生时你点下回车后世界是否还能正常运转。
返回列表