首页 / 资讯中心 / 文章详情

服务器间文件迁移实战:rsync与scp的参数、权限与应急方案

服务器间文件迁移实战:rsync与scp的参数、权限与应急方案 ★ FEATURED ARTICLE
把文件夹从一台 Ubuntu 服务器搬到另一台听起来就是一条cp -r的事可一旦你开始在生产环境里做这件事问题就全冒出来了文件传过去权限丢了、软链接断了一地、传到一半 SSH 断了、同步一次把线上文件给覆盖了。这篇文章我把自己平时在真实服务器之间倒腾文件夹的完整思路和命令整理出来包括工具选型、参数拆解、权限陷阱、速度排查以及 SSH 不通时的应急传输方案希望给同样被这个问题折腾过的朋友一个可以直接照着抄的参考。我默认你跟我一样是个常年在终端里敲命令的干活人所以下面不会讲太多虚的全部是敲得出来的命令和踩过的坑。1. 想清楚你是要传一次还是传一辈子先定场景再选命令很多人一上来就问哪个命令传文件夹最快这问题本身就是个坑。互传文件夹在不同场景下的诉求完全不一样先搞清楚自己属于哪一类后面选工具、配参数才不会跑偏。1.1 一次性迁移、周期性同步、归档备份这三类需求对应不同工具按我自己的经验服务器互传文件夹基本只有三种典型场景第一种是一次性迁移比如从旧的物理机搬到新的云主机目录结构、属主、权限、时间戳都要尽量原样保留传输过程基本只跑一次。这种场景下rsync是最稳妥的选择因为它的-a归档参数能把 Unix 文件元数据完整带走。第二种是周期性同步最常见的就是把某台服务器的构建产物、静态资源、日志目录隔几分钟或者每天凌晨同步到另一台机器。这种场景最重要的是增量两个字——每次都只传有变化的部分而不是把整个目录重新搬一遍。rsync在这块几乎是唯一推荐scp做不到增量。第三种是归档备份你希望保留历史版本、不覆盖已有文件或者把某个文件夹打包压缩后丢到备份机。这种时候反而未必用rsync用tar打包再传或者配合rclone这类带版本管理的工具更合适。除了场景还要想清楚同步方向是单向还是双向。命令行原生工具大多是单向镜像的思路双向同步要处理冲突合并复杂度会指数级上升我自己一般直接用同步盘类工具而不是硬写脚本来处理。1.2 开传之前的三个检查磁盘、网络、SSH 互信我见过太多人上来就执行 rsync传到一半发现目标磁盘满了或者远端目录不存在导致文件全堆到错误路径。开传之前我固定花两分钟做三件事。第一确认磁盘空间。源端执行du -sh 源目录看大小目标端执行df -h确认剩余空间。不要只看源目录大小还要想想解压后的膨胀比例、同步过程中的临时文件建议至少留出 1.5 到 2 倍空间余量。第二确认端口和防火墙。默认 SSH 走 22 端口如果服务器在云上记得检查安全组的入站规则有没有放行内网互传就无所谓但如果跨公网还要确认目标机 SSH 服务实际监听端口。第三配置 SSH 免密登录。传一次两次用密码能忍周期性同步必须配 SSH Key。手动传公钥最简单的方式是用ssh-copy-idssh-copy-id user目标服务器IP之后建议用ssh user目标服务器IP echo ok验证一遍免密是否生效。别小看这一步crontab 里的同步任务如果依赖密码输入99% 会失败得无声无息。2. scp 和 rsync 的取舍看似相似实则是两套思维scp和rsync是大家最常用的两个命令表面上看都能把文件从 A 传到 B但底层逻辑完全不同。选错了轻则浪费带宽重则把整个目录搞乱。2.1 scp 适合简单、立刻、短连接的复制scp本质上就是把文件复制到远程它的工作模式是很朴素的整文件传输。命令也很直观# 递归复制整个目录 scp -r /var/www/html user192.168.1.10:/var/www/ # 指定端口 scp -P 2222 -r /data/backup user203.0.113.10:/backups/ # 开启压缩复制 scp -C -r /var/www/html user192.168.1.10:/var/www/scp最好的使用场景是文件量不大、一次性操作、不需要关心目标端是否已经有相同文件。比如临时传一个配置文件、传一个安装包scp打一行命令就走没有任何心理负担。但它有两个硬伤。第一个是不支持增量哪怕目标端已经有 99.99% 相同的文件它也会傻乎乎地把整个目录重新传一遍。第二个是中断后不能优雅续传传一半网络断了前面的进度全部作废只能重新开始。在跨公网、弱网环境下这种特性非常致命。2.2 rsync 的增量算法到底省在哪里rsync的核心价值是它独特的增量传输算法。它会在传输前先对比源端和目标端的文件差异只传输那些真正有变化的部分。有两点机制让它在传文件夹时特别强第一它默认会看文件的大小和修改时间mtime如果目标端存在同名文件且大小、mtime 一致它会直接跳过。这意味着你反复执行同一条 rsync 命令第二次开始速度会快到惊人。第二对于真正需要更新的文件rsync 也不是整个重传而是把文件切块对比两端哪些块不一致只传不一致的块然后在目标端重组。这个设计对断点续传也有天然优势——配合--partial参数中断后留下的临时文件可以作为下次对比的基础。实际使用中rsync还支持通过 SSH 隧道进行加密传输你不需要额外搭建任何服务目标机只要有sshd就能用。2.3 一个容易踩的坑目标端已有同名文件时很多人第一次用 rsync 会踩一个看起来很简单但后果严重的坑目标目录里已有的同名文件在不带任何参数时会被保留rsync 会认为两边相同就直接跳过。如果你本想用新版本覆盖旧版本却发现旧文件还在原地第一反应往往是困惑。比如这条rsync -av /source/app/ user192.168.1.10:/destination/app/假设源端app/里有个旧文件config.yml你把它改了之后重新同步rsync 会因为 mtime 变化而正常覆盖但如果源端把某个文件删除了那目标端这个文件会一直留着因为它不在 rsync 的同步逻辑里。想实现完全一致必须显式添加--delete参数。这点后面第三章细说。3. rsync 落地模板从单次搬家到定时增量同步下面我把 rsync 从最基础到进阶的用法拆开讲每个参数都解释清楚它为什么存在。这一节完全可以当命令手册来抄。3.1 最常用的一行命令-avz 拆开理解先看最经典的用法rsync -avz --progress /var/www/html user192.168.1.10:/var/www/-a是--archive归档模式它本身是一个组合参数等于-rlptgoD。拆开看-r递归子目录-l保留软链接符号-p保留文件权限-t保留修改时间-g保留属组-o保留属主-D保留设备文件和特殊文件这里要特别提一个经常被忽略的细节-o保留属主是需要 root 权限的。普通用户在接收端没有权限把文件属主改成源端的 UID所以在没有 sudo 的情况下-a里的-o多半是静默失效的。如果你需要完整保留 root 属主关系接收端要用有 sudo 权限的账号执行rsync -avz --rsync-pathsudo rsync /var/www/html user192.168.1.10:/var/www/--rsync-pathsudo rsync会让远程端用 sudo 来执行 rsync这样权限才够。注意远程端还要配置相应账号的 sudo 免密否则这一招会卡在密码输入上。-z是传输时压缩带宽有限时很有用但这个参数不是万能的我在第五章会专门吐槽。--progress显示传输进度它其实是个很粗糙的进度提示会一行一行刷当前文件的传输状态。传大量小文件时你会发现它刷得飞快别激动那不代表速度代表的是文件数量很多。还有一个小细节源路径结尾的斜杠。如果你写/var/www/html/这意味着把 html 目录里的内容传过去如果不写斜杠写/var/www/html那就是把 html 这个目录连同它本身一起传过去。目标路径是否带斜杠也有类似的合并进已有目录与生成同名目录的语义差异。我自己多年的习惯是永远在源端用带斜杠的形式目标端给出精确目录然后配合--dry-run先看一眼基本不会出事。3.2 同步镜像与永远一致需求--delete 何时用如果你的需求是让目标目录永远和源目录一模一样那必须加--deletersync -avz --delete /data/app/ user192.168.1.10:/data/app/它的作用是目标端存在但源端不存在的文件会被删除掉。听起来很吓人但只要你的同步方向是明确的单向镜像这个参数反而是正确且必要的。镜像同步如果不加--delete时间久了目标端会悄悄积累一堆源端已经删除的垃圾文件占用空间不说还会造成两边看起来一样但又不一样的混乱。什么时候绝对不能加--delete当目标端有需要独立保留的文件时。比如你要把日志同步到备份机但备份机还有其他归档文件放在同一目录下这时候一旦加--delete那些归档会连骨头都不剩。再提醒一个更隐蔽的坑--delete在某些 rsync 版本中默认不会删除目录里那些被排除的文件但如果你用了--delete-excluded就要特别注意它会连排除掉的目录也一并删掉。除非确实知道自己在做什么否则别随手加。3.3 排除目录与控制范围--exclude、--include实际传项目目录的时候很多目录根本不必传node_modules、.git、vendor、__pycache__、各种日志。用--exclude可以精确控制rsync -avz --delete \ --excludenode_modules \ --exclude.git \ --exclude*.log \ --excludetmp/ \ /data/app/ user192.168.1.10:/data/app/项目大了之后命令行会很长这时候把规则写进文件更清爽# exclude.txt node_modules .git *.log tmp/ cache/然后执行rsync -avz --delete --exclude-fromexclude.txt /data/app/ user192.168.1.10:/data/app/这里注意--excludetmp/结尾斜杠表示只排除目录不排除同名的tmp文件。另外排除规则支持模式匹配*和?都可用想排除所有隐藏文件可以用--exclude.*。如果你只想传某些指定文件可以反过来用--include配合--exclude*做白名单但这种用法容易把自己绕晕我一般直接改用--files-from指定清单文件。3.4 定时同步的 crontab 示例周期性的文件同步最朴素的方案就是写进 crontab。我常用的模板是这样的# 每天凌晨 3 点同步记录日志 0 3 * * * rsync -avz --delete /data/app/ user192.168.1.10:/data/app/ /var/log/rsync_app.log 21这一步有几个细节决定成败环境变量问题。crontab 的执行环境和你手动 shell 不一样PATH可能没有 rsync 的路径。建议在 crontab 里写绝对路径/usr/bin/rsync。SSH key 问题。cron 执行的用户和手动用户的 HOME 可能不同~/.ssh/id_rsa找不到就会静默失败。可以在 rsync 命令里用-e ssh -i /绝对路径/id_rsa指定密钥。日志问题。crontab 的重定向是追加到文件但那个文件默认可能没有写权限。先把touch /var/log/rsync_app.log chown 当前用户 /var/log/rsync_app.log处理一下。每次定时任务跑完我还会顺手看一眼日志里的 sent X bytes received Y bytes 行。如果某天同步数据量异常暴增说明源端可能有未经预料的大文件变更这份日志能帮你第一时间发现问题。4. 文件不是 COPY 过去就行权限、属主、时间戳决定服务是否正常技术上讲rsync 把文件内容传完了可文件传完后服务能不能正常跑完全是另一码事。这一节说的都是我在部署现场踩过的真坑。4.1 为什么传到新机器后服务莫名起不来最常见的问题就一个权限和属主不对。例子很典型你把源服务器上由 root 拥有的网站目录传到新服务器接收端用的账号是普通用户 deploy。即便用了-a目标端文件的属主往往还是执行 rsync 的那个账号或者因为 sudo 而变成 root而 Web 服务进程运行用户是www-data。结果就是 Nginx 或者 PHP-FPM 根本没有权限读取这些文件页面直接 403 或者 500。还有一种常见情况是文件权限位不对。源端出于安全考虑把配置文件的权限设成了600这本来没毛病但传到目标端后如果目标端的服务进程换了用户去读这个文件就会被拒绝访问。静态资源如果权限是640而目标端 Web 服务用户不在文件属组里同样读不了。另一个容易被忽略的坑是软链接。很多项目目录里会有相对路径的软链接比如current - releases/20250101。如果传输时没有持久化软链接机制或者目标端目录结构有细微差异链接会指向不存在的路径服务自然就崩了。rsync 的-l参数保留软链接本身但不会改变链接指向的内容这点心里要有数。4.2 用 --chown 和 --chmod 在传输时修正属主权限正确做法不是在传完之后手动 chown而是在 rsync 传输过程中就把属主和权限修正到位。rsync 提供了两个非常好用的参数。假设源端的目录属主是 root目标端希望同步完成后文件全部归属www-data并且目录权限是755、文件权限是644rsync -avz --delete \ --chownwww-data:www-data \ --chmodF644,D755 \ /var/www/html/ user192.168.1.10:/var/www/html/这里--chownwww-data:www-data会覆盖源端的属主属组信息直接映射成目标端需要的用户和组。--chmodF644,D755里F表示只对普通文件应用后面的权限D表示只对目录应用。这样一条命令既完成了传输也完成了部署时的权限标准化。如果目标端没有 www-data 用户要用--rsync-pathsudo rsync让远程端以 root 身份写入。因为只有 root 才有权限把文件的属主改成 www-data。还有一种更精细的做法是用--usermaproot:www-data --groupmaproot:www-data它的意思是把源端 root 属主映射成目标端 www-data 属主其他用户保持不变。适合源端有多个用户、只想定向修正 root 的场景。4.3 传完后如何快速验证一致性传完不等于完事我每次大文件传输后都习惯性做两件事验证。第一遍是用 rsync 自身的校验模式干跑一遍rsync -rcn --delete /var/www/html/ user192.168.1.10:/var/www/html/-c会忽略 mtime强制比对文件内容校验和-n是 dry-run只输出差异不实际写入搭配--delete能看出源和目标是否完全一致。如果没有任何输出说明两边确实已经一致如果有输出列出的是仍存在差异的文件。第二遍更彻底如果文件量不是特别大我会直接在两端生成 md5 清单再 diff# 源端 (cd /var/www/html find . -type f -exec md5sum {} \; | sort -k2 /tmp/src.md5) # 目标端 ssh user192.168.1.10 (cd /var/www/html find . -type f -exec md5sum {} \; | sort -k2 /tmp/dst.md5) # 对比 diff /tmp/src.md5 /tmp/dst.md5diff无输出说明两端文件内容完全一致。这个办法适合在快速信任 md5 环境里用对于超大目录会比较耗 CPU但胜在结果明确。5. 传输慢的时候先查这四件事再抱怨传文件夹好慢是服务器互传里被问烂了的问题。绝大多数时候慢的根源不是 rsync 本身而是用错了姿势。按下面四个方向排查基本能定位到问题。5.1 大量小文件才是慢的主因先明确一点rsync 是按文件逐个处理的每个文件都会有建立连接、对比元数据的开销。如果你传的是一个几万个小文件的目录哪怕每个文件只有 1KBrsync 也要一个个遍历对比速度会比传几个 10GB 的大文件慢得多。针对大量小文件我一般这样处理如果只是临时整目录搬家先用tar打包再传能大幅减少文件数量带来的开销tar czf - /var/www/html | ssh user192.168.1.10 tar xzf - -C /var/www/这种打包再传的方式传输时的吞吐量会好看很多因为 tar 把上万个文件合并成了一个流式文件。如果必须长期增量同步那么第一次全量传输建议也用 tar 打包做种子后面再用 rsync 增量补齐。这样首传速度快后面增量精准。尽量减少 rsync 的遍历范围。比如你只改了src/和public/两个目录就不要把整个项目根目录丢给 rsynccd 到对应子目录再同步效率天差地别。5.2 -z 压缩是不是越压越快不一定-z压缩参数非常容易被滥用。很多人看到压缩就脑补出带宽省了所以快了实际上压缩的代价是 CPU 计算时间而且某些文件格式本身已经被压缩过再压也压不出多少空间。举几个实际场景传静态网站HTML、CSS、图片时图片和视频本身已经高度压缩tar 或者 rsync 的-z对它们基本无效纯浪费 CPU。传数据库备份、文本日志时文本压缩率很高-z有明显收益。在内网千兆环境里两端磁盘和 CPU 往往是瓶颈压缩反而拖慢速度在跨公网 10Mbps 的链路里压缩能显著缩短传输时间。我的经验是先不加-z试一轮如果发现带宽跑不满或者传输总量明显小于源目录体积再加-z对比一轮。实测数据最可靠不要凭感觉。5.3 限速与断点续传--bwlimit 与 --partial 的正确用法跨公网传输时最怕的是把生产服务器的带宽打满导致正常业务请求超时。rsync 提供了限速参数# 限制传输带宽为 5MB/s单位是KB/s rsync -avz --bwlimit5120 /data/app/ user192.168.1.10:/data/app/这个参数非常好用单位是 KB/s5120 就是 5MB/s。凌晨备份任务可以放宽些白天跑就保守一点。注意它限制的是 rsync 的传输速率不是网络总带宽。断点续传方面关键是这两个参数rsync -avz --partial --append-verify /data/app/ user192.168.1.10:/data/app/--partial让 rsync 在中断后不删除目标端残留的临时文件下次同步时能基于这部分结果继续。--append-verify表示对已经部分传完的文件先追加再校验适用场景是源端文件只在末尾追加增长比如日志文件。如果文件是随机修改的--append-verify会导致校验失败此时去掉--append-verify用普通的增量算法即可。还有一个避免 SSH 断线假死的参数rsync -avz --timeout600 --partial /data/app/ user192.168.1.10:/data/app/--timeout单位是秒如果 600 秒内没有任何数据传输连接超时后 rsync 退出而不是一直挂着。对于无人值守的定时任务这个参数能避免进程僵尸化。5.4 用 dry-run 先确认要传多少速度慢还一个常见误解你以为要传 100GB结果 rsync 卡了半天是在遍历扫描。先跑一次 dry-run 能明确到底要传什么rsync -avzn --delete /data/app/ user192.168.1.10:/data/app/-n表示只列出会发生的变更不实际传输。输出里列出的就是待传文件如果这个文件列表很长说明确实有大量差异如果列表很短而实际传了很久那问题大概率出在网络带宽或者源端磁盘 IO 上。另外如果你发现 rsync 每次都要全量扫描大目录但实际只改了几个文件可以考虑给 rsync 加--itemize-changes参数查看每个文件的变更状态快速定位为什么它认为文件有变化——经常是时间戳被某些脚本故意 touch 了。6. SSH 不通时的应急传输tar 管道与 nc 直连某些环境下 SSH 服务是不可用的或者出于安全考虑没开 22 端口这时候你还有别的方案能把文件夹整个搬过去。当然前提是网络层能通应用层自己想办法解决。6.1 通过 SSH 的 tar 管道目标机只需要有 sshd如果目标机能通过 SSH 登录但没装 rsync或者 rsync 版本太老导致参数不兼容直接用 tar 管道是最省事的方案。它的原理是用 SSH 建立加密通道把 tar 流直接喂给对端解压。# 把本地目录推到远端 tar czf - /var/www/html | ssh user192.168.1.10 tar xzf - -C /var/www/ # 从远端目录拉到本地 ssh user192.168.1.10 tar czf - /var/www/html | tar xzf - -C /var/www/这个方案比 rsync 粗暴但足够通用目标机只要有tar和sshd就能完成文件夹互传。它的缺点也很明显——不能增量、不能断点续传、权限处理要手动搞但作为应急手段非常可靠。有个细节ssh命令后面引号里的tar xzf -如果加了sudo记得写成sudo tar xzf - -C /var/www/且远程账号要有对应 sudo 权限。6.2 nc 直连局域网里最暴力的传法如果连 SSH 都用不了但两台服务器在同一内网段并且能直接 TCP 通信ncnetcat是最后一个快速通道。先在接收端启动监听准备接收数据流并解压到目标目录nc -l -p 9999 | tar xzf - -C /var/www/再到发送端把目录打包后推过去tar czf - /var/www/html | nc 192.168.1.10 9999这里nc -l -p 9999是在接收端开一个 TCP 监听端口发送端的nc会把 tar 数据流直接怼过去。顺序上必须先启动接收端监听再启动发送端否则连接会失败。几个实战注意点端口号要选一个没被占用的可以先ss -tuln | grep 9999确认。传输过程没有加密内网可信环境用可以跨公网千万不要这么裸传。nc传完不会自动给出结束提示你会看到终端好像卡住了实则是两端进程因为流结束会自动退出。可以通过检查目标端解压后文件数量是否正确来判断是否完成。如果要传大文件且不想遭遇缓存问题可以在 tar 命令里加--warningno-file-changed或者在解压端用pv看进度但核心思路不变。6.3 顺手提一下 sshfs想直接当本地目录操作时还有一种偶尔会用到的方式是sshfs把远端目录挂载到本地然后你就可以用cp、mv这些熟悉的命令直接操作像操作本地目录一样自然。# 安装 sshfsUbuntu 下 sudo apt install sshfs # 挂载 sshfs user192.168.1.10:/var/www/html /mnt/remote # 复制目录 cp -a /local_backup/* /mnt/remote/ # 用完卸载 fusermount -u /mnt/remote这种方式适合偶尔手动维护、不想记住一堆 rsync 参数的场景。但性能我不太推荐它走 FUSE 用户态文件系统传输大文件或大量小文件时延迟都偏高而且网络一旦抖动挂载点会卡住连去卸载都要等半天。生产环境定时同步老老实实用 rsync 就好。最后再分享一个我的习惯每次要传大文件夹前不管场景多紧急都先花 30 秒明确一下传完的标准是什么。是内容一致就行还是权限属主也要一致是允许目标端有额外文件还是必须完全镜像标准定清楚了选命令、配参数都会顺很多传完验收也有了依据。这个习惯救了我好多次也分享给你们。
阅读完成 · 觉得有帮助?
咨询建站