rsync比纯PHP更可靠,因其原生支持增量同步、校验、压缩、排除规则和错误重试;PHP仅宜调用rsync命令,需配置SSH免密、权限及防注入,并避免在Web请求中直接执行。

用 rsync 命令比纯 PHP 更可靠
PHP 本身没有内置的、健壮的跨服务器文件同步机制。硬用 fopen + file_get_contents + file_put_contents 拉取远程文件,会遇到权限、超时、断点续传、目录结构重建、软链接处理等一堆问题。实际项目中,rsync 是唯一值得信任的选择——它原生支持增量、校验、压缩、排除规则和错误重试。
PHP 只需调用系统命令即可驱动它:
exec("rsync -avz --delete /local/path/ user@remote:/remote/path/ 2>&1", $output, $return_code);
注意几点:
-
-a保留权限/时间戳/符号链接,-v显示过程(调试用),-z压缩传输,--delete删除目标端多余文件(慎用,先测试) - 必须确保 PHP 进程有执行
rsync的权限,且已配置好 SSH 免密登录(用ssh-keygen+ssh-copy-id) -
exec默认被禁用,检查disable_functions是否含exec;若禁用,改用shell_exec或切换到 CLI 模式运行脚本 - 不要拼接用户输入进命令字符串,否则存在严重命令注入风险;路径应预先校验(如用
realpath+strpos判断是否在允许根目录下)
PHP 本地目录间同步可用 RecursiveIteratorIterator
如果只是同步两个本地路径(比如开发环境复制静态资源到构建目录),不用碰网络,PHP 自带迭代器足够用。
立即学习“PHP免费学习笔记(深入)”;
核心逻辑是遍历源目录,对每个文件/子目录,在目标目录创建对应结构:
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('/src', FilesystemIterator::SKIP_DOTS));
foreach ($it as $file) {
$relPath = $it->getSubPathName();
$dst = '/dst/' . $relPath;
if ($file->isDir()) {
mkdir($dst, 0755, true);
} else {
copy($file->getPathname(), $dst);
}
}
关键细节:
- 务必用
FilesystemIterator::SKIP_DOTS,否则.和..会被当普通目录处理,导致无限递归或路径错乱 -
mkdir($dst, 0755, true)的第三个参数true表示递归创建父目录,否则遇到深层嵌套会报错 - 没做内容比对,每次都是全量覆盖;如需跳过未修改文件,得用
filemtime对比源/目标最后修改时间,或用md5_file(小文件适用,大文件慢)
copy 失败常见原因和绕过方式
直接调用 copy 报 failed to open stream: Permission denied 很常见,不是因为目标目录不可写,而是 PHP 进程用户(如 www-data)没权限读源文件(尤其当源在 /home/user/xxx 时)。
解决办法分场景:
- 源文件属主不是 web 用户?用
chown或chmod调整权限,或把 web 用户加进源文件所在组并设组读权限 - 目标路径是 NFS 或挂载卷?某些挂载选项(如
noexec、nosuid)不影响copy,但mkdir可能失败,此时换用touch+file_put_contents绕过 - 文件超大(>2GB)?
copy在部分 PHP 版本(尤其是 Windows 下)会失败,改用stream_copy_to_stream流式处理:
$src = fopen('/bigfile.zip', 'r');
$dst = fopen('/dst/bigfile.zip', 'w');
stream_copy_to_stream($src, $dst);
fclose($src); fclose($dst);
同步任务不能放在 Web 请求里跑
一个同步操作可能耗时几十秒,而 Nginx/Apache 默认请求超时是 30–60 秒,PHP 的 max_execution_time 也常设为 30。直接在 HTTP 请求中执行,大概率卡死或被杀掉进程,用户还看不到进度。
正确做法只有两种:
- 转成后台异步任务:用
exec("nohup php sync.php > /dev/null 2>&1 &")启动,再通过 Redis 或数据库记录状态,前端轮询查进度 - 改用定时任务:把同步逻辑写成独立 CLI 脚本(
php /path/to/sync.php),用cron每 5 分钟拉一次,适合对实时性要求不高的场景(如日志归档、备份镜像)
别信“用 set_time_limit(0) 就能撑住”的说法——Web 服务器层面的超时你控制不了,而且长连接会吃光 worker 进程。



















