rsync 这东西,属于那种"会用之后再也回不去"的命令。说白了,它就是 Linux 下做文件同步和增量备份的标配工具,能本地拷贝,也能走 SSH 把数据推到远程服务器,还能在传输时压缩、限速、断点续传,最关键是它只同步"有变化的部分",而不是每次吭哧吭哧把整个目录重新搬一遍。正因为这个增量机制,它在备份海量文件、发布部署、维护镜像站这类场景里几乎是无解的存在。这篇东西我打算照着实战的思路来写,不是把 man 手册翻译一遍。我会先把它的核心机制讲透,然后把最常用的命令形式一个个拆开,再讲几个我在真实项目里踩过的坑和性能调优的方法。如果你之前只是机械地复制粘贴 rsync 参数,这篇可能会让你对它的理解上一个台阶。1. rsync 的同步机制:为什么它能做到"只传变化的部分"要真正用好 rsync,先得明白它底层是怎么判断"哪些文件需要同步"的。这个机制说透了其实不复杂,核心就三步:比对、查找差异、高效传输。1.1 默认的快速检查:文件大小 + 修改时间rsync 在没有加任何额外选项时,会先对源端和目标端的文件做一次"快速检查",看的是两个东西:文件大小是否一致,修改时间(mtime)是否一致。如果这两项都相同,rsync 就会认为这个文件没变化,直接跳过,不传输。这就是它默认比较高效的原因——不用真的把每个文件内容都读出来做比对。这个机制在实际中有个非常典型的应用场景:初次全量同步后,后续每次都只扫一遍目录元数据,把新增或改动的文件传过去。比如一个网站目录里有 10 万个文件,第一次 rsync 可能要跑 20 分钟,但第二次如果没有内容变更,它可能几秒钟就完成了,因为它只是把两边的文件列表拿过来比对了一遍,发现全部一样,啥也不用做。注意:这里有个容易栽跟头的坑——有些程序在修改文件内容时,会刻意保留原始修改时间(比如用touch -r去恢复时间戳),或者有些文件系统同步场景下 mtime 会丢失精度。如果只靠大小和时间来判断,就可能出现"文件内容变了但 rsync 没发现"的情况。1.2 真正的增量传输:滚动校验和算法如果文件大小或 mtime 有变化,rsync 会进入"增量传输模式"。这个模式背后的算法是它最核心的技术资产:滚动校验和(Rolling Checksum)。过程大致是:rsync 把目标端的旧文件切成固定大小的小块(默认 700 字节左右,实际按块大小参数而定),对每个块计算一个弱的校验和(Adler-32)和一个强的校验和(MD5)。然后它把源端新文件的数据流也切成同样大小的块,一步步滑动比对这两组校验和。比如新文件第 1 块和第 2 块之间重叠 1 个字节,然后继续滑动,看窗口里有没有哪个块的校验和恰好能匹配上目标端旧文件里的某一块。如果能匹配上,就说明这段数据在目标端已经存在,那 rsync 不会重复传输它,只会在最终的重建指令里告诉接收端"这里直接引用旧的块";如果匹配不上,那这段数据就会被标记为"需要传输的新数据"。整个过程的最终结果是:接收端通过"少量新数据 + 大量对旧块的引用"重新拼出完整的新文件。这个机制的好处是惊人的。比如你有一个 4GB 的数据库备份文件,只是里面某一行数据变了,常规scp会把整个 4GB 重新传一遍,而 rsync 可能只需要传几十 KB 的变化量。不论是在内网还是跨机房同步,这个差距是数量级的。1.3 接收端重建:为什么断点续传天然成立虽然 rsync 不会直接"续传"一个传输到一半的文件,但它天然支持类似的效果。假设你在同步一个大文件时网络中断了,rsync 会在目标端留下一个临时文件(默认以.后缀结尾),或者如果--partial的话会保留已接收的部分内容。下次重新运行时,rsync 会拿这个不完整的临时文件和源文件做增量比对,已经传过去的部分如果恰好能匹配上,就不会再传,接着从断点处继续。如果你在做超大文件同步(比如几十 GB 的磁盘镜像),我强烈建议加上--partial --append-verify这组参数。--append-verify的语义是:假设目标端已有的内容是源文件的前缀,直接把剩余部分追加过去后整体校验一遍。这个组合特别适合那种"大部分数据已经在了,只差最后一段"的场景,能极大缩短重新同步的时间。2. 最常用的 rsync 命令形式:从本地备份到远程同步全覆盖rsync 的语法骨架就一句话:rsync [选项] 源路径 目标路径。源和目标可以在本地,也可以是用户@主机:路径形式的远程路径。搞懂几个高频组合,日常工作基本就够用了。2.1 本地数据迁移与备份最常见的用法当然是把数据从一个目录拷贝到另一个目录,或者挂载盘之间搬数据。我通常会这样写:rsync -avh --progress /data/project/ /backup/project/这里的-a是归档模式,它其实是-rlptgoD的组合:递归拷贝、保留符号链接、保留权限、保留时间戳、保留属主/属组、保留设备文件与特殊文件。-v是输出详细过程,-h是给人看的可读性输出(文件大小显示成 KB/MB/GB)。很多人会问:这不就是cp -a吗?区别在哪?区别在于两点。第一,cp是"无脑复制",只要源目录里有文件,它就不管目标目录里有没有同名文件,直接覆写;rsync 则会先比对,如果文件没变化就跳过。一次大的备份任务,用cp可能要跑 1 小时,用 rsync 第二次以后可能只要 5 分钟。第二,rsync 中断后能接续,cp 不行。如果目标是一个全新挂载的空白磁盘,直接把所有文件带属性搬过去:rsync -avh --delete /data/www/ /mnt/newdisk/www/加上--delete的含义是:以源端为准,把目标端有但源端没有的文件删掉。这在做镜像同步时是刚需,但同时也是一把双刃剑——你必须在确认源路径写对的情况下才敢用,否则后果相当惨烈。后面我专门讲这个坑。2.2 走 SSH 的远程同步远程同步最常用的传输通道是 SSH。这不需要额外启动 rsync 的服务端守护进程,只要目标机器上有 rsync 和 SSH 服务就行。写法极其自然:rsync -avh --progress /data/logs/ user@192.168.1.100:/data/backup/logs/如果你想用不同的 SSH 端口:rsync -avh -e 'ssh -p 2222 -i /path/to/key' /data/ user@host:/remote/path/-e选项可以指定使用任何远程 shell 程序。这里写成ssh -p 2222后,rsync 底层就会调用这个命令去建立连接。不只是端口,如果你要通过跳板机(ProxyJump)、指定密钥、甚至走 SSH 隧道,都可以写在-e里面。我个人的习惯是给运维机器配置好 SSH 密钥后,把常用的同步任务固定成脚本或者 alias。比如说日志归档任务,一条命令搞定,无人值守也不需要交互输入密码。2.3 拉取远程文件到本地很多教程只教你推(push),不太强调拉(pull)的场景。但运维时"从服务器把数据拉回来"的频率一点都不低。写法也就是把源和目标调换位置:rsync -avh user@192.168.1.100:/var/log/nginx/ ./nginx-logs/交互式操作时排在后面那个路径是你当前所在环境的路径。推和拉在逻辑上完全对称,唯一需要注意的是:如果本地目录不是以/结尾,rsync 会把远程目录的原名套一层;如果以/结尾,则代表目录内的内容直接铺进目标目录。2.4 两个最容易搞错的路径语义# 写法A:源路径带斜杠 rsync -avh /src/ /dst/ # 写法B:源路径不带斜杠 rsync -avh /src /dst/这两者的区别经常让新手抓狂。带斜杠时,rsync 同步的是目录"里面的内容",即/src/a.txt会变成/dst/a.txt;不带斜杠时,rsync 连目录本身一起搬,即/src/a.txt会变成/dst/src/a.txt。这个语义在目标端也有对应规则。目标路径带斜杠和不带斜杠在大多数情况下结果一样,但如果目标目录不存在,不带斜杠时 rsync 会创建同名目录,带斜杠时则以最后的目录名为准。我的建议是:写脚本时源路径结尾一律不带斜杠,目标路径结尾一律带斜杠,这样语义最不容易产生歧义。这看起来是个风格问题,但真的能省掉很多误解和事故。3. 构建增量备份体系:快照、排除与并发控制rsync 真正能封神的地方,不在于单条命令,而在于你能用它搭一套自动化的增量备份体系。我在这里分享一个我自己在项目中实际使用过多次的备份架构,它没有依赖任何复杂的备份软件,就用 rsync 加 shell 脚本和 cron 就能形成完整的链条。3.1 利用硬链接实现"伪快照"式版本管理如果你每天跑一次全量备份并把所有文件原样复制一份,磁盘再大也会扛不住。标准的解法是rsync + 硬链接做每日快照。思路是:第一天:正常全量备份到/backup/day1/第二天:备份到/backup/day2/,但开启--link-dest=/backup/day1--link-dest是一个非常精巧的参数:当 rsync 发现目标文件与指定的参考目录中对应文件内容一致时,它不会真正复制文件,而是创建一个指向参考目录里那个文件的硬链接。硬链接不占额外数据空间,但你在/backup/day2/里看到的是一个完整的目录结构。这样,只有真正变化的数据才会占用新空间,没变化的文件在磁盘上只占一个目录项。一个典型的执行脚本长这样:#!/bin/bash # 每日快照脚本 BACKUP_BASE=/backup SOURCE=/data/project TODAY=$(date +%Y-%m-%d) YESTERDAY=$(date -d "yesterday" +%Y-%m-%d) mkdir -p $BACKUP_BASE/$TODAY if [ -d "$BACKUP_BASE/$YESTERDAY" ]; then rsync -avh --delete \ --link-dest=$BACKUP_BASE/$YESTERDAY \ $SOURCE $BACKUP_BASE/$TODAY/ else rsync -avh $SOURCE $BACKUP_BASE/$TODAY/ fi这样跑一年下来,365 天的目录都在,但整体的磁盘占用大约等于"全量数据 + 一年的变更量",而不是 365 份完整拷贝。恢复数据时任意挑选某一天目录,完整性和可读性都非常直观。这个模式在生产环境里是被大量验证过的经典方案。3.2 排除规则:备份不是什么都往里面塞一个常见的反面教材是把整个家目录直接 rsync 到备份盘,结果垃圾文件、缓存、.git目录全都被同步过去了,又慢又占空间。正确做法是用--exclude或专门的--exclude-from文件来定义排除规则。rsync -avh --delete --exclude 'node_modules' --exclude '.git' --exclude '*.tmp' /data/project/ /backup/project/更干净的方式是把规则写进一个文件:cat > /etc/rsync-exclude.txt << 'EOF' *.log .cache/ node_modules/ .DS_Store EOF rsync -avh --delete --exclude-from=/etc/rsync-exclude.txt /data/project/ /backup/project/值得注意的坑:--exclude规则的匹配是基于路径的"根目录相对路径"的。也就是说,node_modules会匹配任何层级下名为 node_modules 的文件或目录;但如果你写的是/node_modules,那就只会匹配根目录下的。权限粒度很细,建议先在测试目录里验证一遍你的规则再跑真实任务。3.3 限速与并发:别让备份把业务带宽吃光在业务服务器上跑备份任务,最怕的就是备份流量把出口带宽打满,导致线上服务变卡。rsync 提供了--bwlimit参数,单位是 KB/s:rsync -avh --bwlimit=5000 /data/ user@backup-server:/backup/上面这个例子把传输速率限制在 5MB/s 以内。适合白天业务高峰期做流量控制,深夜任务跑全量时放开速度。我通常会在脚本里判断当前时间,或者用 cron 分别安排高峰和低谷任务。大文件传输和高并发场景下,你可以考虑用--rsync-path结合并行分片,或者直接改用rsync配合xargs实现多目录并发同步。简单做法是:把一个大目录按子目录拆开,多个 rsync 进程各自负责一部分,互不干扰。不过这里有一个前提:如果同一份文件被两个 rsync 进程同时写入同一个目标路径,会出现不可预知的相互覆盖风险。所以并发前务必规划好"各自写各自的目标目录"。4. 真实踩坑记录与深度排查链路这一节我要写点"文档里查不到"的东西。这些坑我几乎都亲手踩过,每一个都花了不少时间排查,写出来能帮你少走很多弯路。4.1 那些年我们手滑删过的数据:关于 --delete 的惨痛记忆rsync 的--delete选项是我见过的最容易造成生产事故的参数,没有之一。它的逻辑很简单:同步完成后,目标端删除源端不存在的文件。但"目标端不存在"的判断基准是"目标目录"和"源目录"在当前命令执行时的状态。我第一次在脚本里加--delete时犯的错误是:把源路径末尾的斜杠漏了。写成:rsync -avh --delete /data/project /backup/project/表面看没什么问题,但如果没有斜杠,源目录在 rsync 的语义里是/data/project这个"目录本身",而目标是/backup/project/目录内部。在一些严格语义下,这会导致目标端的/backup/project/project/子结构中的多余内容被删除。虽然最终数据不一定丢,但如果你本意是让两个目录完全镜像,斜杠的差异会直接导致同步方向的错乱。更要命的情况是:源路径写了个不存在的目录或变量为空。rsync -avh --delete "$SOURCE_DIR/" "$BACKUP_DIR/"如果$SOURCE_DIR因为环境问题没有正常赋值,变成了空字符串,命令实际执行的是rsync -avh --delete "/" "$BACKUP_DIR/"。注意,""加上末尾斜杠会被解析成根目录!这意味着 rsync 会拿根目录去比对备份目录,备份目录里任何不在根目录中的内容都会被删除。结果就是:备份目录被清空。我后来给自己定下的规矩是两条铁律:生产环境的 rsync 脚本里,源路径变量必须经过set -u检查,且用[[ -d "$SOURCE_DIR" ]]验证目录存在;凡是--delete的命令,先在目标端开一个空目录做一次 dry-run 测试,输出结果确认无误再动真格的。4.2 文件时间戳与内容变更的罗生门前面说过,默认快速检查依赖大小和时间戳。但在某些场景下,这个机制会失灵。最典型的是:用 CVS/SVN/Git 等版本管理工具 checkout 代码后,所有文件的时间戳会被设置为当前时间,但文件内容可能并没有变化。此时 rsync 对比会发现源端和目标端的文件 mtime 不一致,于是老老实实把每个文件都重新传一遍——虽然内容完全一样。这不是 bug,机制设计如此。但如果你面对的是一个几十 GB 的代码库,一次全量同步就会从"只要几秒"变成"要跑半小时"。解法是使用--itemize-changes配合--checksum(-c)选项,强制 rsync 用内容校验和代替时间戳判断。代价是源端和目标端都要读一遍所有文件内容来计算校验和,虽然省了带宽,但增加了磁盘 IO 和 CPU 开销。取舍建议是:文件数量巨大但单文件较小的场景下用-c反而划算;大文件多且时间戳可信时,默认策略更高效。4.3 增量同步后文件还是不对:字符集与软链接我还遇到过一个问题:从 Windows 机器通过某种挂载方式同步含中文文件名的目录过来,明明 rsync 显示同步成功,但目标端的中文文件名乱码,或者某些文件始终"同步不过去"。这通常不是 rsync 本身的问题,而是源文件系统或者 SSH 会话环境的字符集问题。rsync 传输的是字节流,不会主动对文件名做转码。如果源端是 GBK 编码的文件名,目标文件系统是 UTF-8,那目标端看到的就是无法正确解码的字节序列。解决办法是在挂载层面统一编码,或者用--iconv参数做字符集转换:rsync -avh --iconv=UTF-8,GBK /source/ user@host:/target/--iconv的用法是"源端字符集,目标端字符集",这个参数虽然看起来冷门,但在跨系统同步时却是救命稻草。不过需要注意:转码规则一旦用错,文件名反而会被搞乱,建议先在临时目录测试。软链接(symlink)也是容易出错的地方。默认-a模式会保留软链接本身,即同步过去后还是链接,而不是链接指向的文件内容。这在多数情况下是正确行为,但有些新手会误以为"链接的目标文件也一起同步了",结果在目标端看到软链接指向一个不存在的绝对路径,于是报错。如果你想跟随软链接同步其指向的真实内容,可以用-L(即--copy-links),但小心别把它和-a混用成-aL,那样会把所有链接都转换成真实文件,目标端目录结构会发生改变。4.4 跑批任务常见报错的排查链路下面列出几个我在使用 rsync 过程中遇到的高频报错,以及对应的排查思路:报错现象直接原因排查与解决思路rsync: connection unexpectedly closed网络不稳定,或 SSH 连接被中间设备断开先单独测试ssh host是否能连通;大文件同步时加--timeout参数,并开启--partial做断点续传rsync: [sender] write error: Broken pipe目标端磁盘空间不足或写入失败检查目标端df -h;加--ignore-errors只会掩盖问题,正确做法是腾出空间或换目录rsync error: some files/attrs were not transferred有文件因权限或特殊属性未能同步用-v输出查看具体哪些文件失败;检查源端文件的属组权限,必要时用sudo执行 rsyncrsync: mkdir ... failed: Permission denied目标目录没有创建权限检查目标目录属主;或者先手动 mkdir,再让 rsync 只同步内容@ERROR: chroot failed用了 rsync daemon 模式,但路径配置错误检查/etc/rsyncd.conf里的path和use chroot配置;不是 SSH 模式的问题排查这类问题有个通用心法:先缩小范围,再做变量隔离。先用一条最小命令(比如只看一个文件)复现,然后逐步增加参数,确认是哪个选项引发的问题。永远不要在一个 10 万文件的完整同步任务上做实验。5. 性能调优与运维脚本模板同步上千 GB 数据时,rsync 的默认参数往往不是最优的。这一节针对大文件、海量小文件、跨公网三种典型场景,分别给出我实测过的调优思路。5.1 海量小文件场景:让 rsync 别再逐文件等待当你同步一个包含 50 万个图片文件的目录时,默认的 rsync 是一个文件一个文件地处理,每个文件都要经过一次文件系统调用和网络交互,吞吐量惨不忍睹。这时候有几个办法:用--info=progress2代替老的--progress:progress2可以显示整体进度百分比,而不是每个文件的进度,在文件数量极大时输出日志更友好。调大-T临时目录所在磁盘的 IO 性能:rsync 在接收端写入临时文件后做 rename。如果临时目录所在磁盘和最终目标目录在同一个卷,性能通常没问题;如果跨卷,会多一次复制。确保共享同一文件系统能减少一次写放大。用--no-whole-file:rsync 默认在本地传输或者远程传输时,对大文件会使用"整个文件直接写"的策略。但对小文件,--no-whole-file会丢开这个限制,强制走增量块比对逻辑,有时反而更快。具体要看你传输的通道和文件大小分布,没有绝对标准。真正的大招:用 tar 管道。如果源端和目标端都是 Linux 且网络质量好,可以构造管道让数据流式打包传输:tar -C /data/project -cf - . | ssh user@host "tar -C /backup/project -xf -"这个方案的优点是减少每个文件的握手开销,速度会快很多;缺点是丧失了 rsync 的增量能力,只适合"首次全量灌数据",后续增量再回到 rsync。5.2 公网传输:压缩与断点续传的取舍跨公网同步最金贵的是带宽。如果网络带宽有限,建议开启压缩选项-z,源端和目标端会各自消耗一些 CPU,但传输字节数会大幅下降。对于文本文件、日志、JSON 配置,压缩率极其可观;但对已经压缩过的.jpg、.zip、.mp4文件,压缩几乎没效果,反而白白消耗 CPU,所以更要看文件类型决定。跨公网做大文件传输,我会组合这些参数:rsync -avz --partial --bwlimit=5000 --timeout=60 \ --progress user@remote:/data/bigfile.iso /local/dest/--partial的作用是:如果传输中断,保留目标端已经接收的部分,下次续传时基于这些已存在数据做增量处理。--timeout=60防止连接假死时任务一直挂着不退出。--bwlimit控制流量,防止影响其他业务。对于几十 GB 的大文件,这个组合能让中断重跑的成本极低。5.3 全套运维脚本模板:一键实现带日志和告警的同步最后分享我最常用的一套脚本模板,可以直接改改变量就用。支持:增量备份、日志轮转、失败邮件或 Webhook 通知(可以替换为任意通知渠道)。#!/bin/bash # backup-with-rsync.sh set -euo pipefail SOURCE_DIR="/data/project/" BACKUP_BASE="/backup" LOG_DIR="/var/log/rsync-backup" TIMESTAMP=$(date +%Y%m%d-%H%M%S) LOGFILE="$LOG_DIR/backup-$TIMESTAMP.log" DEST_DIR="$BACKUP_BASE/current" mkdir -p "$LOG_DIR" "$BACKUP_BASE" # 执行 rsync 同步 rsync -avh --delete \ --exclude-from=/etc/rsync-exclude.txt \ --link-dest="$BACKUP_BASE/current" \ "$SOURCE_DIR" "$DEST_DIR/" 2>&1 | tee "$LOGFILE" # 同步完成后,创建当日时间戳快照目录的硬链接引用 if [ $? -eq 0 ]; then snap_dir="$BACKUP_BASE/snapshot-$TIMESTAMP" mkdir -p "$snap_dir" cp -al "$DEST_DIR"/. "$snap_dir"/ find "$BACKUP_BASE" -maxdepth 1 -type d -name 'snapshot-*' -mtime +30 -exec rm -rf {} \; else echo "RSYNC FAILED, log: $LOGFILE" | mail -s "backup failed" ops@example.com exit 1 fi这个脚本里cp -al是对当前备份做一个硬链接快照,几乎不消耗额外磁盘,但它能生成一个带日期的版本目录。配合find ... -mtime +30 -exec rm -rf,可以自动清理 30 天以前的快照目录。里面--link-dest="$BACKUP_BASE/current"是在每次同步时参考上一次当前版,实现增量硬链接。邮件通知的部分,你完全可以替换成自己团队的告警机器人或监控接口。关键点是:rsync 脚本一定要有失败退出码和通知机制,否则备份静默失败比不备份更可怕。6. 平时看不到的 Rsync 使用细节与判断技巧前面这些内容已经足够让你解决日常工作中 90% 的同步需求。但如果你想让自己的 rsync 水平更上一层楼,这里还有几个"平时文档里不常展开但实测很有用"的细节值得掌握。6.1 dry-run 模式:高危操作前必做的安全网不管你的 rsync 技能多熟练,只要是带--delete或者在关键目录之间移动数据,先干跑一遍永远是最稳妥的做法:rsync -avh --delete --dry-run /data/project/ /backup/project/--dry-run会让 rsync 只做对比和模拟输出,不会真正写任何文件。你可以从输出内容中看到哪些文件会被传输、哪些会被删除,然后决定是否真的要执行。在我自己的运维习惯里,涉及线上数据的删除或移动操作,必须先干跑,再执行,这个习惯帮我拦住过好几次手滑操作。6.2 利用 rsync 检查两端一致性有时候同步跑完了,你需要验证两端是否真的完全一致。rsync 的-c参数在这里可以当"校验工具"用,但它只是判断"文件是否相同",不是逐字节比对哈希。如果你需要可靠的一致性校验,可以用--checksum跑一次同步,观察是否还有需要传输的文件;注意这会让两端都读取所有文件内容,比较耗时,但对关键数据做定期校验是划算的。另一个轻量技巧是让 rsync 输出变更列表,然后抓取统计信息:rsync -avh --stats --dry-run /data/ /backup/输出末尾的统计段落会告诉你"文件传输数量、创建的字节数、相比总字节数的比例"等信息,非常直观。我经常用这个方式在做完同步后确认"两边应该已经一致"。6.3 用 rsync 模拟"移动"操作rsync 没有内置 move 选项,但你可以先同步再删除,或者直接用--remove-source-files。这个参数的行为是:成功传输完一个文件后,删除源端的该文件。它经常被用在数据迁移场景:比如把文件从一台老服务器搬到新服务器,搬完自动清理源端。rsync -avh --remove-source-files /data/old/ user@new-server:/data/new/需要注意的是,--remove-source-files只会删除普通文件,不会删除目录。而且它和--delete正好凑成一对:一个是目标端向源端看齐,一个是源端向目标端移动。组合使用时务必清楚自己到底想干什么。6.4 输出信息识别:从-v输出判断 rsync 在做什么很多人看rsync -v的刷屏输出很不耐烦,直接 2>/dev/null 丢掉日志。但如果你愿意花一分钟理解它的输出结构,排查问题会快得多。每次同步的文件行通常以几个特殊前缀字符开头:>:文件需要传输到接收端<:文件从接收端拉取到本地(少见)c:由于校验和不一致需要重建文件d:目录本身被处理L:符号链接被创建或修改f:普通文件后面跟的字段是文件大小、时间、权限变化等。我建议保留日志,并定期用grep抓取非>和d前缀的非预期行,这样很容易发现哪些文件的属性异常、哪些权限变动、哪些符号链接出问题。这是我个人维护备份系统时最喜欢用的巡检手段。6.5 一个反向思维:rsync 慢的时候,不一定是 rsync 的锅如果同步速度远低于你的预期,别急着在 rsync 参数上使劲。先确认瓶颈在哪一层:网络层:用iperf3或scp一个大文件测一下两端实际带宽,确认是不是链路本身的问题;磁盘层:用iotop或iostat看源端和目标端的磁盘利用率是否打满,很多备份盘是机械盘,随机读写速度只有几十 MB/s,再快的网络也无力回天;文件系统层:如果你在用 NFS、CIFS 之类的网络文件系统做中转,rsync 可能并不适合在这种叠加层上运行,瓶颈会受协议开销影响非常严重。我见过最典型的"rsync 很慢"问题,其实是因为目标端是一块 USB 外接盘或者网络 NAS 挂载盘,单个大文件顺序写还行,一遇到海量小文件的随机写就完全跑不起来。解决办法是换 SSD 或本地盘做暂存,再定期搬运。在这些问题都排查完之后,还觉得 rsync 效率不满意,再去考虑换更底层的工具(比如针对 ZFS 的zfs send/receive,或者针对对象存储的rclone)。没有什么工具是万能的,但 rsync 在"类 Unix 系统之间同步普通文件"这个领域,目前依然没有谁能替代它。
rsync 这东西,属于那种"会用之后再也回不去"的命令。说白了,它就是 Linux 下做文件同步和增量备份的标配工具,能本地拷贝,也能走 SSH 把数据推到远程服务器,还能在传输时压缩、限速、断点续传,最关键是它只同步"有变化的部分",而不是每次吭哧吭哧把整个目录重新搬一遍。正因为这个增量机制,它在备份海量文件、发布部署、维护镜像站这类场景里几乎是无解的存在。这篇东西我打算照着实战的思路来写,不是把 man 手册翻译一遍。我会先把它的核心机制讲透,然后把最常用的命令形式一个个拆开,再讲几个我在真实项目里踩过的坑和性能调优的方法。如果你之前只是机械地复制粘贴 rsync 参数,这篇可能会让你对它的理解上一个台阶。1. rsync 的同步机制:为什么它能做到"只传变化的部分"要真正用好 rsync,先得明白它底层是怎么判断"哪些文件需要同步"的。这个机制说透了其实不复杂,核心就三步:比对、查找差异、高效传输。1.1 默认的快速检查:文件大小 + 修改时间rsync 在没有加任何额外选项时,会先对源端和目标端的文件做一次"快速检查",看的是两个东西:文件大小是否一致,修改时间(mtime)是否一致。如果这两项都相同,rsync 就会认为这个文件没变化,直接跳过,不传输。这就是它默认比较高效的原因——不用真的把每个文件内容都读出来做比对。这个机制在实际中有个非常典型的应用场景:初次全量同步后,后续每次都只扫一遍目录元数据,把新增或改动的文件传过去。比如一个网站目录里有 10 万个文件,第一次 rsync 可能要跑 20 分钟,但第二次如果没有内容变更,它可能几秒钟就完成了,因为它只是把两边的文件列表拿过来比对了一遍,发现全部一样,啥也不用做。注意:这里有个容易栽跟头的坑——有些程序在修改文件内容时,会刻意保留原始修改时间(比如用touch -r去恢复时间戳),或者有些文件系统同步场景下 mtime 会丢失精度。如果只靠大小和时间来判断,就可能出现"文件内容变了但 rsync 没发现"的情况。1.2 真正的增量传输:滚动校验和算法如果文件大小或 mtime 有变化,rsync 会进入"增量传输模式"。这个模式背后的算法是它最核心的技术资产:滚动校验和(Rolling Checksum)。过程大致是:rsync 把目标端的旧文件切成固定大小的小块(默认 700 字节左右,实际按块大小参数而定),对每个块计算一个弱的校验和(Adler-32)和一个强的校验和(MD5)。然后它把源端新文件的数据流也切成同样大小的块,一步步滑动比对这两组校验和。比如新文件第 1 块和第 2 块之间重叠 1 个字节,然后继续滑动,看窗口里有没有哪个块的校验和恰好能匹配上目标端旧文件里的某一块。如果能匹配上,就说明这段数据在目标端已经存在,那 rsync 不会重复传输它,只会在最终的重建指令里告诉接收端"这里直接引用旧的块";如果匹配不上,那这段数据就会被标记为"需要传输的新数据"。整个过程的最终结果是:接收端通过"少量新数据 + 大量对旧块的引用"重新拼出完整的新文件。这个机制的好处是惊人的。比如你有一个 4GB 的数据库备份文件,只是里面某一行数据变了,常规scp会把整个 4GB 重新传一遍,而 rsync 可能只需要传几十 KB 的变化量。不论是在内网还是跨机房同步,这个差距是数量级的。1.3 接收端重建:为什么断点续传天然成立虽然 rsync 不会直接"续传"一个传输到一半的文件,但它天然支持类似的效果。假设你在同步一个大文件时网络中断了,rsync 会在目标端留下一个临时文件(默认以.后缀结尾),或者如果--partial的话会保留已接收的部分内容。下次重新运行时,rsync 会拿这个不完整的临时文件和源文件做增量比对,已经传过去的部分如果恰好能匹配上,就不会再传,接着从断点处继续。如果你在做超大文件同步(比如几十 GB 的磁盘镜像),我强烈建议加上--partial --append-verify这组参数。--append-verify的语义是:假设目标端已有的内容是源文件的前缀,直接把剩余部分追加过去后整体校验一遍。这个组合特别适合那种"大部分数据已经在了,只差最后一段"的场景,能极大缩短重新同步的时间。2. 最常用的 rsync 命令形式:从本地备份到远程同步全覆盖rsync 的语法骨架就一句话:rsync [选项] 源路径 目标路径。源和目标可以在本地,也可以是用户@主机:路径形式的远程路径。搞懂几个高频组合,日常工作基本就够用了。2.1 本地数据迁移与备份最常见的用法当然是把数据从一个目录拷贝到另一个目录,或者挂载盘之间搬数据。我通常会这样写:rsync -avh --progress /data/project/ /backup/project/这里的-a是归档模式,它其实是-rlptgoD的组合:递归拷贝、保留符号链接、保留权限、保留时间戳、保留属主/属组、保留设备文件与特殊文件。-v是输出详细过程,-h是给人看的可读性输出(文件大小显示成 KB/MB/GB)。很多人会问:这不就是cp -a吗?区别在哪?区别在于两点。第一,cp是"无脑复制",只要源目录里有文件,它就不管目标目录里有没有同名文件,直接覆写;rsync 则会先比对,如果文件没变化就跳过。一次大的备份任务,用cp可能要跑 1 小时,用 rsync 第二次以后可能只要 5 分钟。第二,rsync 中断后能接续,cp 不行。如果目标是一个全新挂载的空白磁盘,直接把所有文件带属性搬过去:rsync -avh --delete /data/www/ /mnt/newdisk/www/加上--delete的含义是:以源端为准,把目标端有但源端没有的文件删掉。这在做镜像同步时是刚需,但同时也是一把双刃剑——你必须在确认源路径写对的情况下才敢用,否则后果相当惨烈。后面我专门讲这个坑。2.2 走 SSH 的远程同步远程同步最常用的传输通道是 SSH。这不需要额外启动 rsync 的服务端守护进程,只要目标机器上有 rsync 和 SSH 服务就行。写法极其自然:rsync -avh --progress /data/logs/ user@192.168.1.100:/data/backup/logs/如果你想用不同的 SSH 端口:rsync -avh -e 'ssh -p 2222 -i /path/to/key' /data/ user@host:/remote/path/-e选项可以指定使用任何远程 shell 程序。这里写成ssh -p 2222后,rsync 底层就会调用这个命令去建立连接。不只是端口,如果你要通过跳板机(ProxyJump)、指定密钥、甚至走 SSH 隧道,都可以写在-e里面。我个人的习惯是给运维机器配置好 SSH 密钥后,把常用的同步任务固定成脚本或者 alias。比如说日志归档任务,一条命令搞定,无人值守也不需要交互输入密码。2.3 拉取远程文件到本地很多教程只教你推(push),不太强调拉(pull)的场景。但运维时"从服务器把数据拉回来"的频率一点都不低。写法也就是把源和目标调换位置:rsync -avh user@192.168.1.100:/var/log/nginx/ ./nginx-logs/交互式操作时排在后面那个路径是你当前所在环境的路径。推和拉在逻辑上完全对称,唯一需要注意的是:如果本地目录不是以/结尾,rsync 会把远程目录的原名套一层;如果以/结尾,则代表目录内的内容直接铺进目标目录。2.4 两个最容易搞错的路径语义# 写法A:源路径带斜杠 rsync -avh /src/ /dst/ # 写法B:源路径不带斜杠 rsync -avh /src /dst/这两者的区别经常让新手抓狂。带斜杠时,rsync 同步的是目录"里面的内容",即/src/a.txt会变成/dst/a.txt;不带斜杠时,rsync 连目录本身一起搬,即/src/a.txt会变成/dst/src/a.txt。这个语义在目标端也有对应规则。目标路径带斜杠和不带斜杠在大多数情况下结果一样,但如果目标目录不存在,不带斜杠时 rsync 会创建同名目录,带斜杠时则以最后的目录名为准。我的建议是:写脚本时源路径结尾一律不带斜杠,目标路径结尾一律带斜杠,这样语义最不容易产生歧义。这看起来是个风格问题,但真的能省掉很多误解和事故。3. 构建增量备份体系:快照、排除与并发控制rsync 真正能封神的地方,不在于单条命令,而在于你能用它搭一套自动化的增量备份体系。我在这里分享一个我自己在项目中实际使用过多次的备份架构,它没有依赖任何复杂的备份软件,就用 rsync 加 shell 脚本和 cron 就能形成完整的链条。3.1 利用硬链接实现"伪快照"式版本管理如果你每天跑一次全量备份并把所有文件原样复制一份,磁盘再大也会扛不住。标准的解法是rsync + 硬链接做每日快照。思路是:第一天:正常全量备份到/backup/day1/第二天:备份到/backup/day2/,但开启--link-dest=/backup/day1--link-dest是一个非常精巧的参数:当 rsync 发现目标文件与指定的参考目录中对应文件内容一致时,它不会真正复制文件,而是创建一个指向参考目录里那个文件的硬链接。硬链接不占额外数据空间,但你在/backup/day2/里看到的是一个完整的目录结构。这样,只有真正变化的数据才会占用新空间,没变化的文件在磁盘上只占一个目录项。一个典型的执行脚本长这样:#!/bin/bash # 每日快照脚本 BACKUP_BASE=/backup SOURCE=/data/project TODAY=$(date +%Y-%m-%d) YESTERDAY=$(date -d "yesterday" +%Y-%m-%d) mkdir -p $BACKUP_BASE/$TODAY if [ -d "$BACKUP_BASE/$YESTERDAY" ]; then rsync -avh --delete \ --link-dest=$BACKUP_BASE/$YESTERDAY \ $SOURCE $BACKUP_BASE/$TODAY/ else rsync -avh $SOURCE $BACKUP_BASE/$TODAY/ fi这样跑一年下来,365 天的目录都在,但整体的磁盘占用大约等于"全量数据 + 一年的变更量",而不是 365 份完整拷贝。恢复数据时任意挑选某一天目录,完整性和可读性都非常直观。这个模式在生产环境里是被大量验证过的经典方案。3.2 排除规则:备份不是什么都往里面塞一个常见的反面教材是把整个家目录直接 rsync 到备份盘,结果垃圾文件、缓存、.git目录全都被同步过去了,又慢又占空间。正确做法是用--exclude或专门的--exclude-from文件来定义排除规则。rsync -avh --delete --exclude 'node_modules' --exclude '.git' --exclude '*.tmp' /data/project/ /backup/project/更干净的方式是把规则写进一个文件:cat > /etc/rsync-exclude.txt << 'EOF' *.log .cache/ node_modules/ .DS_Store EOF rsync -avh --delete --exclude-from=/etc/rsync-exclude.txt /data/project/ /backup/project/值得注意的坑:--exclude规则的匹配是基于路径的"根目录相对路径"的。也就是说,node_modules会匹配任何层级下名为 node_modules 的文件或目录;但如果你写的是/node_modules,那就只会匹配根目录下的。权限粒度很细,建议先在测试目录里验证一遍你的规则再跑真实任务。3.3 限速与并发:别让备份把业务带宽吃光在业务服务器上跑备份任务,最怕的就是备份流量把出口带宽打满,导致线上服务变卡。rsync 提供了--bwlimit参数,单位是 KB/s:rsync -avh --bwlimit=5000 /data/ user@backup-server:/backup/上面这个例子把传输速率限制在 5MB/s 以内。适合白天业务高峰期做流量控制,深夜任务跑全量时放开速度。我通常会在脚本里判断当前时间,或者用 cron 分别安排高峰和低谷任务。大文件传输和高并发场景下,你可以考虑用--rsync-path结合并行分片,或者直接改用rsync配合xargs实现多目录并发同步。简单做法是:把一个大目录按子目录拆开,多个 rsync 进程各自负责一部分,互不干扰。不过这里有一个前提:如果同一份文件被两个 rsync 进程同时写入同一个目标路径,会出现不可预知的相互覆盖风险。所以并发前务必规划好"各自写各自的目标目录"。4. 真实踩坑记录与深度排查链路这一节我要写点"文档里查不到"的东西。这些坑我几乎都亲手踩过,每一个都花了不少时间排查,写出来能帮你少走很多弯路。4.1 那些年我们手滑删过的数据:关于 --delete 的惨痛记忆rsync 的--delete选项是我见过的最容易造成生产事故的参数,没有之一。它的逻辑很简单:同步完成后,目标端删除源端不存在的文件。但"目标端不存在"的判断基准是"目标目录"和"源目录"在当前命令执行时的状态。我第一次在脚本里加--delete时犯的错误是:把源路径末尾的斜杠漏了。写成:rsync -avh --delete /data/project /backup/project/表面看没什么问题,但如果没有斜杠,源目录在 rsync 的语义里是/data/project这个"目录本身",而目标是/backup/project/目录内部。在一些严格语义下,这会导致目标端的/backup/project/project/子结构中的多余内容被删除。虽然最终数据不一定丢,但如果你本意是让两个目录完全镜像,斜杠的差异会直接导致同步方向的错乱。更要命的情况是:源路径写了个不存在的目录或变量为空。rsync -avh --delete "$SOURCE_DIR/" "$BACKUP_DIR/"如果$SOURCE_DIR因为环境问题没有正常赋值,变成了空字符串,命令实际执行的是rsync -avh --delete "/" "$BACKUP_DIR/"。注意,""加上末尾斜杠会被解析成根目录!这意味着 rsync 会拿根目录去比对备份目录,备份目录里任何不在根目录中的内容都会被删除。结果就是:备份目录被清空。我后来给自己定下的规矩是两条铁律:生产环境的 rsync 脚本里,源路径变量必须经过set -u检查,且用[[ -d "$SOURCE_DIR" ]]验证目录存在;凡是--delete的命令,先在目标端开一个空目录做一次 dry-run 测试,输出结果确认无误再动真格的。4.2 文件时间戳与内容变更的罗生门前面说过,默认快速检查依赖大小和时间戳。但在某些场景下,这个机制会失灵。最典型的是:用 CVS/SVN/Git 等版本管理工具 checkout 代码后,所有文件的时间戳会被设置为当前时间,但文件内容可能并没有变化。此时 rsync 对比会发现源端和目标端的文件 mtime 不一致,于是老老实实把每个文件都重新传一遍——虽然内容完全一样。这不是 bug,机制设计如此。但如果你面对的是一个几十 GB 的代码库,一次全量同步就会从"只要几秒"变成"要跑半小时"。解法是使用--itemize-changes配合--checksum(-c)选项,强制 rsync 用内容校验和代替时间戳判断。代价是源端和目标端都要读一遍所有文件内容来计算校验和,虽然省了带宽,但增加了磁盘 IO 和 CPU 开销。取舍建议是:文件数量巨大但单文件较小的场景下用-c反而划算;大文件多且时间戳可信时,默认策略更高效。4.3 增量同步后文件还是不对:字符集与软链接我还遇到过一个问题:从 Windows 机器通过某种挂载方式同步含中文文件名的目录过来,明明 rsync 显示同步成功,但目标端的中文文件名乱码,或者某些文件始终"同步不过去"。这通常不是 rsync 本身的问题,而是源文件系统或者 SSH 会话环境的字符集问题。rsync 传输的是字节流,不会主动对文件名做转码。如果源端是 GBK 编码的文件名,目标文件系统是 UTF-8,那目标端看到的就是无法正确解码的字节序列。解决办法是在挂载层面统一编码,或者用--iconv参数做字符集转换:rsync -avh --iconv=UTF-8,GBK /source/ user@host:/target/--iconv的用法是"源端字符集,目标端字符集",这个参数虽然看起来冷门,但在跨系统同步时却是救命稻草。不过需要注意:转码规则一旦用错,文件名反而会被搞乱,建议先在临时目录测试。软链接(symlink)也是容易出错的地方。默认-a模式会保留软链接本身,即同步过去后还是链接,而不是链接指向的文件内容。这在多数情况下是正确行为,但有些新手会误以为"链接的目标文件也一起同步了",结果在目标端看到软链接指向一个不存在的绝对路径,于是报错。如果你想跟随软链接同步其指向的真实内容,可以用-L(即--copy-links),但小心别把它和-a混用成-aL,那样会把所有链接都转换成真实文件,目标端目录结构会发生改变。4.4 跑批任务常见报错的排查链路下面列出几个我在使用 rsync 过程中遇到的高频报错,以及对应的排查思路:报错现象直接原因排查与解决思路rsync: connection unexpectedly closed网络不稳定,或 SSH 连接被中间设备断开先单独测试ssh host是否能连通;大文件同步时加--timeout参数,并开启--partial做断点续传rsync: [sender] write error: Broken pipe目标端磁盘空间不足或写入失败检查目标端df -h;加--ignore-errors只会掩盖问题,正确做法是腾出空间或换目录rsync error: some files/attrs were not transferred有文件因权限或特殊属性未能同步用-v输出查看具体哪些文件失败;检查源端文件的属组权限,必要时用sudo执行 rsyncrsync: mkdir ... failed: Permission denied目标目录没有创建权限检查目标目录属主;或者先手动 mkdir,再让 rsync 只同步内容@ERROR: chroot failed用了 rsync daemon 模式,但路径配置错误检查/etc/rsyncd.conf里的path和use chroot配置;不是 SSH 模式的问题排查这类问题有个通用心法:先缩小范围,再做变量隔离。先用一条最小命令(比如只看一个文件)复现,然后逐步增加参数,确认是哪个选项引发的问题。永远不要在一个 10 万文件的完整同步任务上做实验。5. 性能调优与运维脚本模板同步上千 GB 数据时,rsync 的默认参数往往不是最优的。这一节针对大文件、海量小文件、跨公网三种典型场景,分别给出我实测过的调优思路。5.1 海量小文件场景:让 rsync 别再逐文件等待当你同步一个包含 50 万个图片文件的目录时,默认的 rsync 是一个文件一个文件地处理,每个文件都要经过一次文件系统调用和网络交互,吞吐量惨不忍睹。这时候有几个办法:用--info=progress2代替老的--progress:progress2可以显示整体进度百分比,而不是每个文件的进度,在文件数量极大时输出日志更友好。调大-T临时目录所在磁盘的 IO 性能:rsync 在接收端写入临时文件后做 rename。如果临时目录所在磁盘和最终目标目录在同一个卷,性能通常没问题;如果跨卷,会多一次复制。确保共享同一文件系统能减少一次写放大。用--no-whole-file:rsync 默认在本地传输或者远程传输时,对大文件会使用"整个文件直接写"的策略。但对小文件,--no-whole-file会丢开这个限制,强制走增量块比对逻辑,有时反而更快。具体要看你传输的通道和文件大小分布,没有绝对标准。真正的大招:用 tar 管道。如果源端和目标端都是 Linux 且网络质量好,可以构造管道让数据流式打包传输:tar -C /data/project -cf - . | ssh user@host "tar -C /backup/project -xf -"这个方案的优点是减少每个文件的握手开销,速度会快很多;缺点是丧失了 rsync 的增量能力,只适合"首次全量灌数据",后续增量再回到 rsync。5.2 公网传输:压缩与断点续传的取舍跨公网同步最金贵的是带宽。如果网络带宽有限,建议开启压缩选项-z,源端和目标端会各自消耗一些 CPU,但传输字节数会大幅下降。对于文本文件、日志、JSON 配置,压缩率极其可观;但对已经压缩过的.jpg、.zip、.mp4文件,压缩几乎没效果,反而白白消耗 CPU,所以更要看文件类型决定。跨公网做大文件传输,我会组合这些参数:rsync -avz --partial --bwlimit=5000 --timeout=60 \ --progress user@remote:/data/bigfile.iso /local/dest/--partial的作用是:如果传输中断,保留目标端已经接收的部分,下次续传时基于这些已存在数据做增量处理。--timeout=60防止连接假死时任务一直挂着不退出。--bwlimit控制流量,防止影响其他业务。对于几十 GB 的大文件,这个组合能让中断重跑的成本极低。5.3 全套运维脚本模板:一键实现带日志和告警的同步最后分享我最常用的一套脚本模板,可以直接改改变量就用。支持:增量备份、日志轮转、失败邮件或 Webhook 通知(可以替换为任意通知渠道)。#!/bin/bash # backup-with-rsync.sh set -euo pipefail SOURCE_DIR="/data/project/" BACKUP_BASE="/backup" LOG_DIR="/var/log/rsync-backup" TIMESTAMP=$(date +%Y%m%d-%H%M%S) LOGFILE="$LOG_DIR/backup-$TIMESTAMP.log" DEST_DIR="$BACKUP_BASE/current" mkdir -p "$LOG_DIR" "$BACKUP_BASE" # 执行 rsync 同步 rsync -avh --delete \ --exclude-from=/etc/rsync-exclude.txt \ --link-dest="$BACKUP_BASE/current" \ "$SOURCE_DIR" "$DEST_DIR/" 2>&1 | tee "$LOGFILE" # 同步完成后,创建当日时间戳快照目录的硬链接引用 if [ $? -eq 0 ]; then snap_dir="$BACKUP_BASE/snapshot-$TIMESTAMP" mkdir -p "$snap_dir" cp -al "$DEST_DIR"/. "$snap_dir"/ find "$BACKUP_BASE" -maxdepth 1 -type d -name 'snapshot-*' -mtime +30 -exec rm -rf {} \; else echo "RSYNC FAILED, log: $LOGFILE" | mail -s "backup failed" ops@example.com exit 1 fi这个脚本里cp -al是对当前备份做一个硬链接快照,几乎不消耗额外磁盘,但它能生成一个带日期的版本目录。配合find ... -mtime +30 -exec rm -rf,可以自动清理 30 天以前的快照目录。里面--link-dest="$BACKUP_BASE/current"是在每次同步时参考上一次当前版,实现增量硬链接。邮件通知的部分,你完全可以替换成自己团队的告警机器人或监控接口。关键点是:rsync 脚本一定要有失败退出码和通知机制,否则备份静默失败比不备份更可怕。6. 平时看不到的 Rsync 使用细节与判断技巧前面这些内容已经足够让你解决日常工作中 90% 的同步需求。但如果你想让自己的 rsync 水平更上一层楼,这里还有几个"平时文档里不常展开但实测很有用"的细节值得掌握。6.1 dry-run 模式:高危操作前必做的安全网不管你的 rsync 技能多熟练,只要是带--delete或者在关键目录之间移动数据,先干跑一遍永远是最稳妥的做法:rsync -avh --delete --dry-run /data/project/ /backup/project/--dry-run会让 rsync 只做对比和模拟输出,不会真正写任何文件。你可以从输出内容中看到哪些文件会被传输、哪些会被删除,然后决定是否真的要执行。在我自己的运维习惯里,涉及线上数据的删除或移动操作,必须先干跑,再执行,这个习惯帮我拦住过好几次手滑操作。6.2 利用 rsync 检查两端一致性有时候同步跑完了,你需要验证两端是否真的完全一致。rsync 的-c参数在这里可以当"校验工具"用,但它只是判断"文件是否相同",不是逐字节比对哈希。如果你需要可靠的一致性校验,可以用--checksum跑一次同步,观察是否还有需要传输的文件;注意这会让两端都读取所有文件内容,比较耗时,但对关键数据做定期校验是划算的。另一个轻量技巧是让 rsync 输出变更列表,然后抓取统计信息:rsync -avh --stats --dry-run /data/ /backup/输出末尾的统计段落会告诉你"文件传输数量、创建的字节数、相比总字节数的比例"等信息,非常直观。我经常用这个方式在做完同步后确认"两边应该已经一致"。6.3 用 rsync 模拟"移动"操作rsync 没有内置 move 选项,但你可以先同步再删除,或者直接用--remove-source-files。这个参数的行为是:成功传输完一个文件后,删除源端的该文件。它经常被用在数据迁移场景:比如把文件从一台老服务器搬到新服务器,搬完自动清理源端。rsync -avh --remove-source-files /data/old/ user@new-server:/data/new/需要注意的是,--remove-source-files只会删除普通文件,不会删除目录。而且它和--delete正好凑成一对:一个是目标端向源端看齐,一个是源端向目标端移动。组合使用时务必清楚自己到底想干什么。6.4 输出信息识别:从-v输出判断 rsync 在做什么很多人看rsync -v的刷屏输出很不耐烦,直接 2>/dev/null 丢掉日志。但如果你愿意花一分钟理解它的输出结构,排查问题会快得多。每次同步的文件行通常以几个特殊前缀字符开头:>:文件需要传输到接收端<:文件从接收端拉取到本地(少见)c:由于校验和不一致需要重建文件d:目录本身被处理L:符号链接被创建或修改f:普通文件后面跟的字段是文件大小、时间、权限变化等。我建议保留日志,并定期用grep抓取非>和d前缀的非预期行,这样很容易发现哪些文件的属性异常、哪些权限变动、哪些符号链接出问题。这是我个人维护备份系统时最喜欢用的巡检手段。6.5 一个反向思维:rsync 慢的时候,不一定是 rsync 的锅如果同步速度远低于你的预期,别急着在 rsync 参数上使劲。先确认瓶颈在哪一层:网络层:用iperf3或scp一个大文件测一下两端实际带宽,确认是不是链路本身的问题;磁盘层:用iotop或iostat看源端和目标端的磁盘利用率是否打满,很多备份盘是机械盘,随机读写速度只有几十 MB/s,再快的网络也无力回天;文件系统层:如果你在用 NFS、CIFS 之类的网络文件系统做中转,rsync 可能并不适合在这种叠加层上运行,瓶颈会受协议开销影响非常严重。我见过最典型的"rsync 很慢"问题,其实是因为目标端是一块 USB 外接盘或者网络 NAS 挂载盘,单个大文件顺序写还行,一遇到海量小文件的随机写就完全跑不起来。解决办法是换 SSD 或本地盘做暂存,再定期搬运。在这些问题都排查完之后,还觉得 rsync 效率不满意,再去考虑换更底层的工具(比如针对 ZFS 的zfs send/receive,或者针对对象存储的rclone)。没有什么工具是万能的,但 rsync 在"类 Unix 系统之间同步普通文件"这个领域,目前依然没有谁能替代它。