319 行
6.7 KiB
Markdown
319 行
6.7 KiB
Markdown
|
|
# T038 缓存池实现
|
|||
|
|
|
|||
|
|
> 审查Agent:MiMo
|
|||
|
|
> 日期:2026-07-03
|
|||
|
|
> 审查范围:Redis/Valkey缓存系统设计
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、缓存系统架构
|
|||
|
|
|
|||
|
|
### 1.1 缓存层级
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────┐
|
|||
|
|
│ 应用层缓存 │
|
|||
|
|
│ (Go进程内缓存, TTL短) │
|
|||
|
|
├─────────────────────────────────────┤
|
|||
|
|
│ Redis/Valkey缓存 │
|
|||
|
|
│ (分布式缓存, TTL中) │
|
|||
|
|
├─────────────────────────────────────┤
|
|||
|
|
│ PostgreSQL数据库 │
|
|||
|
|
│ (持久化存储, 查询慢) │
|
|||
|
|
└─────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 缓存策略
|
|||
|
|
|
|||
|
|
| 数据类型 | 缓存位置 | TTL | 更新策略 |
|
|||
|
|
|---------|---------|-----|---------|
|
|||
|
|
| 角色信息 | Redis | 5分钟 | 写时失效 |
|
|||
|
|
| 会话数据 | Redis | 30分钟 | 写时失效 |
|
|||
|
|
| 配置数据 | Redis | 1小时 | 定时刷新 |
|
|||
|
|
| 战斗数据 | Redis | 10分钟 | 写时失效 |
|
|||
|
|
| 排行榜 | Redis | 1分钟 | 实时更新 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、缓存Key设计
|
|||
|
|
|
|||
|
|
### 2.1 Key命名规范
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
{模块}:{类型}:{ID}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 Key列表
|
|||
|
|
|
|||
|
|
| 模块 | Key格式 | TTL | 说明 |
|
|||
|
|
|------|---------|-----|------|
|
|||
|
|
| 角色 | `char:{id}` | 5min | 角色基本信息 |
|
|||
|
|
| 角色属性 | `char:{id}:stats` | 5min | 角色战斗属性 |
|
|||
|
|
| 会话 | `session:{id}` | 30min | 玩家会话数据 |
|
|||
|
|
| 功法 | `manual:{char_id}:{id}` | 5min | 功法实例数据 |
|
|||
|
|
| 技能 | `skill:{char_id}:{id}` | 5min | 技能实例数据 |
|
|||
|
|
| 货币 | `currency:{char_id}` | 5min | 货币余额 |
|
|||
|
|
| 排行榜 | `rank:{type}:{tier}` | 1min | 各类排行榜 |
|
|||
|
|
| 战斗 | `battle:{id}` | 10min | 战斗记录 |
|
|||
|
|
| 配置 | `config:{key}` | 1hour | 配置数据 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、缓存操作封装
|
|||
|
|
|
|||
|
|
### 3.1 基础操作
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 缓存操作接口
|
|||
|
|
type CacheInterface interface {
|
|||
|
|
Get(key string) (string, error)
|
|||
|
|
Set(key string, value string, ttl time.Duration) error
|
|||
|
|
Del(key string) error
|
|||
|
|
Exists(key string) (bool, error)
|
|||
|
|
Incr(key string) (int64, error)
|
|||
|
|
Expire(key string, ttl time.Duration) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Redis实现
|
|||
|
|
type RedisCache struct {
|
|||
|
|
client *redis.Client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c *RedisCache) Get(key string) (string, error) {
|
|||
|
|
return c.client.Get(ctx, key).Result()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c *RedisCache) Set(key string, value string, ttl time.Duration) error {
|
|||
|
|
return c.client.Set(ctx, key, value, ttl).Err()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (c *RedisCache) Del(key string) error {
|
|||
|
|
return c.client.Del(ctx, key).Err()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 业务封装
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 角色缓存
|
|||
|
|
func GetCharacterCache(charID string) (*Character, error) {
|
|||
|
|
key := "char:" + charID
|
|||
|
|
data, err := cache.Get(key)
|
|||
|
|
if err != nil {
|
|||
|
|
// 缓存未命中,从数据库加载
|
|||
|
|
char, err := loadCharacterFromDB(charID)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
// 写入缓存
|
|||
|
|
cache.Set(key, json.Marshal(char), 5*time.Minute)
|
|||
|
|
return char, nil
|
|||
|
|
}
|
|||
|
|
var char Character
|
|||
|
|
json.Unmarshal([]byte(data), &char)
|
|||
|
|
return &char, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func SetCharacterCache(charID string, char *Character) error {
|
|||
|
|
key := "char:" + charID
|
|||
|
|
data, _ := json.Marshal(char)
|
|||
|
|
return cache.Set(key, string(data), 5*time.Minute)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func InvalidateCharacterCache(charID string) error {
|
|||
|
|
keys := []string{
|
|||
|
|
"char:" + charID,
|
|||
|
|
"char:" + charID + ":stats",
|
|||
|
|
"currency:" + charID,
|
|||
|
|
}
|
|||
|
|
return cache.Del(keys...)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、缓存一致性
|
|||
|
|
|
|||
|
|
### 4.1 写时失效策略
|
|||
|
|
|
|||
|
|
当数据更新时,立即失效相关缓存:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func UpdateCharacter(charID string, data map[string]interface{}) error {
|
|||
|
|
// 1. 更新数据库
|
|||
|
|
err := updateDB(charID, data)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 失效缓存
|
|||
|
|
InvalidateCharacterCache(charID)
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 缓存穿透防护
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 防止缓存穿透:查询不存在的数据时,缓存空值
|
|||
|
|
func GetWithProtection(key string) (string, error) {
|
|||
|
|
data, err := cache.Get(key)
|
|||
|
|
if err == redis.Nil {
|
|||
|
|
// 缓存未命中
|
|||
|
|
data, err := loadFromDB(key)
|
|||
|
|
if err != nil {
|
|||
|
|
// 数据不存在,缓存空值(短TTL)
|
|||
|
|
cache.Set(key, "", 1*time.Minute)
|
|||
|
|
return "", nil
|
|||
|
|
}
|
|||
|
|
cache.Set(key, data, 5*time.Minute)
|
|||
|
|
return data, nil
|
|||
|
|
}
|
|||
|
|
return data, err
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、缓存监控
|
|||
|
|
|
|||
|
|
### 5.1 监控指标
|
|||
|
|
|
|||
|
|
| 指标 | 说明 | 告警阈值 |
|
|||
|
|
|------|------|---------|
|
|||
|
|
| 命中率 | 缓存命中/总请求 | <80% |
|
|||
|
|
| 内存使用 | Redis内存使用量 | >80% |
|
|||
|
|
| 连接数 | Redis连接数 | >1000 |
|
|||
|
|
| 延迟 | 缓存操作延迟 | >10ms |
|
|||
|
|
|
|||
|
|
### 5.2 监控命令
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看Redis状态
|
|||
|
|
redis-cli info memory
|
|||
|
|
|
|||
|
|
# 查看命中率
|
|||
|
|
redis-cli info stats | grep keyspace_hits
|
|||
|
|
|
|||
|
|
# 查看连接数
|
|||
|
|
redis-cli info clients
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、缓存预热
|
|||
|
|
|
|||
|
|
### 6.1 启动预热
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func WarmupCache() {
|
|||
|
|
// 预热配置数据
|
|||
|
|
loadConfigCache()
|
|||
|
|
|
|||
|
|
// 预热排行榜
|
|||
|
|
loadRankCache()
|
|||
|
|
|
|||
|
|
// 预热热门角色
|
|||
|
|
loadHotCharacters()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 定时刷新
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 定时刷新配置缓存
|
|||
|
|
go func() {
|
|||
|
|
ticker := time.NewTicker(1 * time.Hour)
|
|||
|
|
for range ticker.C {
|
|||
|
|
loadConfigCache()
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、缓存配置
|
|||
|
|
|
|||
|
|
### 7.1 Redis配置
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
redis:
|
|||
|
|
addr: "localhost:6379"
|
|||
|
|
password: ""
|
|||
|
|
db: 0
|
|||
|
|
pool_size: 10
|
|||
|
|
min_idle_conns: 5
|
|||
|
|
max_retries: 3
|
|||
|
|
retry_delay: 100ms
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 缓存配置
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
cache:
|
|||
|
|
character_ttl: 5m
|
|||
|
|
session_ttl: 30m
|
|||
|
|
config_ttl: 1h
|
|||
|
|
battle_ttl: 10m
|
|||
|
|
rank_ttl: 1m
|
|||
|
|
max_memory: 1gb
|
|||
|
|
eviction_policy: allkeys-lru
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、性能优化
|
|||
|
|
|
|||
|
|
### 8.1 批量操作
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 批量获取
|
|||
|
|
func GetMulti(keys []string) (map[string]string, error) {
|
|||
|
|
return cache.MGet(keys...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量设置
|
|||
|
|
func SetMulti(data map[string]string, ttl time.Duration) error {
|
|||
|
|
pipe := cache.Pipeline()
|
|||
|
|
for k, v := range data {
|
|||
|
|
pipe.Set(ctx, k, v, ttl)
|
|||
|
|
}
|
|||
|
|
_, err := pipe.Exec(ctx)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 本地缓存
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 使用Go自带的sync.Map作为L1缓存
|
|||
|
|
var localCache sync.Map
|
|||
|
|
|
|||
|
|
func GetWithLocalCache(key string) (string, error) {
|
|||
|
|
// L1: 本地缓存
|
|||
|
|
if val, ok := localCache.Load(key); ok {
|
|||
|
|
return val.(string), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// L2: Redis缓存
|
|||
|
|
data, err := cache.Get(key)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 写入L1缓存(短TTL)
|
|||
|
|
localCache.Store(key, data)
|
|||
|
|
go func() {
|
|||
|
|
time.Sleep(30 * time.Second)
|
|||
|
|
localCache.Delete(key)
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
return data, nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*缓存池实现 v1.0 | 2026-07-03 | T038*
|