做共享目录实时同步,踩过这些坑

简介: 本文详解使用 Java WatchService 实现共享目录实时同步的完整方案,对比轮询弊端,解决递归监听、事件漏报、重复触发、重启丢事件等核心问题,通过事件防抖、目录兜底扫描、快照比对补偿机制,实现稳定的 RAG 知识库文件自动同步,梳理生产落地全程踩坑细节与最佳实践。

我们之前的知识库平台,文档入口都在平台内:用户上传文件,系统解析内容,写入向量库,后面再拿来做 RAG。

后来客户提了一个很实际的诉求:他们已经有一套共享目录的使用习惯,不想每次都打开平台再上传一遍。最好是文件丢到共享目录里,系统自己发现、自己同步到知识库。

听起来像是“监听一个目录”这么简单,但真做起来,坑还挺多。

一开始为什么没用轮询

最直观的方案当然是定时扫目录。

比如每 5 分钟把共享目录完整扫一遍,和上一次的文件列表做 diff:新增的同步,删除的删除,变更的重新解析。

这个方案实现成本低,也容易兜底。但我们没有直接把它当成主链路,主要是几个问题:

  • 延迟不好控制:5 分钟扫一次,用户上传后平均要等 2 分半才能被发现;扫得更频繁,IO 压力又上来了。
  • 目录大了以后不划算:共享目录下文件一多,每次全量遍历都挺浪费。
  • 修改判断不够稳:只看文件大小容易漏,只看修改时间又容易受文件系统和网络盘行为影响。

所以最后的思路是:实时监听做主链路,定时扫描做补偿。

实时监听这块,用的是 java.nio.file.WatchService

WatchService 基本够用,但别只看 Demo

WatchService 是 Java 对操作系统文件事件通知的一层封装。Linux 下通常走 inotify,Windows 下是 ReadDirectoryChangesW。

最简单的写法确实很短:

WatchService watchService = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("/share");
dir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);

然后单独起一个线程阻塞等事件:

WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
   
    // 处理事件
}
key.reset();

这里有个小细节:key.reset() 不能忘。忘了以后,这个目录后续事件就收不到了。

但如果只按这个 Demo 写,放到共享目录同步这种场景里,很快就会踩坑。

坑一:它不会递归监听子目录

这是我们第一版最容易想当然的地方。

dir.register(...) 只监听当前这一层目录。比如你监听了 /share,那 /share/A 里面发生了文件变更,默认是收不到的。

而我们的目录结构大概是:

/share/知识库名/文档
/share/知识库名/子目录/文档

层级不固定,用户还可能随时新建目录。所以启动时必须先把整棵目录树走一遍,把每个子目录都注册上。

Files.walkFileTree(root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
    new SimpleFileVisitor<>() {
   
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
   
            register(dir);
            return FileVisitResult.CONTINUE;
        }
    });

还不够。

运行过程中如果用户新建了目录,也要在收到 ENTRY_CREATE 后马上补注册:

if (kind == ENTRY_CREATE && Files.isDirectory(child)) {
   
    registerAll(child);
}

这一步如果漏了,系统看起来还在正常监听,但新目录下面的文件其实已经静悄悄地漏掉了。

坑二:新目录注册得再快,也可能漏文件

这个问题更隐蔽一点。

假设用户不是一个文件一个文件上传,而是直接 cp -r 拷贝一整个目录进来。操作系统可能先告诉你“新建了目录”,然后目录里的文件事件很快就出来了。

理想顺序是:

1. 收到新目录 /share/A/B
2. 给 /share/A/B 注册监听
3. 收到 B 目录下 file1.txt、file2.txt 的创建事件

但实际情况可能是:

1. 收到新目录 /share/A/B
2. 还没来得及注册监听
3. file1.txt、file2.txt 的事件已经发生了

这时文件就漏了。

我们的处理比较朴素:发现新目录后,先注册整棵子树,然后主动扫一遍这个新目录,把里面已经存在的文件按“创建事件”补发出去。

if (Files.isDirectory(child)) {
   
    registerAll(child);
    emitCreateForExistingFiles(child);
}
private void emitCreateForExistingFiles(Path dir) {
   
    try (Stream<Path> pathStream = Files.walk(dir)) {
   
        pathStream
            .filter(Files::isRegularFile)
            .forEach(eventHandler::onCreate);
    }
}

这个逻辑看起来有点重复,但在目录批量拷贝场景下很有用。我们后来也接受了它可能带来的重复事件,因为下游会做幂等。

坑三:MODIFY 事件会比想象中多

文件写入不是一个瞬间完成的动作。

尤其是大文件、网络共享目录、Office 文档这类场景,系统可能连续抛出很多 MODIFY 事件。如果每个事件都触发一次解析和上传,资源会被打爆,处理结果也不稳定。

所以事件进来以后,我们没有立刻执行重活,而是先做了一层防抖。

String debounceKey = eventType + "|" + fullPath;
long now = System.currentTimeMillis();
Long last = debounceMap.get(debounceKey);
if (last != null && now - last < windowMillis) {
   
    return;
}
debounceMap.put(debounceKey, now);

这里的 key 用的是“事件类型 + 文件路径”。这样同一个文件的 CREATEMODIFY 不会互相覆盖。

实际工程里,我们还会等文件稳定一小段时间再处理。否则有些文件刚创建出来,还没写完,解析线程就冲上去了。

坑四:应用重启期间一定会丢事件

WatchService 只负责运行时监听。应用停了,事件就没了,不会帮你补。

这件事在本地测试时不明显,但生产环境一定要考虑:发布重启、机器重启、服务异常退出,这些时间窗口里用户仍然可能往共享目录里放文件。

所以我们加了一套快照比对,专门做补偿。

补偿扫描时,会记录当前目录下文件的这些信息:

  • 文件路径
  • 文件大小
  • 最后修改时间
  • 文件 hash

这些快照存在 MySQL,而不是本地文件。当前虽然是单实例,但后面如果做集群,状态至少不是绑死在某台机器上。

下一次扫描时,把当前快照和上一次快照做 diff:

  • 新出现的文件,补发 CREATE
  • 大小、修改时间或 hash 变化的文件,补发 MODIFY
  • 快照里有、当前目录没有的文件,补发 DELETE

补偿扫描有两个触发点:

  • 服务启动后立刻跑一次,补重启期间的事件
  • 定时跑一次,默认 5 分钟,兜底处理监听漏掉的情况
@Scheduled(fixedDelayString = "${doc.share-sync.reconcile-delay-ms:300000}")
public void scheduledReconcile() {
   
    reconcile("scheduled");
}

这也是为什么前面说,我们不是完全不用轮询,而是不把轮询当实时链路。

最后拆成了几个组件

为了后面好排查问题,我们没有把监听、解析、补偿都塞进一个类里,而是拆成了几个比较明确的组件。

组件 职责
ShareDirWatcher 基于 WatchService 做实时监听,负责递归注册子目录
ShareFileEventHandler 做事件防抖、路径解析,以及把文件事件转换成业务处理
ShareDirReconcileJob 做停机补偿和定时快照比对
ShareFileSnapshotStore 把文件快照持久化到 MySQL

配置上也留了开关,方便分阶段上线:

doc:
  share-sync:
    enabled: true
    dry-run: true
    root-dir: /share
    settle-seconds: 5
    reconcile-delay-ms: 300000

第一阶段我们只开 dry-run,先把日志打全:监听到了什么文件、解析出了哪个知识库、会触发什么动作。路径解析和去重逻辑都确认没问题后,再打开真实写入。

还有几个容易忽略的小点

OVERFLOW 事件

WatchService 的事件队列满了以后,可能会收到 OVERFLOW。这说明中间已经有事件丢了。我们的处理是记录日志,然后依赖下一轮补偿扫描修复。

删除事件只能拿到路径,别指望再判断类型

收到 ENTRY_DELETE 时,文件已经没了。你能拿到的通常只是相对路径,不能再通过 Files.isDirectory 判断它之前是文件还是目录。

所以删除事件统一交给下游处理,由数据库里的历史记录判断之前是什么。

重复事件比漏事件更容易接受

监听程序和补偿程序可能同时发现同一个新文件,重复上传是有可能的。这个问题不能只靠内存去重解决,最后还是要靠数据库唯一索引和业务幂等兜底。

在这类同步任务里,我更愿意接受“重复发现一次”,也不愿意悄悄漏掉一个文件。

小结

这次做下来,我对 WatchService 的定位更清楚了:它适合做实时感知,但不能单独承担“可靠同步”的全部责任。

真正能上线的方案,至少要补上这几块:

  • 递归注册子目录
  • 新目录创建后的兜底扫描
  • 文件事件防抖和稳定等待
  • 重启后的快照比对补偿
  • 下游幂等和唯一索引兜底

最后我们的方案是:监听负责快,补偿负责稳,数据库负责最终一致性。

这套设计不算复杂,但每一块都少不了。尤其是共享目录这种场景,用户怎么拷文件、网络盘怎么抛事件、服务什么时候重启,都不是代码能完全控制的。能做的就是把主链路和兜底链路都设计清楚,先 dry-run 跑一段时间,再逐步放开真实写入。

相关文章
|
13天前
|
消息中间件 数据可视化 API
阿里云短信服务怎么接入?从签名、模板、API 到发送回执,一文讲清楚
本片文章将围绕阿里云短信服务的完整接入链路,拆解从资质申请、签名审核、模板配置、运营商报备,到 API 发送和状态回执的关键步骤,帮助产品经理、运营人员、技术负责人和开发者快速理解短信服务接入流程,提前做好上线准备。
205 5
|
14天前
|
数据采集 人工智能 安全
阿里巴巴 & 蚂蚁共建 LoongSuite GenAI 可观测语义规范:从统一数据语言到规模化落地
阿里巴巴与蚂蚁集团联合推出 LoongSuite GenAI 可观测语义规范,在 OpenTelemetry 标准之上,为 AI Agent、Skill、Token 级推理等场景建立统一数据语言。从链路追踪到引擎“显微镜”,本文揭秘如何让 GenAI 应用真正可看见、可分析、可治理。
167 13
|
28天前
|
弹性计算 安全 关系型数据库
阿里云特惠云服务器99元和199元1年新购续费同价:配置、适用场景与专属组合套餐解析
阿里云推出的99元1年和199元1年新购续费同价云服务器因价格实惠、性能适中,深受个人和普通企业用户的喜爱。99元经济型e实例适合个人开发者等搭建轻量级应用;199元通用算力型u1实例则能稳定支持中小型企业官网等场景。此外,阿里云还提供建站礼包、安全防护、弹性数据库、高效存储及多场景组合套餐等专属优惠,并构建了一个丰富、灵活、高性价比的云产品生态,助力用户无忧上云、轻松降本。
|
28天前
|
编解码 人工智能 API
HappyHorse(快乐小马)介绍指南:150亿参数量、Transformer单流架构,生成视频定价最低0.9元/秒
HappyHorse(快乐小马)是阿里ATH创新事业部研发的原生多模态AI视频生成大模型,2026年4月登顶全球Video Arena双榜。采用40层单流Transformer架构,首创音画联合生成技术,15B参数,支持1080P/3–15秒视频生成,单H100卡38秒出片,中文理解与人物一致性突出,已通过阿里云百炼、官网及千问App开放灰度测试。
1710 7
|
16天前
|
存储 人工智能 固态存储
阿里云4核云服务器租用价格解析:4核8G、4核16G、4核32G配置最新收费标准与活动价格
本文介绍了阿里云4核云服务器的配置选择、价格体系及购买策略。4核配置涵盖经济型e实例、通用算力型u2i/u2a、计算型c9i/c9a、通用型g9及内存型r9等多个实例族,分别适用于个人博客、企业Web应用、AI推理及大数据处理等场景。同时,文中列出了4核8G、16G、32G在各实例下的官方标准价及2026年活动价(如u2i实例4核8G低至1252.63元/年起)。建议用户根据业务需求选型,结合优惠券实现折上折,有效降低上云成本。
|
1月前
|
弹性计算 安全 测试技术
阿里云新用户账号注册流程、新老用户判定及2026年新用户优惠活动
2026年,初次选购阿里云产品的用户需先完成账号注册与实名认证以享受新用户优惠。注册可通过支付宝扫码或账号密码方式进行,并建议绑定电子邮箱。实名认证分个人和企业两类,企业认证可享更高购买限额和专属优惠。新用户判定标准为无收费云产品购买记录,新用户可参与云产品免费试用、轻量应用服务器抢购、ECS云服务器折扣等多重优惠活动,建议用户根据业务需求选择适合的优惠活动,并在购买前领取优惠券以进一步降低成本。
463 6
|
16天前
|
人工智能 架构师 测试技术
AI编程王炸组合:顶级三剑客 OpenSpec 定方向,Superpowers定纪律,Harness定协同
AI编程王炸组合:顶级三剑客 OpenSpec 定方向,Superpowers定纪律,Harness定协同
|
1月前
|
人工智能 弹性计算 安全
阿里云免费部署 Hermes Agent 教程:零门槛搭建自进化 AI 智能体
阿里云免费提供Hermes Agent一键部署方案:基于ECS、百炼大模型与计算巢,零代码、几分钟即可搭建开源自进化AI智能体。支持跨会话记忆、多平台接入、私有化部署,兼顾易用性与数据安全,个人提效与企业数字化皆适用。
|
1天前
|
运维 开发者
同样标注为 Claude,为何效果差异明显:中转链路模型一致性排查实录
同样标注为 Claude,为什么线上效果会出现明显差异?本文基于一次真实排查,给出“总览体检—来源下钻—隔离对照—复检恢复”的工程化方法,重点解决中转链路中的模型一致性与路由漂移问题。适合正在做大模型应用稳定性治理、可观测性建设与故障复盘的团队参考。
32 2
同样标注为 Claude,为何效果差异明显:中转链路模型一致性排查实录
|
1天前
|
SQL 人工智能 JSON
智能问数(Text2SQL)工业级落地,纯 AI 黑盒方案都没戏
本文剖析Text2SQL领域“高准确率宣传”与“无公开DEMO”之间的矛盾,指出黑盒方案因AI幻觉、不可解释、不可审计,难担企业级信任;润乾NLQ采用白盒路线——以人类可读可确认的“规范文本”为中间层,AI仅作翻译,后续规则编译100%确定,真正实现稳定、可解释、可落地的智能问数。

热门文章

最新文章