koatty_store 1.7.0 → 1.8.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/.rollup.config.js +59 -59
- package/.vscode/launch.json +25 -0
- package/CHANGELOG.md +44 -27
- package/LICENSE +29 -29
- package/README.md +2 -2
- package/dist/LICENSE +29 -29
- package/dist/README.md +2 -2
- package/dist/index.d.ts +310 -94
- package/dist/index.js +765 -159
- package/dist/index.mjs +765 -159
- package/dist/package.json +105 -103
- package/package.json +105 -103
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @Author: richen
|
|
3
|
-
* @Date:
|
|
3
|
+
* @Date: 2025-06-09 12:32:48
|
|
4
4
|
* @License: BSD (3-Clause)
|
|
5
5
|
* @Copyright (c) - <richenlin(at)gmail.com>
|
|
6
6
|
* @HomePage: https://koatty.org/
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { flatten, isNil, union, isUndefined } from 'lodash';
|
|
9
9
|
import * as helper from 'koatty_lib';
|
|
10
10
|
import { EventEmitter } from 'events';
|
|
11
|
+
import { LRUCache } from 'lru-cache';
|
|
11
12
|
import { DefaultLogger } from 'koatty_logger';
|
|
12
13
|
import { Cluster, Redis } from 'ioredis';
|
|
13
14
|
import genericPool from 'generic-pool';
|
|
@@ -49,7 +50,7 @@ var messages;
|
|
|
49
50
|
messages["mutuallyExclusiveNXXX"] = "ERR XX and NX options at the same time are not compatible";
|
|
50
51
|
})(messages || (messages = {}));
|
|
51
52
|
class MemoryCache extends EventEmitter {
|
|
52
|
-
databases =
|
|
53
|
+
databases = new Map();
|
|
53
54
|
options;
|
|
54
55
|
currentDBIndex;
|
|
55
56
|
connected;
|
|
@@ -57,18 +58,89 @@ class MemoryCache extends EventEmitter {
|
|
|
57
58
|
multiMode;
|
|
58
59
|
cache;
|
|
59
60
|
responseMessages;
|
|
61
|
+
ttlCheckTimer = null;
|
|
60
62
|
/**
|
|
61
63
|
* Creates an instance of MemoryCache.
|
|
62
|
-
* @param {
|
|
64
|
+
* @param {MemoryCacheOptions} options
|
|
63
65
|
* @memberof MemoryCache
|
|
64
66
|
*/
|
|
65
67
|
constructor(options) {
|
|
66
68
|
super();
|
|
67
|
-
this.options = {
|
|
68
|
-
|
|
69
|
+
this.options = {
|
|
70
|
+
database: 0,
|
|
71
|
+
maxKeys: 1000,
|
|
72
|
+
evictionPolicy: 'lru',
|
|
73
|
+
ttlCheckInterval: 60000, // 1分钟检查一次过期键
|
|
74
|
+
maxAge: 1000 * 60 * 60, // 默认1小时过期
|
|
75
|
+
...options
|
|
76
|
+
};
|
|
77
|
+
this.currentDBIndex = options.database || 0;
|
|
69
78
|
this.connected = false;
|
|
70
79
|
this.lastSave = Date.now();
|
|
71
80
|
this.multiMode = false;
|
|
81
|
+
this.responseMessages = [];
|
|
82
|
+
// 初始化数据库和缓存
|
|
83
|
+
if (!this.databases.has(this.currentDBIndex)) {
|
|
84
|
+
this.databases.set(this.currentDBIndex, this.createLRUCache());
|
|
85
|
+
}
|
|
86
|
+
this.cache = this.databases.get(this.currentDBIndex);
|
|
87
|
+
// 启动TTL检查定时器
|
|
88
|
+
this.startTTLCheck();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 创建LRU缓存实例
|
|
92
|
+
*/
|
|
93
|
+
createLRUCache() {
|
|
94
|
+
return new LRUCache({
|
|
95
|
+
max: this.options.maxKeys || 1000,
|
|
96
|
+
ttl: this.options.maxAge || 1000 * 60 * 60, // 1小时默认
|
|
97
|
+
updateAgeOnGet: true, // 访问时更新age
|
|
98
|
+
dispose: (value, key, reason) => {
|
|
99
|
+
// 键被淘汰时的回调 - 直接使用lru-cache的事件机制
|
|
100
|
+
this.emit('evict', key, value, reason);
|
|
101
|
+
},
|
|
102
|
+
onInsert: (value, key) => {
|
|
103
|
+
// 键被插入时的回调
|
|
104
|
+
this.emit('insert', key, value);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 启动TTL检查定时器
|
|
110
|
+
*/
|
|
111
|
+
startTTLCheck() {
|
|
112
|
+
if (this.ttlCheckTimer) {
|
|
113
|
+
clearInterval(this.ttlCheckTimer);
|
|
114
|
+
}
|
|
115
|
+
this.ttlCheckTimer = setInterval(() => {
|
|
116
|
+
this.cleanExpiredKeys();
|
|
117
|
+
}, this.options.ttlCheckInterval || 60000);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 清理过期键
|
|
121
|
+
*/
|
|
122
|
+
cleanExpiredKeys() {
|
|
123
|
+
for (const [_dbIndex, cache] of this.databases) {
|
|
124
|
+
const keysToDelete = [];
|
|
125
|
+
cache.forEach((item, key) => {
|
|
126
|
+
if (item.timeout && item.timeout <= Date.now()) {
|
|
127
|
+
keysToDelete.push(key);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
keysToDelete.forEach(key => {
|
|
131
|
+
cache.delete(key);
|
|
132
|
+
this.emit('expire', key);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 停止TTL检查
|
|
138
|
+
*/
|
|
139
|
+
stopTTLCheck() {
|
|
140
|
+
if (this.ttlCheckTimer) {
|
|
141
|
+
clearInterval(this.ttlCheckTimer);
|
|
142
|
+
this.ttlCheckTimer = null;
|
|
143
|
+
}
|
|
72
144
|
}
|
|
73
145
|
/**
|
|
74
146
|
*
|
|
@@ -77,8 +149,10 @@ class MemoryCache extends EventEmitter {
|
|
|
77
149
|
* @memberof MemoryCache
|
|
78
150
|
*/
|
|
79
151
|
createClient() {
|
|
80
|
-
this.databases
|
|
81
|
-
|
|
152
|
+
if (!this.databases.has(this.options.database)) {
|
|
153
|
+
this.databases.set(this.options.database, this.createLRUCache());
|
|
154
|
+
}
|
|
155
|
+
this.cache = this.databases.get(this.options.database);
|
|
82
156
|
this.connected = true;
|
|
83
157
|
// exit multi mode if we are in it
|
|
84
158
|
this.discard(null, true);
|
|
@@ -94,6 +168,7 @@ class MemoryCache extends EventEmitter {
|
|
|
94
168
|
*/
|
|
95
169
|
quit() {
|
|
96
170
|
this.connected = false;
|
|
171
|
+
this.stopTTLCheck();
|
|
97
172
|
// exit multi mode if we are in it
|
|
98
173
|
this.discard(null, true);
|
|
99
174
|
this.emit('end');
|
|
@@ -108,6 +183,40 @@ class MemoryCache extends EventEmitter {
|
|
|
108
183
|
end() {
|
|
109
184
|
return this.quit();
|
|
110
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* 获取缓存统计信息
|
|
188
|
+
*/
|
|
189
|
+
info() {
|
|
190
|
+
const stats = {
|
|
191
|
+
databases: this.databases.size,
|
|
192
|
+
currentDB: this.currentDBIndex,
|
|
193
|
+
keys: this.cache ? this.cache.length : 0,
|
|
194
|
+
maxKeys: this.options.maxKeys,
|
|
195
|
+
hits: 0,
|
|
196
|
+
misses: 0,
|
|
197
|
+
memory: this.getMemoryUsage()
|
|
198
|
+
};
|
|
199
|
+
// 如果缓存支持统计信息
|
|
200
|
+
if (this.cache && typeof this.cache.dump === 'function') {
|
|
201
|
+
const dump = this.cache.dump();
|
|
202
|
+
stats.keys = dump.length;
|
|
203
|
+
}
|
|
204
|
+
return stats;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 估算内存使用量
|
|
208
|
+
*/
|
|
209
|
+
getMemoryUsage() {
|
|
210
|
+
let totalSize = 0;
|
|
211
|
+
for (const [, cache] of this.databases) {
|
|
212
|
+
cache.forEach((item, key) => {
|
|
213
|
+
// 粗略估算:key长度 + JSON序列化后的大小
|
|
214
|
+
totalSize += key.length * 2; // Unicode字符占2字节
|
|
215
|
+
totalSize += JSON.stringify(item).length * 2;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return totalSize;
|
|
219
|
+
}
|
|
111
220
|
/**
|
|
112
221
|
*
|
|
113
222
|
*
|
|
@@ -154,12 +263,12 @@ class MemoryCache extends EventEmitter {
|
|
|
154
263
|
if (!helper.isNumber(dbIndex)) {
|
|
155
264
|
return this._handleCallback(callback, null, messages.invalidDBIndex);
|
|
156
265
|
}
|
|
157
|
-
if (!this.databases.
|
|
158
|
-
this.databases
|
|
266
|
+
if (!this.databases.has(dbIndex)) {
|
|
267
|
+
this.databases.set(dbIndex, this.createLRUCache());
|
|
159
268
|
}
|
|
160
269
|
this.multiMode = false;
|
|
161
270
|
this.currentDBIndex = dbIndex;
|
|
162
|
-
this.cache = this.databases
|
|
271
|
+
this.cache = this.databases.get(dbIndex);
|
|
163
272
|
return this._handleCallback(callback, messages.ok);
|
|
164
273
|
}
|
|
165
274
|
// ---------------------------------------
|
|
@@ -238,7 +347,7 @@ class MemoryCache extends EventEmitter {
|
|
|
238
347
|
else if (onlyexist) {
|
|
239
348
|
return this._handleCallback(callback, retVal);
|
|
240
349
|
}
|
|
241
|
-
this.cache
|
|
350
|
+
this.cache.set(key, this._makeKey(value.toString(), 'string', pttl));
|
|
242
351
|
return this._handleCallback(callback, messages.ok);
|
|
243
352
|
}
|
|
244
353
|
/**
|
|
@@ -268,7 +377,8 @@ class MemoryCache extends EventEmitter {
|
|
|
268
377
|
expire(key, seconds, callback) {
|
|
269
378
|
let retVal = 0;
|
|
270
379
|
if (this._hasKey(key)) {
|
|
271
|
-
|
|
380
|
+
const pttl = seconds * 1000;
|
|
381
|
+
this.cache.set(key, { ...this.cache.get(key), timeout: Date.now() + pttl });
|
|
272
382
|
retVal = 1;
|
|
273
383
|
}
|
|
274
384
|
return this._handleCallback(callback, retVal);
|
|
@@ -288,7 +398,7 @@ class MemoryCache extends EventEmitter {
|
|
|
288
398
|
for (let itr = 0; itr < keys.length; itr++) {
|
|
289
399
|
const key = keys[itr];
|
|
290
400
|
if (this._hasKey(key)) {
|
|
291
|
-
|
|
401
|
+
this.cache.delete(key);
|
|
292
402
|
retVal++;
|
|
293
403
|
}
|
|
294
404
|
}
|
|
@@ -379,7 +489,7 @@ class MemoryCache extends EventEmitter {
|
|
|
379
489
|
decrby(key, amount, callback) {
|
|
380
490
|
let retVal = null;
|
|
381
491
|
try {
|
|
382
|
-
retVal = this._addToKey(key, -amount);
|
|
492
|
+
retVal = this._addToKey(key, 0 - amount);
|
|
383
493
|
}
|
|
384
494
|
catch (err) {
|
|
385
495
|
return this._handleCallback(callback, null, err);
|
|
@@ -395,7 +505,7 @@ class MemoryCache extends EventEmitter {
|
|
|
395
505
|
this._testType(key, 'hash', true, callback);
|
|
396
506
|
}
|
|
397
507
|
else {
|
|
398
|
-
this.cache
|
|
508
|
+
this.cache.set(key, this._makeKey({}, 'hash'));
|
|
399
509
|
}
|
|
400
510
|
if (!this._hasField(key, field)) {
|
|
401
511
|
retVal = 1;
|
|
@@ -458,7 +568,7 @@ class MemoryCache extends EventEmitter {
|
|
|
458
568
|
for (let itr = 0; itr < fields.length; itr++) {
|
|
459
569
|
const field = fields[itr];
|
|
460
570
|
if (this._hasField(key, field)) {
|
|
461
|
-
delete this.cache
|
|
571
|
+
delete this.cache.get(key).value[field];
|
|
462
572
|
retVal++;
|
|
463
573
|
}
|
|
464
574
|
}
|
|
@@ -579,22 +689,18 @@ class MemoryCache extends EventEmitter {
|
|
|
579
689
|
this._testType(key, 'list', true, callback);
|
|
580
690
|
}
|
|
581
691
|
else {
|
|
582
|
-
this.cache
|
|
692
|
+
this.cache.set(key, this._makeKey([], 'list'));
|
|
583
693
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
this.
|
|
587
|
-
retVal = val.length;
|
|
694
|
+
this._getKey(key).push(value.toString());
|
|
695
|
+
retVal = this._getKey(key).length;
|
|
696
|
+
this.persist(key);
|
|
588
697
|
return this._handleCallback(callback, retVal);
|
|
589
698
|
}
|
|
590
699
|
/**
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* @param
|
|
594
|
-
* @param
|
|
595
|
-
* @param {Function} [callback]
|
|
596
|
-
* @returns {*}
|
|
597
|
-
* @memberof MemoryCache
|
|
700
|
+
* List:从左侧推入
|
|
701
|
+
* @param key
|
|
702
|
+
* @param value
|
|
703
|
+
* @param callback
|
|
598
704
|
*/
|
|
599
705
|
lpush(key, value, callback) {
|
|
600
706
|
let retVal = 0;
|
|
@@ -602,14 +708,31 @@ class MemoryCache extends EventEmitter {
|
|
|
602
708
|
this._testType(key, 'list', true, callback);
|
|
603
709
|
}
|
|
604
710
|
else {
|
|
605
|
-
this.cache
|
|
711
|
+
this.cache.set(key, this._makeKey([], 'list'));
|
|
606
712
|
}
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
this._setKey(key,
|
|
610
|
-
retVal = val.length;
|
|
713
|
+
const list = this._getKey(key);
|
|
714
|
+
retVal = list.unshift(value);
|
|
715
|
+
this._setKey(key, list);
|
|
611
716
|
return this._handleCallback(callback, retVal);
|
|
612
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* List:获取指定索引的元素
|
|
720
|
+
* @param key
|
|
721
|
+
* @param index
|
|
722
|
+
* @param callback
|
|
723
|
+
*/
|
|
724
|
+
lindex(key, index, callback) {
|
|
725
|
+
if (!this._hasKey(key)) {
|
|
726
|
+
return this._handleCallback(callback, null);
|
|
727
|
+
}
|
|
728
|
+
this._testType(key, 'list', true, callback);
|
|
729
|
+
const list = this._getKey(key);
|
|
730
|
+
if (index < 0) {
|
|
731
|
+
index = list.length + index;
|
|
732
|
+
}
|
|
733
|
+
const value = index >= 0 && index < list.length ? list[index] : null;
|
|
734
|
+
return this._handleCallback(callback, value);
|
|
735
|
+
}
|
|
613
736
|
/**
|
|
614
737
|
*
|
|
615
738
|
*
|
|
@@ -622,9 +745,11 @@ class MemoryCache extends EventEmitter {
|
|
|
622
745
|
let retVal = null;
|
|
623
746
|
if (this._hasKey(key)) {
|
|
624
747
|
this._testType(key, 'list', true, callback);
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
748
|
+
const list = this._getKey(key);
|
|
749
|
+
if (list.length > 0) {
|
|
750
|
+
retVal = list.shift();
|
|
751
|
+
this.persist(key);
|
|
752
|
+
}
|
|
628
753
|
}
|
|
629
754
|
return this._handleCallback(callback, retVal);
|
|
630
755
|
}
|
|
@@ -640,9 +765,11 @@ class MemoryCache extends EventEmitter {
|
|
|
640
765
|
let retVal = null;
|
|
641
766
|
if (this._hasKey(key)) {
|
|
642
767
|
this._testType(key, 'list', true, callback);
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
768
|
+
const list = this._getKey(key);
|
|
769
|
+
if (list.length > 0) {
|
|
770
|
+
retVal = list.pop();
|
|
771
|
+
this.persist(key);
|
|
772
|
+
}
|
|
646
773
|
}
|
|
647
774
|
return this._handleCallback(callback, retVal);
|
|
648
775
|
}
|
|
@@ -660,8 +787,8 @@ class MemoryCache extends EventEmitter {
|
|
|
660
787
|
const retVal = [];
|
|
661
788
|
if (this._hasKey(key)) {
|
|
662
789
|
this._testType(key, 'list', true, callback);
|
|
663
|
-
const
|
|
664
|
-
const length =
|
|
790
|
+
const list = this._getKey(key);
|
|
791
|
+
const length = list.length;
|
|
665
792
|
if (stop < 0) {
|
|
666
793
|
stop = length + stop;
|
|
667
794
|
}
|
|
@@ -677,7 +804,7 @@ class MemoryCache extends EventEmitter {
|
|
|
677
804
|
if (stop >= 0 && stop >= start) {
|
|
678
805
|
const size = stop - start + 1;
|
|
679
806
|
for (let itr = start; itr < size; itr++) {
|
|
680
|
-
retVal.push(
|
|
807
|
+
retVal.push(list[itr]);
|
|
681
808
|
}
|
|
682
809
|
}
|
|
683
810
|
}
|
|
@@ -701,7 +828,7 @@ class MemoryCache extends EventEmitter {
|
|
|
701
828
|
this._testType(key, 'set', true, callback);
|
|
702
829
|
}
|
|
703
830
|
else {
|
|
704
|
-
this.cache
|
|
831
|
+
this.cache.set(key, this._makeKey([], 'set'));
|
|
705
832
|
}
|
|
706
833
|
const val = this._getKey(key);
|
|
707
834
|
const length = val.length;
|
|
@@ -773,19 +900,29 @@ class MemoryCache extends EventEmitter {
|
|
|
773
900
|
* @memberof MemoryCache
|
|
774
901
|
*/
|
|
775
902
|
spop(key, count, callback) {
|
|
776
|
-
let retVal =
|
|
903
|
+
let retVal = [];
|
|
777
904
|
count = count || 1;
|
|
778
|
-
if (
|
|
779
|
-
|
|
905
|
+
if (typeof count === 'function') {
|
|
906
|
+
callback = count;
|
|
907
|
+
count = 1;
|
|
780
908
|
}
|
|
781
909
|
if (this._hasKey(key)) {
|
|
782
|
-
retVal = [];
|
|
783
910
|
this._testType(key, 'set', true, callback);
|
|
784
911
|
const val = this._getKey(key);
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
912
|
+
const keys = Object.keys(val);
|
|
913
|
+
const keysLength = keys.length;
|
|
914
|
+
if (keysLength) {
|
|
915
|
+
if (count >= keysLength) {
|
|
916
|
+
retVal = keys;
|
|
917
|
+
this.del(key);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
for (let itr = 0; itr < count; itr++) {
|
|
921
|
+
const randomNum = Math.floor(Math.random() * keys.length);
|
|
922
|
+
retVal.push(keys[randomNum]);
|
|
923
|
+
this.srem(key, keys[randomNum]);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
789
926
|
}
|
|
790
927
|
}
|
|
791
928
|
return this._handleCallback(callback, retVal);
|
|
@@ -852,14 +989,14 @@ class MemoryCache extends EventEmitter {
|
|
|
852
989
|
discard(callback, silent) {
|
|
853
990
|
// Clear the queue mode, drain the queue, empty the watch list
|
|
854
991
|
if (this.multiMode) {
|
|
855
|
-
this.cache = this.databases
|
|
992
|
+
this.cache = this.databases.get(this.currentDBIndex);
|
|
856
993
|
this.multiMode = false;
|
|
857
994
|
this.responseMessages = [];
|
|
858
995
|
}
|
|
859
|
-
|
|
860
|
-
return this._handleCallback(callback,
|
|
996
|
+
if (!silent) {
|
|
997
|
+
return this._handleCallback(callback, messages.ok);
|
|
861
998
|
}
|
|
862
|
-
return
|
|
999
|
+
return null;
|
|
863
1000
|
}
|
|
864
1001
|
// ---------------------------------------
|
|
865
1002
|
// ## Internal - Key ##
|
|
@@ -872,11 +1009,11 @@ class MemoryCache extends EventEmitter {
|
|
|
872
1009
|
* @returns {*}
|
|
873
1010
|
* @memberof MemoryCache
|
|
874
1011
|
*/
|
|
875
|
-
pttl(key,
|
|
1012
|
+
pttl(key, _callback) {
|
|
876
1013
|
let retVal = -2;
|
|
877
1014
|
if (this._hasKey(key)) {
|
|
878
|
-
if (!isNil(this.cache
|
|
879
|
-
retVal = this.cache
|
|
1015
|
+
if (!isNil(this.cache.get(key)?.timeout)) {
|
|
1016
|
+
retVal = this.cache.get(key).timeout - Date.now();
|
|
880
1017
|
// Prevent unexpected errors if the actual ttl just happens to be -2 or -1
|
|
881
1018
|
if (retVal < 0 && retVal > -3) {
|
|
882
1019
|
retVal = -3;
|
|
@@ -886,7 +1023,7 @@ class MemoryCache extends EventEmitter {
|
|
|
886
1023
|
retVal = -1;
|
|
887
1024
|
}
|
|
888
1025
|
}
|
|
889
|
-
return
|
|
1026
|
+
return retVal;
|
|
890
1027
|
}
|
|
891
1028
|
/**
|
|
892
1029
|
*
|
|
@@ -901,7 +1038,7 @@ class MemoryCache extends EventEmitter {
|
|
|
901
1038
|
let retVal = 0;
|
|
902
1039
|
if (this._hasKey(key)) {
|
|
903
1040
|
if (!isNil(this._key(key).timeout)) {
|
|
904
|
-
this.
|
|
1041
|
+
this.cache.set(key, { ...this.cache.get(key), timeout: null });
|
|
905
1042
|
retVal = 1;
|
|
906
1043
|
}
|
|
907
1044
|
}
|
|
@@ -916,7 +1053,7 @@ class MemoryCache extends EventEmitter {
|
|
|
916
1053
|
* @memberof MemoryCache
|
|
917
1054
|
*/
|
|
918
1055
|
_hasKey(key) {
|
|
919
|
-
return this.cache.
|
|
1056
|
+
return this.cache.has(key);
|
|
920
1057
|
}
|
|
921
1058
|
/**
|
|
922
1059
|
*
|
|
@@ -940,8 +1077,8 @@ class MemoryCache extends EventEmitter {
|
|
|
940
1077
|
* @memberof MemoryCache
|
|
941
1078
|
*/
|
|
942
1079
|
_key(key) {
|
|
943
|
-
this.cache
|
|
944
|
-
return this.cache
|
|
1080
|
+
this.cache.get(key).lastAccess = Date.now();
|
|
1081
|
+
return this.cache.get(key);
|
|
945
1082
|
}
|
|
946
1083
|
/**
|
|
947
1084
|
*
|
|
@@ -966,7 +1103,7 @@ class MemoryCache extends EventEmitter {
|
|
|
966
1103
|
}
|
|
967
1104
|
}
|
|
968
1105
|
else {
|
|
969
|
-
this.cache
|
|
1106
|
+
this.cache.set(key, this._makeKey('0', 'string'));
|
|
970
1107
|
}
|
|
971
1108
|
const val = keyValue + amount;
|
|
972
1109
|
this._setKey(key, val.toString());
|
|
@@ -1019,8 +1156,7 @@ class MemoryCache extends EventEmitter {
|
|
|
1019
1156
|
* @memberof MemoryCache
|
|
1020
1157
|
*/
|
|
1021
1158
|
_setKey(key, value) {
|
|
1022
|
-
this.cache
|
|
1023
|
-
this.cache[key].lastAccess = Date.now();
|
|
1159
|
+
this.cache.set(key, { ...this.cache.get(key), value: value, lastAccess: Date.now() });
|
|
1024
1160
|
}
|
|
1025
1161
|
/**
|
|
1026
1162
|
*
|
|
@@ -1048,7 +1184,7 @@ class MemoryCache extends EventEmitter {
|
|
|
1048
1184
|
}
|
|
1049
1185
|
}
|
|
1050
1186
|
else {
|
|
1051
|
-
this.cache
|
|
1187
|
+
this.cache.set(key, this._makeKey({}, 'hash'));
|
|
1052
1188
|
}
|
|
1053
1189
|
fieldValue = useFloat ? parseFloat(`${value}`) : parseInt(`${value}`);
|
|
1054
1190
|
amount = useFloat ? parseFloat(`${amount}`) : parseInt(`${amount}`);
|
|
@@ -1157,6 +1293,333 @@ class MemoryCache extends EventEmitter {
|
|
|
1157
1293
|
}
|
|
1158
1294
|
return;
|
|
1159
1295
|
}
|
|
1296
|
+
/**
|
|
1297
|
+
* 字符串追加操作
|
|
1298
|
+
* @param key
|
|
1299
|
+
* @param value
|
|
1300
|
+
* @param callback
|
|
1301
|
+
*/
|
|
1302
|
+
append(key, value, callback) {
|
|
1303
|
+
let retVal = 0;
|
|
1304
|
+
if (this._hasKey(key)) {
|
|
1305
|
+
this._testType(key, 'string', true, callback);
|
|
1306
|
+
const existingValue = this._getKey(key);
|
|
1307
|
+
const newValue = existingValue + value;
|
|
1308
|
+
this._setKey(key, newValue);
|
|
1309
|
+
retVal = newValue.length;
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
this.cache.set(key, this._makeKey(value, 'string'));
|
|
1313
|
+
retVal = value.length;
|
|
1314
|
+
}
|
|
1315
|
+
return this._handleCallback(callback, retVal);
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* 获取字符串长度
|
|
1319
|
+
* @param key
|
|
1320
|
+
* @param callback
|
|
1321
|
+
*/
|
|
1322
|
+
strlen(key, callback) {
|
|
1323
|
+
let retVal = 0;
|
|
1324
|
+
if (this._hasKey(key)) {
|
|
1325
|
+
this._testType(key, 'string', true, callback);
|
|
1326
|
+
retVal = this._getKey(key).length;
|
|
1327
|
+
}
|
|
1328
|
+
return this._handleCallback(callback, retVal);
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* 获取子字符串
|
|
1332
|
+
* @param key
|
|
1333
|
+
* @param start
|
|
1334
|
+
* @param end
|
|
1335
|
+
* @param callback
|
|
1336
|
+
*/
|
|
1337
|
+
getrange(key, start, end, callback) {
|
|
1338
|
+
let retVal = '';
|
|
1339
|
+
if (this._hasKey(key)) {
|
|
1340
|
+
this._testType(key, 'string', true, callback);
|
|
1341
|
+
const value = this._getKey(key);
|
|
1342
|
+
retVal = value.substring(start, end + 1);
|
|
1343
|
+
}
|
|
1344
|
+
return this._handleCallback(callback, retVal);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* 设置子字符串
|
|
1348
|
+
* @param key
|
|
1349
|
+
* @param offset
|
|
1350
|
+
* @param value
|
|
1351
|
+
* @param callback
|
|
1352
|
+
*/
|
|
1353
|
+
setrange(key, offset, value, callback) {
|
|
1354
|
+
let retVal = 0;
|
|
1355
|
+
if (this._hasKey(key)) {
|
|
1356
|
+
this._testType(key, 'string', true, callback);
|
|
1357
|
+
const existingValue = this._getKey(key);
|
|
1358
|
+
const newValue = existingValue.substring(0, offset) + value + existingValue.substring(offset + value.length);
|
|
1359
|
+
this._setKey(key, newValue);
|
|
1360
|
+
retVal = newValue.length;
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
// 如果键不存在,创建一个足够长的字符串
|
|
1364
|
+
const newValue = ''.padEnd(offset, '\0') + value;
|
|
1365
|
+
this.cache.set(key, this._makeKey(newValue, 'string'));
|
|
1366
|
+
retVal = newValue.length;
|
|
1367
|
+
}
|
|
1368
|
+
return this._handleCallback(callback, retVal);
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* 批量获取
|
|
1372
|
+
* @param keys
|
|
1373
|
+
* @param callback
|
|
1374
|
+
*/
|
|
1375
|
+
mget(...keys) {
|
|
1376
|
+
const callback = this._retrieveCallback(keys);
|
|
1377
|
+
const retVal = [];
|
|
1378
|
+
for (const key of keys) {
|
|
1379
|
+
if (this._hasKey(key)) {
|
|
1380
|
+
this._testType(key, 'string', false, callback);
|
|
1381
|
+
retVal.push(this._getKey(key));
|
|
1382
|
+
}
|
|
1383
|
+
else {
|
|
1384
|
+
retVal.push(null);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return this._handleCallback(callback, retVal);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* 批量设置
|
|
1391
|
+
* @param keyValuePairs
|
|
1392
|
+
* @param callback
|
|
1393
|
+
*/
|
|
1394
|
+
mset(...keyValuePairs) {
|
|
1395
|
+
const callback = this._retrieveCallback(keyValuePairs);
|
|
1396
|
+
// 确保参数是偶数个
|
|
1397
|
+
if (keyValuePairs.length % 2 !== 0) {
|
|
1398
|
+
return this._handleCallback(callback, null, messages.wrongArgCount.replace('%0', 'mset'));
|
|
1399
|
+
}
|
|
1400
|
+
for (let i = 0; i < keyValuePairs.length; i += 2) {
|
|
1401
|
+
const key = keyValuePairs[i];
|
|
1402
|
+
const value = keyValuePairs[i + 1];
|
|
1403
|
+
this.cache.set(key, this._makeKey(value.toString(), 'string'));
|
|
1404
|
+
}
|
|
1405
|
+
return this._handleCallback(callback, messages.ok);
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* 获取所有键
|
|
1409
|
+
* @param pattern
|
|
1410
|
+
* @param callback
|
|
1411
|
+
*/
|
|
1412
|
+
keys(pattern = '*', callback) {
|
|
1413
|
+
const retVal = [];
|
|
1414
|
+
this.cache.forEach((_item, key) => {
|
|
1415
|
+
if (pattern === '*' || this.matchPattern(key, pattern)) {
|
|
1416
|
+
retVal.push(key);
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
return this._handleCallback(callback, retVal);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* 简单的模式匹配
|
|
1423
|
+
* @param key
|
|
1424
|
+
* @param pattern
|
|
1425
|
+
*/
|
|
1426
|
+
matchPattern(key, pattern) {
|
|
1427
|
+
if (pattern === '*')
|
|
1428
|
+
return true;
|
|
1429
|
+
// 转换glob模式为正则表达式
|
|
1430
|
+
const regexPattern = pattern
|
|
1431
|
+
.replace(/\*/g, '.*')
|
|
1432
|
+
.replace(/\?/g, '.')
|
|
1433
|
+
.replace(/\[([^\]]*)\]/g, '[$1]');
|
|
1434
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
1435
|
+
return regex.test(key);
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* 获取随机键
|
|
1439
|
+
* @param callback
|
|
1440
|
+
*/
|
|
1441
|
+
randomkey(callback) {
|
|
1442
|
+
const keys = [];
|
|
1443
|
+
this.cache.forEach((_item, key) => {
|
|
1444
|
+
keys.push(key);
|
|
1445
|
+
});
|
|
1446
|
+
if (keys.length === 0) {
|
|
1447
|
+
return this._handleCallback(callback, null);
|
|
1448
|
+
}
|
|
1449
|
+
const randomIndex = Math.floor(Math.random() * keys.length);
|
|
1450
|
+
return this._handleCallback(callback, keys[randomIndex]);
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* 重命名键
|
|
1454
|
+
* @param oldKey
|
|
1455
|
+
* @param newKey
|
|
1456
|
+
* @param callback
|
|
1457
|
+
*/
|
|
1458
|
+
rename(oldKey, newKey, callback) {
|
|
1459
|
+
if (!this._hasKey(oldKey)) {
|
|
1460
|
+
return this._handleCallback(callback, null, messages.nokey);
|
|
1461
|
+
}
|
|
1462
|
+
const value = this.cache.get(oldKey);
|
|
1463
|
+
this.cache.set(newKey, value);
|
|
1464
|
+
this.cache.delete(oldKey);
|
|
1465
|
+
return this._handleCallback(callback, messages.ok);
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* 安全重命名键(目标键不存在时才重命名)
|
|
1469
|
+
* @param oldKey
|
|
1470
|
+
* @param newKey
|
|
1471
|
+
* @param callback
|
|
1472
|
+
*/
|
|
1473
|
+
renamenx(oldKey, newKey, callback) {
|
|
1474
|
+
if (!this._hasKey(oldKey)) {
|
|
1475
|
+
return this._handleCallback(callback, null, messages.nokey);
|
|
1476
|
+
}
|
|
1477
|
+
if (this._hasKey(newKey)) {
|
|
1478
|
+
return this._handleCallback(callback, 0);
|
|
1479
|
+
}
|
|
1480
|
+
const value = this.cache.get(oldKey);
|
|
1481
|
+
this.cache.set(newKey, value);
|
|
1482
|
+
this.cache.delete(oldKey);
|
|
1483
|
+
return this._handleCallback(callback, 1);
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* 获取键的类型
|
|
1487
|
+
* @param key
|
|
1488
|
+
* @param callback
|
|
1489
|
+
*/
|
|
1490
|
+
type(key, callback) {
|
|
1491
|
+
if (!this._hasKey(key)) {
|
|
1492
|
+
return this._handleCallback(callback, 'none');
|
|
1493
|
+
}
|
|
1494
|
+
const item = this.cache.get(key);
|
|
1495
|
+
return this._handleCallback(callback, item.type);
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* 清空当前数据库
|
|
1499
|
+
* @param callback
|
|
1500
|
+
*/
|
|
1501
|
+
flushdb(callback) {
|
|
1502
|
+
this.cache.clear();
|
|
1503
|
+
return this._handleCallback(callback, messages.ok);
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* 清空所有数据库
|
|
1507
|
+
* @param callback
|
|
1508
|
+
*/
|
|
1509
|
+
flushall(callback) {
|
|
1510
|
+
this.databases.clear();
|
|
1511
|
+
this.cache = this.createLRUCache();
|
|
1512
|
+
this.databases.set(this.currentDBIndex, this.cache);
|
|
1513
|
+
return this._handleCallback(callback, messages.ok);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* 获取数据库大小
|
|
1517
|
+
* @param callback
|
|
1518
|
+
*/
|
|
1519
|
+
dbsize(callback) {
|
|
1520
|
+
const size = this.cache.size || 0;
|
|
1521
|
+
return this._handleCallback(callback, size);
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Sorted Set基础实现 - 添加成员
|
|
1525
|
+
* @param key
|
|
1526
|
+
* @param score
|
|
1527
|
+
* @param member
|
|
1528
|
+
* @param callback
|
|
1529
|
+
*/
|
|
1530
|
+
zadd(key, score, member, callback) {
|
|
1531
|
+
let retVal = 0;
|
|
1532
|
+
if (this._hasKey(key)) {
|
|
1533
|
+
this._testType(key, 'zset', true, callback);
|
|
1534
|
+
}
|
|
1535
|
+
else {
|
|
1536
|
+
this.cache.set(key, this._makeKey([], 'zset'));
|
|
1537
|
+
}
|
|
1538
|
+
const zset = this._getKey(key);
|
|
1539
|
+
const existing = zset.find((item) => item.member === member);
|
|
1540
|
+
if (existing) {
|
|
1541
|
+
existing.score = score;
|
|
1542
|
+
}
|
|
1543
|
+
else {
|
|
1544
|
+
zset.push({ score, member });
|
|
1545
|
+
retVal = 1;
|
|
1546
|
+
}
|
|
1547
|
+
// 按分数排序
|
|
1548
|
+
zset.sort((a, b) => a.score - b.score);
|
|
1549
|
+
this._setKey(key, zset);
|
|
1550
|
+
return this._handleCallback(callback, retVal);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Sorted Set - 获取成员分数
|
|
1554
|
+
* @param key
|
|
1555
|
+
* @param member
|
|
1556
|
+
* @param callback
|
|
1557
|
+
*/
|
|
1558
|
+
zscore(key, member, callback) {
|
|
1559
|
+
if (!this._hasKey(key)) {
|
|
1560
|
+
return this._handleCallback(callback, null);
|
|
1561
|
+
}
|
|
1562
|
+
this._testType(key, 'zset', true, callback);
|
|
1563
|
+
const zset = this._getKey(key);
|
|
1564
|
+
const item = zset.find((item) => item.member === member);
|
|
1565
|
+
return this._handleCallback(callback, item ? item.score : null);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Sorted Set - 获取范围内的成员
|
|
1569
|
+
* @param key
|
|
1570
|
+
* @param start
|
|
1571
|
+
* @param stop
|
|
1572
|
+
* @param callback
|
|
1573
|
+
*/
|
|
1574
|
+
zrange(key, start, stop, callback) {
|
|
1575
|
+
if (!this._hasKey(key)) {
|
|
1576
|
+
return this._handleCallback(callback, []);
|
|
1577
|
+
}
|
|
1578
|
+
this._testType(key, 'zset', true, callback);
|
|
1579
|
+
const zset = this._getKey(key);
|
|
1580
|
+
const length = zset.length;
|
|
1581
|
+
if (stop < 0) {
|
|
1582
|
+
stop = length + stop;
|
|
1583
|
+
}
|
|
1584
|
+
if (start < 0) {
|
|
1585
|
+
start = length + start;
|
|
1586
|
+
}
|
|
1587
|
+
const retVal = zset.slice(start, stop + 1).map((item) => item.member);
|
|
1588
|
+
return this._handleCallback(callback, retVal);
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Sorted Set - 获取成员数量
|
|
1592
|
+
* @param key
|
|
1593
|
+
* @param callback
|
|
1594
|
+
*/
|
|
1595
|
+
zcard(key, callback) {
|
|
1596
|
+
if (!this._hasKey(key)) {
|
|
1597
|
+
return this._handleCallback(callback, 0);
|
|
1598
|
+
}
|
|
1599
|
+
this._testType(key, 'zset', true, callback);
|
|
1600
|
+
const zset = this._getKey(key);
|
|
1601
|
+
return this._handleCallback(callback, zset.length);
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Sorted Set - 删除成员
|
|
1605
|
+
* @param key
|
|
1606
|
+
* @param member
|
|
1607
|
+
* @param callback
|
|
1608
|
+
*/
|
|
1609
|
+
zrem(key, member, callback) {
|
|
1610
|
+
let retVal = 0;
|
|
1611
|
+
if (this._hasKey(key)) {
|
|
1612
|
+
this._testType(key, 'zset', true, callback);
|
|
1613
|
+
const zset = this._getKey(key);
|
|
1614
|
+
const index = zset.findIndex((item) => item.member === member);
|
|
1615
|
+
if (index !== -1) {
|
|
1616
|
+
zset.splice(index, 1);
|
|
1617
|
+
retVal = 1;
|
|
1618
|
+
this._setKey(key, zset);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return this._handleCallback(callback, retVal);
|
|
1622
|
+
}
|
|
1160
1623
|
}
|
|
1161
1624
|
|
|
1162
1625
|
/*
|
|
@@ -1176,7 +1639,12 @@ class MemoryStore {
|
|
|
1176
1639
|
* @memberof MemoryStore
|
|
1177
1640
|
*/
|
|
1178
1641
|
constructor(options) {
|
|
1179
|
-
this.options =
|
|
1642
|
+
this.options = {
|
|
1643
|
+
maxKeys: 1000,
|
|
1644
|
+
evictionPolicy: 'lru',
|
|
1645
|
+
ttlCheckInterval: 60000, // 1分钟
|
|
1646
|
+
...options
|
|
1647
|
+
};
|
|
1180
1648
|
this.client = null;
|
|
1181
1649
|
}
|
|
1182
1650
|
/**
|
|
@@ -1188,7 +1656,11 @@ class MemoryStore {
|
|
|
1188
1656
|
getConnection() {
|
|
1189
1657
|
if (!this.pool) {
|
|
1190
1658
|
this.pool = new MemoryCache({
|
|
1191
|
-
database: this.options.db
|
|
1659
|
+
database: this.options.db || 0,
|
|
1660
|
+
maxKeys: this.options.maxKeys,
|
|
1661
|
+
maxMemory: this.options.maxMemory,
|
|
1662
|
+
evictionPolicy: this.options.evictionPolicy,
|
|
1663
|
+
ttlCheckInterval: this.options.ttlCheckInterval
|
|
1192
1664
|
});
|
|
1193
1665
|
}
|
|
1194
1666
|
if (!this.client) {
|
|
@@ -1204,8 +1676,10 @@ class MemoryStore {
|
|
|
1204
1676
|
* @memberof MemoryStore
|
|
1205
1677
|
*/
|
|
1206
1678
|
async close() {
|
|
1207
|
-
this.client
|
|
1208
|
-
|
|
1679
|
+
if (this.client) {
|
|
1680
|
+
this.client.end();
|
|
1681
|
+
this.client = null;
|
|
1682
|
+
}
|
|
1209
1683
|
}
|
|
1210
1684
|
/**
|
|
1211
1685
|
* release
|
|
@@ -1248,6 +1722,20 @@ class MemoryStore {
|
|
|
1248
1722
|
return -1;
|
|
1249
1723
|
}
|
|
1250
1724
|
}
|
|
1725
|
+
/**
|
|
1726
|
+
* 获取缓存统计信息
|
|
1727
|
+
*/
|
|
1728
|
+
getStats() {
|
|
1729
|
+
if (this.client) {
|
|
1730
|
+
return this.client.info();
|
|
1731
|
+
}
|
|
1732
|
+
return {
|
|
1733
|
+
keys: 0,
|
|
1734
|
+
memory: 0,
|
|
1735
|
+
hits: 0,
|
|
1736
|
+
misses: 0
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1251
1739
|
}
|
|
1252
1740
|
|
|
1253
1741
|
/*
|
|
@@ -1268,6 +1756,9 @@ class RedisStore {
|
|
|
1268
1756
|
options;
|
|
1269
1757
|
pool;
|
|
1270
1758
|
client;
|
|
1759
|
+
reconnectAttempts = 0;
|
|
1760
|
+
maxReconnectAttempts = 5;
|
|
1761
|
+
reconnectDelay = 1000; // 初始重连延迟1秒
|
|
1271
1762
|
/**
|
|
1272
1763
|
* Creates an instance of RedisStore.
|
|
1273
1764
|
* @param {RedisStoreOpt} options
|
|
@@ -1327,7 +1818,7 @@ class RedisStore {
|
|
|
1327
1818
|
return opt;
|
|
1328
1819
|
}
|
|
1329
1820
|
/**
|
|
1330
|
-
* create connection by native
|
|
1821
|
+
* create connection by native with improved error handling
|
|
1331
1822
|
*
|
|
1332
1823
|
* @param {number} [connNum=0]
|
|
1333
1824
|
* @returns {*} {Promise<Redis | Cluster>}
|
|
@@ -1339,32 +1830,77 @@ class RedisStore {
|
|
|
1339
1830
|
}
|
|
1340
1831
|
const defer = helper.getDefer();
|
|
1341
1832
|
let connection;
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
this.options.keyPrefix = "";
|
|
1350
|
-
connection.on('end', () => {
|
|
1351
|
-
if (connNum < 3) {
|
|
1352
|
-
connNum++;
|
|
1353
|
-
defer.resolve(this.connect(connNum));
|
|
1833
|
+
try {
|
|
1834
|
+
if (!helper.isEmpty(this.options.clusters)) {
|
|
1835
|
+
connection = new Cluster([...this.options.clusters], {
|
|
1836
|
+
redisOptions: this.options,
|
|
1837
|
+
enableOfflineQueue: false,
|
|
1838
|
+
retryDelayOnFailover: 100
|
|
1839
|
+
});
|
|
1354
1840
|
}
|
|
1355
1841
|
else {
|
|
1356
|
-
|
|
1357
|
-
|
|
1842
|
+
connection = new Redis({
|
|
1843
|
+
...this.options,
|
|
1844
|
+
enableOfflineQueue: false,
|
|
1845
|
+
retryDelayOnFailover: 100,
|
|
1846
|
+
lazyConnect: true
|
|
1847
|
+
});
|
|
1358
1848
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1849
|
+
// 去除prefix, 防止重复
|
|
1850
|
+
this.options.keyPrefix = "";
|
|
1851
|
+
connection.on('error', (err) => {
|
|
1852
|
+
DefaultLogger.Error(`Redis connection error: ${err.message}`);
|
|
1853
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
1854
|
+
this.scheduleReconnect(connNum);
|
|
1855
|
+
}
|
|
1856
|
+
else {
|
|
1857
|
+
defer.reject(new Error(`Redis connection failed after ${this.maxReconnectAttempts} attempts`));
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
connection.on('end', () => {
|
|
1861
|
+
DefaultLogger.Warn('Redis connection ended');
|
|
1862
|
+
if (connNum < 3) {
|
|
1863
|
+
this.scheduleReconnect(connNum + 1);
|
|
1864
|
+
}
|
|
1865
|
+
else {
|
|
1866
|
+
this.close();
|
|
1867
|
+
defer.reject(new Error('Redis connection end after 3 attempts'));
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
connection.on('ready', () => {
|
|
1871
|
+
DefaultLogger.Info('Redis connection ready');
|
|
1872
|
+
this.client = connection;
|
|
1873
|
+
this.reconnectAttempts = 0; // 重置重连计数
|
|
1874
|
+
defer.resolve(connection);
|
|
1875
|
+
});
|
|
1876
|
+
// 主动连接
|
|
1877
|
+
if (connection instanceof Redis) {
|
|
1878
|
+
await connection.connect();
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
catch (error) {
|
|
1882
|
+
DefaultLogger.Error(`Failed to create Redis connection: ${error.message}`);
|
|
1883
|
+
defer.reject(error);
|
|
1884
|
+
}
|
|
1364
1885
|
return defer.promise;
|
|
1365
1886
|
}
|
|
1366
1887
|
/**
|
|
1367
|
-
*
|
|
1888
|
+
* 计划重连,使用指数退避策略
|
|
1889
|
+
* @private
|
|
1890
|
+
* @param {number} connNum
|
|
1891
|
+
*/
|
|
1892
|
+
scheduleReconnect(connNum) {
|
|
1893
|
+
this.reconnectAttempts++;
|
|
1894
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
1895
|
+
DefaultLogger.Info(`Scheduling Redis reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
1896
|
+
setTimeout(() => {
|
|
1897
|
+
this.connect(connNum).catch(err => {
|
|
1898
|
+
DefaultLogger.Error(`Reconnect attempt ${this.reconnectAttempts} failed: ${err.message}`);
|
|
1899
|
+
});
|
|
1900
|
+
}, delay);
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* get connection from pool with improved configuration
|
|
1368
1904
|
*
|
|
1369
1905
|
* @returns {*}
|
|
1370
1906
|
* @memberof RedisStore
|
|
@@ -1375,38 +1911,57 @@ class RedisStore {
|
|
|
1375
1911
|
create: () => {
|
|
1376
1912
|
return this.connect();
|
|
1377
1913
|
},
|
|
1378
|
-
destroy: () => {
|
|
1379
|
-
|
|
1914
|
+
destroy: (resource) => {
|
|
1915
|
+
if (resource && typeof resource.disconnect === 'function') {
|
|
1916
|
+
resource.disconnect();
|
|
1917
|
+
}
|
|
1918
|
+
return Promise.resolve();
|
|
1380
1919
|
},
|
|
1381
1920
|
validate: (resource) => {
|
|
1382
|
-
return Promise.resolve(resource.status === 'ready');
|
|
1921
|
+
return Promise.resolve(resource && resource.status === 'ready');
|
|
1383
1922
|
}
|
|
1384
1923
|
};
|
|
1385
1924
|
this.pool = genericPool.createPool(factory, {
|
|
1386
1925
|
max: this.options.poolSize || 10, // maximum size of the pool
|
|
1387
|
-
min: 2 // minimum size of the pool
|
|
1926
|
+
min: Math.min(2, this.options.poolSize || 2), // minimum size of the pool
|
|
1927
|
+
acquireTimeoutMillis: 30000, // 30秒获取连接超时
|
|
1928
|
+
testOnBorrow: true, // 借用时测试连接
|
|
1929
|
+
evictionRunIntervalMillis: 30000, // 30秒检查一次空闲连接
|
|
1930
|
+
idleTimeoutMillis: 300000, // 5分钟空闲超时
|
|
1931
|
+
softIdleTimeoutMillis: 180000 // 3分钟软空闲超时
|
|
1388
1932
|
});
|
|
1389
1933
|
this.pool.on('factoryCreateError', function (err) {
|
|
1390
|
-
DefaultLogger.Error(err);
|
|
1934
|
+
DefaultLogger.Error(`Redis pool create error: ${err.message}`);
|
|
1391
1935
|
});
|
|
1392
1936
|
this.pool.on('factoryDestroyError', function (err) {
|
|
1393
|
-
DefaultLogger.Error(err);
|
|
1937
|
+
DefaultLogger.Error(`Redis pool destroy error: ${err.message}`);
|
|
1394
1938
|
});
|
|
1395
1939
|
}
|
|
1396
1940
|
return this.pool.acquire();
|
|
1397
1941
|
}
|
|
1398
1942
|
/**
|
|
1399
|
-
* close connection
|
|
1943
|
+
* close connection with proper cleanup
|
|
1400
1944
|
*
|
|
1401
1945
|
* @returns {*}
|
|
1402
1946
|
* @memberof RedisStore
|
|
1403
1947
|
*/
|
|
1404
1948
|
async close() {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1949
|
+
try {
|
|
1950
|
+
if (this.pool) {
|
|
1951
|
+
await this.pool.drain();
|
|
1952
|
+
await this.pool.clear();
|
|
1953
|
+
this.pool = null;
|
|
1954
|
+
}
|
|
1955
|
+
if (this.client) {
|
|
1956
|
+
if (typeof this.client.disconnect === 'function') {
|
|
1957
|
+
this.client.disconnect();
|
|
1958
|
+
}
|
|
1959
|
+
this.client = null;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
catch (error) {
|
|
1963
|
+
DefaultLogger.Error(`Error closing Redis connection: ${error.message}`);
|
|
1964
|
+
}
|
|
1410
1965
|
}
|
|
1411
1966
|
/**
|
|
1412
1967
|
*
|
|
@@ -1449,16 +2004,16 @@ class RedisStore {
|
|
|
1449
2004
|
try {
|
|
1450
2005
|
conn = await this.defineCommand("getCompare", {
|
|
1451
2006
|
numberOfKeys: 1,
|
|
1452
|
-
lua: `
|
|
1453
|
-
local remote_value = redis.call("get",KEYS[1])
|
|
1454
|
-
|
|
1455
|
-
if (not remote_value) then
|
|
1456
|
-
return 0
|
|
1457
|
-
elseif (remote_value == ARGV[1]) then
|
|
1458
|
-
return redis.call("del",KEYS[1])
|
|
1459
|
-
else
|
|
1460
|
-
return -1
|
|
1461
|
-
end
|
|
2007
|
+
lua: `
|
|
2008
|
+
local remote_value = redis.call("get",KEYS[1])
|
|
2009
|
+
|
|
2010
|
+
if (not remote_value) then
|
|
2011
|
+
return 0
|
|
2012
|
+
elseif (remote_value == ARGV[1]) then
|
|
2013
|
+
return redis.call("del",KEYS[1])
|
|
2014
|
+
else
|
|
2015
|
+
return -1
|
|
2016
|
+
end
|
|
1462
2017
|
`
|
|
1463
2018
|
});
|
|
1464
2019
|
return conn.getCompare(name, value);
|
|
@@ -1498,7 +2053,7 @@ const defaultOptions = {
|
|
|
1498
2053
|
class CacheStore {
|
|
1499
2054
|
client;
|
|
1500
2055
|
options;
|
|
1501
|
-
static
|
|
2056
|
+
static instances = new Map();
|
|
1502
2057
|
/**
|
|
1503
2058
|
* Creates an instance of CacheStore.
|
|
1504
2059
|
* @param {StoreOptions} options
|
|
@@ -1518,17 +2073,70 @@ class CacheStore {
|
|
|
1518
2073
|
}
|
|
1519
2074
|
}
|
|
1520
2075
|
/**
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
2076
|
+
* 获取单例实例,支持多配置实例管理
|
|
1523
2077
|
* @static
|
|
1524
|
-
* @
|
|
2078
|
+
* @param {StoreOptions} [options]
|
|
2079
|
+
* @param {string} [instanceKey='default'] 实例键名,用于区分不同配置的实例
|
|
2080
|
+
* @returns {CacheStore}
|
|
2081
|
+
*/
|
|
2082
|
+
static getInstance(options, instanceKey = 'default') {
|
|
2083
|
+
// 生成配置哈希作为实例键的一部分
|
|
2084
|
+
const configHash = options ? this.generateConfigHash(options) : 'default';
|
|
2085
|
+
const fullKey = `${instanceKey}_${configHash}`;
|
|
2086
|
+
if (this.instances.has(fullKey)) {
|
|
2087
|
+
return this.instances.get(fullKey);
|
|
2088
|
+
}
|
|
2089
|
+
const instance = new CacheStore(options);
|
|
2090
|
+
this.instances.set(fullKey, instance);
|
|
2091
|
+
return instance;
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* 生成配置哈希
|
|
2095
|
+
* @private
|
|
2096
|
+
* @static
|
|
2097
|
+
* @param {StoreOptions} options
|
|
2098
|
+
* @returns {string}
|
|
1525
2099
|
*/
|
|
1526
|
-
static
|
|
1527
|
-
|
|
1528
|
-
|
|
2100
|
+
static generateConfigHash(options) {
|
|
2101
|
+
const configStr = JSON.stringify({
|
|
2102
|
+
type: options.type,
|
|
2103
|
+
host: options.host,
|
|
2104
|
+
port: options.port,
|
|
2105
|
+
db: options.db,
|
|
2106
|
+
keyPrefix: options.keyPrefix
|
|
2107
|
+
});
|
|
2108
|
+
// 简单哈希函数
|
|
2109
|
+
let hash = 0;
|
|
2110
|
+
for (let i = 0; i < configStr.length; i++) {
|
|
2111
|
+
const char = configStr.charCodeAt(i);
|
|
2112
|
+
hash = ((hash << 5) - hash) + char;
|
|
2113
|
+
hash = hash & hash; // 转换为32位整数
|
|
1529
2114
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
2115
|
+
return Math.abs(hash).toString(36);
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* 清理指定实例
|
|
2119
|
+
* @static
|
|
2120
|
+
* @param {string} [instanceKey='default']
|
|
2121
|
+
*/
|
|
2122
|
+
static async clearInstance(instanceKey = 'default') {
|
|
2123
|
+
const keysToRemove = Array.from(this.instances.keys()).filter(key => key.startsWith(`${instanceKey}_`));
|
|
2124
|
+
for (const key of keysToRemove) {
|
|
2125
|
+
const instance = this.instances.get(key);
|
|
2126
|
+
if (instance) {
|
|
2127
|
+
await instance.close();
|
|
2128
|
+
this.instances.delete(key);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* 清理所有实例
|
|
2134
|
+
* @static
|
|
2135
|
+
*/
|
|
2136
|
+
static async clearAllInstances() {
|
|
2137
|
+
const promises = Array.from(this.instances.values()).map(instance => instance.close());
|
|
2138
|
+
await Promise.all(promises);
|
|
2139
|
+
this.instances.clear();
|
|
1532
2140
|
}
|
|
1533
2141
|
getConnection() {
|
|
1534
2142
|
return this.client.getConnection();
|
|
@@ -1539,11 +2147,13 @@ class CacheStore {
|
|
|
1539
2147
|
release(conn) {
|
|
1540
2148
|
return this.client.release(conn);
|
|
1541
2149
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2150
|
+
/**
|
|
2151
|
+
* 获取底层实现客户端,用于访问特定实现的功能
|
|
2152
|
+
* 例如:Redis的defineCommand, getCompare等
|
|
2153
|
+
* @returns {MemoryStore | RedisStore}
|
|
2154
|
+
*/
|
|
2155
|
+
getRawClient() {
|
|
2156
|
+
return this.client;
|
|
1547
2157
|
}
|
|
1548
2158
|
/**
|
|
1549
2159
|
* handler for native client
|
|
@@ -1603,13 +2213,6 @@ class CacheStore {
|
|
|
1603
2213
|
expire(name, timeout) {
|
|
1604
2214
|
return this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]);
|
|
1605
2215
|
}
|
|
1606
|
-
/**
|
|
1607
|
-
* 删除key
|
|
1608
|
-
* @param name
|
|
1609
|
-
*/
|
|
1610
|
-
rm(name) {
|
|
1611
|
-
return this.wrap('del', [`${this.options.keyPrefix || ""}${name}`]);
|
|
1612
|
-
}
|
|
1613
2216
|
/**
|
|
1614
2217
|
*
|
|
1615
2218
|
*
|
|
@@ -1647,8 +2250,8 @@ class CacheStore {
|
|
|
1647
2250
|
* @param incr
|
|
1648
2251
|
* @returns {*}
|
|
1649
2252
|
*/
|
|
1650
|
-
incrby(name,
|
|
1651
|
-
return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`,
|
|
2253
|
+
incrby(name, increment) {
|
|
2254
|
+
return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`, increment]);
|
|
1652
2255
|
}
|
|
1653
2256
|
/**
|
|
1654
2257
|
* 将 key 所储存的值减去减量
|
|
@@ -1656,8 +2259,8 @@ class CacheStore {
|
|
|
1656
2259
|
* @param {any} name
|
|
1657
2260
|
* @param {any} decr
|
|
1658
2261
|
*/
|
|
1659
|
-
decrby(name,
|
|
1660
|
-
return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`,
|
|
2262
|
+
decrby(name, decrement) {
|
|
2263
|
+
return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`, decrement]);
|
|
1661
2264
|
}
|
|
1662
2265
|
/**
|
|
1663
2266
|
* 哈希写入
|
|
@@ -1666,13 +2269,16 @@ class CacheStore {
|
|
|
1666
2269
|
* @param value
|
|
1667
2270
|
* @param timeout
|
|
1668
2271
|
*/
|
|
1669
|
-
hset(name, key, value, timeout) {
|
|
1670
|
-
const
|
|
1671
|
-
if (typeof timeout
|
|
1672
|
-
|
|
2272
|
+
async hset(name, key, value, timeout) {
|
|
2273
|
+
const result = await this.wrap('hset', [`${this.options.keyPrefix || ""}${name}`, key, value]);
|
|
2274
|
+
if (typeof timeout === 'number') {
|
|
2275
|
+
await this.set(`${name}:${key}_ex`, 1, timeout);
|
|
1673
2276
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
2277
|
+
else {
|
|
2278
|
+
// 如果没有指定timeout,设置一个永久标记,避免hget时误删
|
|
2279
|
+
await this.set(`${name}:${key}_ex`, 1);
|
|
2280
|
+
}
|
|
2281
|
+
return result;
|
|
1676
2282
|
}
|
|
1677
2283
|
/**
|
|
1678
2284
|
* 哈希获取
|
|
@@ -1705,7 +2311,7 @@ class CacheStore {
|
|
|
1705
2311
|
this.hdel(name, key);
|
|
1706
2312
|
return 0;
|
|
1707
2313
|
}
|
|
1708
|
-
return dataArr[1] || 0;
|
|
2314
|
+
return Number(dataArr[1]) || 0;
|
|
1709
2315
|
});
|
|
1710
2316
|
}
|
|
1711
2317
|
/**
|
|
@@ -1714,10 +2320,10 @@ class CacheStore {
|
|
|
1714
2320
|
* @param key
|
|
1715
2321
|
* @returns {*}
|
|
1716
2322
|
*/
|
|
1717
|
-
hdel(name, key) {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
return
|
|
2323
|
+
async hdel(name, key) {
|
|
2324
|
+
await this.del(`${name}:${key}_ex`);
|
|
2325
|
+
const result = await this.wrap('hdel', [`${this.options.keyPrefix || ""}${name}`, key]);
|
|
2326
|
+
return result;
|
|
1721
2327
|
}
|
|
1722
2328
|
/**
|
|
1723
2329
|
* 返回哈希表 key 中域的数量
|
|
@@ -1731,11 +2337,11 @@ class CacheStore {
|
|
|
1731
2337
|
* 给哈希表指定key,增加increment
|
|
1732
2338
|
* @param name
|
|
1733
2339
|
* @param key
|
|
1734
|
-
* @param
|
|
2340
|
+
* @param increment
|
|
1735
2341
|
* @returns {*}
|
|
1736
2342
|
*/
|
|
1737
|
-
hincrby(name, key,
|
|
1738
|
-
return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key,
|
|
2343
|
+
hincrby(name, key, increment) {
|
|
2344
|
+
return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key, increment]);
|
|
1739
2345
|
}
|
|
1740
2346
|
/**
|
|
1741
2347
|
* 返回哈希表所有key-value
|
|
@@ -1824,12 +2430,12 @@ class CacheStore {
|
|
|
1824
2430
|
* @param timeout
|
|
1825
2431
|
* @returns {*}
|
|
1826
2432
|
*/
|
|
1827
|
-
sadd(name, value, timeout) {
|
|
1828
|
-
const
|
|
1829
|
-
if (typeof timeout
|
|
1830
|
-
|
|
2433
|
+
async sadd(name, value, timeout) {
|
|
2434
|
+
const result = await this.wrap('sadd', [`${this.options.keyPrefix || ""}${name}`, value]);
|
|
2435
|
+
if (typeof timeout === 'number') {
|
|
2436
|
+
await this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]);
|
|
1831
2437
|
}
|
|
1832
|
-
return
|
|
2438
|
+
return result;
|
|
1833
2439
|
}
|
|
1834
2440
|
/**
|
|
1835
2441
|
* 返回集合的基数(集合中元素的数量)
|
|
@@ -1881,7 +2487,7 @@ class CacheStore {
|
|
|
1881
2487
|
* @returns {*}
|
|
1882
2488
|
*/
|
|
1883
2489
|
smove(source, destination, member) {
|
|
1884
|
-
return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix}${destination}`, member]);
|
|
2490
|
+
return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix || ""}${destination}`, member]);
|
|
1885
2491
|
}
|
|
1886
2492
|
}
|
|
1887
2493
|
|