Files
license-manager/backend/docs/modules_design/2026.1.11-缓存模块结构设计.md
T

11 KiB

缓存模块设计

基于Clean Architecture设计,支持内存缓存和Redis缓存两种实现方式,提供统一的缓存接口,便于在不同环境下切换。

设计理念

  • 抽象接口:通过接口抽象不同缓存实现
  • 配置驱动:支持运行时配置缓存类型和参数
  • 优雅降级:缓存异常时不影响业务逻辑
  • 性能监控:提供缓存命中率等指标
  • 序列化支持:自动处理常见数据类型的序列化

核心架构

1. 缓存接口设计

// Cache 缓存接口
type Cache interface {
    // 基础操作
    Get(ctx context.Context, key string) (string, error)
    Set(ctx context.Context, key string, value string, ttl time.Duration) error
    Del(ctx context.Context, keys ...string) error
    Exists(ctx context.Context, key string) (bool, error)

    // TTL操作
    TTL(ctx context.Context, key string) (time.Duration, error)
    Expire(ctx context.Context, key string, ttl time.Duration) error

    // 数值操作
    Incr(ctx context.Context, key string) (int64, error)
    Decr(ctx context.Context, key string) (int64, error)

    // 批量操作
    MGet(ctx context.Context, keys ...string) ([]string, error)
    MSet(ctx context.Context, pairs map[string]string, ttl time.Duration) error

    // 工具方法
    Ping(ctx context.Context) error
    Close() error
}

// CacheWithTTL 带TTL的缓存接口
type CacheWithTTL interface {
    Cache
    GetWithTTL(ctx context.Context, key string) (string, time.Duration, error)
}

// Serializer 序列化接口
type Serializer interface {
    Marshal(v interface{}) ([]byte, error)
    Unmarshal(data []byte, v interface{}) error
}

2. 缓存配置

// CacheConfig 缓存配置
type CacheConfig struct {
    Type     string        `mapstructure:"type"`      // 缓存类型: memory, redis
    TTL      time.Duration `mapstructure:"ttl"`       // 默认TTL
    Enabled  bool          `mapstructure:"enabled"`   // 是否启用缓存
    Redis    RedisConfig   `mapstructure:"redis"`     // Redis配置
    Memory   MemoryConfig  `mapstructure:"memory"`    // 内存配置
}

// RedisConfig Redis配置
type RedisConfig struct {
    Host            string        `mapstructure:"host"`
    Port            int           `mapstructure:"port"`
    Password        string        `mapstructure:"password"`
    DB              int           `mapstructure:"db"`
    PoolSize        int           `mapstructure:"pool_size"`
    MinIdleConns    int           `mapstructure:"min_idle_conns"`
    ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"`
    DialTimeout     time.Duration `mapstructure:"dial_timeout"`
    ReadTimeout     time.Duration `mapstructure:"read_timeout"`
    WriteTimeout    time.Duration `mapstructure:"write_timeout"`
}

// MemoryConfig 内存配置
type MemoryConfig struct {
    MaxSize int `mapstructure:"max_size"` // 最大缓存条目数
    CleanupInterval time.Duration `mapstructure:"cleanup_interval"` // 清理间隔
}

实现方案

1. 内存缓存实现

// memoryCache 内存缓存实现
type memoryCache struct {
    data    sync.Map
    ttl     sync.Map
    maxSize int
    mu      sync.RWMutex
}

// NewMemoryCache 创建内存缓存
func NewMemoryCache(config MemoryConfig) Cache {
    cache := &memoryCache{
        maxSize: config.MaxSize,
        data:    sync.Map{},
        ttl:     sync.Map{},
    }

    // 启动清理协程
    if config.CleanupInterval > 0 {
        go cache.cleanup(config.CleanupInterval)
    }

    return cache
}

func (m *memoryCache) Get(ctx context.Context, key string) (string, error) {
    value, ok := m.data.Load(key)
    if !ok {
        return "", ErrCacheMiss
    }

    // 检查TTL
    if ttl, exists := m.ttl.Load(key); exists {
        if time.Now().After(ttl.(time.Time)) {
            m.data.Delete(key)
            m.ttl.Delete(key)
            return "", ErrCacheMiss
        }
    }

    return value.(string), nil
}

func (m *memoryCache) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
    // 简单的容量检查(生产环境建议使用更完善的LRU缓存库)
    if m.maxSize > 0 {
        // 这里可以实现简单的容量控制逻辑
        // 生产环境建议使用 github.com/hashicorp/golang-lru 等专业库
    }

    m.data.Store(key, value)
    if ttl > 0 {
        m.ttl.Store(key, time.Now().Add(ttl))
    }

    return nil
}

2. Redis缓存实现

// redisCache Redis缓存实现
type redisCache struct {
    client *redis.Client
    ttl    time.Duration
}

// NewRedisCache 创建Redis缓存
func NewRedisCache(config RedisConfig, defaultTTL time.Duration) (Cache, error) {
    client := redis.NewClient(&redis.Options{
        Addr:     fmt.Sprintf("%s:%d", config.Host, config.Port),
        Password: config.Password,
        DB:       config.DB,
        PoolSize: config.PoolSize,
    })

    // 测试连接
    if err := client.Ping(context.Background()).Err(); err != nil {
        return nil, fmt.Errorf("failed to connect to Redis: %w", err)
    }

    return &redisCache{
        client: client,
        ttl:    defaultTTL,
    }, nil
}

func (r *redisCache) Get(ctx context.Context, key string) (string, error) {
    value, err := r.client.Get(ctx, key).Result()
    if err == redis.Nil {
        return "", ErrCacheMiss
    }
    if err != nil {
        return "", fmt.Errorf("cache get error: %w", err)
    }
    return value, nil
}

func (r *redisCache) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
    if ttl == 0 {
        ttl = r.ttl
    }
    return r.client.Set(ctx, key, value, ttl).Err()
}

3. 缓存工厂

// NewCache 创建缓存实例
func NewCache(config CacheConfig) (Cache, error) {
    if !config.Enabled {
        return &noOpCache{}, nil
    }

    switch config.Type {
    case "memory":
        return NewMemoryCache(config.Memory)
    case "redis":
        return NewRedisCache(config.Redis, config.TTL)
    default:
        return nil, fmt.Errorf("unsupported cache type: %s", config.Type)
    }
}

// noOpCache 空操作缓存(当缓存被禁用时使用)
type noOpCache struct{}

func (n *noOpCache) Get(ctx context.Context, key string) (string, error) {
    return "", ErrCacheMiss
}

func (n *noOpCache) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
    return nil
}
// ... 其他方法都返回nil或默认值

高级功能

1. 序列化支持

// JSONSerializer JSON序列化器
type JSONSerializer struct{}

func (j *JSONSerializer) Marshal(v interface{}) ([]byte, error) {
    return json.Marshal(v)
}

func (j *JSONSerializer) Unmarshal(data []byte, v interface{}) error {
    return json.Unmarshal(data, v)
}

// Cached 带序列化的缓存包装器
type Cached struct {
    Cache
    Serializer Serializer
}

func (c *Cached) SetObject(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
    data, err := c.Serializer.Marshal(value)
    if err != nil {
        return err
    }
    return c.Cache.Set(ctx, key, string(data), ttl)
}

func (c *Cached) GetObject(ctx context.Context, key string, dest interface{}) error {
    value, err := c.Cache.Get(ctx, key)
    if err != nil {
        return err
    }
    return c.Serializer.Unmarshal([]byte(value), dest)
}

2. 缓存装饰器

// CacheDecorator 缓存装饰器
type CacheDecorator struct {
    Cache
    prefix string // 键前缀
}

func (c *CacheDecorator) buildKey(key string) string {
    if c.prefix != "" {
        return c.prefix + ":" + key
    }
    return key
}

func (c *CacheDecorator) Get(ctx context.Context, key string) (string, error) {
    return c.Cache.Get(ctx, c.buildKey(key))
}

func (c *CacheDecorator) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
    return c.Cache.Set(ctx, c.buildKey(key), value, ttl)
}

3. 缓存键管理

// KeyBuilder 缓存键构建器
type KeyBuilder struct {
    prefix string
    separator string
}

func NewKeyBuilder(prefix string) *KeyBuilder {
    return &KeyBuilder{
        prefix: prefix,
        separator: ":",
    }
}

func (k *KeyBuilder) Build(parts ...string) string {
    if k.prefix != "" {
        parts = append([]string{k.prefix}, parts...)
    }
    return strings.Join(parts, k.separator)
}

// 预定义键模式
const (
    UserKeyPrefix    = "user"
    SessionKeyPrefix = "session"
    SMSCodePrefix    = "sms:code"
    SMSLimitPrefix   = "sms:limit"
)

// 使用示例
keyBuilder := NewKeyBuilder("app")
userKey := keyBuilder.Build(UserKeyPrefix, "123") // "app:user:123"

配置示例

内存缓存配置

cache:
  enabled: true
  type: memory
  ttl: 30m
  memory:
    max_size: 10000
    cleanup_interval: 10m

Redis缓存配置

cache:
  enabled: true
  type: redis
  ttl: 30m
  redis:
    host: localhost
    port: 6379
    password: ""
    db: 0
    pool_size: 10
    min_idle_conns: 5
    conn_max_idle_time: 30m
    dial_timeout: 5s
    read_timeout: 3s
    write_timeout: 3s

错误处理

var (
    ErrCacheMiss = errors.New("cache miss")
    ErrCacheClosed = errors.New("cache closed")
)

// 优雅降级:缓存错误不影响业务
func safeCacheOperation(cache Cache, operation func() error) error {
    defer func() {
        if r := recover(); r != nil {
            // 记录错误日志,但不影响业务
        }
    }()

    if cache == nil {
        return nil // 缓存未启用,直接跳过
    }

    return operation()
}

监控指标

// CacheStats 缓存统计信息
type CacheStats struct {
    Hits        int64   // 命中次数
    Misses      int64   // 未命中次数
    Errors      int64   // 错误次数
    HitRate     float64 // 命中率
    TotalOps    int64   // 总操作数
    AvgRespTime int64   // 平均响应时间(纳秒)
}

func (s *CacheStats) RecordHit() {
    s.Hits++
    s.updateHitRate()
}

func (s *CacheStats) RecordMiss() {
    s.Misses++
    s.updateHitRate()
}

func (s *CacheStats) RecordError() {
    s.Errors++
}

func (s *CacheStats) updateHitRate() {
    total := s.Hits + s.Misses
    if total > 0 {
        s.HitRate = float64(s.Hits) / float64(total)
    }
}

使用示例

// 初始化缓存
cache, err := NewCache(config.Cache)
if err != nil {
    log.Fatal(err)
}

// 基础使用
cache.Set(ctx, "user:123", "user_data", time.Hour)
data, err := cache.Get(ctx, "user:123")

// 带序列化的使用
cached := &Cached{
    Cache: cache,
    Serializer: &JSONSerializer{},
}

user := User{ID: 123, Name: "John"}
cached.SetObject(ctx, "user:123", user, time.Hour)

var userData User
cached.GetObject(ctx, "user:123", &userData)

实施计划

第一阶段:核心功能 (P0)

  1. 实现缓存接口和基础实现类
  2. 支持内存缓存 (sync.Map)
  3. 支持Redis缓存 (go-redis/v9)
  4. 实现缓存工厂和配置加载
  5. 集成到现有项目中替换现有缓存逻辑

第二阶段:增强功能 (P1)

  1. 添加序列化支持 (JSONSerializer)
  2. 实现缓存键构建器 (KeyBuilder)
  3. 添加TTL查询和过期时间管理
  4. 完善批量操作支持

第三阶段:监控运维 (P2)

  1. 实现缓存统计和监控指标
  2. 添加性能监控和告警
  3. 优化连接池和错误重试
  4. 支持缓存预热和配置热更新