1 changed files with 175 additions and 0 deletions
@ -0,0 +1,175 @@ |
|||||
|
# 可降级缓存系统设计 |
||||
|
|
||||
|
**日期:** 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. 核心类型 |
||||
|
|
||||
|
```typescript |
||||
|
// 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` |
||||
|
|
||||
|
```typescript |
||||
|
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:** |
||||
|
```typescript |
||||
|
interface RedisOptions { |
||||
|
host: string |
||||
|
port: number |
||||
|
password?: string |
||||
|
db?: number |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 7. 工厂函数 |
||||
|
|
||||
|
**文件:** `index.ts` |
||||
|
|
||||
|
```typescript |
||||
|
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. 使用方式 |
||||
|
|
||||
|
```typescript |
||||
|
// 初始化(在 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. 依赖 |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"ioredis": "^5.x" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
内存驱动无外部依赖。 |
||||
Loading…
Reference in new issue