必须先配连接池、超时、前缀和回源逻辑再初始化redis.client:poolsize设为并发数2–4倍(最低10),timeout设300ms,所有操作传带超时的context,键加服务前缀并哈希敏感字段,空值缓存防穿透,singleflight防击穿,错峰过期防雪崩,本地缓存ttl设为远程1/3~1/2且需广播失效。

直接用 redis.Client 初始化后就往业务里塞,90% 的微服务缓存故障都从这一步开始。必须先配连接池、超时、前缀和回源逻辑,再碰业务代码。
怎么初始化一个不泄漏、不断连、不卡死的 *redis.Client
线上最常见的是连接耗尽或请求卡死,根源不是 Redis 慢,而是 Go 客户端没设底线。
-
PoolSize必须显式设,建议为预估并发请求数的 2–4 倍(最低 10),比如 QPS=200、平均延迟 40ms,PoolSize: 24是安全起点 -
Timeout和ReadTimeout/WriteTimeout不能依赖默认值——go-redis/v9默认是 0(无限等待),第一件事就是设Timeout: 300 * time.Millisecond - 所有操作必须传带超时的
context.Context,例如ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second);别用context.Background() - 单节点用
redis.NewClient(),哨兵用redis.NewFailoverClient(),集群用redis.NewClusterClient()——混用会导致路由失败或静默丢请求 - 加
MinIdleConns: 5和MaxConnAge: 30 * time.Minute,防止 SLB 或防火墙静默断连后复用失效连接
缓存键设计不当,等于给所有服务埋雷
键名看着只是字符串拼接,实际决定着运维可查性、多服务隔离性和 cluster slot 分布稳定性。
- 强制加服务前缀,如
"user-service:user:123",而非"user:123";否则上线新服务时可能覆盖旧数据 - 避免把 email、JSON 片段等长/变长字段直接塞进 key——Redis key 长度限制默认 512MB,但实际影响 slot 计算和内存碎片
- 手机号、身份证号等敏感字段,先哈希再拼入 key:
fmt.Sprintf("order-service:order:%x", md5.Sum([]byte(phone))) - 统一用 UTF-8 编码,禁用 gob 序列化结果当 key(二进制不可读、无法 debug、监控系统无法识别)
穿透、击穿、雪崩不是“加锁就能解决”的问题
这三类问题 95% 出在现场完全没做应对,靠 DB 硬扛;防御点不在 Redis 配置,而在 Go 层的 TTL 控制和空值处理逻辑。
- 穿透:对查不到的 key,写入
{"empty":true}并设随机 TTL(如2*time.Minute + time.Duration(rand.Intn(60))*time.Second),避免空 key 集中失效 - 击穿:热 key 过期瞬间大量请求打 DB,用
golang.org/x/sync/singleflight的Do()合并请求,只放行一次回源,其余协程等待返回后统一填缓存 - 雪崩:错峰过期是成本最低的解法——基础 TTL 加
rand.Intn(120)秒偏移;高频配置项甚至可设永不过期,靠事件通知刷新
本地缓存不是“加个 sync.Map 就完事”
本地缓存只有配合远程缓存才有意义,且必须明确谁是权威源、失效如何同步、GC 压力怎么控。
- 本地缓存 TTL 应设为远程缓存的 1/3~1/2(如远程 30 分钟,本地 10 分钟),避免本地 stale 数据长期不更新
- 写操作时,先删 Redis,再删本地缓存;或发 Pub/Sub 消息通知其他实例清理——不能只删本机
- 别用裸
sync.Map存结构体,它不触发 GC 清理;推荐github.com/dgraph-io/ristretto,自带淘汰策略和回调,内存更可控 - 本地缓存只适用于读多写少、一致性要求低的数据(如地区字典、开关配置),用户余额、订单状态这类强一致场景必须直读 Redis
真正难的不是“怎么存”,而是“什么时候删、删哪里、删不掉怎么办”。缓存失效路径比写入路径更复杂,也更容易被忽略——尤其在多实例部署下,一次 cache.Delete() 调用是否广播到所有节点,决定了数据一致性边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











