You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中如何等待FTP服务器文件完全下载完成后再读取处理

问题核心原因

两个调度线程池未对文件写入状态做协同标识,线程池2无法区分pending目录内的文件是「写入中未完成」还是「写入完成可处理」,直接读取并移动半写文件,最终导致数据截断丢失。

可落地方案

方案1:临时后缀标记(改动成本最低)

  • 实现逻辑:
    1. 线程池1下载文件时,写入过程统一给文件加临时后缀,例如下载目标为order_20240601.csv时,写入中的文件命名为order_20240601.csv.downloading
    2. 等文件完全写入、IO流正常关闭、本地文件大小与FTP服务器记录的源文件大小校验一致后,执行同目录下的原子重命名,去掉.downloading后缀转为正式待处理文件
    3. 线程池2扫描pending目录时,直接过滤所有带.downloading后缀的文件,仅处理命名符合正式规则的文件
  • 优势:不需要引入额外组件,文件系统级标记天然持久化,服务重启也不会出现状态错乱;同分区下文件重命名为原子操作,不会出现中间状态。
  • 注意点:必须加文件大小校验步骤,避免网络闪断、下载线程异常退出时,半写文件被误改名。

方案2:目录隔离拆分(生产环境首选)

  • 实现逻辑:
    1. 新增独立的downloading临时目录,线程池1所有下载中的文件全部写入该目录,文件名可搭配临时后缀做双重标记
    2. 文件下载完成并通过完整性校验后,通过原子移动操作将文件从downloading目录转移到pending目录
    3. 线程池2仅扫描pending目录下的文件,完全不接触下载流程中的文件
  • 优势:两个线程池的操作目录完全隔离,从流程上杜绝了读取半写文件的可能,后续做异常补偿、失败重试也更清晰:重启服务时只需要扫描downloading目录的残留文件,判断是否需要重新下载即可。

方案3:文件锁+状态校验(适配无法改文件名/目录的场景)

  • 实现逻辑:
    1. 线程池1写入文件时,持有当前文件的操作系统级排他锁(例如Java生态使用FileLock实现),写入完成关闭流时自动释放锁
    2. 线程池2扫描到文件后,先尝试获取文件的共享锁,如果获取失败说明文件仍被写入线程占用,直接跳过等待下一个调度周期
    3. 加兜底校验:对成功获取锁的文件,间隔2秒两次读取文件大小,两次大小完全一致再做处理
  • 注意点:不要使用JVM内存级锁,避免服务重启后锁状态失效;部署前需要验证存储介质对文件锁的支持,部分网络文件系统(NFS)的文件锁机制存在兼容性问题。

方案4:内存状态标记(适配低延迟处理场景)

  • 实现逻辑:
    1. 维护线程安全的下载中文件集合(例如使用ConcurrentHashMap存储文件名、下载开始时间等元数据),线程池1开始下载时将文件标记为下载中,完成校验后移除标记
    2. 线程池2扫描文件时,首先过滤掉集合内标记为下载中的文件
    3. 同样保留文件大小双次校验的兜底逻辑,避免服务重启导致内存标记丢失,误处理半写文件
  • 优势:不需要改动文件命名和目录结构,文件下载完成后可以最快速度被线程池2感知,不需要等固定调度周期。
选型建议

优先选择目录隔离+临时后缀的组合方案,开发量极小,可靠性最高,对原有业务逻辑的侵入性最低,就算出现宕机、网络中断等异常,也可以通过扫描临时目录的残留文件做补偿,不会出现数据丢失问题。

内容的提问来源于stack exchange,提问作者Malav Mevada

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:12:23