@zenweb/cache
v5.6.0
Published
Zenweb Cache module
Maintainers
Readme
@zenweb/cache
基于 Redis 的对象缓存模块,为 zenweb 框架提供缓存能力。
特性
- 对象自动序列化 / 反序列化(默认 JSON,可自定义)
- 大对象自动压缩存储(默认 gzip,可自定义)
- 缓存击穿防护(分布式锁 + 排队等待)
- 路由级缓存中间件
- 缓存助手(封装 key 生成、获取、更新、删除)
安装
npm i @zenweb/cache快速开始
import { create } from 'zenweb';
import modCache from '@zenweb/cache';
create()
.setup(modCache())
.start();默认连接 127.0.0.1:6379,可通过环境变量配置:
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0基本用法
lockGet — 防击穿缓存获取
最常用的方法。缓存存在直接返回,不存在时自动加锁获取数据并写入缓存,保证并发请求只有一个执行数据获取。
import { $cache } from '@zenweb/cache';
const user = await $cache.lockGet('USER:42', () => db.getUser(42));set / get — 手动管理缓存
// 写入
await $cache.set('KEY', { foo: 'bar' }, 300); // 300 秒过期
// 读取
const data = await $cache.get<{ foo: string }>('KEY');
// 删除
await $cache.del('KEY');直接输出压缩数据
对于已压缩的缓存数据(如 gzip),可以跳过解压直接输出给浏览器:
const result = await $cache.lockGet('DATA', fetchLargeData, {
parse: false,
decompress: false,
});
if (result.compressed) {
ctx.set('Content-Encoding', 'gzip');
}
ctx.body = result.data;路由缓存中间件
使用 cached() 中间件自动缓存控制器返回结果,缓存命中时不会执行控制器方法:
import { Get } from 'zenweb';
import { cached } from '@zenweb/cache';
export class UserController {
@Get(cached())
async list() {
return db.getUsers(); // 结果自动缓存,默认 key 为请求路径
}
@Get(cached('USER-LIST'))
async list2() {
return db.getUsers(); // 指定固定 key
}
@Get(cached(ctx => `USER:${ctx.params.id}`))
async detail(ctx: Context) {
return db.getUser(ctx.params.id); // 动态 key
}
}lockGet 选项
await $cache.lockGet('KEY', fetch, {
noWait: true, // 缓存不存在时立即返回 undefined,后台异步获取
localStore: {}, // 请求级本地缓存,避免同一请求重复读取 Redis
preRefresh: 30, // 剩余 TTL < 30 秒时后台异步刷新
refresh: true, // 强制刷新,跳过缓存读取
});noWait 适用场景: 非关键数据、耗时较长的操作,不希望阻塞请求。
return await $cache.lockGet('STATS', async () => {
await sleep(3000);
return getStats();
}, { noWait: true }) || "数据加载中,请稍后重试";localStore 适用场景: 同一请求内多次访问相同缓存,避免重复网络开销。
const store = {};
// 首次从 Redis 获取并写入 store
const a = await $cache.lockGet('KEY', fetch, { localStore: store });
// 后续从 store 直接返回
const b = await $cache.lockGet('KEY', fetch, { localStore: store });缓存助手
将缓存的 key 生成、获取、更新、删除封装到一起,适合在 service 层使用:
import { $cacheHelper } from '@zenweb/cache';
const userCache = $cacheHelper('USER', (id: number) => db.getUser(id));
await userCache.get(42); // key: "USER:42",缓存不存在时自动获取
await userCache.set(user, 42); // 手动设置
await userCache.del(42); // 删除也可以用函数自定义 key 生成规则:
const orderCache = $cacheHelper(
(userId: number, status: string) => `ORDERS:${userId}:${status}`,
(userId, status) => db.getOrders(userId, status),
);
await orderCache.get(42, 'active');自定义序列化与压缩
默认使用 JSON 序列化 + gzip 压缩。成品模块 @zenweb/cache-opt 使用 MessagePack 序列化 + LZ4 压缩,满足大部分场景需求,推荐直接使用:
npm i @zenweb/cache-optimport { create } from 'zenweb';
import modCache from '@zenweb/cache';
import cacheOpt from '@zenweb/cache-opt';
create()
.setup(modCache(cacheOpt()))
.start();需要自定义 Redis 等配置时,直接传入 SetupOption:
app.setup(modCache(cacheOpt({
redis: { host: '127.0.0.1', port: 6379 },
set: { ttl: 300 },
})));如需完全自定义实现(例如 msgpackr + snappy):
import { create } from 'zenweb';
import modCache from '@zenweb/cache';
import { Packr } from 'msgpackr';
import * as snappy from 'snappy';
const SNAPPY_HEADER = Buffer.from('SNAPPY');
const packer = new Packr();
create()
.setup(modCache({
serializer: {
serialize(data) { return packer.pack(data); },
deserialize(data) { return packer.unpack(data); },
},
compressor: {
isCompressed(data) {
return SNAPPY_HEADER.equals(data.subarray(0, SNAPPY_HEADER.length));
},
async compress(data) {
const compressed = await snappy.compress(data);
return Buffer.concat([SNAPPY_HEADER, compressed]);
},
async decompress(data) {
const result = await snappy.uncompress(data.subarray(SNAPPY_HEADER.length));
return typeof result === 'string' ? Buffer.from(result) : result;
},
},
}))
.start();API
Cache 实例方法(通过 $cache 调用)
| 方法 | 说明 |
|------|------|
| set(key, value, ttl?) | 缓存对象,自动序列化,大对象自动压缩 |
| get<T>(key, opt?) | 获取缓存,自动解压反序列化 |
| lockGet<T>(key, fetch, opt?) | 防击穿获取,缓存不存在时加锁调用 fetch |
| del(key) | 删除缓存 |
| ttl(key) | 获取剩余有效期(秒) |
| setRaw(key, value, ttl?) | 直接存储 Buffer,不经过序列化 |
| getRaw(key) | 直接获取 Buffer,不经过解压反序列化 |
| singleRunner(key, run, opt?) | 分布式单例执行器 |
其他导出
| 导出 | 说明 |
|------|------|
| cached(key?, opt?) | 路由缓存中间件 |
| $cacheHelper(key, fetch?, opt?) | 缓存助手工厂 |
| Locker | Redis 分布式锁 |
| cacheKey(...args) | 缓存 key 生成工具 |
| CacheResult | { compressed: boolean, data: Buffer } |
配置项
| 选项 | 默认值 | 说明 |
|------|--------|------|
| ttl | 60 | 缓存有效期(秒),0 = 永久 |
| compressMinLength | 1024 | 最小压缩长度(字节),0 = 不压缩 |
| compressStoreRatio | 0.8 | 压缩后需小于原始 × 此比率才存储 |
| retryTimeout | 5000 | 获取锁重试超时(毫秒) |
| retryDelay | 500 | 重试间隔(毫秒) |
| lockTimeout | 10000 | 锁自动释放超时(毫秒) |
