示例说明
缓存的三层语义各司其职:max 限制容量按最近使用淘汰冷键;ttl 决定数据新鲜度上限;staleTtl 允许过期但未超期的旧值继续服务,同时触发后台刷新——用户拿到即时响应,数据在几秒内收敛到最新。单飞表合并并发的刷新请求,防止过期瞬间击穿到下游。
技术要点
- max 与 ttl 分别约束容量与新鲜度
- staleTtl 窗口内旧值可用并触发刷新
- inflight 表合并并发刷新请求
代码解读
lru-cache 的 fetchMethod 内建单飞语义:同键并发读取只会有一次真实请求;getRate 直接利用该能力。revalidate 演示手写单飞的通用形态:任务入表后同键请求全部复用同一 Promise,finally 确保无论成败都清理表项,防止失败的请求被永久缓存。
使用建议
stale 窗口要小于下游对旧数据的容忍上限。写操作后应主动 delete 相关键让下次读走回源。监控命中率与刷新失败率,刷新连续失败说明下游故障需要熔断。
代码示例
import { LRUCache } from 'lru-cache';
const cache = new LRUCache({
max: 500,
ttl: 60_000,
staleTtl: 300_000,
allowStale: true,
fetchMethod: async (key) => {
const res = await fetch('https://api.example.com/rates/' + key);
if (!res.ok) throw new Error('upstream ' + res.status);
return res.json();
},
});
const inflight = new Map();
async function getRate(currency) {
const value = await cache.fetch(currency);
return value;
}
async function revalidate(key, loader) {
if (inflight.has(key)) return inflight.get(key);
const task = loader()
.then((fresh) => { cache.set(key, fresh); return fresh; })
.finally(() => { inflight.delete(key); });
inflight.set(key, task);
return task;
}
const usd = await getRate('usd');
const again = await getRate('usd');
console.log('rate:', usd.rate, 'second read:', again.rate);