本地缓存远程文件的核心是按需缓存元信息或小块数据而非整文件,推荐落地磁盘(如$home/.cache/myapp/)并结合etag/if-none-match实现零传输验证,避免oom与无效请求。

本地缓存远程文件的核心不是“把文件整个存进内存”,而是按需缓存内容摘要、元信息或小块数据,避免重复 HTTP 请求和解析开销。直接 io.Copy 到内存或用 sync.Map 存完整 []byte 很容易 OOM,尤其面对 MB 级文件时。
用 http.Client + local disk 缓存响应体
真正落地的方案是把远程文件落地到本地磁盘(如 $HOME/.cache/myapp/),再由 Go 程序读取。这比纯内存缓存更可控、可复用、可清理。
- 用
filepath.Join(os.UserCacheDir(), "myapp", "remote")定义缓存根路径,跨平台兼容 - 远程 URL 的 key 应做安全哈希(如
sha256.Sum256([]byte(url)).Hex()[:16]),避免路径遍历或非法字符 - 写入前先
os.MkdirAll(dir, 0755),写完调os.Chmod(path, 0444)防误改 - 读取时检查
os.Stat().ModTime()是否过期,不过期就跳过网络请求;过期则os.Remove()后重拉
用 etag + if-none-match 减少无效下载
绝大多数 HTTP 服务支持 ETag 和 If-None-Match。这是零传输成本的缓存验证机制,比自己维护时间戳更可靠。
- 首次请求后,提取响应头
resp.Header.Get("ETag")并存到本地(比如 JSON 文件或 SQLite) - 下次请求前,设置
req.Header.Set("If-None-Match", etag) - 收到
304 Not Modified就直接读本地副本;收到200就覆盖旧文件并更新 etag - 注意:有些 CDN 或反向代理会删掉
ETag,此时 fallback 到Last-Modified+If-Modified-Since
别用 map[string][]byte 缓存大文件内容
这是新手最常踩的坑:把整张图片、PDF 或 ZIP 读进 map[string][]byte,结果 goroutine 堆积、GC 频繁、RSS 暴涨。
-
sync.Map不解决内存膨胀问题,它只解决并发安全;map里存大[]byte= 引用一直挂着,GC 不回收 - 若必须内存缓存,优先用
bigcache(注意调大shards参数),它内部用[]byte池 + 分片锁,但依然不推荐缓存 >1MB 的 value - 更合理的方式是缓存解析结果:比如远程 YAML 配置,缓存的是
map[string]interface{}而非原始字节流
HTTP handler 里别每次 new cache 实例
像 gcache.New(100).LRU().Build() 这种写法如果放在 handler 内部,等于每请求新建一个 cache,完全失效。
- 必须定义为包级变量或注入到 struct 中,例如:
var fileMetaCache = gcache.New(1000).LRU().ExpireIn(10*time.Minute).Build() - 缓存 key 应包含语义:比如
"meta:" + hash存文件头信息,"content:" + hash存实际内容(后者建议走磁盘) - 如果 value 是结构体且含指针字段(如
*http.Response),记得在Delete()后手动置空,否则 GC 无法回收底层连接或 body
真正难的不是“怎么存”,而是“什么时候删”——磁盘缓存要配定期 find $CACHE_DIR -mmin +1440 -delete,内存缓存要设 MaxSize + 淘汰策略,而 HTTP 头校验逻辑一旦漏掉 304 处理,缓存就彻底退化成摆设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











