做服务器间的文件传输,很多人第一反应就是用scp,或者干脆套用rsync。我见过不少同事把scp当成万能工具,传大文件传一半断线了就得从头再来,传海量小文件更是慢到让人怀疑硬盘是不是坏了。今天想认真聊聊lftp这个老牌工具——它看起来其貌不扬,但真正解决了我大量服务器间文件同步、迁移和备份的痛点。这篇文章会从为什么选它、基础命令怎么用、镜像同步实战,到自动化脚本和问题排查,完整分享我在这套方案里的操作习惯。适合搞运维、做部署、搭备份的同学直接对照实践。
1. 服务器间传输为什么我最后选了lftp
1.1 日常服务器间传文件的几个老大难
先说场景。搞过生产环境的都知道,服务器和服务器之间传文件从来不只是“把文件拷过去”那么简单。我这边最常见的三类问题:
第一,文件大、链路不稳。数据库备份、日志压缩包、媒体资源,动辄几十 GB。网络一抖,scp直接断掉,没有续传能力,又得重传一遍。短时间还好,几十 GB 的文件重传一次就是几十分钟甚至几小时。
第二,目录结构复杂,需要增量同步。站点目录里可能有几十万个文件,有静态资源、动态脚本、临时缓存。每次都全量传递,服务器I/O和带宽根本吃不消。这时候需要的是“只把新增和变更的文件传过去”。
第三,环境受限,没法装额外软件。两台服务器之间可能只有传统的 FTP 服务,或者只能走 SSH/SFTP 通道;其中一台还是精简系统,rsync都没装。你要么装半天依赖,要么就得找一种“只装客户端就能干活”的工具。
这些痛点堆在一起,scp应付不了,rsync倒是能解决一部分,但受限于“两端都得有 rsync”、以及遇到非 SSH 协议时的尴尬。最后我把方案定在lftp上,是因为它把这些情况都覆盖了。
1.2 lftp在传输方案里的定位和取舍
lftp是一个类 Unix 系统下的文件传输程序,支持 FTP、SFTP、HTTP、HTTPS、FTPS 等一堆协议。它最出名的是三件事:断点续传、并发传输、镜像同步。
- 断点续传:传大文件中断了,用
-c参数接着传,不用重新开始。 - 并发传输:一个文件可以用多个连接分段下载,一堆小文件也可以并行传。
- 镜像同步:内置
mirror命令,能递归比对远端和本地的目录差异,然后增量同步,行为类似rsync。
它更讨喜的地方在于:绝大部分场景下,只需要在本地安装lftp一个客户端,对端只要有对应的服务协议(FTP 服务或者系统自带 SSH/SFTP 服务)就能工作,不需要对端额外装任何插件。
对比一下更直观:
| 方案 | 断点续传 | 增量同步 | 多协议支持 | 对端依赖 |
|---|---|---|---|---|
| scp | 不支持 | 不支持 | 基本只有 SSH | 需要 SSH 服务 |
| rsync | 部分支持 | 支持 | 通常走 SSH,也支持rsync daemon | 两端都要有 rsync |
| lftp | 支持 | 支持 | FTP/SFTP/HTTP/FTPS | 只要目标服务能连上 |
| ftp命令 | 不支持 | 不支持 | 只有 FTP | 需要 FTP 服务 |
打个比方:scp像一次性把所有箱子搬上车,途中掉一个就得全部卸下来重来;rsync像带着一个盘点表去仓库,只拿走缺的货,但它要求两边仓库管理员都认识你;而lftp像是自带麻绳和清单的老手,不管对面是什么仓库,只要能开门,它就能断点续搬、多线程搬、增量搬。
所以我的结论是:如果两台服务器都能装rsync,rsync在纯增量同步和本地文件校验上确实更强;但如果你要传超大文件、目标端只有 FTP、或者不想在对端做任何安装操作,lftp就是更省心的选择。甚至后来很多批次发布任务,我也直接用它来做,因为一条mirror命令就可以完成全量/增量切换,避免维护两套脚本。
2. lftp基础操作与关键配置
2.1 安装和.lftprc初始化
安装lftp很简单。我在 CentOS/RHEL 上习惯用:
yum install -y lftpUbuntu/Debian 上:
apt-get install -y lftpmacOS 有 Homebrew 的话也可以brew install lftp。装完先别急着连,建议先写一个.lftprc配置文件放在家目录下,把常用的网络参数固化下来,避免每次敲一堆set命令。
我常用的.lftprc示例:
set net:timeout 15 set net:max-retries 3 set net:reconnect-interval-base 5 set ftp:passive-mode on set ssl:verify-certificate no set mirror:parallel-transfer-count 4 set mirror:use-pget-n 1逐条说下我的理解:
net:timeout 15:控制每次连接或操作的最长等待时间。网络质量差时,如果不设这个,lftp 可能一直卡着,日志看起来像死掉了一样。net:max-retries 3:操作失败后的重试次数。我一般设 3 次,配合断点续传已经很稳。net:reconnect-interval-base 5:重连的基础等待时间,如果连续失败会按倍数退避,避免短时间内反复重试打爆服务端。ftp:passive-mode on:主动/被动模式的开关。绝大多数外网环境的 FTP 服务都要求被动模式,默认开启它省去很多“连得上但列不出目录”的烦恼。ssl:verify-certificate no:关闭证书强校验。我知道很多人会担心安全问题,但在内网环境自签证书的场景太普遍了,开着校验反而连不上。如果有条件做正规证书,建议保留校验。mirror:parallel-transfer-count 4:mirror 命令并行传输的默认连接数。mirror:use-pget-n 1:mirror 时每个文件默认使用多少个连接分段下载。设 1 表示每个文件单连接,避免对小文件也启动多连接反而拖慢速度。
配置文件写好后,可以用lftp -e 'set ...'临时覆盖,也可以直接改.lftprc永久生效。建议对批量服务器做同步任务时,公共参数统一放.lftprc,任务相关的参数在脚本里显式指定,这样既灵活又清晰。
2.2 最常用的连接与上传下载命令
连接服务器我习惯用:
lftp sftp://user@10.0.0.5 -p 22回车后它会提示输入密码。如果需要自动化,可以在连接命令里带密码:
lftp -u user,password sftp://10.0.0.5:22但说实话,这种把密码直接写在命令行的方式,在history里会留痕,生产环境不建议这么做。我一般更推荐两种方案:
一种是把登录信息写到~/.netrc:
machine 10.0.0.5 login user password yourpassword然后 lftp 连接时会自动读取,不用每次输入。
另一种是走 SSH 密钥,也是我目前主要用的方式,后面自动化章节会详细讲。
进入 lftp 交互环境后,常用的命令和本地 shell 很像,而且支持Tab补全:
| 命令 | 作用 |
|---|---|
ls/cd | 查看/切换远端目录 |
lcd | 切换本地目录 |
get | 下载单个文件 |
put | 上传单个文件 |
mget | 批量下载 |
mput | 批量上传 |
pget | 多线程分段下载 |
! | 执行本地 shell 命令 |
单文件断点续传是我最常用的功能,比如:
get -c /backup/appdb_full_20250601.sql.gz加了-c后,如果网络断了,再执行一次,它会从断点处继续下载。put -c同理,上传大文件时很管用。
pget是多线程下载,适合那种特别大的单个文件:
pget -n 8 -c /backup/large_archive.tar.gz-n 8表示开 8 个连接分段下载同一个文件。注意,pget的多线程能力依赖远端 FTP 服务支持多连接读取,如果服务端做了单 IP 并发限制,分段太多反而容易被限流。
批量上传时我常用mput:
mput /data/logs/*.log这个命令会把匹配的文件传到当前远端目录。批量下载则用mget。
还有一个容易被忽略的!命令,可以在不退出 lftp 的情况下执行本地操作,比如先本地建目录:
lcd /data/backups !mkdir -p /data/backups说实话,这些命令单独看都不复杂,难的是组合。实战里真正让我觉得 lftp 不可替代的,还是它的mirror镜像同步能力,下一章展开聊。
3. 核心实战:用mirror实现服务器间目录镜像同步
3.1 mirror命令的两种方向与参数拆解
mirror命令是 lftp 做服务器间文件同步的核心。它的基本逻辑是:递归对比远端和本地目录的差异,然后按参数决定是下载新增、上传变更,还是删除多余文件。
我最常用的两种方向:
# 下载方向:把远端目录同步到本地 lftp -u user,pass sftp://10.0.0.5 <<EOF mirror /data/publish /data/replica quit EOF# 上传方向:把本地目录同步到远端 lftp -u user,pass sftp://10.0.0.5 <<EOF mirror -R /data/replica /data/publish quit EOF注意-R参数,表示 Reverse,上传方向。没有-R就是下载方向。这个搞反了后果很严重,尤其是带--delete的时候,会把目标端的文件删掉。
mirror的常用参数我整理成了一张表:
| 参数 | 作用 | 常用场景 |
|---|---|---|
--parallel=N | 同时传 N 个文件 | 大量小文件分发 |
--only-newer | 只同步比目标端新的文件 | 增量同步 |
--delete | 删除目标端多余文件 | 完整镜像,两端一致 |
--exclude | 排除匹配的文件/目录 | 跳过缓存目录、日志 |
--verbose | 打印每个文件处理结果 | 调试和审计 |
--log=FILE | 输出详细日志到文件 | 日常任务留痕 |
--use-pget-n=N | 每个文件用 N 个连接 | 少数大文件场景 |
--continue/-c | 失败后继续上次传输 | 不稳定网络 |
增量同步我一般用--only-newer,它会比较文件修改时间,只传输远端比本地新(或本地比远端新)的文件。前提是两个服务器时间尽量一致,否则时间戳判断会出错。我吃过这个亏:一台机器时区没同步,同步回来的文件全是“新文件”,跑了整整一夜。后来我把所有服务器都统一走 NTP 时间同步,问题才消停。
全量镜像则用--delete,比如发布目录需要和代码仓库目录完全一致,本地已经删掉的旧文件,远端也要删。不过这个参数我建议永远配合--dry-run先用一遍,确认没问题再真的跑。--dry-run会模拟一遍同步过程,告诉你哪些文件会被上传、下载、删除,但不会真正操作文件。
3.2 一个真实的双机备份同步案例
举个例子,我之前管过一批应用服务器,生产节点有一台,内网机房有一台备份机。每天晚上需要把生产节点上的发布目录/opt/app/webroot增量同步到备份机的/data/backup/webroot。
当时生产节点连的是 SFTP 服务,我写了一个这样的 lftp 命令:
lftp -u appbackup,xxxxxx sftp://10.0.2.20 <<EOF set net:timeout 20 set net:max-retries 3 cd /data/backup mirror --parallel=4 --only-newer --verbose --log=/var/log/lftp_webroot.log /opt/app/webroot /data/backup/webroot quit EOF注意这里我用了cd /data/backup,是为了确保远端工作目录正确。mirror后面的源路径是远端路径,目标路径是本地路径。因为这是下载方向,所以远端在左边,本地在右边。如果是上传方向,源路径写到-R后面,本地在左、远端在右,顺序别搞反。
第一次执行我建议先跑一遍全量镜像:
mirror --parallel=4 --verbose /opt/app/webroot /data/backup/webroot让目标目录结构完全建立起来。从第二次开始,加上--only-newer做增量,速度非常快,基本上几秒到几十秒就结束。因为增量阶段只需要比对文件时间和大小。
这里还有一个小细节:--only-newer其实还会判断文件大小,只有当时间或者大小有变化时才会重新传输。我测试过,如果时间变了但大小没变,它也会重新传;但如果时间没变大小变了,是否会传取决于具体版本实现。为了保险,涉及关键配置文件的同步,我通常会在目标端额外跑一次校验脚本,对关键文件比对 md5。
整个过程中最怕的是断网。lftp 呢,好在有--continue,即使同步到一半断了,重新跑同一条命令,它会自动跳过已经完成的文件,继续剩下的。配合net:max-retries和net:reconnect-interval-base,我见过一条同步任务在连续断线三次后自己恢复,最后完整跑完。
3.3 排除目录和文件的高级用法
生产目录里总有不需要同步的东西。比如:
runtime/cache这类临时缓存目录*.log日志文件.git目录
如果每次都把这几类数据同步过去,增量同步的优势会被拖垮。这时候用--exclude:
mirror --only-newer --exclude 'runtime/cache' --exclude '*.log' --exclude '.git/' /opt/app /data/backup/app注意写法:规则的匹配基准是相对于同步根目录的路径,--exclude可以重复使用,一次排除一个模式。
这里有个容易踩的坑:排除日志文件时,如果写--exclude '*.log',它只匹配文件名后缀为.log的文件,但不会排除logs/整个目录。如果日志目录里全是.log文件,这个规则倒也能达到效果;但如果里面还有.txt之类的文件,就得再补一条--exclude 'logs/'。我一般习惯把目录排除写得更明确,比如--exclude '/logs/',两端都带斜杠,避免误排除同名路径。
还有一个实用技巧:--exclude-glob和--exclude-from。--exclude-from可以指定一个文件列表,每行一个排除规则,适合规则特别多的场景。比如我在一个项目里整理了二十多条排除规则,没有堆在命令行里,而是放在/etc/lftp_exclude.list中,命令写成:
mirror --exclude-from=/etc/lftp_exclude.list /opt/app /data/backup/app这样维护起来清晰得多,而且可以放到版本控制里,团队协作也方便。
4. 把同步脚本化:免密登录、定时调度与日志
4.1 SSH密钥免密登录与安全实践
自动化之前必须先解决免密问题。如果脚本里直接写明文密码,或者靠交互输入,定时任务根本跑不起来。我主要用 SSH 密钥方式。
在调度机(也就是执行同步脚本的这台)上:
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_lftp -N ""把公钥拷到目标服务器上:
ssh-copy-id -i ~/.ssh/id_rsa_lftp.pub user@10.0.2.20如果目标服务器没有ssh-copy-id,也可以手动把公钥追加到目标机器的~/.ssh/authorized_keys文件里。然后测试:
ssh -i ~/.ssh/id_rsa_lftp user@10.0.2.20能免密登录,说明密钥配置成功。
lftp 走 SFTP 协议时,底层调用的就是本机ssh命令。所以只要 SSH 本身能免密,lftp 也就能免密。但有些环境的默认 SSH 参数会触发 host key 交互确认,脚本里就卡住了。我在 .lftprc 里用一行配置解决:
set sftp:connect-program "ssh -a -x -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa_lftp"意思是不用代理、不转发 X11、不交互验证 host key、指定密钥文件。如果你有多个目标服务器,更优雅的做法是在~/.ssh/config里按 Host 配置好密钥和参数,然后 lftp 连接时直接用别名,比如:
lftp sftp://backup-server这里的backup-server就是~/.ssh/config里配置的 Host 别名。这种方式最干净,脚本里连 IP、用户名、密钥都不用写,所有连接细节都收敛在 SSH 配置里。
4.2 一键同步脚本示例与定时任务配置
自动化脚本我一般写成这样,放在/opt/scripts/lftp_sync_webroot.sh:
#!/bin/bash LOCK_FILE=/tmp/lftp_sync_webroot.lock LOG_DIR=/var/log/lftp_sync LOG_FILE="${LOG_DIR}/webroot_$(date +%Y%m%d_%H%M%S).log" SRC_USER=appbackup SRC_HOST=10.0.2.20 SRC_DIR=/opt/app/webroot DST_DIR=/data/backup/webroot mkdir -p "${LOG_DIR}" # 防止任务重复执行 if [ -f "${LOCK_FILE}" ]; then echo "$(date '+%F %T') 另一个同步任务还在运行,跳过本次执行" >> "${LOG_FILE}" exit 1 fi touch "${LOCK_FILE}" trap 'rm -f "${LOCK_FILE}"' EXIT # 连接参数放在 .lftprc 里,命令参数在这里显式控制 lftp -u "${SRC_USER}" "sftp://${SRC_HOST}" <<EOF >> "${LOG_FILE}" set net:timeout 20 set net:max-retries 3 mirror --parallel=4 --only-newer --delete --exclude-from=/etc/lftp_exclude.list --verbose "${SRC_DIR}" "${DST_DIR}" quit EOF # 清理超过 30 天的日志 find "${LOG_DIR}" -name '*.log' -mtime +30 -exec rm -f {} \;几个关键点:
- 加了锁文件,避免 crontab 执行周期重叠。如果上一个任务还没跑完,新任务直接退出,防止多个 lftp 进程同时访问同一个目录造成混乱。
trap保证脚本无论正常退出还是异常退出,锁文件都会被清理。我一开始没加这行,结果一次同步卡死,锁文件残留,后续任务全被拒之门外,排查了半天。- 每次执行生成独立日志,按日期命名,方便回溯。
- 日志只保留 30 天,避免服务器磁盘被日志占满。别小看这个,lftp 的
--verbose日志在几十万小文件同步时,一天能写几百 MB。
然后配置 crontab:
0 2 * * * /opt/scripts/lftp_sync_webroot.sh >/dev/null 2>&1凌晨 2 点执行,避开业务高峰期的带宽占用。如果同步量大,可以再加一个错峰参数,让不同任务间隔半小时跑。
4.3 同步脚本的执行结果判断与告警
脚本跑完怎么知道成没成?我习惯在 lftp 命令执行后直接判断退出码:
if [ $? -eq 0 ]; then echo "$(date '+%F %T') 同步完成" >> "${LOG_FILE}" else echo "$(date '+%F %T') 同步失败,请检查" >> "${LOG_FILE}" fi但这里注意,lftp 的退出码并不总是可靠的。有时候连接断了,但镜像命令局部失败,进程退出码依然可能为 0。所以更稳的做法是检查日志里是否有错误关键字,比如Failure、Error、Fatal:
if grep -iE "fatal|error|failure" "${LOG_FILE}" > /dev/null 2>&1; then echo "同步异常" >> "${LOG_FILE}" fi我甚至会解析mirror生成的统计信息。mirror --verbose结束时,lftp 会打印类似“文件传输完成”的统计行。把它抓到后,可以确认本次传输了多少文件、多少字节,再决定要不要后续校验。
告警这块,最朴素的做法是发邮件。脚本里加一行:
mail -s "lftp同步失败: webroot" ops@example.com < "${LOG_FILE}"也可以用 curl 调 Webhook 推送到企业微信或者钉钉。我个人实践是:白天跑的任务才告警,凌晨备份任务失败就等上班再看日志,夜里不打电话、不发短信,不然早就被骚扰疯了。
5. 实战中常见的坑与排查实录
5.1 中文文件名乱码问题
中文文件名在服务器间传输是高频问题。如果远端是 Windows 上的 FTP 服务,或者某些传统 Unix 系统使用 GBK 编码,而本地的 lftp 默认按 UTF-8 处理,你会看到一堆乱码,甚至文件名直接变成问号。
解决办法是在连接时指定字符集:
set ftp:charset GBK set file:charset UTF-8意思是“远端传输用 GBK 编码,本地文件系统用 UTF-8 编码”。lftp 会在两端做自动转换。如果是纯 Linux 到 Linux 的环境,一般不会遇到这个问题,我遇到的大多是对接 Windows FTP 服务、或者老系统导出的文件名时才需要加这两行。
还有一个相关坑:某些文件系统不区分大小写,但远端区分。同步时如果本地生成了Test.txt和test.txt,在目标端就会互相覆盖。这种情况mirror本身不会报错,但你会莫名丢文件。我的建议是文件命名规范里强制小写,从源头规避。
5.2 连接超时、断线重传与并发限制
超时是拿 lftp 做定时同步最常碰到的问题。表现是:日志里出现Timeout,命令挂起很久,然后失败。
我排查超时的思路按顺序查:
- 网络层:先
ping目标机器,再看端口通不通。 - 服务端配置:如果是 FTP,检查服务端超时时间。如果服务端设置了 30 秒无操作就断开,而你的同步任务恰好需要较长时间扫描目录,就会断。
- lftp 参数:把
net:timeout调大一点,并开启自动重试。
推荐一组稳妥参数:
set net:timeout 30 set net:max-retries 5 set net:reconnect-interval-base 3 set net:reconnect-interval-multiplier 2 set net:reconnect-same-server yes这里的退避逻辑是:连接失败后先等 3 秒,第二次等到 6 秒,第三次 12 秒,以此类推,最多重试 5 次。这个参数组合能扛住大多数网络抖动。
并发限制这个问题,也分享一个实际案例。我曾经在一台老的 FTP 服务器上跑mirror --parallel=10,结果同步还没开始,服务端直接返回421 Too many connections。一开始以为是服务器挂掉了,后来发现是被 lftp 的并发连接打爆了。解决方式很简单:把--parallel降到 2 或 3,同时去掉--use-pget-n,让每个文件单连接传输。对于老服务器,稳定比速度更重要。
5.3 --delete误删风险与dry-run的必要性
这个必须单独拎出来说。--delete是 mirror 命令里最危险的参数。它不只是“删除目标端多余文件”,而是会让目标端完全镜像源端。如果你写反了方向,比如本来想下载同步,结果不小心写成mirror -R --delete 本地 远端,远端会被清成和本地一模一样,后果不堪设想。
我有一次就差点出事。做发布同步时,本地目录是临时解压出来的,里面少了一个子目录。因为加了--delete,如果真跑起来,远端对应子目录会被整个删掉。后来我养成了一个习惯:任何第一次使用的同步命令,先加--dry-run跑一遍:
mirror -R --delete --dry-run --verbose /data/publish /data/wwwroot--dry-run会列出所有将要执行的操作,但不会真正改动文件。我会看两遍:第一遍扫一眼有没有意外的 delete 操作,第二遍确认 transfer 的文件数量和预期一致。没问题后再真正执行。
如果实在不放心,上传方向加--exclude把关键目录保护起来,比如:
--exclude '/database_backups/'同时在脚本里先做一次目标端的快照存档,哪怕万一手滑,至少能回滚。
5.4 时间戳漂移导致的增量误判
--only-newer依赖文件时间戳判断是否需要同步。如果两台服务器的时间不一致,时间戳漂移会让 lftp 产生两种误判:
- 源端时间比目标端晚,导致本来没变过的文件每次都被当作新文件重新同步。
- 源端时间比目标端早,导致真正更新过的文件反而不被同步。
排查方法很简单:在两端各自执行date,看时间差是否在合理范围内。
解决方法是统一 NTP 时间同步。配置好系统级时间同步后,这个问题基本消失。如果是线下环境没法连外网 NTP,也可以用内网搭建一个时间服务器,把各节点都指向它。我强烈建议在做增量同步前,先把所有服务器的时区和时间统一了,不然后患无穷。
还有一种情况:某些应用发布文件时会保留原文件的 mtime,而文件内容实际已经变了。此时--only-newer会漏同步。遇到这种场景,我会改用--only-missing先兜底,或者直接定期做一次全量同步。全量同步虽然慢一点,但能保证一致性。
5.5 我的一些经验和后续拓展思路
如果让我总结 lftp 使用心得,我最想说的一句话是:别贪快,先保证不断。--parallel不是越大越好,--use-pget-n不是每个文件都值得开。大文件用多线程,小文件靠并行文件数,这个度需要根据你的实际服务端能力和带宽来调。我一般是从 2 开始,逐步往上加,找到稳定的临界点。
至于后续扩展,lftp 的能力不止服务器间同步。我还在用它做远程下载大文件的任务队列,配合queue命令一次排队一批任务;也尝试过用它做多种协议之间的中转拉取,比如从 HTTP 抓取文件再推送到 SFTP 服务器。它就像一个低调但功能齐全的瑞士军刀,很多地方都能派上用场。如果你只是把它当“加强版 ftp”用,那就真的小看它了。