竹简文档
缓存

HashCache

基于哈希数据结构的二维键值对缓存接口

HashCache

HashCache 定义了基于哈希(Hash)数据结构的缓存操作接口,用于管理二维键值对数据。

接口定义

type HashCache[K any, F comparable, V any, S any] interface {
    Get(ctx context.Context, key K, field F) (*V, bool, error)
    Set(ctx context.Context, key K, field F, value *V) error
    GetAll(ctx context.Context, key K) (map[F]V, error)
    GetAllStruct(ctx context.Context, key K) (S, error)
    SetAll(ctx context.Context, key K, fields map[F]*V) error
    SetAllStruct(ctx context.Context, key K, value S) error
    Exists(ctx context.Context, key K, field F) (bool, error)
    Remove(ctx context.Context, key K, fields ...F) error
    Delete(ctx context.Context, key K) error
}

泛型参数

字段

类型

注意: 字段键类型 F 必须是可比较的(comparable),因为需要作为 map 的键。

数据结构

哈希结构为:key → field → value

user:123 → {
    "name": "张三",
    "email": "zhangsan@example.com",
    "age": "25"
}

方法说明

Get

获取指定字段的值。

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

参数:

  • ctx - context.Context 上下文
  • key - 哈希键
  • field - 字段键

返回值:

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

Set

设置单个字段的值。

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

参数:

  • ctx - context.Context 上下文
  • key - 哈希键
  • field - 字段键
  • value - 指向值的指针

返回值:

  • error - 错误信息

GetAll

获取哈希表中的所有字段和值。

GetAll(ctx context.Context, key K) (map[F]V, error)

参数:

  • ctx - context.Context 上下文
  • key - 哈希键

返回值:

  • map[F]V - 所有字段和值的映射
  • error - 错误信息

GetAllStruct

获取哈希表中的所有字段和值,直接映射到指定结构体。

GetAllStruct(ctx context.Context, key K) (S, error)

参数:

  • ctx - context.Context 上下文
  • key - 哈希键

返回值:

  • S - 结构体映射结果
  • error - 错误信息

SetAll

批量设置多个字段的值。

SetAll(ctx context.Context, key K, fields map[F]*V) error

参数:

  • ctx - context.Context 上下文
  • key - 哈希键
  • fields - 字段和值的映射

返回值:

  • error - 错误信息

SetAllStruct

批量设置多个字段的值,使用指定结构体进行写入。

SetAllStruct(ctx context.Context, key K, value S) error

参数:

  • ctx - context.Context 上下文
  • key - 哈希键
  • value - 结构体数据

返回值:

  • error - 错误信息

Exists

检查指定字段是否存在。

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

参数:

  • ctx - context.Context 上下文
  • key - 哈希键
  • field - 字段键

返回值:

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

Remove

从哈希表中移除指定的字段。

Remove(ctx context.Context, key K, fields ...F) error

参数:

  • ctx - context.Context 上下文
  • key - 哈希键
  • fields - 要移除的字段列表(可变参数)

返回值:

  • error - 错误信息

Delete

删除整个哈希表。

Delete(ctx context.Context, key K) error

参数:

  • ctx - context.Context 上下文
  • key - 哈希键

返回值:

  • error - 错误信息

实现示例

用户配置缓存

cache/user_config.go
import (
    "context"
    "encoding/json"

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

type UserConfig struct {
    Theme    string `json:"theme"`
    Language string `json:"language"`
    Timezone string `json:"timezone"`
}

type UserConfigCache struct {
    *xCache.Cache
}

// Get 获取单个配置项
func (c *UserConfigCache) Get(ctx context.Context, userID string, field string) (*string, bool, error) {
    key := "user:config:" + userID

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

    return &val, true, nil
}

// Set 设置单个配置项
func (c *UserConfigCache) Set(ctx context.Context, userID string, field string, value *string) error {
    key := "user:config:" + userID
    return c.RDB.HSet(ctx, key, field, *value).Err()
}

// GetAll 获取所有配置
func (c *UserConfigCache) GetAll(ctx context.Context, userID string) (map[string]string, error) {
    key := "user:config:" + userID

    result, err := c.RDB.HGetAll(ctx, key).Result()
    if err != nil {
        return nil, err
    }

    return result, nil
}

// SetAll 批量设置配置
func (c *UserConfigCache) SetAll(ctx context.Context, userID string, fields map[string]*string) error {
    key := "user:config:" + userID

    data := make(map[string]interface{})
    for field, value := range fields {
        if value != nil {
            data[field] = *value
        }
    }

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

// Exists 检查配置项是否存在
func (c *UserConfigCache) Exists(ctx context.Context, userID string, field string) (bool, error) {
    key := "user:config:" + userID
    return c.RDB.HExists(ctx, key, field).Result()
}

// Remove 删除配置项
func (c *UserConfigCache) Remove(ctx context.Context, userID string, fields ...string) error {
    key := "user:config:" + userID
    return c.RDB.HDel(ctx, key, fields...).Err()
}

// Delete 删除所有配置
func (c *UserConfigCache) Delete(ctx context.Context, userID string) error {
    key := "user:config:" + userID
    return c.RDB.Del(ctx, key).Err()
}

使用配置缓存

service/user_config.go
type UserConfigService struct {
    cache *UserConfigCache
}

func NewUserConfigService(cache *xCache.Cache) *UserConfigService {
    return &UserConfigService{
        cache: &UserConfigCache{Cache: cache},
    }
}

// GetTheme 获取用户主题
func (s *UserConfigService) GetTheme(ctx context.Context, userID string) (string, error) {
    theme, exists, err := s.cache.Get(ctx, userID, "theme")
    if err != nil {
        return "", err
    }
    if !exists {
        return "default", nil
    }
    return *theme, nil
}

// UpdateTheme 更新用户主题
func (s *UserConfigService) UpdateTheme(ctx context.Context, userID string, theme string) error {
    // 更新数据库
    if err := s.updateThemeInDB(userID, theme); err != nil {
        return err
    }

    // 更新缓存
    return s.cache.Set(ctx, userID, "theme", &theme)
}

// GetAllConfig 获取所有配置
func (s *UserConfigService) GetAllConfig(ctx context.Context, userID string) (map[string]string, error) {
    config, err := s.cache.GetAll(ctx, userID)
    if err != nil {
        return nil, err
    }

    // 如果缓存为空,从数据库加载
    if len(config) == 0 {
        config, err = s.loadConfigFromDB(userID)
        if err != nil {
            return nil, err
        }

        // 写入缓存
        fields := make(map[string]*string)
        for k, v := range config {
            val := v
            fields[k] = &val
        }
        _ = s.cache.SetAll(ctx, userID, fields)
    }

    return config, nil
}

// ResetConfig 重置配置
func (s *UserConfigService) ResetConfig(ctx context.Context, userID string) error {
    // 删除数据库配置
    if err := s.deleteConfigFromDB(userID); err != nil {
        return err
    }

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

使用场景

用户配置

type UserConfigCache interface {
    xCache.HashCache[string, string, string]
}

适用于:

  • 用户偏好设置
  • 界面配置
  • 通知设置

商品详情

type ProductCache interface {
    xCache.HashCache[string, string, interface{}]
}

适用于:

  • 商品价格
  • 库存数量
  • 商品属性

会话数据

type SessionCache interface {
    xCache.HashCache[string, string, string]
}

适用于:

  • 用户会话
  • 临时数据
  • 表单状态

最佳实践

1. 字段命名规范

使用清晰的字段名:

// ✅ 好的命名
fields := map[string]*string{
    "theme":    &theme,
    "language": &language,
    "timezone": &timezone,
}

// ❌ 避免的命名
fields := map[string]*string{
    "t": &theme,
    "l": &language,
    "z": &timezone,
}

2. 批量操作优化

优先使用 GetAllSetAll

// ✅ 批量获取
config, _ := cache.GetAll(ctx, userID)

// ❌ 逐个获取
theme, _ := cache.Get(ctx, userID, "theme")
language, _ := cache.Get(ctx, userID, "language")
timezone, _ := cache.Get(ctx, userID, "timezone")

3. 部分更新

只更新变化的字段:

func (s *UserConfigService) UpdatePartial(ctx context.Context, userID string, updates map[string]string) error {
    fields := make(map[string]*string)
    for k, v := range updates {
        val := v
        fields[k] = &val
    }
    return s.cache.SetAll(ctx, userID, fields)
}

4. 字段删除

使用 Remove 而不是 Delete

// ✅ 删除特定字段
cache.Remove(ctx, userID, "theme", "language")

// ❌ 删除整个哈希表(除非确实需要)
cache.Delete(ctx, userID)

性能优化

使用 Pipeline

批量操作多个哈希表:

func (c *UserConfigCache) SetMultipleUsers(ctx context.Context, configs map[string]map[string]*string) error {
    pipe := c.RDB.Pipeline()

    for userID, fields := range configs {
        key := "user:config:" + userID
        data := make(map[string]interface{})
        for field, value := range fields {
            if value != nil {
                data[field] = *value
            }
        }
        pipe.HSet(ctx, key, data)
    }

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

字段数量控制

避免单个哈希表字段过多:

// ✅ 合理的字段数量(< 100)
user:config:123 → {
    "theme": "dark",
    "language": "zh-CN",
    "timezone": "Asia/Shanghai"
}

// ❌ 字段过多(> 1000)
user:data:123 → {
    "field1": "value1",
    "field2": "value2",
    // ... 1000+ fields
}

注意事项

  1. 字段类型限制:字段键必须是 comparable 类型
  2. 内存占用:大量字段会占用较多内存,考虑分片
  3. 原子性:单个字段操作是原子的,但多字段操作不保证原子性
  4. 过期时间:哈希表整体过期,无法为单个字段设置 TTL
  5. 序列化:复杂值类型需要序列化为字符串存储

On this page