ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

rsync增量同步实战:从本地备份到远程服务器的核心用法

rsync增量同步实战:从本地备份到远程服务器的核心用法

数据同步这件事,我前后折腾了快十年。从最早拿scp一条条拷文件,到后来用rsync做主备同步,再到处理百万级小文件的增量同步,rsync一直是我不愿意换的方案。它的核心价值就一句话:只传变化的文件,第一次全量,之后都走增量。这个特性决定了它不仅是拷贝工具,更是一套同步引擎。

这篇内容聚焦rsync的基本使用。我会从命令格式、常用参数讲起,用本地目录、远程服务器两个真实场景走一遍完整操作,再补充几个我在线上踩过的坑。适合刚接触rsync、想用它替代scp/cp做备份或同步的人,也适合用过但没系统梳理过参数细节的同学。看过之后,你至少能独立完成日常的文件同步工作,并且知道遇到问题时该往哪个方向排查。

1. rsync是什么:从一个文件拷贝问题说起

1.1 为什么不用cp和scp

先说一个最基础的问题:既然cp能复制文件,scp能跨机器复制,rsync存在的意义是什么?我理解的核心差异有三点。

第一,cp和scp是无脑全量复制。不管你源目录里有多少文件没变化,它都重新读一遍、重新写一遍。文件少的时候无所谓,一旦到了几万个文件、几百GB数据,全量复制的时间成本就非常难看了。rsync默认只同步有差异的部分,文件内容一致、时间戳一致,就直接跳过,这是它效率高的根本原因。

第二,cp和scp中断就完蛋。传了一半网络断了,下次只能从头再来。rsync支持断点续传,中途断了,下次执行会自动跳过已传完的文件,并且对未传完的文件尝试接着传。对于大文件,这个能力几乎等于救命良药。

第三,scp没有清理目标目录的能力。如果我想让目标目录和源目录完全一模一样,源目录里删掉的文件,目标目录也应该删掉。scp做不到,cp也做不到,但rsync加一个--delete参数就解决了。

所以,rsync的定位不是“另一个复制工具”,而是“带差异计算、带完整性校验、带清理策略的同步工具”。理解了这个定位,后续所有参数就都围绕这三个方面展开。

1.2 rsync的核心能力与适用场景

rsync的整个设计都建立在增量同步之上。它的工作流程可以简单拆成四步:先扫描源和目标两侧的文件列表,然后计算差异,再对差异部分做传输,最后校验完整性。这四步都是自动完成的,我们真正操作时通常只需要关心第三步的传输方式和传输策略。

适用场景大致分三类:

  • 本地目录之间的备份,比如把数据盘上的项目目录同步到另一个备份目录。
  • 本地与远程服务器之间的单向部署或备份,比如把编译好的产物推送到测试服务器,或者每天凌晨把服务器的数据库导出文件拉到本地。
  • 服务器之间的数据同步,比如把一台应用服务器上的静态资源同步到另一台,实现简单的负载分担。

这几类场景的共同特点是:重复操作多、增量价值高、对一致性有一定要求。如果你只是偶尔拷一两个文件,scp就够了,完全不需要rsync。但只要你开始考虑“以后每天都要同步”,rsync就是那个可以长期依赖的答案。

2. rsync基本用法:命令结构与常用参数

2.1 命令格式与本地同步

rsync的命令格式和cp很像,最基础的形式是:

rsync [选项] 源路径 目标路径

源路径可以带反斜杠结尾,也可以不带,这个细节后面单独讲。先说一条最常用的本地同步命令:

rsync -av /home/user/project/ /backup/project/

这里-a表示归档模式,它等价于-rlptgoD的组合,也就是递归同步、保留符号链接、保留权限、保留时间戳、保留属主和属组、保留设备文件与特殊文件。-v表示verbose,显示同步过程详情。

我建议所有读者从-av起步,因为它能覆盖绝大多数需求。之后需要保留硬链接就加-H,需要压缩传输就加-z,需要删除多余文件就加--delete。不要一上来就堆一堆参数,要先理解每个参数背后的语义。

我实际测试过一条本地同步,源目录里约有1.2万个文件、接近6GB的数据。第一次执行rsync -av用时约5分钟,第二次立刻再执行,只用了十几秒,输出行全部是skipping non-regular file或者被跳过的日志。这就是增量同步最直观的体感:第二次跑的时间几乎可以忽略,因为差异为0。

2.2 远程同步的两种方式

rsync跨机器同步有两种方式。一种是通过rsync协议连接远程rsync服务,另一种是直接利用SSH连接远程主机。后者更常用,因为不需要在远端单独配置rsync服务,只要对方能SSH登录,rsync程序存在即可。

使用SSH方式时,源路径或目标路径写成用户名@主机名:路径的形式。比如把本地目录推送到远程服务器:

rsync -av /home/user/project/ user@192.168.1.20:/data/backup/project/

也可以把远程数据拉回本地:

rsync -av user@192.168.1.20:/data/logs/ /local/logs/

如果你不想用交互式密码登录,可以配合SSH密钥。rsync不直接管认证,它把SSH命令当作一个通道,所以你在.ssh/config里配置的别名、端口、跳板机参数都能起作用,只需要在rsync命令里指定-e参数,或者干脆用默认的ssh。

-e的典型例子是改端口:

rsync -av -e "ssh -p 2222" /local/data/ user@192.168.1.30:/backup/data/

我这里有个经验:尽量把SSH连接相关的参数写在~/.ssh/config里,而不是写在一长串-e里。比如在config里定义:

Host backup-server HostName 192.168.1.30 Port 2222 User rsyncuser IdentityFile ~/.ssh/id_ed25519

然后rsync命令就能写成:

rsync -av /local/data/ backup-server:/backup/data/

这样命令短、可读性强,而且SSH层面的超时、压缩、认证参数全部集中管理,比每次写一大串-e稳妥。

2.3 常用参数详解与取舍

rsync的参数太多,不需要全部背下来,但有几个高频参数必须吃透。我先列一张速查表:

参数作用我的使用建议
-a归档模式,保留权限、时间戳、属主等默认必加
-v显示详细输出日常调试必加,脚本里可以去掉
-z传输时压缩数据跨公网且文件文本占比高时建议加
-h人类可读格式输出配合-v使用
--progress显示每个文件的传输进度同步大文件时加
--delete删除目标端多余文件做镜像同步时加,需谨慎
-n试运行,只预览不实际执行任何危险操作前先跑一遍
--exclude排除指定文件或目录常用
--include配合exclude实现白名单高级用法
-P等于--partial --progress断点续传场景推荐

我特别想展开说两个容易被忽视的参数。

第一个是--delete。它让目标目录严格跟随源目录,源目录删除的文件,目标目录也会被删除。这个能力很强大,但风险也高。假如你在命令里写错了源路径,比如源路径少了一层目录,那么所有本应保留的目标文件都可能被删掉。我的习惯是:任何带--delete的命令,第一次必须先加-n试运行,查看将要执行的动作,确认无误后再去掉-n真正执行。另外,--delete的位置最好紧跟目标路径之前,语义上它作用于本次同步的接收端。

第二个是路径末尾的斜杠。/src/表示把src目录里的内容同步过去,而/src表示把src这个目录本身也同步过去。举个例子,假设源目录是/data/project,里面有index.html和assets/。

rsync -av /data/project/ /backup/p/

执行后,/backup/p下会有index.html和assets/,也就是源目录的内容直接铺在目标目录下。

rsync -av /data/project /backup/p/

执行后,/backup/p下会有project/,project目录里再放index.html和assets/。斜杠有无会产生完全不一样的结果,这个细节我见过太多人在线上翻车,建议专门找两个目录测试一下,形成肌肉记忆。

3. 实操案例:从备份到增量同步

3.1 本地目录备份的完整流程

下面用两个实际场景把命令串起来。第一个场景是本地目录备份,比如我需要每天把/data/mysql-backup同步到/data/archive,保留完整结构,同时自动清理归档端已不存在的旧目录。

先建一个测试目录模拟真实路径,并放两个文件:

mkdir -p /data/mysql-backup/db1 echo "test" > /data/mysql-backup/db1/table1.sql echo "other" > /data/mysql-backup/db2.sql

第一次全量同步,我习惯加--delete和-F来保留空目录结构。-F会读取并执行rsync-filter规则,新手可以先忽略,但最终建议掌握。执行命令:

rsync -avh --delete /data/mysql-backup/ /data/archive/

输出里会显示db1目录被创建,db2.sql文件被传输。此时检查目标目录:

ls /data/archive/

你会发现db1和db2.sql都在,和源目录一致。

第二次同步前,我在源目录里新增一个文件,同时删掉一个旧文件,模拟业务变化:

touch /data/mysql-backup/db1/table2.sql rm /data/mysql-backup/db2.sql

再次执行同一条rsync命令,输出会比第一次简短,只有差异部分。table2.sql会被传输,db2.sql会被删除。这就是永久增量加镜像对齐的效果。

这个过程中真正值得学习的是我的执行习惯:先试运行,再真正运行。我先执行:

rsync -anvh --delete /data/mysql-backup/ /data/archive/

注意参数里的-n。它会打印“将要做什么”而不真正执行。我看到删除列表只有db2.sql一个文件,和预期一致,才去掉-n正式执行。线上环境这个习惯帮我挡过至少两次事故,一次是源路径写错,一次是误加--delete导致目标目录清空。

3.2 远程服务器同步的完整流程

第二个场景是远程同步。假设我有一台业务服务器需要把/var/www下的静态资源同步到备份服务器。备份服务器IP用10.0.0.8代称,登录用户是syncuser。

先测试SSH连通性:

ssh syncuser@10.0.0.8

确保能登录后,执行首轮全量推送:

rsync -avz --progress /var/www/ syncuser@10.0.0.8:/backup/www/

这里加了-z,因为静态资源里有很多文本类文件,压缩传输能明显减少带宽消耗。--progress用于观察大文件的传输进度。

如果端口不是默认22,则用-e指定:

rsync -avz -e "ssh -p 2222" /var/www/ syncuser@10.0.0.8:/backup/www/

我建议把远程同步也纳入SSH密钥认证,而不是依赖密码。生成密钥并在远端放置公钥的步骤不在这篇里展开,但有一条原则:rsync命令本身不要硬编码密码,也尽量不要交互式输入密码,否则在cron定时任务里会卡住。

远程同步的坑点之一是防火墙或安全组限制。rsync走SSH通道时,双方网络必须允许SSH端口(默认22)的TCP连接。如果同步超时或者提示连接被拒绝,先检查端口连通性,再检查远端用户权限和目标目录写权限。不要一上来就怀疑rsync本身,大部分远程同步失败都是网络层或权限层的问题。

3.3 排除规则:不同步哪些东西

备份项目源码时,通常不想同步临时文件、缓存目录、日志等在目标端无意义的文件。rsync用--exclude解决这个问题。

我在某高校的项目中,原项目是Python写的,目录里有__pycache__、.venv、node_modules这些依赖和缓存,它们体积大且可以事后重建。完整备份需要非常多时间,于是改用排除规则:

rsync -avh --delete \ --exclude='__pycache__/' \ --exclude='.venv/' \ --exclude='node_modules/' \ --exclude='*.tmp' \ /home/user/proj/ /backup/proj-clean/

这里我列出四个排除规则,注意目录排除后面带斜杠,只匹配目录;文件排除用通配符*.tmp,匹配所有后缀为tmp的文件。

排除规则也可以写进文件里管理。比如创建/home/user/rsync-exclude.txt:

__pycache__/ .venv/ node_modules/ *.tmp

然后命令改为:

rsync -avh --delete --exclude-from=/home/user/rsync-exclude.txt /home/user/proj/ /backup/proj-clean/

当排除规则增多或者由运维统一管理时,放入独立文件更清晰,也方便不同项目复用。如果某个目录被上层规则排除,但你希望重新包含进来,可以用--include,顺序是先include后exclude,具体按rsync的规则顺序理解,这个高级用法就不深入展开了。

3.4 用定时任务自动同步

rsync的价值在日常自动运行中体现得最充分。我用cron做一个最简单的每日凌晨同步。

在服务器上执行crontab -e,写入:

0 3 * * * /usr/bin/rsync -aqz --delete /data/mysql-backup/ /data/archive/ >> /var/log/mysql-backup.log 2>&1

注意三点。第一,调用rsync要用绝对路径,可以在命令行先执行which rsync查看,防止cron环境PATH不完整导致命令找不到。第二,脚本中建议去掉-v,改用-q静默模式,只在出错时输出日志,避免每天生成大量无意义日志。第三,重定向输出到日志文件,方便出问题时回溯。

如果你希望每天只保留最近7天或30天的备份版本,rsync本身不负责版本管理,可以结合--link-dest实现快照链,也可以配合find定时清理历史目录,不过那是后话。

顺带提醒一个我踩过的坑:cron执行rsync时,有些环境变量比如SSH_AUTH_SOCK不会自动加载,导致依赖SSH密钥的任务失败。解决办法是在cron任务前加上source /etc/profile,或者让密钥放在默认位置并使用无密码的key。这个操作在部署自动备份时几乎一定会碰到。

4. 常见问题与排查技巧实录

4.1 同步后文件权限不对

用rsync -a同步时,权限和属主会保留。但如果你使用-r代替-a,或者目标文件系统不支持chown,就会出现权限丢失的情况。

最典型的表现:用普通用户同步文件到服务器后,目标文件的属主变成了当前执行命令的用户,而不是源文件原来的属主。这通常是因为普通用户没有权限去执行chown操作,rsync会忽略这类错误并继续同步其余文件。

解决方案是尽量以root用户或目标端拥有sudo权限的用户执行同步,并在重要同步中检查输出里的chown failed字样。如果目标端用的是FTP挂载目录或某些网盘的挂载点,文件系统可能不支持完整的Unix权限,这时只能接受不保留属主属性,或者改用--chmod指定目标文件的权限层级。

在本地目录之间做备份时,我建议目标目录也放在同一类文件系统上,比如都是ext4或xfs,这样可以完整保留acl和xattr。跨文件系统类型时先做一次小范围测试,观察权限和硬链接保留情况。

4.2 中断后如何续传

rsync中断后,下次执行同一命令时会自动跳过已完成文件,无需额外操作。但对于一个传了一半的大文件,rsync默认策略是重新传输整个文件,因为目标端没有文件或已有不完整的文件不匹配。

要支持大文件断点续传,至少需要--partial参数。它的含义是保留部分传输的文件,下次再执行时,rsync会对比源文件和目标端部分文件的块校验,只续传缺失的块。对超大文件来说,这个能力非常关键。

我的推荐是组合使用-P,它等于--partial --progress。一条命令同时完成两件事:保留断点、显示进度。

rsync -avP /data/bigfile.tar user@10.0.0.8:/backup/bigfile.tar

如果传输中断,直接重跑这条命令,rsync会从断点位置继续,而不是从头开始。实测一个8GB的库备份文件,断点续传比全量重传节省了约70%的时间,尤其是在网络带宽不稳定的场景下。

还有一个相关参数--append,用于“只追加”的场景。如果源文件只在尾部增长而前面内容不变,比如日志文件,--append可以跳过整块校验,直接从目标文件当前末尾开始同步。注意它和--partial的语义不同,不能混用,使用时需要评估源文件是否满足尾部增量的条件。

4.3 海量小文件同步慢

rsync效率主要受两个因素影响:文件数量和数据体积。当你面对十万级、百万级小文件时,rsync的扫描阶段可能比传输阶段更耗时,因为需要逐文件对比元数据。

优化方向有几个。

第一,优先使用-c做校验?不建议。-c会强制校验文件内容,虽然保证一致性更强,但极大的增加了CPU与IO消耗。默认的快速检查基于大小+修改时间,对绝大多数场景已经足够。只有在源和目标两侧文件时间戳经常被意外修改、又必须保证内容完全一致时才考虑加-c。

第二,适当调整rsync的批量传输参数。例如:

rsync -av --delete --no-whole-file --block-size=8192 /src/ /dst/

这里的参数可以针对小文件场景微调,但真正的性能瓶颈往往是文件系统IO。如果源目录在机械硬盘上,扫描大量小文件会非常慢,建议先把源数据冷备排序再同步,或者使用并行rsync工具。

第三,考虑并行化。最简单的方式是把一个大目录拆成多个子目录,分别后台执行rsync。例如:

for dir in /data/app/*/; do rsync -aq "$dir" /backup/app/ & done wait

这个方案会启动多个rsync并发进程,但要注意IO竞争。如果磁盘本身已经接近饱和,并发进程反而会让整体效率下降。建议先用iostat观察磁盘压力再决定并发度。

另一个提示是不要在同步目录里包含挂载点或跨文件系统的链接,否则可能出现递归复制或死循环。用-x参数可以限制为单一文件系统,防止把其他挂载点一并同步出去。

4.4 常见错误速查表

我把日常运维中最常遇到的rsync错误整理成一张表,方便你遇到问题时直接对照排查。

错误提示可能原因处理思路
rsync: failed to set times on /path目标文件系统不支持精确时间戳,或权限不足检查目标端挂载类型与写权限,换用--no-times临时绕开
rsync: link_stat "/path" failed: No such file or directory源路径写错,或路径包含不存在的前缀检查路径拼写,用ls确认源路径存在
rsync error: some files/attrs were not transferred部分文件传输失败,通常与权限或特殊文件有关查看日志具体文件,确认目标端目录属主和SELinux/AppArmor策略
rsync: connection unexpectedly closedSSH通道被断开,或网络不稳定检查SSH连通性、端口、防火墙;重试并加--partial
ERROR: module is read only远程rsync守护进程配置了只读模块如果是守护进程方式同步,检查远端rsyncd.conf
chown: Operation not permitted普通用户无法调整属主切换root/sudo执行,或接受默认属主
IO error encountered -- skipping file磁盘IO错误或文件被占用检查磁盘空间、inode是否耗尽,排除正在写入的文件

排查任何rsync问题,第一步都是回到日志。携带-v和--stats执行一次试运行,把输出保存下来逐行看。很多时候解决方案就藏在日志里,比在搜索引擎里大海捞针有效得多。

5. 我对rsync的几点实操体会

最后分享几条我在实际使用中形成的习惯,不系统,但每条都换来过便利。

第一,命令先试运行。任何带--delete的同步,在正式执行前我都会先加-n跑一遍,确认输出的删除列表符合预期。这个习惯成本极低,收益极高。

第二,用文件管理排除规则。规则一旦超过三条,就不建议写在命令里了,写进排除文件,同步命令始终保持简短,也方便多个项目共用同一套规则。

第三,日志必须留。无论是手动执行还是cron自动执行,输出都要追加到日志文件。rsync的错误经常发生在深夜里,如果没有日志,第二天早上面对一个中断的同步任务毫无头绪。

第四,不要迷信--delete。它能够实现镜像同步,但它也要求源路径极度准确。凡是能用快照方式解决版本回退需求的场景,我更倾向于保留历史版本而不是让目标端总是和源端完全一致。

rsync的基本使用并不复杂,真正有价值的是理解它背后的增量同步模型,以及把每个参数用对、把每个坑避开。从这篇文章出发,你完全可以先跑通一条本地同步,再扩展一条远程同步,最后把它接入定时任务。后续还有更深入的方向,比如rsync守护进程模式、带宽限制、批处理脚本,都可以在这个基础之上继续摸索。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进