日常跟服务器打交道,最常干的一件事就是把文件从一台机器复制到另一台机器。以前我偷懒用 scp,批量小文件传得慢不说,传错了还得整目录重来。直到换了 rsync,这种"多台机器间同步目录"的活才算真正稳下来。这篇就把 rsync 远程文件同步的常用姿势、背后的工作原理,以及我在生产环境里踩过的坑一次说清楚。无论你是第一次用还是已经抄过几条命令,应该都能找到点有用信息。
1. 项目全貌与方案选型
1.1 为什么是 rsync 而不是 scp、cp 或 FTP
刚开始接触 Linux 时,我的文件传输三板斧是 scp、tar 管道、ftp。等手上的目录越来越大,比如一个站点打包后 20GB,再往另一台服务器传时,问题就暴露了:每次都是全量拷贝,哪怕只改了一个 2KB 的配置文件,也要把 20GB 从头传一遍;传到一半网络断了,只能删掉重来;目录里需要排除缓存、日志、.git 这些不必要的内容时,scp 直接抓瞎。
rsync 解决的正是这几个核心痛点。它最大的特点是增量传输:源和目标做一次对比,只有真正变化的数据块会被传输。传输过程中断,下次接着断点继续跑,不需要从头再来。它还能在传输前通过 include/exclude 规则过滤文件路径,比如排除掉 node_modules、tmp、cache 这类目录,同步效率会高很多。
从定位上讲,rsync 是"文件同步工具",不是简单的"文件复制工具"。它不仅仅把文件从 A 搬到 B,而是会让 B 朝着 A 的状态收敛。配合--delete参数,目标端多出来的文件也会被清理掉,两边最终达到一致状态。这一点在发布代码、镜像目录、灾备同步场景里非常实用。
1.2 远程同步的两种通道模式:SSH 与 rsync daemon
rsync 做远程同步时,有两种常见的通道选择。第一种是走 SSH 协议,命令格式类似rsync -avz /local/path user@host:/remote/path。这种模式直接复用系统的 SSH 认证机制,不需要额外开端口,只要目标机器能 SSH 登录就能同步,是最常用的方式。
第二种是 rsync daemon 模式,命令格式变成rsync -avz /local/path user@host::module_name。它会监听独立的 873 端口,通过 rsyncd.conf 配置文件暴露一个或多个"模块",客户端按模块名访问。daemon 模式的优势是不依赖 SSH 账号体系,可以独立控制只读、可写权限,在内网或者对端没有开放 SSH 的情况下更灵活。
我的建议是:大多数场景优先走 SSH 模式,省事、安全、好排查;只有在做集中式备份服务器、需要给不同客户端开放不同目录时,才引入 daemon 模式。SSH 模式所有的权限控制都落在了 sshd 身上,人家已经打磨了很多年,比自己搭一套认证体系可靠得多。
2. 核心原理与增量同步幕后
2.1 增量传输算法:文件分块与校验
rsync 的增量同步一直让我觉得"黑科技",直到我查了它的算法才明白,其实是很经典的滚动校验设计。当传输一个大文件时,rsync 会先把目标端的文件切分成固定大小的数据块,比如每块 512 字节或 1KB,对每一块计算出两个校验值:一个弱滚动校验值和一个强 MD5 校验值。
源端这边,同样从文件开头逐个字节向后滑动,对每个窗口位置计算弱滚动校验值,并与目标端的块校验值做比对。如果某一段的弱校验值匹配,再进一步做强校验确认。这样匹配上的数据块,就直接引用目标端已有内容,不在网络上重复传输;只有完全匹配不上的片段,才作为增量数据发送过去。
举个例子,一个 100MB 的日志文件,只在中间追加了 2KB 内容。普通 scp 要把 100MB 全传一遍,rsync 则只传输变化的那 2KB 以及极少量校验元数据,实际网络开销可以忽略不计。这也是为什么 rsync 在"大文件小改动"场景下优势特别明显。
2.2 同步判定策略:默认只比较大小和修改时间
rsync 默认判定一个文件是否需要重新传输,依据是"文件大小 + 修改时间(mtime)"。如果两边文件大小一样,且 mtime 一样,就默认文件未变化,跳过传输。这是默认行为,也是速度快的核心原因,因为大多数情况下不需要把文件内容整个读一遍。
但这也带来一个常见坑:如果同步后不小心把目标端文件的 mtime 改了,或者文件大小碰巧一样但内容确实不同,rsync 会漏掉差异。比如从某个备份工具里恢复出来的文件,mtime 被统一重写,rsync 一看大小一样就跳过了。应对办法是加-c参数,让 rsync 对每个文件做一次实际内容校验,以内容为准。代价是速度会明显下降,因为每个文件都得读一遍,所以只建议在需要保证严格一致的场景才加。
还有一个实用的附加能力是断点续传。传输大文件中途网络中断时,rsync 下次执行会接续上次的位置继续,前提是目标端保留了.rsync-partial临时文件。加--partial参数就能保留部分传输的文件,避免每次都从头传。
3. 高频参数与命令速查
3.1 必知参数解析
rsync 参数很多,但日常远程同步真正高频的其实就十来个。把这几个用熟,基本上能解决 90% 的同步需求。
-a:归档模式,等价于-rlptgoD,表示递归同步并保留符号链接、权限、属主、属组、时间戳。这是最基础的参数,远程同步默认应该带上。-v:显示详细输出,可以看到哪些文件被传输、哪些被跳过。-z:传输时压缩,适合带宽有限的场景。要注意,如果本地网络已经是千兆内网,压缩反而会增加 CPU 消耗,有时不如不压。--delete:删除目标端多余文件,让目标端和源端完全一致。首次使用务必配合--dry-run演练一遍。--exclude:排除指定文件或目录,支持通配符。比如--exclude 'cache/'、--exclude '*.log'。--include:与 exclude 配合使用,先排除大部分,再保留特定子集。--dry-run:只演练,不实际传输。在执行删除类操作前,我每次必加。--progress:显示每个文件的传输进度,适合大文件传输。--bwlimit:限制带宽,格式为 KB/s。避免同步任务挤占线上业务的网络带宽。--partial:保留部分传输的文件,以便断点续传。-e:指定远程 shell,比如-e 'ssh -p 2222'用非默认 SSH 端口。
3.2 经典组合命令
下面几条命令是我在日常工作中用得最多的组合,可以直接抄。
# 基本远程同步:从本地推送到远端 rsync -avz /home/www/ user@192.168.1.10:/data/www/ # 从远端拉取到本地 rsync -avz user@192.168.1.10:/data/www/ /home/www/ # 同步并严格删除目标端多余文件,先演练 rsync -avz --delete --dry-run /home/www/ user@192.168.1.10:/data/www/ # 排除缓存和日志目录 rsync -avz --exclude 'cache/' --exclude '*.log' /home/www/ user@192.168.1.10:/data/www/ # 使用非默认 SSH 端口 rsync -avz -e 'ssh -p 2222' /home/www/ user@192.168.1.10:/data/www/有一个细节值得注意:源路径末尾带不带斜杠,含义完全不同。/home/www/带斜杠表示同步目录里面的内容;/home/www不带斜杠表示同步这个目录本身。也就是说,执行rsync -avz /home/www/ user@host:/data/www/,目标是/data/www/目录里出现的是 www 下的内容;执行rsync -avz /home/www user@host:/data/www/,目标端会出现/data/www/www/的子目录。这个坑我踩过不止一次,每次都会单独检查一遍路径写法。
4. 实操:两台服务器之间同步网站目录
4.1 场景与前置条件
我在维护两台服务器,A 机是应用发布源,代码更新后要同步到 B 机作为测试环境。目录结构大概是这样的:/home/www/下面有app/、static/、storage/cache/、runtime/logs/、.git/等内容。同步时希望排除缓存目录、日志目录和 .git 目录,否则会把大量无意义文件传到目标端。
前置条件很简单:两台机器都能通过 SSH 连通,SSH 端口选择 22。为了免输密码,我提前配置了密钥认证,用ssh-keygen生成密钥对,然后执行ssh-copy-id user@192.168.1.10把公钥推过去。没有密钥认证的话,rsync 每次执行都会提示输入密码,这在手动执行几次问题不大,但一旦放到 crontab 定时任务里就无法自动运行了。
4.2 执行同步与验证
先执行一次演练命令,确认排除规则和删除行为没有异常:
rsync -avz --delete --exclude '.git/' --exclude 'runtime/logs/' --exclude 'storage/cache/' --dry-run /home/www/ user@192.168.1.10:/data/www/演练输出中,所有标记为deleting的行都是目标端会被清理的文件;所有标记为sending的就是实际会传输的文件。我建议仔细检查一遍,特别是校验会不会误删目标端独立的配置文件。确认无误后,去掉--dry-run正式执行:
rsync -avz --delete --exclude '.git/' --exclude 'runtime/logs/' --exclude 'storage/cache/' /home/www/ user@192.168.1.10:/data/www/执行结束后,到目标端验证一下:
# 检查文件数量和总大小是否一致 du -sh /data/www/ # 抽查一个文件的 checksum md5sum /data/www/app/config.php /home/www/app/config.php我通常还会跑一次rsync -avz --delete --dry-run看是否还输出内容。如果第二次演练完全没有输出,说明两边已经处于完全一致状态。
4.3 crontab 定时自动同步
手动同步解决偶尔的发布需求没问题,但有些同步需要固定周期跑,比如每 10 分钟同步一次配置更新。下面是我的 crontab 写法:
*/10 * * * * flock -n /tmp/rsync_site.lock -c "rsync -avz --delete --exclude '.git/' --exclude 'runtime/logs/' --exclude 'storage/cache/' /home/www/ user@192.168.1.10:/data/www/ >> /var/log/rsync_site.log 2>&1"这里用flock -n做了一个简单的进程锁,避免上一次同步还没结束下一次任务就启动,导致资源浪费或数据状态错乱。如果同步任务本身执行时间比较久,这个锁就格外重要。日志统一追加到/var/log/rsync_site.log,排错时直接翻这个文件就行。
注意:crontab 里的命令在最小化环境变量下运行,rsync 命令尽量写全路径。如果不确定,用
which rsync查一下,比如/usr/bin/rsync或/usr/local/bin/rsync,否则可能遇到"命令找不到"的问题。
5. rsync 守护进程模式配置实战
5.1 什么时候需要 daemon 模式
SSH 模式虽好,但并不是所有场景都适用。有一次甲方那边只给了一台"备份服务器",不允许直接开 SSH 登录,但允许专门开一个 rsync 端口用于接收数据。这时候 daemon 模式就派上用场了:备份服务器跑 rsync 进程,监听 873 端口;生产机器作为客户端,只通过 rsync 协议写入备份目录,完全不触及 SSH。
另一个典型场景是多个客户端往一台中心服务器推送日志。daemon 模式可以为每个客户端单独配置一个模块,设置不同的只读、可写权限和路径,比管理一堆系统账号轻松。
5.2 配置 rsyncd.conf 并启动
daemon 模式的配置文件是/etc/rsyncd.conf。下面是一个最小可用的配置示例:
uid = root gid = root use chroot = no max connections = 4 pid file = /var/run/rsyncd.pid log file = /var/log/rsync.log [backup] path = /data/backup comment = backup module read only = no list = yes auth users = backupuser secrets file = /etc/rsyncd.secrets重点说几个配置项:uid和gid决定了 rsync 进程以什么身份读写文件,我配置成 root 是为了避免权限问题,但如果有安全洁癖,建议单独建一个系统用户并做好目录权限;use chroot = no是允许用户访问模块路径之外的内容,安全要求严格的场景必须设为yes;auth users配合secrets file做登录认证,密码文件格式是用户名:密码,每行一个。
密码文件的权限一定要收紧:
chmod 600 /etc/rsyncd.secrets启动服务并设置开机自启:
systemctl enable rsync systemctl start rsync如果系统里没有 rsync 服务,也可以手动启动:
rsync --daemon --config=/etc/rsyncd.conf5.3 客户端使用与安全加固
客户端在连接 daemon 模式时,格式是双冒号加模块名,比如:
rsync -avz --password-file=/etc/rsync.pass /home/www/ backupuser@192.168.1.20::backup/--password-file是存放密码的文件,同样需要chmod 600 /etc/rsync.pass。如果不指定,rsync 会交互式询问密码。
安全加固这一块算我的重点提醒。rsync daemon 默认没有加密,所有数据在网络上明文传输,千万不要把它直接暴露在公网。我的做法是:
- 只在内网 IP 上监听,配置文件里写明
address = 192.168.1.20,不监听 0.0.0.0; - 在安全组或防火墙层面,把 873 端口限制为只允许特定生产服务器 IP 访问;
- 开启模块级认证,定期更换密码文件中的密码。
把 daemon 模式暴露在公网端口上,不只是数据泄露风险,还会面临各种口令爆破攻击。这是我绝对不会妥协的一条底线。
6. 性能调优与带宽控制
6.1 大数据量传输的限速与压缩
rsync 跑在线上机器上,一个常见问题是同步任务把带宽打满,影响正常业务请求。解决办法是用--bwlimit限速。比如限制为 5MB/s:
rsync -avz --bwlimit=5120 /data/backup/ user@192.168.1.10:/backup/注意--bwlimit的单位是 KB/s,所以 5MB/s 要写成 5120。我一般先观察正常业务的带宽占用,留出一定余量再设定限速值。如果是半夜跑的备份任务,带宽会比较空闲,这时可以适当调高。
-z压缩参数也有学问。跨机房公网传输时,带宽有限,压缩带来的收益非常明显,尤其同步文本、日志、代码这类可压缩率高的数据。但如果是同步已经压缩过的文件,比如.zip、.jpg、.mp4,压半天也压不了多少,反而白白消耗 CPU。内网千兆环境下,我更倾向于不加-z,开启压缩后 CPU 容易成为瓶颈。
6.2 大量小文件场景优化
rsync 同步大量小文件时容易表现出"慢"的感觉,比如一个目录下有几十万个文件。而且如果每次只改了一个文件,rsync 也要先把整个目录结构扫描一遍。这种场景没有银弹,但有优化思路。
如果你确认源端和目标端的目录结构绝大多数没变,可以使用--size-only跳过 mtime 比较,只按大小判断文件是否变化。这能让同步快很多,但风险是如果大小一样但内容不同,可能会漏同步。另一个思路是用--ignore-existing,只同步目标端不存在的文件,适合"从源端往目标端单向补文件"的场景,比如分发新一批图片素材:
rsync -av --ignore-existing /source/images/ user@host:/target/images/如果文件数量实在太大,以至于 rsync 二次扫描本身都很慢,可以考虑先用tar把目录打包再传输。但这样做会丢掉 rsync 的增量能力和细粒度排除功能,属于"最后手段"。我现在的习惯是:先把目录按业务拆开单个同步,避免一个超大目录绑死所有任务。
7. 常见问题与排查技巧实录
7.1 连接失败:端口不通与认证报错
远程同步最常遇到的错误就是ssh: connect to host xxx port 22: Connection refused,或者 daemon 模式的rsync: connection unexpectedly closed。这类问题排查思路很直接:先确认网络通不通,再确认端口服务在不在。
我一般一步步来:
# 1. 测试网络通不通 ping 192.168.1.10 # 2. 测试远程端口开没开 nc -vz 192.168.1.10 22 nc -vz 192.168.1.10 873 # 3. 查看本机是否能手动 SSH 登录 ssh user@192.168.1.10如果手动 SSH 可以但 rsync 不行,重点检查是不是 SSH 端口被改过。确认后用-e 'ssh -p 端口号'指定。对于 daemon 模式,rsync: connection refused大概率是服务没启动或防火墙拦截,在目标机器上执行ss -lnp | grep 873确认监听状态。
还有一种诡异情况是@ERROR: auth failed on module,这通常是密码文件里的用户名或密码与 rsyncd.secrets 文件不匹配。检查一下 secrets 文件里的格式,注意每行应当是用户名:密码,不能有空格,文件权限也不能大于 600,否则 rsync 会拒绝读取并报认证失败。
7.2 权限与属性异常:属主、权限和符号链接
同步过去后,目标端文件属主变成 root 或者权限变成 644,和源端不一致。这里要区分两种情况:一是 rsync 命令里没有带-a,导致属主、权限、时间戳等都没有被保留;二是在 daemon 模式下,uid和gid配置成了 root,文件自然归 root 所有。
如果是 SSH 模式,把参数改成-avz即可。如果期望保留源端的属主信息,rsync 进程需要有足够权限,普通用户同步的文件通常只能保留为自己的属主,这是系统权限机制决定的。
符号链接也是常见坑。rsync 默认不跟随符号链接,而是直接把符号链接本身复制过去,也就是说同步后的链接仍然指向原路径,而不是链接指向的内容。如果你的本意是同步真实文件,源端就不要用符号链接;如果只是想让链接保持原样,那现在的默认行为反而是对的。我曾经把一个包含大量符号链接的目录同步给同事,结果对方那边全是断链,这就是没搞懂默认行为的结果。
7.3 脚本化踩坑与恢复
定时任务里的 rsync 偶尔会出现"同步一半进程就没了"的情况。查日志发现退出码是 24,这是 rsync 自定义的"部分文件因网络中断或校验失败导致消失"的表示,不一定是致命错误。判断标准是看日志文件里是否有大量file has vanished之类的行。如果是文件在传输过程中被其他进程删除,就会报这个提示。大部分场景下重跑一次就能解决。
我更想提醒的是--delete在脚本里的危险性。有一次我写定时同步时忘了加--exclude,结果目标端一个保存配置文件的目录被整个清掉了,因为源端根本没有这个目录。自从那次之后,我在所有涉及--delete的脚本里都强制要求三步走:
- 先用
--dry-run跑一遍,把输出保存到日志; - 检查日志中的
deleting行是否都在预期范围; - 确认无误后才去掉
--dry-run正式执行。
如果删错了,恢复也比较被动。所以在做任何--delete同步前,建议先给目标端做一个低成本快照备份,比如用rsync -a --link-dest做硬链接增量备份,这样即使误删也有后悔药。
8. 进阶扩展:简易实时同步与增量快照
8.1 inotifywait + rsync 实时同步
rsync 本身是"手动或定时触发"的工具,不是实时监听工具。但可以配合 Linux 的 inotify 机制实现接近实时的同步。思路是用inotifywait监控源目录的文件变动事件,一旦有创建、修改、删除等事件,就触发一次 rsync 增量同步。
我写过一个极简脚本:
#!/bin/bash MONITOR_DIR=/home/www/ TARGET_HOST=user@192.168.1.10 TARGET_PATH=/data/www/ while true; do inotifywait -r -e modify,create,delete,move "$MONITOR_DIR" rsync -avz --delete "$MONITOR_DIR" "$TARGET_HOST:$TARGET_PATH" done这个脚本的缺点是事件触发频繁时,rsync 可能连续跑很多次。简单优化是加入一个去抖逻辑:触发后等 5 秒,如果期间再次触发就重置等待时间,确定安静后再开始同步。准实时方案在生产环境用起来很方便,但监控目录范围过大时,inotify 的时间开销也不小,建议只对真正需要实时同步的子目录做监控。
8.2 硬链接快照思路:让备份带时间点
rsync 的另一个衍生玩法是快照式备份。核心是用--link-dest参数,把上一次备份目录作为参考,生成一个新的完整目录结构,但内容相同的文件通过硬链接复用磁盘空间,实际占用只增加变化部分的大小。
比如:
rsync -avz --delete --link-dest=../backup-20250101 /home/www/ user@host:/data/backup-20250102/这样每天跑一次,就可以在磁盘上保留多天的完整备份,同时磁盘消耗只相当于增量。恢复时直接拷贝对应日期的目录即可,非常直观。这个思路也被很多开源备份工具采用,原理和我手写 rsync 命令完全一致。
我实际用过一段时间的体会是:rsync 这个工具看起来简单,但其实要真正用好,需要理解它的同步模型和每个参数背后到底在干什么。大多数事故都不是 rsync 本身的问题,而是对操作路径、删除逻辑和数据一致性理解不够。所以每次同步前先加--dry-run,再小的操作也值得演练一次,这个习惯真的能救命。后续如果你们有兴趣,我可以再把硬链接快照脚本和实时同步去抖逻辑展开讲讲,都是生产环境直接能落地的方案。