贝利信息

Golang本地缓存与分布式缓存如何选择_性能与一致性分析

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

本地缓存适合什么场景?

当数据读多写少、更新不频繁、且允许短暂不一致(比如用户配置、静态字典、开关状态),sync.Mapristretto 这类内存缓存就足够了。它没有网络开销,延迟在纳秒到微秒级,吞吐量轻松过百万 QPS。

但要注意:进程重启后全丢;多实例部署时各节点缓存不同步;无法主动失效——比如你改了数据库里某条商品价格,所有本地缓存不会自动刷新。

分布式缓存该选 Redis 还是其他?

Redis 是事实标准,但不是万能解。如果你需要强一致性(比如库存扣减)、原子操作(INCRSETNX)、或复杂数据结构(ZSET 做排行榜),那必须上 Redis。但它的网络 RTT、序列化开销、连接池争用,会让 P99 延迟跳到毫秒级。

如果只是做纯读缓存,且能接受最终一致,Redis Cluster 节点扩缩容时会出现短暂 MOVEDASK 错误;而 etcdConsul 更适合元数据类缓存(服务发现、配置中心),它们不支持 LRU 驱逐,也不适合存大 Value。

本地 + 分布式混合缓存怎么搭才不翻车?

常见模式是「先查本地,未命中再查 Redis,回填本地」,但这个逻辑本身有竞态:两个 goroutine 同时查不到,都会去 Redis 加载,造成击穿和重复写本地缓存。

正确做法是加一层轻量级本地锁(比如 singleflight.Group),让同 key 的并发请求只放行一个去加载,其余等待返回。同时要控制本地缓存 TTL 略短于 Redis,防止本地一直不更新。

var cacheGroup singleflight.Group

func 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 := redisC

lient.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 事务 + Lua 脚本保证读写原子性,或引入消息队列异步双删;你要高性能,就得接受几秒甚至几分钟的不一致,靠定时任务或监听 binlog 主动刷新缓存。

最容易被忽略的是「缓存雪崩」:大量 key 设置相同过期时间,到期后集体失效,瞬间打垮下游 DB。解决方案不是加随机 offset(治标),而是用「永不过期 + 后台异步更新」策略,或者用 RedisEXPIRE 配合 GETEX 命令实现懒更新。