You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

4.5 KiB

可降级缓存系统设计

日期: 2026-05-22 状态: approved

1. 概述

设计一个 Server-only 的通用 K-V 缓存系统 packages/cache,支持 Redis 主存储 + Memory 内存兜底。当 Redis 不可用时,自动降级到内存缓存,对调用方透明。

2. 架构

packages/cache/
├── package.json
├── index.ts              ← 导出工厂函数和类型
└── lib/
    ├── types.ts          ← 核心类型定义
    ├── managers/
    │   └── cache-manager.ts   ← CacheManager 实现
    └── drivers/
        ├── base.ts             ← Driver 基类 / 接口
        ├── memory-driver.ts    ← 内存驱动
        └── redis-driver.ts     ← Redis 驱动(ioredis)

设计原则

  • 分层适配器:调用方只跟 CacheManager 交互,不感知底层存储
  • 手动显式降级:按 drivers 数组顺序尝试,返回第一个成功结果
  • 扩展性:新增驱动(如 cloudflare-kv-driver)只需实现 CacheDriver 接口

3. 核心类型

// lib/types.ts
export interface CacheOptions {
  ttl?: number          // 默认 TTL(秒),0 = 不过期
  namespace?: string    // key 前缀隔离(未来扩展)
}

export interface CacheEntry<T = unknown> {
  value: T
  expiresAt: number | null // Unix ms,null = 永不过期
}

export interface CacheDriver {
  name: string
  get<T = unknown>(key: string): Promise<T | null>
  set<T = unknown>(key: string, value: T, ttl?: number): Promise<void>
  del(key: string): Promise<void>
  exists(key: string): Promise<boolean>
  clear(): Promise<void>
}

export interface CacheManagerOptions {
  drivers: CacheDriver[]
  defaultTtl?: number
}

4. CacheManager

文件: lib/managers/cache-manager.ts

export class CacheManager {
  private drivers: CacheDriver[]
  private defaultTtl: number

  constructor(options: CacheManagerOptions)

  async get<T = unknown>(key: string): Promise<T | null>
  async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void>
  async del(key: string): Promise<void>
  async exists(key: string): Promise<boolean>
  async clear(): Promise<void>

  withPrefix(ns: string): CacheManager
}

降级逻辑(get): 遍历 drivers,返回第一个成功结果,全部失败返回 null

一致性逻辑(set/del/exists/clear): 遍历所有 driver 并发写入,确保降级后各层数据一致。

5. MemoryDriver

文件: lib/drivers/memory-driver.ts

  • Map<string, { value, expiresAt }> 存储
  • TTL = 0 不设置过期时间
  • 过期检查在 get 时懒清理(检查 expiresAt 是否已到)
  • clear() 清空整个 Map

6. RedisDriver

文件: lib/drivers/redis-driver.ts

  • 使用 ioredis 客户端
  • value 序列化为 JSON.stringify 存储
  • TTL = 0 时使用 persist(不设过期)
  • clear() 使用 FLUSHDB 清空当前数据库(假设 prod 使用独立 db)
  • 需支持 disconnect 关闭连接

RedisOptions:

interface RedisOptions {
  host: string
  port: number
  password?: string
  db?: number
}

7. 工厂函数

文件: index.ts

interface CacheFactoryOptions {
  redis?: {
    host: string
    port: number
    password?: string
    db?: number
  }
  memory?: boolean      // 是否启用内存兜底,默认 true
  defaultTtl?: number   // 默认 TTL(秒)
}

function createCache(options: CacheFactoryOptions): CacheManager

行为:

  • 如果指定了 redis,则将其作为主驱动加入 drivers 数组首位
  • 如果 memory !== false,则将 MemoryDriver 加入 drivers 数组末位
  • 如果既没有 redis 也没有内存(memory: false),抛出错误

8. 使用方式

// 初始化(在 Nitro 插件或 server/utils 中
const cache = createCache({
  redis: { host: '127.0.0.1', port: 6379 },
  defaultTtl: 300,
})

// API 路由中使用
export default defineEventHandler(async (event) => {
  const key = 'users:list'

  const cached = await cache.get(key)
  if (cached) return cached

  const data = await fetchUsers()
  await cache.set(key, data)
  return data
})

9. 错误处理

  • get 操作:单个 driver 失败时 warn 并尝试下一个;全部失败返回 null
  • set/del/exists/clear 操作:单个 driver 失败时 warn 但不中断(因为是并发写)
  • Redis 连接失败(如网络不可达):触发降级,不影响服务启动

10. 依赖

{
  "ioredis": "^5.x"
}

内存驱动无外部依赖。