文件缓存过期需手动管理,Go标准库不支持自动TTL;必须显式记录时间戳、主动校验并删除,配合可控的守护任务与分步防护式清理。

文件缓存过期不能靠系统自动处理
Go 标准库不提供文件写入时自带 TTL 的能力,os.WriteFile 或 os.Create 写出的文件永远不会自动删除。所谓“过期”,必须由你主动记录时间、主动判断、主动调用 os.Remove。常见错误是写完文件就加个注释“有效期5分钟”,结果缓存堆积如山。
写入时必须嵌入可验证的时间戳
不要依赖文件系统 mtime 做唯一依据——它可能被其他进程修改,或因 NFS/容器挂载导致不一致。真正可控的做法是把过期时间显式存进数据本身或配对元数据中:
- 小而结构化的缓存(如 JSON 配置):直接在内容里加
"expires_at": 1749688500字段,读取时json.Unmarshal后校验time.Unix(expiresAt, 0).Before(time.Now()) - 二进制或大文件:写
cache.bin同时写cache.bin.meta,后者只存一行纳秒级时间戳(fmt.Fprintln(metaFile, expireAt.UnixNano())),读主文件前先读 meta 并解析 - 高频写入场景(如每秒千级临时文件):维护一个内存
map[string]time.Time或轻量 SQLite 表,写入后立即注册路径 → 过期时间映射,清理时查表而非遍历文件系统
守护任务要可控、可停、可测
别在 init() 或 main() 里直接起 go func() { for range time.Tick(...) } ——这会导致测试时 goroutine 泄漏、服务未监听就开扫、无法优雅关闭。
- 封装成结构体,带
Start()和Stop()方法,内部用done chan struct{}控制退出 - 启动时机必须晚于服务就绪:HTTP server 调用
ListenAndServe成功后,或 gRPC server 启动完成回调里触发Start() - 停止时机必须早于服务关闭:收到 SIGTERM 后,先调
cacheCleaner.Stop(),再等 goroutine 退出,最后调server.GracefulStop() - 每次扫描前加
if len(fileList) == 0 { continue },空目录跳过,避免无谓调度
扫描与删除必须分步且带防护
用 filepath.WalkDir 扫描目录时,别在遍历回调里直接 os.Remove ——一旦某文件正被其他 goroutine 读取,Remove 可能失败,但更糟的是:若没做错误处理,整个清理流程会中断,后续文件全被跳过。
立即学习“go语言免费学习笔记(深入)”;
- 第一阶段只收集候选路径:
for _, entry := range entries { if stat.ModTime().Before(now) { candidates = append(candidates, entry.Name()) } } - 第二阶段逐个删除,每个
os.Remove单独包裹if err != nil && !os.IsNotExist(err) { log.Printf("failed to remove %s: %v", path, err) } - 删除前加日志:
log.Printf("removing expired cache file: %s (modtime: %v)", path, stat.ModTime()),便于事后审计 - 生产环境建议先
os.Rename(path, tempPath)到隔离目录,再os.RemoveAll(tempDir),避免清理中断导致残留文件卡住磁盘


















