Go用Redis做分布式缓存需配置连接池、带超时context、空值占位防穿透、TTL扰动防雪崩、慎用本地缓存;细节决定生产可用性。
Go 用
做分布式缓存,别直接上
就完事
很多 Go 项目一说“分布式缓存”,立刻拉
,配个
,写两行
/
就上线。结果压测时连接数暴涨、超时频发、缓存击穿没兜底——不是库不行,是没处理好连接复用和上下文生命周期。
默认不启用连接池复用,每个请求都可能新建连接;必须显式配置
(根据 QPS 和 Redis 实例规格调)
所有
/
调用必须带
,且不能传
这种永不过期的上下文;建议统一用
别在 handler 里反复
—— 如果
是结构体,先
再存;Redis 不认 Go 类型,直接传 struct 会存成空字符串
缓存穿透:查不到的 key 频繁打到 DB,
怎么兜住
用户查一个永远不存在的
,缓存没命中,DB 也没数据,下次还来——这就是穿透。靠 Redis 自身解决不了,得在 Go 层加一层“空值占位”逻辑。
查询前先
,如果返回
,说明缓存里真没有;这时不要直接查 DB,而是先
(注意值是字符串
,不是
)
后续请求再读到这个
,就跳过 DB 直接返回空响应;但要注意:业务层解包时得判断
,否则会把字符串当真实数据
别用
手动实现布隆过滤器——Go 生态有现成的
,但小项目没必要,空值缓存 + 短 TTL 更简单可控
缓存雪崩:大量 key 同时过期,DB 瞬间被打垮,
别写死
所有缓存都设
,整点一到,全崩。这不是 Redis 的问题,是 Go 代码里没做过期时间扰动。
设置 TTL 时别直接写死:
;改成
别在全局 init 或变量初始化阶段生成随机数种子——Go 的
默认用固定 seed,每次重启后 jitter 都一样;应该在每次 Set 前调
或用
的线程安全变体
如果用了 Redis Cluster,还要注意:不同 slot 的 key 过期时间扰动要独立计算,不能共用一个 jitter 变量
本地缓存 + 分布式缓存双写不一致,
不是银弹
有人想“加速热点 key”,在 Go 进程内加一层
,读缓存先查本地,查不到再查 Redis,写的时候两个地方一起更新。听起来合理,实际极易出错。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
立即学习
“
go语言免费学习笔记(深入)
”;
适合读多写少,但一旦涉及“删除”或“更新”,本地 Map 和 Redis 很难严格同步;比如 A 服务删了 Redis 的 key,B 服务本地 Map 还缓着旧值,就会返回脏数据
更稳妥的做法是只用 Redis,但给热点 key 加
(Redis 6.2+)或用
脚本保证原子读+续期;或者用
这类带 TTL 和淘汰策略的本地 cache 库,它支持基于 LFU 的自动驱逐,比手写
安全得多
如果真要用双写,务必加版本号或时间戳字段,读时校验本地缓存的
是否等于 Redis 中的
,不等就丢弃本地值并重载
分布式缓存真正难的从来不是“怎么连上 Redis”,而是怎么让多个 Go 实例在失效、穿透、雪崩、并发更新这些边界条件下,行为可预测、数据不越界。参数、上下文、时间扰动、空值标记——这些细节堆起来,才构成生产可用的缓存逻辑。
redisgithub.com/go-redis/redis/v9go-redis/v9redis.NewClientSetGetredis.NewClient&redis.Options{PoolSize: 20}GetSetcontext.Contextcontext.Background()ctx, cancel := context.WithTimeout(r.Context(), 500*time.Millisecond)client.Set(ctx, key, val, ttl)valjson.Marshalgo-redisuser_id=999999999client.Get(ctx, key)redis.Nilclient.Set(ctx, key, "null", time.Minute)"null"nil"null"if val == "null" { return nil, nil }SETNXgithub.com/yourbasic/bloomtime.Now().Add10 * time.Minuteclient.Set(ctx, key, val, 10*time.Minute)baseTTL := 10 * time.Minute; jitter := time.Duration(rand.Int63n(int64(2*time.Minute))) ; client.Set(ctx, key, val, baseTTL+jitter)randrand.New(rand.NewSource(time.Now().UnixNano()))math/randrand.New(rand.NewSource(time.Now().UnixNano())).Int63n(...)sync.Mapsync.Mapsync.MapGETEXEVALgithub.com/dgraph-io/ristrettosync.Mapversionversion