@zenweb/cache 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cache.d.ts CHANGED
@@ -17,9 +17,13 @@ export declare class CacheResult {
17
17
  * 对象缓存系统
18
18
  */
19
19
  export declare class Cache {
20
- private redis;
20
+ redis: Redis;
21
21
  private option;
22
22
  constructor(redis: Redis, option: Required<SetupOption>);
23
+ /**
24
+ * 取得缓存剩余有效期
25
+ */
26
+ ttl(key: string): Promise<number>;
23
27
  /**
24
28
  * 删除缓存
25
29
  */
@@ -59,10 +63,17 @@ export declare class Cache {
59
63
  * @param key 缓存KEY
60
64
  * @param fetch 缓存设置方法回调
61
65
  */
62
- lockGet<T = unknown>(key: string, fetch: () => Promise<T> | T, _opt: {
66
+ lockGet<T = unknown>(key: string, fetch: () => Promise<T> | T, opt: {
67
+ parse: false;
68
+ noWait: true;
69
+ } & LockGetOption): Promise<CacheResult | undefined>;
70
+ lockGet<T = unknown>(key: string, fetch: () => Promise<T> | T, opt: {
71
+ noWait: true;
72
+ } & LockGetOption): Promise<T | undefined>;
73
+ lockGet<T = unknown>(key: string, fetch: () => Promise<T> | T, opt: {
63
74
  parse: false;
64
75
  } & LockGetOption): Promise<CacheResult>;
65
- lockGet<T = unknown>(key: string, fetch: () => Promise<T> | T, _opt?: {
76
+ lockGet<T = unknown>(key: string, fetch: () => Promise<T> | T, opt?: {
66
77
  parse?: true;
67
78
  } & LockGetOption): Promise<T>;
68
79
  /**
package/dist/cache.js CHANGED
@@ -33,6 +33,12 @@ class Cache {
33
33
  this.redis = redis;
34
34
  this.option = option;
35
35
  }
36
+ /**
37
+ * 取得缓存剩余有效期
38
+ */
39
+ ttl(key) {
40
+ return this.redis.ttl(key);
41
+ }
36
42
  /**
37
43
  * 删除缓存
38
44
  */
@@ -106,110 +112,154 @@ class Cache {
106
112
  return new CacheResult(compressed, data);
107
113
  }
108
114
  }
109
- async lockGet(key, fetch, _opt) {
110
- const opt = Object.assign({}, this.option.set, this.option.lockGet, _opt);
115
+ lockGet(key, fetch, opt) {
116
+ const _opt = Object.assign({}, this.option.set, this.option.lockGet, opt);
117
+ return new LockGet(this, key, fetch, _opt).get();
118
+ }
119
+ /**
120
+ * 单例执行,在并发情况下保证同一个 key 只会单独执行,防止同时处理
121
+ * @param key 锁key
122
+ * @param run 获得锁时调用
123
+ */
124
+ async singleRunner(key, run, _opt) {
125
+ const opt = Object.assign({}, this.option.lockGet, _opt);
126
+ const locker = new locker_1.Locker(this.redis, key, opt.lockTimeout);
127
+ const retryTimeout = typeof opt.retryTimeout === 'number' ? opt.retryTimeout : 0;
128
+ const retryDelay = (opt.retryDelay === undefined || opt.retryDelay < 0) ? 500 : opt.retryDelay;
129
+ let retryTime = 0;
130
+ while (true) {
131
+ // 取得锁并执行
132
+ if (await locker.acquire()) {
133
+ try {
134
+ return await run();
135
+ }
136
+ finally {
137
+ await locker.release();
138
+ }
139
+ }
140
+ // 无法取得锁是否重复尝试
141
+ if (retryTime >= retryTimeout) {
142
+ throw new Error('Unable to acquire lock');
143
+ }
144
+ else {
145
+ retryTime += retryDelay;
146
+ await (0, utils_1.sleep)(retryDelay);
147
+ }
148
+ }
149
+ }
150
+ }
151
+ exports.Cache = Cache;
152
+ class LockGet {
153
+ constructor(cache, key, fetch, opt) {
154
+ this.cache = cache;
155
+ this.key = key;
156
+ this.fetch = fetch;
157
+ this.opt = opt;
158
+ }
159
+ /**
160
+ * 获取数据并设置到缓存中
161
+ */
162
+ async fetchAndSet() {
163
+ const obj = await this.fetch();
111
164
  // 本地存储
112
- if (opt.localStore && key in opt.localStore) {
113
- return opt.localStore[key];
165
+ if (this.opt.localStore) {
166
+ this.opt.localStore[this.key] = obj;
114
167
  }
115
- // 获取数据并设置到缓存中
116
- const fetchAndSet = async () => {
117
- const obj = await fetch();
118
- // 本地存储
119
- if (opt.localStore) {
120
- opt.localStore[key] = obj;
121
- }
122
- const result = await this.set(key, obj, opt);
123
- if ((_opt === null || _opt === void 0 ? void 0 : _opt.parse) !== false) {
124
- return obj;
168
+ const result = await this.cache.set(this.key, obj, this.opt);
169
+ if (this.opt.parse !== false) {
170
+ return obj;
171
+ }
172
+ return new CacheResult(!this.opt.decompress && Boolean(result.compressed), !this.opt.decompress && result.compressed || Buffer.from(result.data));
173
+ }
174
+ /**
175
+ * 预刷新处理
176
+ */
177
+ async preRefresh(preRefresh) {
178
+ const ttl = await this.cache.ttl(this.key);
179
+ _lockDebug('[%s] preRefresh remain: %d', this.key, ttl);
180
+ if (ttl < preRefresh) {
181
+ _lockDebug('[%s] preRefresh -> fetchAndSet', this.key);
182
+ await this.fetchAndSet();
183
+ }
184
+ }
185
+ /**
186
+ * 从缓存中取得数据
187
+ */
188
+ async getCache() {
189
+ // 本地存储
190
+ if (this.opt.localStore && this.key in this.opt.localStore) {
191
+ return this.opt.localStore[this.key];
192
+ }
193
+ // 尝试从 redis 中获取
194
+ const data = await this.cache.get(this.key, this.opt);
195
+ if (data !== undefined) {
196
+ if (!this.locker && this.opt.preRefresh && this.opt.preRefresh > 0) {
197
+ this.preRefresh(this.opt.preRefresh);
125
198
  }
126
- return new CacheResult(!opt.decompress && Boolean(result.compressed), !opt.decompress && result.compressed || Buffer.from(result.data));
127
- };
128
- // 预刷新处理
129
- const preRefresh = async () => {
130
- if (opt.preRefresh && opt.preRefresh > 0) {
131
- const ttl = await this.redis.ttl(key);
132
- _lockDebug('[%s] preRefresh remain: %d', key, ttl);
133
- if (ttl < opt.preRefresh) {
134
- _lockDebug('[%s] preRefresh -> fetchAndSet', key);
135
- await fetchAndSet();
136
- }
199
+ // 本地存储
200
+ if (this.opt.localStore && typeof this.opt.localStore === 'object') {
201
+ this.opt.localStore[this.key] = data;
137
202
  }
138
- };
139
- // 使用锁设置缓存
140
- let locker;
141
- let refresh = opt.refresh || false; // 强制刷新
142
- let retryCount = typeof opt.retryCount === 'number' ? opt.retryCount : 10;
203
+ return data;
204
+ }
205
+ }
206
+ async tryFetch() {
207
+ const retryTimeout = typeof this.opt.retryTimeout === 'number' ? this.opt.retryTimeout : 5000;
208
+ const retryDelay = (this.opt.retryDelay === undefined || this.opt.retryDelay < 0) ? 500 : this.opt.retryDelay;
209
+ let retryTime = 0;
143
210
  while (true) {
144
- if (!refresh) {
211
+ if (!this.locker) {
212
+ this.locker = new locker_1.Locker(this.cache.redis, `${this.key}.LOCK`, this.opt.lockTimeout);
213
+ }
214
+ else {
145
215
  // 尝试从 redis 中获取
146
- const data = await this.get(key, opt);
216
+ const data = await this.getCache();
147
217
  if (data !== undefined) {
148
- if (!locker) {
149
- preRefresh();
150
- }
151
- // 本地存储
152
- if (opt.localStore) {
153
- opt.localStore[key] = data;
154
- }
155
218
  return data;
156
219
  }
157
220
  }
158
- // 已经跳过 redis 获取可以结束强制刷新,进入锁获取,锁获取不成功则代表其他刷新任务已经进行,只要等待获取即可
159
- refresh = false;
160
221
  // 获取锁
161
- if (!locker) {
162
- locker = new locker_1.Locker(this.redis, `${key}.LOCK`, opt.lockTimeout);
163
- }
164
- if (await locker.acquire()) {
165
- _lockDebug('[%s] acquire success', key);
222
+ if (await this.locker.acquire()) {
223
+ _lockDebug('[%s] acquire success', this.key);
166
224
  try {
167
- return await fetchAndSet();
225
+ return await this.fetchAndSet();
168
226
  }
169
227
  finally {
170
- await locker.release();
228
+ await this.locker.release();
171
229
  }
172
230
  }
173
231
  else {
232
+ // 不等待为 true 不需要重试,说明有其他请求更新数据
233
+ if (this.opt.noWait === true) {
234
+ _lockDebug('[%s] no wait', this.key);
235
+ return;
236
+ }
174
237
  // 获取锁失败,有其他请求更新数据,等待后重新获取获取数据
175
- if (retryCount <= 0) {
176
- throw new Error('Unable to acquire lock: ' + key);
238
+ if (retryTime >= retryTimeout) {
239
+ throw new Error('Unable to acquire lock: ' + this.key);
177
240
  }
178
241
  else {
179
- retryCount--;
180
- await (0, utils_1.sleep)(opt.retryDelay || 300);
242
+ retryTime += retryDelay;
243
+ await (0, utils_1.sleep)(retryDelay);
181
244
  }
182
245
  }
183
246
  }
184
247
  }
185
- /**
186
- * 单例执行,在并发情况下保证同一个 key 只会单独执行,防止同时处理
187
- * @param key 锁key
188
- * @param run 获得锁时调用
189
- */
190
- async singleRunner(key, run, _opt) {
191
- const opt = Object.assign({}, _opt);
192
- const locker = new locker_1.Locker(this.redis, key, opt.lockTimeout);
193
- let retryCount = opt.retryCount || 0;
194
- while (true) {
195
- // 取得锁并执行
196
- if (await locker.acquire()) {
197
- try {
198
- return await run();
199
- }
200
- finally {
201
- await locker.release();
202
- }
203
- }
204
- // 无法取得锁是否重复尝试
205
- if (retryCount <= 0) {
206
- throw new Error('Unable to acquire lock');
207
- }
208
- else {
209
- retryCount--;
210
- await (0, utils_1.sleep)(opt.retryDelay || 300);
248
+ async get() {
249
+ // 如果强制刷新则不需要尝试从缓存获取,直接进入锁获取并取得数据
250
+ const refresh = this.opt.refresh || false;
251
+ if (!refresh) {
252
+ // 先尝试从缓存获取
253
+ const data = await this.getCache();
254
+ if (data !== undefined) {
255
+ return data;
211
256
  }
212
257
  }
258
+ // 不等待为 true 直接返回 undefined
259
+ if (this.opt.noWait === true) {
260
+ this.tryFetch();
261
+ return;
262
+ }
263
+ return this.tryFetch();
213
264
  }
214
265
  }
215
- exports.Cache = Cache;
package/dist/options.js CHANGED
@@ -14,6 +14,8 @@ exports.defaultSetOption = {
14
14
  compressLevel: 1,
15
15
  };
16
16
  exports.defaultLockGetOption = {
17
+ retryTimeout: 5000,
18
+ retryDelay: 500,
17
19
  preRefresh: 0,
18
20
  refresh: false,
19
21
  localStore: undefined,
package/dist/types.d.ts CHANGED
@@ -45,7 +45,7 @@ export interface SetupOption {
45
45
  /**
46
46
  * Redis 实例或 Redis 选项
47
47
  */
48
- redis: Redis | RedisOptions;
48
+ redis?: Redis | RedisOptions;
49
49
  /**
50
50
  * 默认缓存设置选项
51
51
  */
@@ -100,15 +100,15 @@ export interface LockOption {
100
100
  */
101
101
  lockTimeout?: number;
102
102
  /**
103
- * 锁获取失败重试次数
103
+ * 数据获取重试超时 (毫秒)
104
104
  * - 0 不进行重试
105
- * - lockGet 默认为 10
105
+ * - lockGet 默认为 5000
106
106
  * - singleRunner 默认为 0
107
107
  */
108
- retryCount?: number;
108
+ retryTimeout?: number;
109
109
  /**
110
- * 锁获取失败重试间隔时间 (毫秒)
111
- * @default 300
110
+ * 数据获取重试间隔 (毫秒)
111
+ * @default 500
112
112
  */
113
113
  retryDelay?: number;
114
114
  }
@@ -138,4 +138,11 @@ export interface LockGetOption extends GetOption, SetOption, LockOption {
138
138
  localStore?: {
139
139
  [key: string]: any;
140
140
  };
141
+ /**
142
+ * 不等待
143
+ * - 当缓存不存在时直接返回 `undefined`,之后使用 `fetch` 取得数据并设置缓存
144
+ * - 应用场景: 某些数据并不参与业务逻辑又比较耗时,当 `fetch` 处理时间过长的情况下可能会造成请求超时,使用 `wait: false` 可以跳过等待
145
+ * @default false
146
+ */
147
+ noWait?: boolean;
141
148
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zenweb/cache",
3
3
  "packageManager": "yarn@4.0.2",
4
- "version": "4.5.0",
4
+ "version": "4.6.0",
5
5
  "description": "Zenweb Cache module",
6
6
  "exports": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -36,6 +36,7 @@
36
36
  "@types/node": "^20.10.6",
37
37
  "cross-env": "^7.0.3",
38
38
  "mocha": "^10.2.0",
39
+ "msgpackr": "^1.11.0",
39
40
  "rimraf": "^4.3.1",
40
41
  "ts-mocha": "^10.0.0",
41
42
  "ts-node": "^10.9.1",