场景
基于 LRU 的缓存是否失效的指标是:是否为最近使用。
在此基础上,新增了 maxAge 字段,表示缓存有效期,数据结构:
1 2
| maxAge: number data: any
|
为什么增加缓存有效期?
有些数据被频繁访问,按照 LRU 策略,不会失效。
但是数据需要刷新,否则会失去实效性,因此新增一个有效期。
如果过期,强行刷存。
什么时候需要自动续期?
当缓存过期后,去请求接口,更新缓存。如果接口失效,那么需要自动续期。
这种情况一般后端接口挂了,不自动续期,会导致雪崩,降低可用率。
设计思路
新的数据结构设计:
1 2 3
| maxAge: number data: any finalExpiration: number
|
在当前时间~有效期之间:缓存有效无需刷新。
在有效期~最终过期时间:缓存失效,可以刷新,自动续期。
在最终过期时间后:不能自动续期。
对于有效期~最终过期,支持两种刷新:
- 同步刷新:阻塞等待接口返回,成功,更新缓存,返回最新结果;失败,返回最新结果。
- 异步刷新:非阻塞,直接返回旧缓存;异步获取请求结果,成功则更新缓存。
对于「刷新」操作,需要从外界传入回调函数。
NestJS 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| import { Injectable, Scope } from '@nestjs/common'; import QuickLRU from 'quick-lru';
@Injectable({ scope: Scope.TRANSIENT }) export class CacheService { private _cache: QuickLRU<string, CacheData>; private _ttl: number;
constructor(ttl = 60 * 1000) { this._cache = new QuickLRU({ maxSize: 1000 }); this._ttl = ttl; }
public set(key, value, finalExpiration?: number) { const ts = Date.now(); finalExpiration = typeof finalExpiration === 'number' && finalExpiration >= ts + this._ttl ? finalExpiration : Infinity;
this._cache.set(key, { ts, value, finalExpiration }); }
public get(key) { const data = this._cache.get(key); if (!data) { return; } const { ts, value } = data; const now = new Date().getTime(); if (now > ts + this._ttl) { this._cache.delete(key); return; } else { return value; } }
public async getWithBack(key, fn: IFunction<any>, finalExpiration?: number, isAsync = true) { const data = this._cache.get(key); if (!data) { return; }
const now = Date.now(); if (now <= data.ts + this._ttl) { return data.value; }
if (now > data.finalExpiration) { this._cache.delete(key); return; }
if (isAsync) { fn() .then(value => this.set(key, value, finalExpiration)) .catch(error => { });
return data.value; } else { try { const value = await fn(); this.set(key, value, finalExpiration); return value; } catch (error) { return data.value; } } } }
interface CacheData { ts: number; finalExpiration: number; value: any; }
interface IFunction<T> { (...args: any): Promise<T>; }
|