Files
juwan-backend/docs/gozero-redis-configuration.md
T
2026-02-23 15:54:33 +08:00

1498 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go-Zero 框架 Redis 配置完全指南
**框架版本:** go-zero v1.5+
**Redis 版本:** 7.0.12
**部署环境:** Kubernetes (juwan namespace)
**文档日期:** 2026年2月22日
---
## 📋 目录
1. [配置概览](#配置概览)
2. [单节点模式](#单节点模式)
3. [Sentinel 哨兵模式](#sentinel-哨兵模式)
4. [集群模式](#集群模式)
5. [配置项详解](#配置项详解)
6. [代码实现](#代码实现)
7. [常用操作示例](#常用操作示例)
8. [高级特性](#高级特性)
9. [性能优化](#性能优化)
10. [故障排查](#故障排查)
11. [最佳实践](#最佳实践)
---
## 🎯 配置概览
### 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`**
```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`**
```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`**
```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`**
```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`**
```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`**
```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`**
```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 配置详解
**完整配置选项:**
```yaml
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 # 写超时(毫秒)
```
### 优势说明
```go
// 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`**
```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`**
```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 完整配置
**结构定义:**
```go
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(必填)
**单节点模式:**
```yaml
Redis:
Host: user-redis-master.juwan.svc.cluster.local:6379
```
**Sentinel 模式:**
```yaml
Redis:
Host: user-redis-sentinel-sentinel.juwan.svc.cluster.local:26379
# 注意:这里填 Sentinel 地址(端口 26379),不是 Redis 地址
```
**集群模式:**
```yaml
Redis:
# 可以填任意一个节点,客户端会自动发现其他节点
- Host: redis-cluster-0:6379
- Host: redis-cluster-1:6379
- Host: redis-cluster-2:6379
```
#### 2. Type(必填)
| 值 | 说明 |
|----|------|
| `node` | 单节点模式 |
| `sentinel` | Sentinel 哨兵模式 |
| `cluster` | 集群模式 |
#### 3. Pass(强烈推荐)
**从环境变量读取(推荐):**
```yaml
Redis:
Pass: ${REDIS_PASSWORD}
```
**硬编码(不推荐):**
```yaml
Redis:
Pass: "your-password" # ❌ 不安全
```
#### 4. Db(可选,默认 0
**适用模式:**`node``sentinel` 模式
```yaml
Redis:
Db: 0 # 数据库编号 0-15
```
**注意:**
- ❌ Cluster 模式不支持多数据库
- ✅ 单节点和 Sentinel 支持 0-15
#### 5. MaxIdle(可选,默认 8
```yaml
Redis:
MaxIdle: 8 # 连接池中最大闲置连接数
```
**建议值:**
- 低并发:`8`
- 中并发:`16`
- 高并发:`32``CPU 核心数 * 2`
#### 6. MaxActive(可选,默认 0
```yaml
Redis:
MaxActive: 0 # 0 表示无限制
# MaxActive: 100 # 或设置一个上限
```
**建议值:**
- 开发环境:`0`(无限制)
- 生产环境:`100-500`(根据实际负载)
#### 7. IdleTimeout(可选,默认 300 秒)
```yaml
Redis:
IdleTimeout: 300 # 秒
```
**说明:** 闲置连接超过此时间会被关闭
#### 8. 超时配置(可选)
```yaml
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`**
```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`**
```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`**
```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`**
```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`**
```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. 基本读写操作
```go
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 操作
```go
// 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 操作
```go
// 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 操作
```go
// 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 操作
```go
// 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(推荐):**
```go
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. 分布式锁
```go
// 获取分布式锁
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
// 使用 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. 缓存穿透防护(布隆过滤器)
```go
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
```go
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. 缓存雪崩防护(随机过期时间)
```go
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. 连接池配置优化
**根据并发量调整:**
```yaml
# 低并发(< 100 QPS
Redis:
MaxIdle: 8
MaxActive: 100
# 中并发(100-1000 QPS
Redis:
MaxIdle: 16
MaxActive: 500
# 高并发(> 1000 QPS
Redis:
MaxIdle: 32
MaxActive: 1000
```
### 2. 超时配置优化
```yaml
Redis:
# 连接超时:通常设置较大值
ConnectTimeout: 5000 # 5 秒
# 读写超时:设置较小值,快速失败
ReadTimeout: 1000 # 1 秒
WriteTimeout: 1000 # 1 秒
```
### 3. Pipeline 批量操作
**避免循环调用:**
```go
// ❌ 不好:循环调用
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. 合理的缓存过期时间
```go
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 命名规范
```go
// 推荐的 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`
**排查步骤:**
```bash
# 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`
**排查:**
```go
// 打印配置(调试用)
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. 性能问题
**慢查询检测:**
```go
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
// 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**
```yaml
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. 配置分离
**开发、测试、生产环境分离:**
```bash
# 开发环境
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. 监控指标
```go
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. 错误处理
```go
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(同步更新):**
```go
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(异步更新):**
```go
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
}
```
---
## 📚 参考资源
### 官方文档
- [go-zero 官方文档](https://go-zero.dev/)
- [go-zero Redis 文档](https://go-zero.dev/docs/tutorials/redis)
- [go-redis 文档](https://redis.uptrace.dev/)
### 示例代码
- [go-zero Examples](https://github.com/zeromicro/go-zero/tree/master/example)
- [go-zero Book Store](https://github.com/zeromicro/go-zero-book-store)
### 相关工具
- [RedisInsight](https://redis.com/redis-enterprise/redis-insight/) - Redis 管理工具
- [redis-cli](https://redis.io/docs/manual/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. 测试连接是否正常
### 生产环境推荐配置
```yaml
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
```
### 关键点提醒
1.**生产环境必须使用 Sentinel 模式**
2.**密码通过环境变量传递,不要硬编码**
3.**合理设置过期时间,防止缓存雪崩**
4.**使用 Pipeline 优化批量操作**
5.**实现缓存降级策略,Redis 故障不影响主流程**
---
**文档版本:** 1.0
**创建日期:** 2026年2月22日
**维护者:** Backend Team
**下次审查:** 2026年3月22日