缓存
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- 错误信息
注意: 需要处理 value 为 nil 的场景。
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- 错误信息
实现示例
用户缓存实现
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()
}使用缓存
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
}注意事项
- TTL 管理:合理设置过期时间,避免缓存雪崩
- 内存占用:大对象考虑压缩或分片存储
- 并发安全:Redis 操作本身是原子的,但业务逻辑需要注意
- 缓存一致性:更新数据库后及时更新或删除缓存