LRU缓存必须严格按访问序淘汰且原子同步map与链表,golang-lru/v2是唯一推荐实现;sync.Map无法记录访问顺序,导致缓存失控、key突失、时序紊乱。

别把 LRU 缓存和语言学习类比——它不是“学过的单词慢慢忘掉”,而是严格按访问序淘汰,且必须原子同步 map 和链表状态;用 golang-lru/v2 是唯一推荐路径,自己手写大概率在并发或容量临界时静默丢数据或 panic。
为什么不能用 sync.Map 实现 LRU
sync.Map 本质是分片哈希表,无访问顺序记录能力,Range 遍历不反映最近使用情况。常见错误现象包括:
- 缓存“击穿”后内存持续上涨,明明设了容量上限却完全失控
- 刚
Add的 key 突然Get不到,实际是被其他 goroutine 并发覆盖或 GC 干扰 - 无法实现
MoveToFront类操作,也就没法维护 LRU 所需的时序
golang-lru/v2 的泛型参数必须显式指定
泛型参数省略或写成 any 会导致类型不安全:
-
lru.New[string, int](128)✅ 正确:Get 返回int,无需断言 -
lru.New[any, any](128)❌ 错误:Get 返回any,强制断言易静默失败(比如存int却断言为string) -
lru.New(128)❌ 编译失败:Go 泛型要求显式类型参数
业务若只用一种 value 类型(如 *User),直接定义泛型参数比用 interface{} 更安全、零开销。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
Resize 不是原地扩容,而是复制重建
cache.Resize(256) 行为远比名字暗示的复杂:
- 新建一个容量为 256 的内部链表,将当前存活项按访问序迁移过去
- 迁移全程持有写锁,期间所有
Get和Add阻塞;上万条目时耗时可达毫秒级 - 历史访问热度清零:新缓存中所有迁移进来的 key 都被视为“最近使用”
- 若你依赖访问频次做降级判断(如只对访问 >3 的 key 预热),Resize 后该逻辑立即失效
onEvicted 回调里不能做阻塞操作
onEvicted 在持有写锁的上下文中执行,不是异步:
- 回调内调用 HTTP 请求、
time.Sleep或数据库写入,会拖慢所有缓存操作 - 正确做法是把 key/value 发到 channel 或 worker pool 异步处理
- 回调本身应只做轻量动作:打日志、释放文件句柄、更新统计计数器
真正容易被忽略的是:onEvicted 触发时机不等于“用户主动调用 RemoveOldest”,它会在 Add、Resize、甚至 Remove 导致淘汰时触发——只要淘汰发生,就执行。

















