优先使用 github.com/patrickmn/go-cache,它并发安全、清理稳定、API简洁;手写 sync.Map + 过期逻辑易致竞态、内存泄漏和过期项滞留,且难以兼顾精确清理与读写性能。

别自己封装带过期的 map —— 95% 场景直接用 github.com/patrickmn/go-cache 就够了。它默认并发安全、清理稳定、API 简洁;手写容易漏删过期项、触发竞态、内存泄漏,且加压后很快失控。
为什么 sync.Map + time.Now() 手写过期缓存大概率出错
常见现象是:Get 返回空但没报错,或内存持续上涨、过期项从不消失。
- 只在
Get时检查expireAt.Before(time.Now()),却没配对调用Delete→ 过期项滞留 - 用
sync.Map后想遍历清理?Range期间不能安全Delete,强行删会丢数据或 panic - 每次
Get都调time.Now():单次开销小,但高并发+锁竞争下延迟明显放大 - 没后台 goroutine 清理:过期项永远卡在内存里,直到下次
Get触发——业务不可控 - 自己起 goroutine 定期扫 map:得手动处理 stop 信号、防重复启动、规避读写冲突,复杂度直线上升
go-cache 初始化两个 time.Duration 参数到底管什么
New(defaultExpiration, cleanupInterval) 中两个参数常被误解:
-
defaultExpiration:仅用于Set时不传duration的兜底值(比如cache.DefaultExpiration表示永不过期) -
cleanupInterval:是 janitor goroutine 的扫描节奏,不是精确过期时间 - 设为
0:禁用自动清理 → 全靠Get惰性删 → 内存泄漏风险高 - 设太小(如
100 * time.Millisecond):goroutine 频繁唤醒,CPU 毛刺明显,低负载时纯属浪费 - 推荐值:业务能容忍的过期延迟上限的 2–3 倍。例如允许最多 5 秒残留,就设
10 * time.Second
Get 返回 interface{} 必须显式类型断言,否则容易静默失败
go-cache 不做运行时类型校验,Get 总是返回 (interface{}, bool):
- 正确写法:
v, ok := c.Get("key").(*User);ok == false表示类型不匹配或 key 不存在 - 错误写法:
v := c.Get("key").(*User)→ 类型断言失败时 panic - 更隐蔽的坑:
ok == false但没检查,直接用v→ nil pointer dereference 或逻辑错乱 - 如果 value 是基础类型(如
int),断言要写成.(*int),不是.(int)—— 因为Set存的是指针
真正难的不是“怎么存”,而是“什么时候删干净”和“删的时候别人还在读写”。go-cache 把 janitor、锁粒度、清理时机都封好了;自己硬拧,往往第一版跑通,第二版加监控就崩,第三版扛不住 QPS 500 就得重写。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











