本地文件缓存的数据一致性解决
记录一次本地缓存的踩坑史,从GET探测到版本号
更新于 2026.08.07
本文目录 8 节

背景
最近在开发OJ系统。OJ系统是一整个微服务的设计,承担判题的判题核心被拿出来独立作为一个服务。用户提交题目后把判题任务丢到MQ,判题服务从MQ拉取判题任务并更新数据库,这是我的解耦方案。
在这个过程中,代码沙箱又被我单独拿了出来,作为一个完全外部的服务,仅提供代码执行服务的API调用,而不涉及任何OJ的业务。
在过去一段时间里,我围绕输入数据缓存这件事,走了不少弯路。代码沙箱,会经常接收外部的输入数据作为stdin给执行的代码作为标准输入。在OJ场景中,一道题目的输入数据可能大至几MB,作为text存储在DB中显然不太合适。于是我决定使用文件存储题目的评测数据。
涉及到文件服务,就不免得使用到阿里云OSS\MinIO这一类对象存储服务。
两个问题是显然的。
- 不能每次都从OSS下载评测数据,因此要建立本地缓存机制
- 题目评测数据有可能更新,要维护OSS和本地缓存的数据一致性
最开始的方案,代码沙箱暴露一个更新缓存接口,我自认为是合理的。但是一部署上线,问题就暴露出来了。沙箱可能多服务器多实例部署,通过挨个请求沙箱接口清除缓存的方式实在难以维护。
踩坑史
这样,我似乎合理地认为探测评测数据的职责应该交给使用者,也就是代码沙箱来做。通过搜索,我发现对象存储服务会提供一个ETag,作为一个文件的唯一标识(这个提法可能欠妥)。可以通过比对本地和远端两个ETag来更新数据。
代码沙箱接收可GET的URL
最初的想法很直观。沙箱拿到一个预签名 URL,先发 HEAD,看 ETag 是否变化,变了再 GET 下载 ZIP。看起来没问题,但实际上,在预签名URL时要指定请求的HTTP方法。如果我们签名GET方法,是没有办法进行HEAD探测的。
由于着急跑通整个流程,我先降级方案,直接通过GET方法探测ETag,然后比对数据。这么做以后系统很快跑起来了,开始做其他功能的完善
这样其实有一个很大的问题。通过GET方法去请求OSS,实际上也是下载了一遍,只是使用文件的服务把后面的内容给丢弃了,这会造成大量的资源浪费。
修改接口,代码沙箱接收两个URL
痛定思痛,决定对代码沙箱开刀。把相关接口改成接收两个URL,同时接收HEAD和GET方法的URL。于是一顿改代码,阿里云报了以下错误:
java.lang.IllegalArgumentException: Only GET or PUT is supported! at com.aliyun.oss.model.GeneratePresignedUrlRequest.setMethod(GeneratePresignedUrlRequest.java:121) ~[aliyun-sdk-oss-3.17.4.jar:na] at ...
是的,Aliyun不完全兼容Amazon S3 API,不支持HEAD方法预签名。尽管MinIO支持,但受制于没有足够的服务器资源,遂决定完全毙掉该方案。
最终方案
绕了一圈之后,我终于意识到一个问题。
我一直在思考“怎么探测 OSS 上的数据有没有变”,却忽略了一个更关键的问题:
为什么要让沙箱去探测?
沙箱只是一个纯执行服务。它不知道题目,也不知道评测数据的业务语义。它只是接收一段代码、一组输入,然后执行。
真正知道“这份数据什么时候变了”的,是判题服务。
当我把这个问题想清楚之后,整个设计一下子变简单了。
把版本交还给判题服务
最终的思路其实非常朴素:
- 判题服务在下发任务时,携带一个
inputDataVersion - 沙箱只做字符串精确比较
- 相同就用本地缓存
- 不同就重新下载
不再做 HEAD、不再解析 ETag、不再关心 OSS 的实现差异。
沙箱从“主动探测远端数据是否变化”,变成“被动接收一个版本号”。
职责一下子清晰了。
新的执行流程
现在的流程是这样的。
- 判题服务构造批量执行请求:
inputDataUrlinputDataVersion
- 沙箱收到请求后:
- 根据 URL 提取
objectKey - 定位本地缓存目录
- 读取
_meta.properties中的version - 做字符串比较
- 根据 URL 提取
情况一:版本一致
直接从本地读取数据,不发任何 HTTP 请求,没有 RTT,没有带宽消耗,纯本地 IO。
情况二:版本不一致或没有缓存
发起一次 GET、下载 ZIP、解压、写入 _meta.properties、记录 version,流程结束。
整个过程没有 HEAD,也没有双 URL 的复杂路径。
Over
起初,我执着于让代码沙箱变得“聪明”,试图让它自主感知数据的变化。但实际上,沙箱作为一个纯粹的执行单元,它最核心的价值是“快”和“稳”,而不是“懂业务”。当我们把定义的权力交还给判题服务,把复杂的网络探测简化为一次字符串比对,整个系统不仅性能提升了,逻辑也变得异常稳固。
评论
无需登录,审核通过后公开。只有博主可以回复。
正在加载…
已公开的评论