32 KiB
Go-Zero 框架 Redis 配置完全指南
框架版本: go-zero v1.5+
Redis 版本: 7.0.12
部署环境: Kubernetes (juwan namespace)
文档日期: 2026年2月22日
📋 目录
🎯 配置概览
Go-Zero Redis 支持的模式
| 模式 | Type 值 | 用途 | 高可用 | 推荐度 |
|---|---|---|---|---|
| 单节点 | node |
开发/测试 | ❌ | ⭐⭐ |
| Sentinel | sentinel |
生产环境 | ✅ | ⭐⭐⭐⭐⭐ |
| 集群 | cluster |
大规模分片 | ✅ | ⭐⭐⭐⭐ |
配置文件位置
app/users/rpc/
├── etc/
│ └── pb.yaml ← Redis 配置写在这里
├── internal/
│ ├── config/
│ │ └── config.go ← 定义配置结构
│ └── svc/
│ └── serviceContext.go ← 初始化 Redis 客户端
└── pb.go
🔵 单节点模式
适用场景
- ✅ 开发环境
- ✅ 测试环境
- ✅ POC 演示
- ❌ 生产环境(无高可用)
配置文件
app/users/rpc/etc/pb.yaml
Name: user.rpc
ListenOn: 0.0.0.0:9001
# Redis 单节点配置
Redis:
Host: user-redis-master.juwan.svc.cluster.local:6379
Type: node
Pass: ${REDIS_PASSWORD} # 从环境变量读取
# Db: 0 # 可选,默认 0
# MaxIdle: 8 # 可选,连接池最大闲置连接数
# MaxActive: 0 # 可选,连接池最大活跃连接数,0 表示无限制
Etcd:
Hosts:
- etcd-service.juwan.svc.cluster.local:2379
Key: user.rpc
Config 结构
app/users/rpc/internal/config/config.go
package config
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
Redis redis.RedisConf // Redis 配置
}
ServiceContext 初始化
app/users/rpc/internal/svc/serviceContext.go
package svc
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"juwan-backend/app/users/rpc/internal/config"
)
type ServiceContext struct {
Config config.Config
Redis *redis.Redis
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Redis: redis.MustNewRedis(c.Redis), // 初始化 Redis
}
}
使用示例
app/users/rpc/internal/logic/getUsersByIdLogic.go
package logic
import (
"context"
"encoding/json"
"fmt"
"time"
"juwan-backend/app/users/rpc/internal/svc"
"juwan-backend/app/users/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUsersByIdLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetUsersByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUsersByIdLogic {
return &GetUsersByIdLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *GetUsersByIdLogic) GetUsersById(in *pb.GetUsersByIdReq) (*pb.GetUsersByIdResp, error) {
// 缓存 key
cacheKey := fmt.Sprintf("user:%d", in.Id)
// 1. 尝试从缓存获取
cached, err := l.svcCtx.Redis.Get(cacheKey)
if err == nil && cached != "" {
// 缓存命中
var user pb.User
if err := json.Unmarshal([]byte(cached), &user); err == nil {
l.Logger.Infof("Cache hit for user:%d", in.Id)
return &pb.GetUsersByIdResp{User: &user}, nil
}
}
// 2. 缓存未命中,从数据库查询
l.Logger.Infof("Cache miss for user:%d, querying DB", in.Id)
user := l.fetchUserFromDB(in.Id)
if user == nil {
return nil, fmt.Errorf("user not found")
}
// 3. 写入缓存(1小时过期)
userJSON, _ := json.Marshal(user)
err = l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600)
if err != nil {
l.Logger.Errorf("Failed to set cache: %v", err)
}
return &pb.GetUsersByIdResp{User: user}, nil
}
func (l *GetUsersByIdLogic) fetchUserFromDB(id int64) *pb.User {
// 实际从数据库查询
// ...
return &pb.User{Id: id, Name: "John Doe"}
}
🟡 Sentinel 哨兵模式
适用场景
- ✅✅✅ 生产环境强烈推荐
- ✅ 自动故障转移
- ✅ 高可用架构
- ✅ 主从自动切换
配置文件
app/users/rpc/etc/pb.yaml
Name: user.rpc
ListenOn: 0.0.0.0:9001
# Redis Sentinel 配置(推荐生产环境使用)
Redis:
- Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
Type: sentinel
Pass: ${REDIS_PASSWORD}
# 或者使用完整配置
# Redis:
# Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
# Type: sentinel
# Pass: ${REDIS_PASSWORD}
# # Sentinel 特有配置
# MasterName: mymaster # Sentinel 主节点名称,默认 mymaster
Etcd:
Hosts:
- etcd-service.juwan.svc.cluster.local:2379
Key: user.rpc
Config 结构(同单节点)
app/users/rpc/internal/config/config.go
package config
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
Redis redis.RedisConf // 支持所有模式
}
ServiceContext 初始化(同单节点)
app/users/rpc/internal/svc/serviceContext.go
package svc
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"juwan-backend/app/users/rpc/internal/config"
)
type ServiceContext struct {
Config config.Config
Redis *redis.Redis
}
func NewServiceContext(c config.Config) *ServiceContext {
// go-zero 会根据 Type 自动选择连接模式
return &ServiceContext{
Config: c,
Redis: redis.MustNewRedis(c.Redis),
}
}
Sentinel 配置详解
完整配置选项:
Redis:
Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
Type: sentinel
Pass: ${REDIS_PASSWORD}
# Sentinel 特有配置
MasterName: mymaster # Sentinel 监控的主节点名称
# 连接池配置(可选)
MaxIdle: 8 # 最大闲置连接数
MaxActive: 0 # 最大活跃连接数,0 表示无限制
IdleTimeout: 300 # 闲置连接超时时间(秒)
# 超时配置(可选)
ConnectTimeout: 5000 # 连接超时(毫秒)
ReadTimeout: 3000 # 读超时(毫秒)
WriteTimeout: 3000 # 写超时(毫秒)
优势说明
// Sentinel 模式的自动故障处理流程:
// 1. 应用连接到 Sentinel
app → Sentinel Service (26379)
// 2. Sentinel 返回当前主节点地址
Sentinel → app: "主节点在 10.244.1.10:6379"
// 3. 应用连接到主节点进行读写
app → Redis Master (10.244.1.10:6379)
// 4. 主节点故障,Sentinel 检测到
Redis Master (✗ 宕机)
Sentinel → 检测 → 投票 → 提升新主节点
// 5. 应用下次请求时自动连接到新主节点
app → Sentinel → "新主节点在 10.244.2.20:6379"
app → New Redis Master (10.244.2.20:6379)
// 整个过程应用无需重启,自动完成切换!
🔴 集群模式
适用场景
- ✅ 大规模数据(需要分片)
- ✅ 超高并发
- ✅ 数据量超过单机内存
- ⚠️ 配置和运维复杂度高
配置文件
app/users/rpc/etc/pb.yaml
Name: user.rpc
ListenOn: 0.0.0.0:9001
# Redis Cluster 配置
Redis:
- Host: redis-cluster-0.redis-cluster.juwan.svc.cluster.local:6379
- Host: redis-cluster-1.redis-cluster.juwan.svc.cluster.local:6379
- Host: redis-cluster-2.redis-cluster.juwan.svc.cluster.local:6379
Type: cluster
Pass: ${REDIS_PASSWORD}
Etcd:
Hosts:
- etcd-service.juwan.svc.cluster.local:2379
Key: user.rpc
Config 结构
app/users/rpc/internal/config/config.go
package config
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
Redis redis.RedisConf
}
集群模式特点
数据分片:
应用请求
↓
根据 key 计算 hash slot (0-16383)
↓
路由到对应的分片节点
↓
┌─────────┬─────────┬─────────┐
│ Shard 1 │ Shard 2 │ Shard 3 │
│ 0-5460 │5461-10922│10923-16383│
└─────────┴─────────┴─────────┘
注意事项:
- ❌ 不支持多 key 操作(如 MGET, MSET)跨分片
- ❌ 不支持事务(MULTI/EXEC)跨分片
- ✅ 单 key 操作完全正常
- ✅ 支持 hash tag 控制 key 分布
📚 配置项详解
redis.RedisConf 完整配置
结构定义:
type RedisConf struct {
Host string // Redis 地址
Type string // 类型: node, sentinel, cluster
Pass string // 密码
Db int // 数据库编号 (0-15),cluster 模式不支持
// Sentinel 模式专用
MasterName string // Sentinel 主节点名称
// 连接池配置
MaxIdle int // 最大闲置连接数
MaxActive int // 最大活跃连接数,0 表示无限制
IdleTimeout time.Duration // 闲置连接超时
// 超时配置
ConnectTimeout time.Duration // 连接超时
ReadTimeout time.Duration // 读超时
WriteTimeout time.Duration // 写超时
// TLS 配置(可选)
Tls bool // 是否启用 TLS
}
各配置项说明
1. Host(必填)
单节点模式:
Redis:
Host: user-redis-master.juwan.svc.cluster.local:6379
Sentinel 模式:
Redis:
Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
# 注意:这里填 Sentinel 地址(端口 26379),不是 Redis 地址
集群模式:
Redis:
# 可以填任意一个节点,客户端会自动发现其他节点
- Host: redis-cluster-0:6379
- Host: redis-cluster-1:6379
- Host: redis-cluster-2:6379
2. Type(必填)
| 值 | 说明 |
|---|---|
node |
单节点模式 |
sentinel |
Sentinel 哨兵模式 |
cluster |
集群模式 |
3. Pass(强烈推荐)
从环境变量读取(推荐):
Redis:
Pass: ${REDIS_PASSWORD}
硬编码(不推荐):
Redis:
Pass: "your-password" # ❌ 不安全
4. Db(可选,默认 0)
适用模式: 仅 node 和 sentinel 模式
Redis:
Db: 0 # 数据库编号 0-15
注意:
- ❌ Cluster 模式不支持多数据库
- ✅ 单节点和 Sentinel 支持 0-15
5. MaxIdle(可选,默认 8)
Redis:
MaxIdle: 8 # 连接池中最大闲置连接数
建议值:
- 低并发:
8 - 中并发:
16 - 高并发:
32或CPU 核心数 * 2
6. MaxActive(可选,默认 0)
Redis:
MaxActive: 0 # 0 表示无限制
# MaxActive: 100 # 或设置一个上限
建议值:
- 开发环境:
0(无限制) - 生产环境:
100-500(根据实际负载)
7. IdleTimeout(可选,默认 300 秒)
Redis:
IdleTimeout: 300 # 秒
说明: 闲置连接超过此时间会被关闭
8. 超时配置(可选)
Redis:
ConnectTimeout: 5000 # 连接超时 5 秒
ReadTimeout: 3000 # 读超时 3 秒
WriteTimeout: 3000 # 写超时 3 秒
💻 代码实现
完整项目结构
app/users/rpc/
├── etc/
│ ├── pb.yaml # 开发环境配置
│ └── pb-prod.yaml # 生产环境配置
├── internal/
│ ├── config/
│ │ └── config.go # 配置结构定义
│ ├── svc/
│ │ └── serviceContext.go # 服务上下文(初始化 Redis)
│ ├── logic/
│ │ ├── addUsersLogic.go
│ │ ├── getUsersByIdLogic.go
│ │ └── ...
│ └── server/
│ └── usercenterServer.go
├── pb/
│ ├── users.pb.go
│ └── users_grpc.pb.go
└── usercenter.go # 主程序入口
1. 配置文件示例
开发环境 etc/pb.yaml:
Name: user.rpc
ListenOn: 0.0.0.0:9001
# 开发环境使用单节点
Redis:
Host: localhost:6379
Type: node
Pass: dev_password
Db: 0
Etcd:
Hosts:
- localhost:2379
Key: user.rpc
Log:
Level: info
Mode: console
生产环境 etc/pb-prod.yaml:
Name: user.rpc
ListenOn: 0.0.0.0:9001
# 生产环境使用 Sentinel
Redis:
Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
Type: sentinel
Pass: ${REDIS_PASSWORD}
MasterName: mymaster
MaxIdle: 16
MaxActive: 100
IdleTimeout: 300
ConnectTimeout: 5000
ReadTimeout: 3000
WriteTimeout: 3000
Etcd:
Hosts:
- etcd-0.etcd.juwan.svc.cluster.local:2379
- etcd-1.etcd.juwan.svc.cluster.local:2379
- etcd-2.etcd.juwan.svc.cluster.local:2379
Key: user.rpc
Log:
Level: error
Mode: file
Path: /var/log/user-rpc
KeepDays: 7
2. Config 定义
internal/config/config.go
package config
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
// Redis 配置
Redis redis.RedisConf
// 其他配置...
// DB postgres.Config
// Kafka kafka.Config
}
3. ServiceContext 初始化
internal/svc/serviceContext.go
package svc
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"juwan-backend/app/users/rpc/internal/config"
)
type ServiceContext struct {
Config config.Config
Redis *redis.Redis
// 其他依赖...
// DB *gorm.DB
}
func NewServiceContext(c config.Config) *ServiceContext {
// 初始化 Redis(支持所有模式:node, sentinel, cluster)
rdb := redis.MustNewRedis(c.Redis)
return &ServiceContext{
Config: c,
Redis: rdb,
}
}
4. 主程序入口
usercenter.go
package main
import (
"flag"
"fmt"
"juwan-backend/app/users/rpc/internal/config"
"juwan-backend/app/users/rpc/internal/server"
"juwan-backend/app/users/rpc/internal/svc"
"juwan-backend/app/users/rpc/pb"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
var configFile = flag.String("f", "etc/pb.yaml", "the config file")
func main() {
flag.Parse()
// 加载配置
var c config.Config
conf.MustLoad(*configFile, &c)
// 初始化服务上下文
ctx := svc.NewServiceContext(c)
// 创建 gRPC 服务
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
pb.RegisterUsercenterServer(grpcServer, server.NewUsercenterServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}
🔧 常用操作示例
1. 基本读写操作
package logic
import (
"context"
"fmt"
"time"
)
type UserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
// Set 操作
func (l *UserLogic) SetUser(userId int64, data string) error {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Set(key, data)
}
// Setex 操作(带过期时间)
func (l *UserLogic) SetUserWithExpiry(userId int64, data string) error {
key := fmt.Sprintf("user:%d", userId)
// 缓存 1 小时
return l.svcCtx.Redis.Setex(key, data, 3600)
}
// Get 操作
func (l *UserLogic) GetUser(userId int64) (string, error) {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Get(key)
}
// Del 操作
func (l *UserLogic) DeleteUser(userId int64) error {
key := fmt.Sprintf("user:%d", userId)
_, err := l.svcCtx.Redis.Del(key)
return err
}
// Exists 检查
func (l *UserLogic) UserExists(userId int64) (bool, error) {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Exists(key)
}
2. Hash 操作
// HSet 操作
func (l *UserLogic) SetUserField(userId int64, field, value string) error {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Hset(key, field, value)
}
// HGet 操作
func (l *UserLogic) GetUserField(userId int64, field string) (string, error) {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Hget(key, field)
}
// HGetAll 操作
func (l *UserLogic) GetAllUserFields(userId int64) (map[string]string, error) {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Hgetall(key)
}
// HMSet 批量设置
func (l *UserLogic) SetUserFields(userId int64, fields map[string]string) error {
key := fmt.Sprintf("user:%d", userId)
return l.svcCtx.Redis.Hmset(key, fields)
}
3. List 操作
// LPush 操作
func (l *UserLogic) AddMessage(userId int64, message string) error {
key := fmt.Sprintf("messages:%d", userId)
_, err := l.svcCtx.Redis.Lpush(key, message)
return err
}
// LRange 操作
func (l *UserLogic) GetMessages(userId int64, start, stop int) ([]string, error) {
key := fmt.Sprintf("messages:%d", userId)
return l.svcCtx.Redis.Lrange(key, start, stop)
}
// LLen 操作
func (l *UserLogic) GetMessageCount(userId int64) (int, error) {
key := fmt.Sprintf("messages:%d", userId)
return l.svcCtx.Redis.Llen(key)
}
4. Set 操作
// SAdd 添加成员
func (l *UserLogic) AddUserTag(userId int64, tag string) error {
key := fmt.Sprintf("user:tags:%d", userId)
_, err := l.svcCtx.Redis.Sadd(key, tag)
return err
}
// SMembers 获取所有成员
func (l *UserLogic) GetUserTags(userId int64) ([]string, error) {
key := fmt.Sprintf("user:tags:%d", userId)
return l.svcCtx.Redis.Smembers(key)
}
// SIsMember 检查成员
func (l *UserLogic) HasUserTag(userId int64, tag string) (bool, error) {
key := fmt.Sprintf("user:tags:%d", userId)
return l.svcCtx.Redis.Sismember(key, tag)
}
5. Sorted Set 操作
// ZAdd 添加成员
func (l *UserLogic) AddToLeaderboard(userId int64, score int64) error {
key := "leaderboard"
_, err := l.svcCtx.Redis.Zadd(key, score, fmt.Sprintf("%d", userId))
return err
}
// ZRevRange 获取排行榜(从高到低)
func (l *UserLogic) GetTopUsers(count int) ([]string, error) {
key := "leaderboard"
return l.svcCtx.Redis.Zrevrange(key, 0, int64(count-1))
}
// ZRank 获取排名
func (l *UserLogic) GetUserRank(userId int64) (int64, error) {
key := "leaderboard"
return l.svcCtx.Redis.Zrank(key, fmt.Sprintf("%d", userId))
}
6. 缓存模式实现
Cache-Aside Pattern(推荐):
func (l *UserLogic) GetUserById(userId int64) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", userId)
// 1. 查缓存
cached, err := l.svcCtx.Redis.Get(cacheKey)
if err == nil && cached != "" {
var user User
if err := json.Unmarshal([]byte(cached), &user); err == nil {
return &user, nil
}
}
// 2. 查数据库
user, err := l.getUserFromDB(userId)
if err != nil {
return nil, err
}
// 3. 写缓存
userJSON, _ := json.Marshal(user)
l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600)
return user, nil
}
func (l *UserLogic) UpdateUser(user *User) error {
// 1. 更新数据库
if err := l.updateUserInDB(user); err != nil {
return err
}
// 2. 删除缓存(下次读取时会重新加载)
cacheKey := fmt.Sprintf("user:%d", user.Id)
l.svcCtx.Redis.Del(cacheKey)
return nil
}
7. 分布式锁
// 获取分布式锁
func (l *UserLogic) AcquireLock(key string, expiry int) (bool, error) {
lockKey := fmt.Sprintf("lock:%s", key)
return l.svcCtx.Redis.Setnx(lockKey, "1")
}
// 释放锁
func (l *UserLogic) ReleaseLock(key string) error {
lockKey := fmt.Sprintf("lock:%s", key)
_, err := l.svcCtx.Redis.Del(lockKey)
return err
}
// 使用示例
func (l *UserLogic) ProcessWithLock(userId int64) error {
lockKey := fmt.Sprintf("user:%d", userId)
// 获取锁
acquired, err := l.AcquireLock(lockKey, 10)
if err != nil {
return err
}
if !acquired {
return fmt.Errorf("failed to acquire lock")
}
defer l.ReleaseLock(lockKey)
// 执行业务逻辑
// ...
return nil
}
8. Pipeline 批量操作
// 使用 go-redis 原生客户端进行 Pipeline
func (l *UserLogic) BatchSetUsers(users []*User) error {
// go-zero 的 Redis 包装了 go-redis,可以获取原生客户端
rdb := l.svcCtx.Redis
pipe := rdb.Pipelined(func(pip redis.Pipeliner) error {
for _, user := range users {
key := fmt.Sprintf("user:%d", user.Id)
userJSON, _ := json.Marshal(user)
pip.Set(context.Background(), key, userJSON, time.Hour)
}
return nil
})
return pipe
}
🚀 高级特性
1. 缓存穿透防护(布隆过滤器)
import "github.com/zeromicro/go-zero/core/bloom"
type UserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
filter *bloom.Filter
}
func (l *UserLogic) GetUserWithBloom(userId int64) (*User, error) {
// 1. 布隆过滤器检查
if !l.filter.Exists([]byte(fmt.Sprintf("%d", userId))) {
return nil, fmt.Errorf("user not found")
}
// 2. 查缓存
cacheKey := fmt.Sprintf("user:%d", userId)
cached, err := l.svcCtx.Redis.Get(cacheKey)
if err == nil && cached != "" {
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}
// 3. 查数据库
user, err := l.getUserFromDB(userId)
if err != nil {
return nil, err
}
// 4. 写缓存
userJSON, _ := json.Marshal(user)
l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600)
return user, nil
}
2. 缓存击穿防护(Singleflight)
import "golang.org/x/sync/singleflight"
type UserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
sg singleflight.Group
}
func (l *UserLogic) GetUserWithSingleflight(userId int64) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", userId)
// 使用 Singleflight 确保同一时刻只有一个请求查询
v, err, _ := l.sg.Do(cacheKey, func() (interface{}, error) {
// 1. 查缓存
cached, err := l.svcCtx.Redis.Get(cacheKey)
if err == nil && cached != "" {
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}
// 2. 查数据库
user, err := l.getUserFromDB(userId)
if err != nil {
return nil, err
}
// 3. 写缓存
userJSON, _ := json.Marshal(user)
l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600)
return user, nil
})
if err != nil {
return nil, err
}
return v.(*User), nil
}
3. 缓存雪崩防护(随机过期时间)
import (
"math/rand"
"time"
)
func (l *UserLogic) SetCacheWithRandomExpiry(key string, value string, baseExpiry int) error {
// 在基础过期时间上增加随机值(±20%)
randomOffset := rand.Intn(baseExpiry / 5)
expiry := baseExpiry + randomOffset - (baseExpiry / 10)
return l.svcCtx.Redis.Setex(key, value, expiry)
}
// 使用示例
func (l *UserLogic) CacheUser(user *User) error {
key := fmt.Sprintf("user:%d", user.Id)
userJSON, _ := json.Marshal(user)
// 基础过期时间 1 小时,实际会在 48-72 分钟之间随机
return l.SetCacheWithRandomExpiry(key, string(userJSON), 3600)
}
⚡ 性能优化
1. 连接池配置优化
根据并发量调整:
# 低并发(< 100 QPS)
Redis:
MaxIdle: 8
MaxActive: 100
# 中并发(100-1000 QPS)
Redis:
MaxIdle: 16
MaxActive: 500
# 高并发(> 1000 QPS)
Redis:
MaxIdle: 32
MaxActive: 1000
2. 超时配置优化
Redis:
# 连接超时:通常设置较大值
ConnectTimeout: 5000 # 5 秒
# 读写超时:设置较小值,快速失败
ReadTimeout: 1000 # 1 秒
WriteTimeout: 1000 # 1 秒
3. Pipeline 批量操作
避免循环调用:
// ❌ 不好:循环调用
for _, user := range users {
key := fmt.Sprintf("user:%d", user.Id)
l.svcCtx.Redis.Set(key, user.Name)
}
// ✅ 推荐:使用 Pipeline
pipe := l.svcCtx.Redis.Pipelined(func(pip redis.Pipeliner) error {
for _, user := range users {
key := fmt.Sprintf("user:%d", user.Id)
pip.Set(context.Background(), key, user.Name, 0)
}
return nil
})
4. 合理的缓存过期时间
const (
CacheExpiryShort = 300 // 5 分钟 - 热点数据
CacheExpiryMedium = 3600 // 1 小时 - 常规数据
CacheExpiryLong = 86400 // 1 天 - 冷数据
)
func (l *UserLogic) SetUserCache(user *User, expiry int) error {
key := fmt.Sprintf("user:%d", user.Id)
userJSON, _ := json.Marshal(user)
return l.svcCtx.Redis.Setex(key, string(userJSON), expiry)
}
5. Key 命名规范
// 推荐的 Key 命名规范
const (
KeyPrefixUser = "user:" // user:123
KeyPrefixSession = "session:" // session:abc123
KeyPrefixCache = "cache:" // cache:user:list
KeyPrefixLock = "lock:" // lock:order:456
KeyPrefixCounter = "counter:" // counter:page:views
)
// 使用函数生成 Key
func UserCacheKey(userId int64) string {
return fmt.Sprintf("%s%d", KeyPrefixUser, userId)
}
func SessionKey(sessionId string) string {
return fmt.Sprintf("%s%s", KeyPrefixSession, sessionId)
}
🔍 故障排查
1. 连接失败
问题: dial tcp xxx:6379: i/o timeout
排查步骤:
# 1. 检查 Redis 服务
kubectl get pods -n juwan | grep redis
# 2. 检查 Service
kubectl get svc -n juwan | grep redis
# 3. 测试网络连通性
kubectl run -it --rm nettest --image=busybox --restart=Never -n juwan -- \
nc -zv user-redis-master 6379
# 4. 查看应用日志
kubectl logs -f user-rpc-xxx -n juwan
解决方案:
- 确认 Host 配置正确
- 确认网络策略允许访问
- 检查 Redis Pod 状态
2. 认证失败
问题: NOAUTH Authentication required
���查:
// 打印配置(调试用)
func main() {
var c config.Config
conf.MustLoad(*configFile, &c)
// 检查密码是否正确加载
fmt.Printf("Redis Config: Host=%s, Pass=%s\n", c.Redis.Host, c.Redis.Pass)
}
解决方案:
- 确认环境变量
REDIS_PASSWORD已设置 - 确认 Secret 正确挂载
- 检查密码是否正确
3. 性能问题
慢查询检测:
import "time"
func (l *UserLogic) GetUserWithMetrics(userId int64) (*User, error) {
start := time.Now()
defer func() {
duration := time.Since(start)
if duration > 100*time.Millisecond {
l.Logger.Warnf("Slow Redis query: %v", duration)
}
}()
// 执行查询
key := fmt.Sprintf("user:%d", userId)
cached, err := l.svcCtx.Redis.Get(key)
// ...
}
常见原因:
- 连接池耗尽 → 增大 MaxActive
- 大 Value 传输 → 拆分或压缩数据
- 网络延迟 → 检查网络质量
4. 内存泄漏
检查连接是否正确关闭:
// go-zero 的 Redis 客户端会自动管理连接
// 但如果使用原生 go-redis 客户端,需要手动关闭
// ❌ 错误示例
func bad() {
rdb := redis.NewClient(&redis.Options{...})
// 使用完后没有关闭
}
// ✅ 正确示例
func good() {
rdb := redis.NewClient(&redis.Options{...})
defer rdb.Close()
// ...
}
📖 最佳实践
1. 环境变量管理
Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-rpc
namespace: juwan
spec:
template:
spec:
containers:
- name: user-rpc
image: user-rpc:v1
env:
# 从 Secret 读取 Redis 密码
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: user-redis
key: password
# 从 ConfigMap 读取其他配置
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: user-rpc-config
key: redis.host
2. 配置分离
开发、测试、生产环境分离:
# 开发环境
go run usercenter.go -f etc/pb-dev.yaml
# 测试环境
go run usercenter.go -f etc/pb-test.yaml
# 生产环境
./usercenter -f etc/pb-prod.yaml
3. 监控指标
import (
"github.com/zeromicro/go-zero/core/metric"
"github.com/zeromicro/go-zero/core/prometheus"
)
var (
redisCacheHit = metric.NewCounterVec(&metric.CounterVecOpts{
Namespace: "user_rpc",
Subsystem: "redis",
Name: "cache_hit_total",
Help: "redis cache hit count",
Labels: []string{"key"},
})
redisCacheMiss = metric.NewCounterVec(&metric.CounterVecOpts{
Namespace: "user_rpc",
Subsystem: "redis",
Name: "cache_miss_total",
Help: "redis cache miss count",
Labels: []string{"key"},
})
)
func (l *UserLogic) GetUserWithMetrics(userId int64) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", userId)
cached, err := l.svcCtx.Redis.Get(cacheKey)
if err == nil && cached != "" {
redisCacheHit.Inc("user")
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}
redisCacheMiss.Inc("user")
// 查询数据库...
}
4. 错误处理
func (l *UserLogic) GetUser(userId int64) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", userId)
// 缓存查询失败不应该中断流程
cached, err := l.svcCtx.Redis.Get(cacheKey)
if err == nil && cached != "" {
var user User
if json.Unmarshal([]byte(cached), &user) == nil {
return &user, nil
}
}
// 缓存失败,降级查询数据库
user, err := l.getUserFromDB(userId)
if err != nil {
return nil, err
}
// 尝试回写缓存,失败不影响返回结果
go func() {
userJSON, _ := json.Marshal(user)
if err := l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600); err != nil {
l.Logger.Errorf("Failed to set cache: %v", err)
}
}()
return user, nil
}
5. 缓存更新策略
Write-Through(同步更新):
func (l *UserLogic) UpdateUser(user *User) error {
// 1. 更新数据库
if err := l.updateUserInDB(user); err != nil {
return err
}
// 2. 同步更新缓存
cacheKey := fmt.Sprintf("user:%d", user.Id)
userJSON, _ := json.Marshal(user)
if err := l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600); err != nil {
l.Logger.Errorf("Failed to update cache: %v", err)
}
return nil
}
Write-Behind(异步更新):
func (l *UserLogic) UpdateUserAsync(user *User) error {
// 1. 立即更新缓存
cacheKey := fmt.Sprintf("user:%d", user.Id)
userJSON, _ := json.Marshal(user)
l.svcCtx.Redis.Setex(cacheKey, string(userJSON), 3600)
// 2. 异步更新数据库
go func() {
if err := l.updateUserInDB(user); err != nil {
l.Logger.Errorf("Failed to update DB: %v", err)
// 回滚缓存
l.svcCtx.Redis.Del(cacheKey)
}
}()
return nil
}
📚 参考资源
官方文档
示例代码
相关工具
- RedisInsight - Redis 管理工具
- redis-cli - Redis 命令行工具
📝 总结
快速开始检查清单
- 1. 在
config.go中定义Redis redis.RedisConf - 2. 在配置文件中添加 Redis 配置
- 3. 在
ServiceContext中初始化redis.MustNewRedis(c.Redis) - 4. 在 Kubernetes中配置环境变量
REDIS_PASSWORD - 5. 在 logic 中使用
l.svcCtx.Redis - 6. 测试连接是否正常
生产环境推荐配置
Redis:
Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
Type: sentinel
Pass: ${REDIS_PASSWORD}
MasterName: mymaster
MaxIdle: 16
MaxActive: 100
ConnectTimeout: 5000
ReadTimeout: 3000
WriteTimeout: 3000
关键点提醒
- ✅ 生产环境必须使用 Sentinel 模式
- ✅ 密码通过环境变量传递,不要硬编码
- ✅ 合理设置过期时间,防止缓存雪崩
- ✅ 使用 Pipeline 优化批量操作
- ✅ 实现缓存降级策略,Redis 故障不影响主流程
文档版本: 1.0
创建日期: 2026年2月22日
维护者: Backend Team
下次审查: 2026年3月22日