golang-lru/v2 适合缓存文件路径、大小、修改时间等轻量元数据,应选用 lru.New[string]*FileInfo,容量设为1024–4096,避免使用 expirable.NewLRU,ETag 需基于内容计算,Range 请求须直通磁盘,上传后需显式失效并通知其他节点。

sync.Map 不适合直接缓存大文件内容,map[string][]byte 更易失控——内存爆掉前你可能只缓存了几十个 2MB 的图片。真正可控的文件缓存,得靠分层 + 语义适配 + 显式生命周期控制。
用 golang-lru/v2 替代裸 map 管理热点元数据
文件缓存的“热”不在全文,而在路径、大小、修改时间、ETag 这些轻量元数据。这些值小、访问频、需淘汰策略,正适合 golang-lru/v2。
- 选
lru.New[string]*FileInfo:键是文件路径(如/assets/logo.png),值是结构体指针,避免拷贝开销 - 容量设为 1024–4096,足够覆盖常见静态资源目录层级,又不会撑满 GC 堆
- 写入时调用
cache.Add(path, &fi),读取失败再触发磁盘加载,不提前预热 - 不要用
expirable.NewLRU:文件修改时间本身已含时效性,过期逻辑应由os.Stat()对比驱动,而非固定 TTL
Range 读必须绕过内存缓存,直通本地磁盘文件
HTTP Range: bytes=100-199 请求若走 []byte 内存缓存,等于强制加载整文件——违背流式读设计初衷。
- 内存缓存只用于全量 GET 或 HEAD;Range 请求一律跳过内存层,用
os.OpenFile(..., os.O_RDONLY)+file.ReadAt(buf, offset) - 本地磁盘缓存目录必须用固定哈希分片(如
./cache/ab/cd/efgh...),避免单目录 inode 膨胀拖慢stat - 对 SSD 设备,启用
mmap(用memmap库或自封装)可提升大文件顺序读吞吐,但注意munmap时机,别让脏页堆积
http.FileServer 不能直接用,要包裹自定义 http.FileSystem
标准 http.FileServer 不设缓存头、不校验 ETag、不支持按需加载,拿来就上线等于放弃 HTTP 缓存红利。
- 实现
Open(name string) (http.File, error)方法,在返回前注入Cache-Control和动态ETag -
ETag必须基于内容计算(md5.Sum(fileBytes)),不能用硬编码字符串或修改时间——外部进程改文件时,os.Stat().ModTime()可能不变 - 若响应 304,务必在
WriteHeader(304)后立即 return,禁止继续调用Write,否则会触发http.ErrBodyWriteAfterHeaders
写操作后必须显式失效,不能依赖“自动同步”
没有分布式锁,也没有中心协调器,上传覆盖一个文件后,其他节点不会知道——除非你主动通知。
立即学习“go语言免费学习笔记(深入)”;
- 上传成功后,调用
cache.Remove(path)清除本节点元数据缓存 - 同时向 Redis Pub/Sub 发布
file:invalidate消息,载荷为{"path":"/x/y/z","version":123456789} - 其他节点监听该 channel,收到后仅做
cache.Remove(path),不拉取新内容——下次读时按需回源 - 不要广播文件内容本身:带宽和序列化成本远高于一次
HEAD校验



















