这是一个轻松结合本地和远程缓存的方法执行代理库,带使用统计输出,使用 TS 编写。
- 仅支持 ES Module,不支持 commonjs
- 方法代理仅支持 AsyncFunction
- 避免并发击穿,缓存失效时,不会并发地向后端服务发起请求
- 支持后台轮询更新,保证缓存一直有效并且是最新的
- 支持后备缓存,即使远程服务崩溃并且缓存已过期,也能获取缓存内容
- 支持远程缓存,如 Redis
- 支持缓存使用情况统计
进程内缓存(local cache) --> 远程缓存服务(remote cache) --> 后备优先缓存(fallback cache) --> 真实调用(real call) --> 后备缓存(fallback Cache)
如图所示:
npm i cache-proxy-plus
// or
pnpm add cache-proxy-plus
import { cacheProxyPlus } from 'cache-proxy-plus'
class Base {
async doBase(id) {
const res = Math.random(id)
return res
}
}
class Manager extends Base {
async doJob(id) {
const res = Math.random()
return res
}
}
const manager = new Manager()
const managerProxy = cacheProxyPlus(manager, { ttl: 2000, statsInterval: 1000 * 10 })
managerProxy.channel.on('stats', s => console.info(s))
setInterval(async () => {
const res1 = await managerProxy.doJob(1)
const res2 = await managerProxy.doJob(2)
const res3 = await managerProxy.doBase(3)
}, 1000)
引入方法时,也可以使用cacheProxy
,代替cacheProxyPlus
。
cacheProxyPlus(target, options)
target
:
- 包含有
AsyncFunction
的class
对象
options
:
exclude
: (默认值:[]
) 排除不需要缓存代理的 method,字符串数组ttl
: (默认值:1000 * 60
) 缓存的过期时间,单位 mscheckPeriod
: (默认值:1000
) 检查缓存过期的时间间隔,单位 msstatsInterval
: (默认值:1000 * 60
* 缓存使用统计打印间隔,单位 msrandomTtl
: (默认值:false
) 是否添加随机超时?计算公式为:ttl * (0.8 + 0.3 * Math.random())
methodTtls
: (默认值:null
) 对某些方法单独配置超时时间,是Object
.- [
方法名
]: (默认值:1000 * 60
) 特定方法名的缓存过期时间,单位 ms
- [
subject
: (默认值:[target.constructor.name]
) 主题,用作 key 前缀,区分不同 target 相同 method 的 key.- 注意:不能是随机字符串,否则重启后 key 就不一样了
fallback
: (默认值:false
) 是否使用后备缓存,后备缓存将保存上一次成功获取的值。在本地和asyncCache都失效的时候,同时还遭遇真实调用获取失败后,从后备缓存获取值。fallbackTtl
: (默认值:1000 * 60 * 60
) 后备缓存的过期时间,单位msfallbackMax
: (默认值:1000 * 10
) 后备缓存的最多能够保存的key数量,使用LRU策略fallbackFirst
: (默认值:false
) 优先使用后备缓存,异步执行update,这样可以不用等到真正的update执行完毕。bgUpdate
: (默认值:false
) 是否支持后台轮询地更新key的值bgUpdateDelay
: (默认值:100
) 后台轮询地更新两个key直接的等待时间bgUpdatePeriodDelay
: (默认值:1000 * 5
) 后台轮询更新一轮后,等待多久才执行下一轮更新bgUpdateExpired
: (默认值:1000 * 60 * 60
) 一个key多久没访问后,会把停止对该key继续后台更新concurrency
: (默认值:10
) 真实请求的总并发限制,包括所有method,避免对后端有过大冲击remoteCache
: (默认值:null
) 异步缓存对象,可以实现使用 redis 和 memcached 等外部缓存,要继承基类RemoteCache
使用远程缓存需要继承RemoteCache
基础类,相关代码: RemoteCache
下面是使用 Redis 作为远程缓存的例子:
import { RemoteCache } from 'cache-proxy-plus'
class RedisCache extends RemoteCache {
private redis = new Redis()
constructor() {
super()
}
async set(key: string, value: any, ttl: number) {
const wrapped = { value }
return this.redis.set(key, JSON.stringify(wrapped), 'PX', ttl + 100)
}
async get(key: string) {
const wrapped = await this.redis.get(key)
return wrapped ? JSON.parse(wrapped).value : null
}
quit() {
this.redis.disconnect()
this.redis.quit()
}
}
有两种情况,一种是清空所有本地缓存,另外一种是清空代理中的某个方法的本地缓存。
// 清空所有本地缓存
managerProxy.channel.clear()
// 清空某个方法本地缓存
managerProxy.channel.clear(methodName)
managerProxy.channel.on('eventName', () => {})
eventName
有:stats
, bg.stats
,bg.break.off
,error
和 expired
managerProxy.channel.on('error', err => {
// background error
})
managerProxy.channel.on('expired', (key, type) => {
// type: expired or fallbackExpired
})
stats
包含有 current
、total
和methods
// 默认每分钟输出一次
managerProxy.channel.on('stats', stats => {
const {
current, // 最近一分钟的统计
total, // 进程启动以来的总统计
methods // 每个方法单独统计
} = stats
// ...
})
每个统计的详情如下:
{
local: 0, // 命中本地缓存次数
remote: 0, // 命中远程缓存次数
update: 0, // 真正发起请求数量
miss: 0, // 缓存没命中数量
expired: 0, // 本地缓存过期数量
failed: 0, // 请求错误次数
wait: 0, // 并发等待成功次数
failedWait: 0, // 并发等待的错误次数
background: 0, // 后台更新次数
failedBackground: 0, // 后台更新错误次数
fallback: 0, // 后备缓存命中次数
fallbackFirst: 0, // 优先后备缓存命中次数
fallbackExpired: 0, // 后备缓存过期数量
}
另外一种获取统计的方式:
const stats = managerProxy.channel.stats()
managerProxy.channel.on('bg.stats', stats => {
// stats 详情
// stats.cycleTime: 一个完整更新周期的时间
// stats.delayTime: 一个完整更新周期的暂停时间
// stats.updateTime: 一个完整更新周期的异步等待时间
// stats.updatedSize: 一个完整更新周期的总共更新了多少个key
})
managerProxy.channel.on('bg.break.off', key => {
// ...
})
Released under the MIT License.