
干运维这行几乎每天都要做服务器间文件传输。小到推个配置文件大到迁移整台服务器的历史数据我最早一直用scp和rsync直到有一次遇到一个“文件特别多、网络特别不稳”的活scp中断了三次rsync在境外链路上慢到让人崩溃最后是靠lftp的mirror和断点续传才把数据完整拿下来。这篇就聊聊我用lftp做服务器间文件传输的完整经验包括工具选型、常用配置、生产环境里的坑适合正在做数据迁移、定时同步任务或者面对大量小文件传输的运维和开发同学参考。1. scp和rsync都搞不定的场景我为什么最后选了lftp先说结论不是scp和rsync不够好而是它们各自有明确的能力边界。很多人在初期选型时没意识到这一点等数据量上来才发现工具选错了返工成本极高。1.1 传输工具的能力对比我在实际工作里经常把几种工具放到同一个维度上对比判断依据很简单能不能断点续传、能不能并行、能不能做目录镜像、在弱网下抗不抗折腾。能力点scprsync走SSHlftp断点续传不支持大文件续传需额外配置天然支持继续传输时自动读取进度多连接并行无无单连接文件级并行和单文件分块并行都支持目录镜像只能递归复制增量同步能力强mirror命令做得最顺手协议支持仅SSH仅SSHFTP、FTPS、HTTP、HTTPS、SFTP、fish弱网自愈能力差断了就要重跑一般重试策略依赖外部配置内置重连和超时策略整体更加耐用这个表格不是要分个高下而是帮你搞清楚什么场景该用哪个。1.2 三个让我放弃传统方案的典型场景先说第一个场景海量小文件迁移。我当时要把旧日志服务器的三百多万个小文件拉到新机器单文件几十KB到几百KB。用scp跑文件数量一多SSH通道里的往返开销会吃掉大量吞吐而且中途任何一次网络抖动都会让整个任务功亏一篑。rsync虽然可以断点续传但在这种规模下第一次全量扫描也要花很久。第二个场景是境外节点之间的数据同步。网络延迟高、抖动频繁rsync的单连接吞吐被RTT限制得很死千兆带宽实际只能跑几十Mbps。这时候lftp的并行优势就出来了mirror --parallel16可以直接把多个文件同时拉起来带宽利用率立刻翻了几倍。第三个场景是老业务系统的FTP接口。很多上下游系统只开放FTP服务scp和rsync根本连不上去这时候lftp几乎是唯一顺手的选择。它不仅能传还能用mirror做增量同步等于给老系统补上了一个现代同步能力。所以我的选型逻辑很简单内网单文件、小批量传输用scp本地增量备份用rsync跨网络、大批量、多文件、需要断点续传的场景直接上lftp。后文所有操作都以这个前提展开。2. 环境准备与基础连接配置lftp的使用门槛很低但配置项非常细。很多人只知道lftp host进去传文件却不知道正确配置.lftprc能让它在生产环境稳定很多。2.1 安装与版本确认Debian/Ubuntu系统直接apt install -y lftpCentOS/RHEL系列yum install -y lftp装完以后先确认版本不同版本的默认行为略有差异我在调试时会习惯性先看版本lftp --version目前主流发行版自带的都是4.8或4.9版本功能上够用。2.2 全局配置文件.lftprclftp的全局配置位于/etc/lftp.conf用户级配置在~/.lftprc。我的建议是机器级别的参数写到用户配置里脚本级别的参数写到每次命令里这样既不会影响其他业务又方便不同任务覆盖。我的~/.lftprc通常长这样set net:timeout 10 set net:max-retries 5 set net:reconnect-interval-base 5 set net:reconnect-interval-multiplier 2 set ftp:passive-mode on set ftp:charset UTF-8 set file:charset UTF-8 set ssl:verify-certificate no解释一下每个参数的实际含义net:timeout是单次网络操作的超时时间单位秒。设太小容易误判设太大会让故障卡很久。10秒是我在混合网络环境下试出来的折中值。net:max-retries是最大重试次数。跨境传输建议调到10内网传输默认5就够。reconnect-interval-base和reconnect-interval-multiplier控制重连间隔的递增策略。第一次等5秒第二次乘2变10秒再往后20秒、40秒避免断线时疯狂请求服务器。ftp:passive-mode on是FTP被动模式的开关这是NAT环境下最稳妥的模式。ssl:verify-certificate no只建议在内网或对方使用自签证书时开启。2.3 连接方式与书签日常手动操作时我直接这样连接lftp -u username,password -p 21 ftp.example.com进入lftp的交互界面后可以先用ls确认目录结构再执行mirror或pget。对于固定的传输关系我强烈建议用书签保存连接信息bookmark add prod_backup ftp.example.com之后每次连接只需要lftp prod_backup书签配置会单独存在~/.lftp/bookmarks里比每次敲一长串参数省心得多。有一点必须提醒不要把密码直接写在交互输入里以外的任何明文文件里。如果脚本要自动登录优先用~/.netrc管理凭证并将权限设为600后面自动化部分我会具体讲。3. mirror命令目录级镜像同步的正确姿势lftp的mirror是整个工具里价值最高的命令。它解决的不只是“把A传到B”而是“让B永远跟上A的变化”。我所有的服务器间目录同步都靠它完成。3.1 mirror核心参数速查用mirror之前先把它的核心参数吃透。我用一个表格汇总了最常碰到的参数后面会逐个展开说明。参数作用我的使用频率-R/--reverse反向镜像把本地推到远程高--parallelN文件级并行N是并发数高--continue/-c断点续传高--only-newer只传更新的文件按mtime判断高--delete删除目标端多余文件低需谨慎--exclude排除匹配正则的文件目录高--dry-run/-n只预览不实际传输高--verbose显示详细传输过程调试时用3.2 完整拉取远程目录到本地最基础的用法是从远程服务器拉取整个目录同时保持断点续传和并行下载lftp -u user,pass -p 21 ftp.example.com EOF mirror --parallel8 --continue --only-newer /remote/data /local/data bye EOF这里有几个关键点--parallel8表示同时下载8个文件。这个值不是越大越好太大会让远程老旧FTP服务器直接拒绝连接我在踩坑部分会细说。--continue保证中途中断后下次执行时从断点继续而不是从头开始。--only-newer让增量同步变得高效只拉取比本地新的文件。3.3 反向推送本地目录到远程有时候本地才是数据源需要把本地新产生的文件推送到其他服务器lftp -u user,pass -p 21 ftp.example.com EOF mirror -R --parallel4 --only-newer --continue /local/data /remote/data bye EOF-R反转过来了第一个参数是本地路径第二个参数是远程路径。这个写法我在日志备份、程序包分发场景里用得最多。3.4 增量同步的判断机制lftp默认判断“文件是否需要传输”的依据是大小只要大小相同就跳过即使内容变了也不重传。所以如果你只改文件内容不改大小默认情况下lftp不会发现这个变化。要规避这个问题加--only-newer后判断逻辑变成大小加mtime思路是“大小不同或者修改时间更新就重新传输”。这比单纯比大小靠谱但也不是万无一失。如果两台服务器的系统时间差太多mtime判断会失真所以做同步前最好先确认两端系统时间基本一致。3.5 第一次执行前必须做dry-run凡是涉及生产目录的mirror我执行前必加--dry-runlftp -u user,pass -p 21 ftp.example.com EOF mirror -n --parallel8 --only-newer /remote/data /local/data bye EOF-n会打印出将要做的操作但不会真传任何文件。这一步能帮你提前发现路径写错、排除规则不生效、对方权限不足等问题避免直接操作后把目录结构搞乱。3.6 稳妥使用--delete参数--delete会让目标端删除源端没有的文件从而让两端完全一致。这个参数很危险因为一旦源端某个目录被误删目标端同步时也会跟着删掉。我的使用准则是只对明确且可重建的目录使用比如缓存目录、临时导出目录。首次同步先在测试目录验证全流程。生产环境至少留一份完整的独立备份。做严格镜像时可以这样写mirror --parallel8 --delete --only-newer --continue /remote/data /local/data删文件之前lftp会逐条提示但脚本环境下这些提示不会弹出来等你确认所以务必靠dry-run先看清楚它会删什么。4. 大文件传输pget多线程下载与断点续传的实战价值目录级同步用mirror单文件大块头的传输就要靠pget了。pget全称是“parallel get”它会把一个文件切成多个分段同时从服务器拉取下载速度提升非常明显。4.1 pget单文件多线程下载比如远程有个4GB的数据库备份包我在lftp交互界面里这样拉lftp -u user,pass -p 21 ftp.example.com pget -n 8 -c /remote/backup_2024.tar.gz-n 8是把文件切成8段并行拉取-c是断点续传。这里要特别解释一下断点续传的底层行为lftp在下载过程中会生成一个隐藏的临时进度文件记录每个分段各自下到了哪里。传输中断后再次执行pget它会读取这个进度文件只把未完成的分段娶回来全部拉完后才重命名成正式文件。所以你在服务器上看到一个类似backup_2024.tar.gz.lftp的隐藏文件时千万不要手欠去删它删了等于之前的进度全丢。我在生产上见过新同事清理“垃圾文件”把临时文件清掉的惨案几百GB的传输瞬间回到原点。4.2 pget和mirror的选择边界很多新手会问既然pget能多线程为什么不把所有小文件也用pget拉答案是分块并行的开销在小文件上是浪费。一个几百KB的文件切成8段每段几十KB网络往返和调度开销反而拖慢速度。合理的做法是大量小文件用mirror靠--parallel做文件级并行。少数大文件用pget靠-n做块级并行。大小文件混合先mirror拉整体目录再把个别大文件单独pget补拉。4.3 限速与带宽保护生产服务器往往不只是专职传文件还跑着业务。用pget把带宽全部占满会导致线上接口响应变慢。lftp支持限速在交互界面里直接设置set net:limit-rate 10485760 pget -n 4 -c /remote/bigfile.isonet:limit-rate的单位是字节每秒10485760就是10MB/s。这个限制对当前会话内所有传输生效我每次在业务高峰期做传输任务前都会主动设置避免影响核心链路。4.4 传输后的完整性校验lftp本身保证传输字节不丢失但这种保证依赖TCP层面的校验。对于特别重要的数据我建议传输完成后主动对比校验和。在远程机器上算md5sum /remote/bigfile.iso /remote/checksum.md5本地算完再和远程的checksum文件比对md5sum -c checksum.md5有些场景还会额外对比目录层级结构和文件总数比如用du对比两端总体大小。虽然粗暴但能快速发现漏传或目录结构不一致的问题。5. 自动化从手动传输到crontab定时同步服务器间文件传输最难的不是单次跑通而是长期稳定地自动执行。这一节我把自己已经跑了一年多的同步脚本简化后放出来里面的每一个细节都是从故障里磨出来的。5.1 凭证管理用.netrc而不是明文变量脚本里直接写密码是最常见的坏习惯。稍微一疏忽密码就会通过shell历史、进程列表或者日志泄露出去。我用的是~/.netrcmachine ftp.example.com login sync_user password 这里填密码这个文件必须设置权限为只有当前用户可读chmod 600 ~/.netrc之后脚本里连接时lftp会自动读取netrc里的凭证无需在脚本里写入任何密码。这一步是自动化脚本能上生产环境的前提。5.2 完整同步脚本模板下面是一个可供直接修改使用的脚本#!/bin/bash FTP_HOSTftp.example.com REMOTE_DIR/remote/data LOCAL_DIR/local/data LOG_BASE/var/log/lftp_sync RUN_DATE$(date %Y%m%d_%H%M%S) LOG_FILE${LOG_BASE}/sync_${RUN_DATE}.log mkdir -p ${LOG_BASE} # 设置会话级配置避免历史配置干扰 lftp ${FTP_HOST} EOF ${LOG_FILE} 21 set net:timeout 15 set net:max-retries 8 set ftp:passive-mode on set ssl:verify-certificate no mirror --parallel8 --continue --only-newer \ --exclude*.tmp \ --excludecache/ \ ${REMOTE_DIR} ${LOCAL_DIR} if mirror_exit_code 0 echo sync success else echo sync failed endif bye EOF # 检查lftp退出码 if [ $? -ne 0 ]; then echo ERROR: lftp exit code $? ${LOG_FILE} exit 1 fi # 只保留最近30天日志 find ${LOG_BASE} -name sync_*.log -mtime 30 -delete脚本里有两个值得注意的细节一是--exclude排除了临时文件和缓存目录这能避免把垃圾数据同步过去二是日志文件按时间戳命名配合find清理规则避免日志无限膨胀。不过上面heredoc里的if mirror_exit_code 0是示意写法真实生产环境中我建议用更朴素的方式判断成功失败直接用外层shell的退出码。lftp执行完交互命令后自身返回码能反映整体操作是否遇到致命问题。所以下面这个简化版本更适合直接使用#!/bin/bash FTP_HOSTftp.example.com REMOTE_DIR/remote/data LOCAL_DIR/local/data LOG_FILE/var/log/lftp_sync_$(date %Y%m%d).log lftp ${FTP_HOST} ${LOG_FILE} 21 EOF set net:timeout 15 set net:max-retries 8 set ftp:passive-mode on set ssl:verify-certificate no mirror --parallel8 --continue --only-newer --exclude*.tmp ${REMOTE_DIR} ${LOCAL_DIR} bye EOF EXIT_CODE$? if [ ${EXIT_CODE} -eq 0 ]; then echo [$(date)] sync OK ${LOG_FILE} else echo [$(date)] sync FAILED, code${EXIT_CODE}, will try again next hour ${LOG_FILE} fi5.3 配crontab定时执行同步任务最怕的是“定时任务每次都跑但没实际传数据”所以我把重试和告警都放在脚本外部。crontab配置如下0 2 * * * /opt/scripts/ftp_sync.sh凌晨2点执行对在线业务的影响最小。如果当天同步失败我的建议是不要立刻自动重试而是等到下一个调度周期再跑避免在故障状态下反复对服务器施加压力。5.4 并发数、带宽与调度策略并发数的选择不是拍脑袋定的。我一般根据三类因素推算源服务器磁盘IO能力、目标服务器磁盘IO能力、两端之间的带宽。千兆内网对延迟不敏感8到10个并发通常能跑满带宽跨境链路延迟高4到6个并发已经足够。带宽保护在自动任务里同样重要。在脚本的lftp会话中加上set net:limit-rate 52428800把传输速率限制在50MB/s配合凌晨时段基本不影响正常业务。这个值需要根据生产环境实际带宽调整不要照抄我的数字。6. 生产环境里踩过的坑防火墙、中文编码与超时问题最后这部分是全文含金量最高的地方。下面每个坑都是我实际遇到过、并花时间定位过的按影响严重程度排序。6.1 日志文件用中文命名导致乱码和漏同步有一阵子公司内部系统导出的报表文件名全带中文同步到新服务器全部变成乱码。排查后发现FTP服务端是GBK编码而lftp默认按UTF-8解析文件名两边对不上。解决方式是在.lftprc或会话里声明两端编码set ftp:charset GBK set file:charset UTF-8ftp:charset表示远程FTP服务器使用的字符编码file:charset表示本地文件系统保存文件时要转换成的编码。不同老系统的编码千差万别跑一次ls看到乱码文件时优先怀疑这个配置项而不是怀疑lftp坏了。6.2 防火墙导致的“数据连接超时”FTP有两种工作模式。主动模式下服务器主动往客户端的随机端口发起数据连接这通常会被客户端防火墙拦截被动模式下客户端往服务器的随机端口发起数据连接相对安全。lftp默认使用被动模式。但如果FTP服务器处在NAT设备后面被动模式要求NAT设备开放动态端口段很多企业防火墙没有放行完整端口段就会出现登录成功、ls正常一执行mirror就报“数据连接超时”。我的排查套路是先关闭被动模式试一次set ftp:passive-mode off如果是主动模式能连通但被动模式不通基本可以确定是服务器侧的被动端口未放行。联系网络管理员开放对应端口范围或者切换到主动模式并保证服务器能访问客户端的出网端口。6.3 并发过高导致连接被服务器拒绝有次在高端配置服务器上我把--parallel开到32满心期待带宽能翻倍结果刚跑几分钟FTP服务器就拒绝连接了。查日志发现对方服务器限制了单个IP的最大连接数32个文件并发加上每个连接本身的控制连接直接触发了防火墙的连接数阈值。后来把并发降到8问题消失。这个教训告诉我并发数不是越高越好它必须同时考虑源端文件数量、目标端连接限制和网络带宽三者的短板。跨公网传输时我甚至会先把并发设为4观察两三分钟再无脑调高。6.4 大目录镜像时的内存占用问题lftp在处理超大目录时会在内存中维护待处理文件列表。目录里文件数量到百万级别lftp进程的常驻内存可能冲到几百MB甚至更高。这在老服务器上容易引发OOM。我的应对方案是先分段同步只同步最近一个月的数据历史数据按年份分批处理避免单次任务面对几百万个文件。必要的时候用--include和--exclude组合把大目录拆成小批次mirror --parallel8 --include2024/ --continue /remote/data /local/data6.5 传输中断但日志显示成功最误导人的一种情况从交互界面手动执行mirror中途CtrlC中断lftp的某些子命令调用链被打断但shell拿到的退出码却是0。我在脚本里遇到过几次检查日志发现文件数对不上才意识到。后来我养成了两个习惯一是脚本里严格检查退出码二是传输结束后在本地统计文件数和目录大小与远程对比。文件数对比用得很频繁lftp ${FTP_HOST} -e du -s ${REMOTE_DIR}; bye本地执行du -s再比对只要总量级差距过大立马告警不依赖lftp自己的状态码。写在最后用lftp做服务器间文件传输本质上是用“可重试、可并行、可镜像”的思路去对抗不稳定的网络环境。工具本身不复杂复杂的是替它考虑好各种边界情况并发开多大、编码怎么设、超时定多少、密码放哪里、文件怎么校验。我把这些经验沉淀成了自己的一套检查清单每次接到新的传输任务只要对照执行基本不会出大问题。如果你也有类似的实践心得欢迎在评论里补充交流尤其期待看到你在极端网络条件下的调度策略。