@jetit/publisher 1.1.0 → 1.1.1
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/package.json +1 -1
- package/src/lib/redis/streams.d.ts +2 -1
- package/src/lib/redis/streams.js +51 -32
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ export declare class Streams {
|
|
|
27
27
|
* const streams = new Streams('POS');
|
|
28
28
|
*/
|
|
29
29
|
constructor(serviceName: string);
|
|
30
|
+
private runClear;
|
|
30
31
|
private createConsumerGroup;
|
|
31
32
|
private isDuplicateMessage;
|
|
32
33
|
private clearDuplicationCheckKeys;
|
|
@@ -124,7 +125,7 @@ export declare class Streams {
|
|
|
124
125
|
* // Attempt to recover messages from the "order.created" stream with an idle timeout of 10 seconds
|
|
125
126
|
* await streams.recoverCrashedConsumerMessages('order.created', 10000);
|
|
126
127
|
*/
|
|
127
|
-
recoverCrashedConsumerMessages(eventName: string, idleTimeout
|
|
128
|
+
recoverCrashedConsumerMessages(eventName: string, idleTimeout?: number): Promise<void>;
|
|
128
129
|
/**
|
|
129
130
|
* This method allows the possibility of a graceful shutdown by cleaning up the
|
|
130
131
|
* redis connections.
|
package/src/lib/redis/streams.js
CHANGED
|
@@ -6,6 +6,9 @@ const registry_1 = require("./registry");
|
|
|
6
6
|
const rxjs_1 = require("rxjs");
|
|
7
7
|
const id_1 = require("@jetit/id");
|
|
8
8
|
const groups_1 = require("./groups");
|
|
9
|
+
function publisherErrorHandler(error) {
|
|
10
|
+
console.error('Publisher Error: ', error);
|
|
11
|
+
}
|
|
9
12
|
class Streams {
|
|
10
13
|
get redisPublisher() {
|
|
11
14
|
if (!this._redisPublisher)
|
|
@@ -41,14 +44,24 @@ class Streams {
|
|
|
41
44
|
this.eventsListened = [];
|
|
42
45
|
this.instanceId = `${serviceName}:${(0, id_1.generateID)('HEX', 'FE')}`;
|
|
43
46
|
this.consumerGroupName = `cg-${serviceName}`;
|
|
44
|
-
const cleanUpInterval = (_a = parseInt(process.env['CLEANUP_INTERVAL'] ||
|
|
47
|
+
const cleanUpInterval = (_a = parseInt(process.env['CLEANUP_INTERVAL'] || `${1000 * 60 * 60}`, 10)) !== null && _a !== void 0 ? _a : 1000 * 60 * 60;
|
|
48
|
+
setTimeout(() => this.runClear(cleanUpInterval), 3000);
|
|
45
49
|
this.cleanUpTimer = setInterval(() => {
|
|
50
|
+
this.runClear(cleanUpInterval);
|
|
51
|
+
}, cleanUpInterval);
|
|
52
|
+
}
|
|
53
|
+
runClear(cleanUpInterval) {
|
|
54
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
console.log('Running Publisher Clearance');
|
|
46
56
|
this.clearDuplicationCheckKeys();
|
|
47
57
|
for (const eventName of this.eventsListened) {
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
process.nextTick(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
yield this.cleanupAcknowledgedMessages(eventName, cleanUpInterval).catch(publisherErrorHandler);
|
|
60
|
+
yield this.republishUnprocessedEvents(eventName).catch(publisherErrorHandler);
|
|
61
|
+
yield this.recoverCrashedConsumerMessages(eventName).catch(publisherErrorHandler);
|
|
62
|
+
}));
|
|
50
63
|
}
|
|
51
|
-
}
|
|
64
|
+
});
|
|
52
65
|
}
|
|
53
66
|
createConsumerGroup(eventName) {
|
|
54
67
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -78,14 +91,16 @@ class Streams {
|
|
|
78
91
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
79
92
|
const processedMessagesKeyPattern = `pm:${this.consumerGroupName}:*`;
|
|
80
93
|
let cursor = '0';
|
|
94
|
+
const transaction = this.redisGroups.pipeline();
|
|
81
95
|
do {
|
|
82
96
|
const [nextCursor, keys] = yield this.redisGroups.scan(cursor, 'MATCH', processedMessagesKeyPattern);
|
|
83
97
|
cursor = nextCursor;
|
|
84
98
|
for (const key of keys) {
|
|
85
99
|
const oneHourAgo = Date.now() - 60 * 60 * 1000;
|
|
86
|
-
|
|
100
|
+
transaction.zremrangebyscore(key, '-inf', oneHourAgo);
|
|
87
101
|
}
|
|
88
102
|
} while (cursor !== '0');
|
|
103
|
+
yield transaction.exec();
|
|
89
104
|
});
|
|
90
105
|
}
|
|
91
106
|
publish(data) {
|
|
@@ -203,35 +218,37 @@ class Streams {
|
|
|
203
218
|
const observable = bs.asObservable().pipe((0, rxjs_1.skip)(1));
|
|
204
219
|
const streamName = `${eventName}:${this.consumerGroupName}`;
|
|
205
220
|
this.redisSubscriber.subscribe(eventName);
|
|
206
|
-
const processMessage = () =>
|
|
221
|
+
const processMessage = () => {
|
|
207
222
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
process.nextTick(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
const result = yield this.redisGroups.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'COUNT', 1, 'BLOCK', 0, 'STREAMS', streamName, '>');
|
|
225
|
+
if (result) {
|
|
226
|
+
const [, streamMessages] = result[0];
|
|
227
|
+
for (const [id, data] of streamMessages) {
|
|
228
|
+
const eventData = JSON.parse(data[1]);
|
|
229
|
+
const messageId = eventData.eventId;
|
|
230
|
+
const isDuplicate = yield this.isDuplicateMessage(streamName, messageId);
|
|
231
|
+
if (isDuplicate) {
|
|
232
|
+
console.warn(`Duplicate message detected: ${messageId}`);
|
|
233
|
+
yield this.redisGroups.xack(streamName, this.consumerGroupName, id);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
bs.next(eventData);
|
|
237
|
+
const pmKey = `pm:${this.consumerGroupName}:${streamName}`;
|
|
238
|
+
const currentTime = Date.now();
|
|
239
|
+
const transaction = this.redisGroups.multi();
|
|
240
|
+
transaction.zadd(pmKey, currentTime, messageId);
|
|
241
|
+
transaction.xack(streamName, this.consumerGroupName, id);
|
|
242
|
+
transaction.zadd(`ack:${streamName}`, Date.now(), id);
|
|
243
|
+
yield transaction.exec();
|
|
219
244
|
}
|
|
220
|
-
bs.next(eventData);
|
|
221
|
-
const pmKey = `pm:${this.consumerGroupName}:${streamName}`;
|
|
222
|
-
const currentTime = Date.now();
|
|
223
|
-
const transaction = this.redisGroups.multi();
|
|
224
|
-
transaction.zadd(pmKey, currentTime, messageId);
|
|
225
|
-
transaction.xack(streamName, this.consumerGroupName, id);
|
|
226
|
-
transaction.zadd(`ack:${streamName}`, Date.now(), id);
|
|
227
|
-
yield transaction.exec();
|
|
228
245
|
}
|
|
229
|
-
}
|
|
246
|
+
}));
|
|
230
247
|
}
|
|
231
248
|
catch (e) {
|
|
232
249
|
console.error(JSON.stringify(e));
|
|
233
250
|
}
|
|
234
|
-
}
|
|
251
|
+
};
|
|
235
252
|
this.redisSubscriber.on('message', () => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
236
253
|
yield processMessage();
|
|
237
254
|
}));
|
|
@@ -259,16 +276,16 @@ class Streams {
|
|
|
259
276
|
if (result) {
|
|
260
277
|
const [, streamMessages] = result[0];
|
|
261
278
|
console.log(`Unprocessed events: ${streamMessages.length}`);
|
|
279
|
+
const transaction = this.redisGroups.multi();
|
|
262
280
|
for (const [id, data] of streamMessages) {
|
|
263
281
|
const eventData = JSON.parse(data[1]);
|
|
264
|
-
const transaction = this.redisGroups.multi();
|
|
265
282
|
// Republishing the events
|
|
266
283
|
transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
|
|
267
284
|
transaction.publish(eventName, '');
|
|
268
285
|
transaction.xack(streamName, this.consumerGroupName, id);
|
|
269
|
-
yield transaction.exec();
|
|
270
286
|
console.log(`Event ${eventName} with ID: ${id} published`);
|
|
271
287
|
}
|
|
288
|
+
yield transaction.exec();
|
|
272
289
|
}
|
|
273
290
|
});
|
|
274
291
|
}
|
|
@@ -286,13 +303,16 @@ class Streams {
|
|
|
286
303
|
* // Attempt to recover messages from the "order.created" stream with an idle timeout of 10 seconds
|
|
287
304
|
* await streams.recoverCrashedConsumerMessages('order.created', 10000);
|
|
288
305
|
*/
|
|
289
|
-
recoverCrashedConsumerMessages(eventName, idleTimeout) {
|
|
306
|
+
recoverCrashedConsumerMessages(eventName, idleTimeout = 30000) {
|
|
290
307
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
291
308
|
const streamName = `${eventName}:${this.consumerGroupName}`;
|
|
292
309
|
const pendingMessages = (yield this.redisGroups.xpending(streamName, this.consumerGroupName));
|
|
293
310
|
if (!pendingMessages)
|
|
294
311
|
return;
|
|
295
312
|
const [, minId, maxId, consumers] = pendingMessages;
|
|
313
|
+
if (!consumers || consumers.length === 0)
|
|
314
|
+
return;
|
|
315
|
+
const transaction = this.redisGroups.multi();
|
|
296
316
|
for (const [consumer, pendingCount] of consumers) {
|
|
297
317
|
if (parseInt(pendingCount) > 0) {
|
|
298
318
|
const pending = (yield this.redisGroups.xpending(streamName, this.consumerGroupName, minId, maxId, Number(pendingCount), consumer));
|
|
@@ -301,15 +321,14 @@ class Streams {
|
|
|
301
321
|
if (claimedMessage) {
|
|
302
322
|
const [, data] = claimedMessage[0];
|
|
303
323
|
const eventData = JSON.parse(data[1]);
|
|
304
|
-
const transaction = this.redisGroups.multi();
|
|
305
324
|
transaction.xadd(streamName, '*', 'data', JSON.stringify(eventData));
|
|
306
325
|
transaction.publish(eventName, '');
|
|
307
326
|
transaction.xack(streamName, this.consumerGroupName, messageId);
|
|
308
|
-
yield transaction.exec();
|
|
309
327
|
}
|
|
310
328
|
}
|
|
311
329
|
}
|
|
312
330
|
}
|
|
331
|
+
yield transaction.exec();
|
|
313
332
|
});
|
|
314
333
|
}
|
|
315
334
|
/**
|