本地缓存适合读多写少、更新不频繁且允许短暂不一致的场景,如用户配置、静态字典;优势是零网络开销、纳秒级延迟、百万级QPS,但存在进程重启丢失、多实例不同步、无法主动失效等问题。

本地缓存适合什么场景?
当数据读多写少、更新不频繁、且允许短暂不一致(比如用户配置、静态字典、开关状态),sync.Map 或 ristretto 这类内存缓存就足够了。它没有网络开销,延迟在纳秒到微秒级,吞吐量轻松过百万 QPS。
但要注意:进程重启后全丢;多实例部署时各节点缓存不同步;无法主动失效——比如你改了数据库里某条商品价格,所有本地缓存不会自动刷新。
- 适用:
http.Handler内部临时缓存请求上下文、短生命周期的计算结果 - 慎用:
goroutine频繁写入同一sync.Map键——高并发下会退化为锁竞争,不如预分配map + sync.RWMutex - 别踩坑:
ristretto默认不开启OnEvict回调,想做缓存穿透防护或日志埋点得手动配
分布式缓存该选 Redis 还是其他?
Redis 是事实标准,但不是万能解。如果你需要强一致性(比如库存扣减)、原子操作(INCR、SETNX)、或复杂数据结构(ZSET 做排行榜),那必须上 Redis。但它的网络 RTT、序列化开销、连接池争用,会让 P99 延迟跳到毫秒级。
如果只是做纯读缓存,且能接受最终一致,Redis Cluster 节点扩缩容时会出现短暂 MOVED 或 ASK 错误;而 etcd 或 Consul 更适合元数据类缓存(服务发现、配置中心),它们不支持 LRU 驱逐,也不适合存大 Value。
立即学习“go语言免费学习笔记(深入)”;
- Redis 用连接池:Go 客户端如
github.com/redis/go-redis/v9必须设置MinIdleConns和MaxConnAge,否则空闲连接堆积导致 TIME_WAIT 爆满 - 避免大 Key:
GET一个 10MB 的 JSON 会阻塞 Redis 单线程,也拖慢 Go 的 goroutine;应提前拆分或压缩 - 不要用
KEYS *:生产环境必须禁用,改用SCAN分批处理
本地 + 分布式混合缓存怎么搭才不翻车?
常见模式是「先查本地,未命中再查 Redis,回填本地」,但这个逻辑本身有竞态:两个 goroutine 同时查不到,都会去 Redis 加载,造成击穿和重复写本地缓存。
正确做法是加一层轻量级本地锁(比如 singleflight.Group),让同 key 的并发请求只放行一个去加载,其余等待返回。同时要控制本地缓存 TTL 略短于 Redis,防止本地一直不更新。
var cacheGroup singleflight.Groupfunc GetItem(id string) (Item, error) { // 先查本地 if item, ok := localCache.Load(id); ok { return item.(Item), nil } // 未命中,用 singleflight 防击穿 v, err, _ := cacheGroup.Do(id, func() (interface{}, error) { item, err := redisClient.Get(ctx, "item:"+id).Result() if err != nil { return nil, err } localCache.Store(id, item) // 回填本地,TTL 设为 redisTTL - 5s return item, nil }) return v.(Item), err }
- 本地缓存键名和 Redis 键名必须严格一致,否则回填失效
-
singleflight不处理缓存删除,DELRedis 后本地仍存在脏数据——需配合发布订阅(如 Redis Pub/Sub)通知其他节点清本地 - 别把
time.Now().Unix()当作本地缓存过期依据,Go 的time.Time不可比较,要用time.Since()判断是否超时
一致性到底能不能兼顾性能?
不能。这是个明确的取舍:你要强一致(比如订单状态变更后立刻可见),就得牺牲性能——用 Redis 事务 + Lua 脚本保证读写原子性,或引入消息队列异步双删;你要高性能,就得接受几秒甚至几分钟的不一致,靠定时任务或监听 binlog 主动刷新缓存。
最容易被忽略的是「缓存雪崩」:大量 key 设置相同过期时间,到期后集体失效,瞬间打垮下游 DB。解决方案不是加随机 offset(治标),而是用「永不过期 + 后台异步更新」策略,或者用 Redis 的 EXPIRE 配合 GETEX 命令实现懒更新。
- 双删失败无重试机制?必须记录失败日志并走补偿任务,否则缓存永远不一致
- 本地缓存没设最大容量?
ristretto默认 128MB,但若 key 小 value 大(比如缓存整张用户表),OOM 风险极高 - 用
json.Marshal存 struct 到 Redis?注意字段 tag 是否含omitempty,空值可能被忽略导致反序列化失败











