竹简文档
缓存

KeyCache

基于字符串数据结构的键值对缓存接口

KeyCache

KeyCache 定义了基于字符串(String)数据结构的缓存操作接口,用于管理单一键值对数据。

接口定义

type KeyCache[K any, V any] interface {
    Get(ctx context.Context, key K) (*V, bool, error)
    Set(ctx context.Context, key K, value *V) error
    Exists(ctx context.Context, key K) (bool, error)
    Delete(ctx context.Context, key K) error
}

泛型参数

字段

类型

方法说明

Get

根据键检索值。

Get(ctx context.Context, key K) (*V, bool, error)

参数:

  • ctx - context.Context 上下文
  • key - 缓存键

返回值:

  • *V - 指向值的指针(如果存在)
  • bool - 键是否存在
  • error - 错误信息

Set

将键值对存入缓存。

Set(ctx context.Context, key K, value *V) error

参数:

  • ctx - context.Context 上下文
  • key - 缓存键
  • value - 指向值的指针(可以为 nil)

返回值:

  • error - 错误信息

注意: 需要处理 valuenil 的场景。

Exists

检查指定键是否存在。

Exists(ctx context.Context, key K) (bool, error)

参数:

  • ctx - context.Context 上下文
  • key - 缓存键

返回值:

  • bool - 键是否存在
  • error - 错误信息

Delete

从缓存中移除指定的键。

Delete(ctx context.Context, key K) error

参数:

  • ctx - context.Context 上下文
  • key - 缓存键

返回值:

  • error - 错误信息

实现示例

用户缓存实现

cache/user.go
import (
    "context"
    "encoding/json"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/redis/go-redis/v9"
    xCache "github.com/bamboo-services/bamboo-base-go/major/cache"
)

type User struct {
    ID       string `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
}

type UserCache struct {
    *xCache.Cache
}

// Get 获取用户缓存
func (c *UserCache) Get(ctx context.Context, userID string) (*User, bool, error) {
    key := "user:" + userID

    val, err := c.RDB.Get(ctx, key).Result()
    if err == redis.Nil {
        return nil, false, nil
    }
    if err != nil {
        return nil, false, err
    }

    var user User
    if err := json.Unmarshal([]byte(val), &user); err != nil {
        return nil, false, err
    }

    return &user, true, nil
}

// Set 设置用户缓存
func (c *UserCache) Set(ctx context.Context, userID string, user *User) error {
    if user == nil {
        return nil
    }

    key := "user:" + userID

    data, err := json.Marshal(user)
    if err != nil {
        return err
    }

    return c.RDB.Set(ctx, key, data, c.TTL).Err()
}

// Exists 检查用户是否存在
func (c *UserCache) Exists(ctx context.Context, userID string) (bool, error) {
    key := "user:" + userID
    count, err := c.RDB.Exists(ctx, key).Result()
    return count > 0, err
}

// Delete 删除用户缓存
func (c *UserCache) Delete(ctx context.Context, userID string) error {
    key := "user:" + userID
    return c.RDB.Del(ctx, key).Err()
}

使用缓存

service/user.go
type UserService struct {
    cache *UserCache
}

func NewUserService(cache *xCache.Cache) *UserService {
    return &UserService{
        cache: &UserCache{Cache: cache},
    }
}

// GetUser 获取用户(带缓存)
func (s *UserService) GetUser(ctx context.Context, userID string) (*User, error) {
    // 先从缓存获取
    user, exists, err := s.cache.Get(ctx, userID)
    if err != nil {
        return nil, err
    }
    if exists {
        return user, nil
    }

    // 缓存未命中,从数据库查询
    user, err = s.getUserFromDB(userID)
    if err != nil {
        return nil, err
    }

    // 写入缓存
    _ = s.cache.Set(ctx, userID, user)

    return user, nil
}

// DeleteUser 删除用户并清除缓存
func (s *UserService) DeleteUser(ctx context.Context, userID string) error {
    // 删除数据库记录
    if err := s.deleteUserFromDB(userID); err != nil {
        return err
    }

    // 删除缓存
    return s.cache.Delete(ctx, userID)
}

使用场景

用户信息缓存

type UserCache interface {
    xCache.KeyCache[string, User]
}

适用于:

  • 用户基本信息
  • 用户权限信息
  • 用户配置

Token 缓存

type TokenCache interface {
    xCache.KeyCache[string, TokenInfo]
}

适用于:

  • JWT Token
  • 刷新 Token
  • 临时访问凭证

配置缓存

type ConfigCache interface {
    xCache.KeyCache[string, Config]
}

适用于:

  • 系统配置
  • 功能开关
  • 动态参数

最佳实践

1. 统一键命名

使用前缀区分不同类型的缓存:

func (c *UserCache) Get(ctx context.Context, userID string) (*User, bool, error) {
    // 使用 "user:" 前缀
    key := "user:" + userID
    // ...
}

2. 处理 nil 值

Set 方法中正确处理 nil 值:

func (c *UserCache) Set(ctx context.Context, userID string, user *User) error {
    // 如果值为 nil,直接返回
    if user == nil {
        return nil
    }
    // ...
}

3. 错误处理

区分缓存未命中和真实错误:

func (c *UserCache) Get(ctx context.Context, userID string) (*User, bool, error) {
    val, err := c.RDB.Get(ctx, key).Result()
    // 缓存未命中不是错误
    if err == redis.Nil {
        return nil, false, nil
    }
    // 其他错误需要返回
    if err != nil {
        return nil, false, err
    }
    // ...
}

4. 序列化选择

根据场景选择合适的序列化方式:

// JSON - 可读性好,兼容性强
data, _ := json.Marshal(user)

// MessagePack - 性能更好,体积更小
data, _ := msgpack.Marshal(user)

// Protocol Buffers - 强类型,跨语言
data, _ := proto.Marshal(user)

性能优化

批量操作

虽然 KeyCache 接口不支持批量操作,但可以使用 Pipeline:

func (c *UserCache) SetBatch(ctx context.Context, users map[string]*User) error {
    pipe := c.RDB.Pipeline()

    for userID, user := range users {
        key := "user:" + userID
        data, _ := json.Marshal(user)
        pipe.Set(ctx, key, data, c.TTL)
    }

    _, err := pipe.Exec(ctx)
    return err
}

缓存预热

在系统启动时预加载热点数据:

func (s *UserService) WarmupCache(ctx context.Context) error {
    // 获取热点用户列表
    hotUsers, err := s.getHotUsers()
    if err != nil {
        return err
    }

    // 批量写入缓存
    ginCtx := &gin.Context{}
    for _, user := range hotUsers {
        _ = s.cache.Set(ginCtx, user.ID, user)
    }

    return nil
}

注意事项

  1. TTL 管理:合理设置过期时间,避免缓存雪崩
  2. 内存占用:大对象考虑压缩或分片存储
  3. 并发安全:Redis 操作本身是原子的,但业务逻辑需要注意
  4. 缓存一致性:更新数据库后及时更新或删除缓存

On this page