必须使用哨兵或集群客户端而非硬编码地址:哨兵用redis.NewFailoverClient并传Sentinels列表,集群用redis.NewClusterClient并填seed nodes,单节点仅限测试且需设MaxRetries和MinIdleConns;所有模式均须配置MaxConnAge防止防火墙断连,并强制为每次操作传带超时的context。

Redis连接初始化必须支持哨兵或集群自动发现
硬编码 "localhost:6379" 或固定 IP 列表在容器化、滚动发布、跨可用区部署中必然失效。容灾的第一道防线是客户端能否自动感知拓扑变化。
用 github.com/go-redis/redis/v8 时,不能只写 redis.NewClient。要根据实际部署模式选型:
- 哨兵模式:用
redis.NewFailoverClient,传入redis.FailoverOptions{Sentinels: []string{"sentinel-0:26379", "sentinel-1:26379"}},客户端会自动从哨兵拉取当前主节点地址 - Redis Cluster:用
redis.NewClusterClient,Addrs填至少 3 个 seed node(如[]string{"redis-node-0:7000", "redis-node-1:7000", "redis-node-2:7000"}),客户端启动后自动获取完整拓扑并监听变更 - 单节点(仅测试):才用
redis.NewClient,且必须设MaxRetries: 3和MinIdleConns: 5
所有模式都必须设 MaxConnAge: 30 * time.Minute,否则长连接可能被中间防火墙静默断开,导致后续请求卡死在 redis: connection pool timeout。
读写操作必须带 context 并设超时
不加 context.WithTimeout 的 Redis 调用,在网络抖动、节点假死、DNS 解析失败时会无限阻塞 goroutine,快速耗尽服务资源。
立即学习“go语言免费学习笔记(深入)”;
正确做法是每个操作前显式派生带超时的 context:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 读操作建议
context.WithTimeout(ctx, 100 * time.Millisecond),避免拖慢接口整体响应 - 写操作可略宽松,如
300 * time.Millisecond,但绝不允许不设超时 - 慎用
SETNX:它本质是阻塞型强互斥,高并发下易引发延迟毛刺;多数场景用带过期时间的SET更稳妥
错误示例:rdb.Get(context.Background(), "key") —— 一旦 Redis 不可用,这个 goroutine 就永远卡住。
缓存失效时必须防穿透+防击穿+防雪崩
这三类问题不是 Redis 配置能解决的,必须在 Go 业务层拦截。容灾不是“换节点”,而是“不让故障扩散”。
- 穿透:对不存在的 key(如恶意构造的
user:-1)反复查询,直接打穿到 DB。解决方案是写空值缓存(rdb.Set(ctx, "user:-1", "", 5*time.Minute)),注意 value 用空字符串而非 nil,避免反序列化失败 - 击穿:热点 key 过期瞬间大量请求涌向 DB。不能只靠加锁(如
sync.Once),要结合本地 LRU 缓存(如bigcache)做二级兜底,让部分请求落在内存层 - 雪崩:大量 key 同一时刻过期。必须错开 TTL,例如基础过期时间 + 随机偏移(
time.Hour + time.Duration(rand.Int63n(int64(10*60*1000))) * time.Millisecond)
所有空值、错误值、降级值,都要统一走相同反序列化路径(如强制用 json.Unmarshal),否则下游服务解析时字段丢失,会误判为“数据异常”。
本地缓存与分布式缓存必须分层隔离且可独立熔断
把 sync.Map 或 bigcache 当作 Redis 的简单镜像,是常见误区。本地缓存应有独立生命周期和熔断策略。
- 本地缓存命中率低于 70% 时,应自动降级——不再回写,避免污染内存;可通过 Prometheus 指标
cache_local_hit_ratio触发 - Redis 客户端异常(如连续 5 次
redis: connection pool timeout)时,本地缓存应切换为“只读模式”,即允许读旧值,但拒绝任何写入,防止脏数据扩散 - 键名必须严格区分层级:
local:user:1001vsredis:user-service:user:1001,避免因前缀混淆导致本地缓存误删远程 key
最易被忽略的是:本地缓存的过期时间必须短于 Redis 的 TTL(建议 ≤ 1/3),否则 Redis 已失效,本地还在返回陈旧数据,容灾就变成了“稳态故障”。

















