redis客户端必须按部署模式选用:集群用newclusterclient,哨兵用newfailoverclient,单机才可用newclient;否则会因moved或readonly错误阻塞请求。

redis.NewClient 不是分布式缓存的入口,直接用它连生产 Redis 集群或哨兵,90% 的请求会卡在 MOVED 或 READONLY 错误里,根本走不到业务逻辑。
用错客户端类型:单节点 vs 集群 vs 哨兵
Redis 部署模式决定客户端初始化方式,硬套 redis.NewClient 是最常见翻车点:
- Redis Cluster(多主分片)→ 必须用
redis.NewClusterClient,传入 2–3 个可连通节点地址,它自动拉取槽位映射;用NewClient会反复报MOVED 12345 192.168.1.100:6379然后 panic - Redis Sentinel(主从高可用)→ 必须用
redis.NewFailoverClient,显式填MasterName和哨兵地址列表(如[]string{"10.0.1.10:26379", "10.0.1.11:26379"});漏一个哨兵,初始化就失败 - 单机或固定主从(无自动故障转移)→ 才能用
redis.NewClient,但生产环境极少见
连接池和超时不是可选项,是存活底线
默认 PoolSize=10、不设 MaxConnAge、用 context.Background() 调 Get,等于给 goroutine 下死亡指令:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
PoolSize要按预估 QPS × 2 ÷ 节点数设(例如集群 5 节点、QPS 500 → 设 200),单节点池别超 200,否则打爆 Redis 的maxclients - 必须配
MinIdleConns: 5和MaxConnAge: 30 * time.Minute,防 NAT/防火墙静默断连后后续请求卡在write: broken pipe - 所有操作带
context.WithTimeout(r.Context(), 300*time.Millisecond),HTTP handler 里别自己 new context;写操作(如Set、Del)同样要超时,分布式锁缺超时 = 假死
结构体存取必须过序列化关
直接 client.Set(ctx, "user:1001", user, ttl) 看似简洁,实际存进去的是空字符串或乱码:
- 字段必须导出(首字母大写),且加
json:"id,omitempty"tag,否则json.Marshal输出空对象 - 时间字段别直接塞
time.Time——json默认转 RFC3339 字符串,gob存纳秒整数,跨服务反序列化必错;统一转int64(user.CreatedAt.Unix())再存 - 多语言场景(Python/Node.js 消费缓存)只认
json;纯 Go 微服务内部可选gob(快 40%,但字段增删不兼容,需提前gob.Register)
穿透/击穿/雪崩得在 Go 层写死逻辑
Redis 自身不解决这三类问题,等线上被打穿再补,代价是 DB 连接耗尽:
- 穿透:DB 查无结果,必须
client.Set(ctx, key, "null", 60*time.Second);业务层读到"null"就返回 nil,不能当真实数据解包 - 击穿:热点 key 过期瞬间并发查库,用
singleflight.Group包Do,同一 key 只放行一个请求回源 - 雪崩:TTL 不能写死,每次
Set前生成随机扰动:baseTTL + time.Duration(rand.Int63n(int64(2*time.Minute))),且 seed 用time.Now().UnixNano(),别用全局rand.Seed()
真正难的不是连上 Redis,而是让每一次 Get 和 Set 都带着上下文约束、连接健康、序列化正确、兜底策略完备——这些细节没写进代码,故障就埋在下次发布之后。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










