@hotmeshio/hotmesh 0.0.37 → 0.0.38
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/README.md +1 -1
- package/build/package.json +1 -1
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +3 -4
- package/build/services/quorum/index.d.ts +6 -6
- package/build/services/quorum/index.js +47 -11
- package/build/services/store/clients/ioredis.d.ts +1 -0
- package/build/services/store/clients/ioredis.js +9 -0
- package/build/services/store/clients/redis.d.ts +1 -0
- package/build/services/store/clients/redis.js +16 -0
- package/build/services/store/index.d.ts +1 -0
- package/build/services/stream/clients/ioredis.d.ts +1 -0
- package/build/services/stream/clients/ioredis.js +33 -24
- package/build/services/stream/clients/redis.d.ts +1 -0
- package/build/services/stream/clients/redis.js +15 -0
- package/build/services/stream/index.d.ts +1 -0
- package/build/services/worker/index.d.ts +1 -0
- package/build/services/worker/index.js +24 -0
- package/build/types/quorum.d.ts +11 -1
- package/build/types/redisclient.d.ts +1 -0
- package/package.json +1 -1
- package/services/hotmesh/index.ts +4 -5
- package/services/quorum/index.ts +48 -12
- package/services/store/clients/ioredis.ts +9 -0
- package/services/store/clients/redis.ts +16 -0
- package/services/store/index.ts +4 -0
- package/services/stream/clients/ioredis.ts +33 -24
- package/services/stream/clients/redis.ts +14 -0
- package/services/stream/index.ts +1 -0
- package/services/worker/index.ts +30 -0
- package/types/quorum.ts +15 -4
- package/types/redisclient.ts +1 -0
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install @hotmeshio/hotmesh
|
|
|
15
15
|
## Understanding HotMesh
|
|
16
16
|
HotMesh inverts the relationship to Redis: those functions that once used Redis as a cache, are instead *cached and governed* by Redis. Consider the following. It's a typical microservices network, with a tangled mess of services and functions. There's important business logic in there (functions *A*, *B* and *C* are critical!), but they're hard to find and access.
|
|
17
17
|
|
|
18
|
-
<img src="
|
|
18
|
+
<img src="./docs/img/operational_data_layer.png" alt="A Tangled Microservices Network with 3 valuable functions buried within" style="max-width:100%;width:600px;">
|
|
19
19
|
|
|
20
20
|
HotMesh creates an *ad hoc*, Redis-backed network of functions and organizes them into a unified service mesh. *Any service with access to Redis can join in the network, bypassing the legacy clutter.*
|
|
21
21
|
|
package/build/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { QuorumService } from '../quorum';
|
|
|
4
4
|
import { WorkerService } from '../worker';
|
|
5
5
|
import { JobState, JobData, JobOutput, JobStatus, JobInterruptOptions } from '../../types/job';
|
|
6
6
|
import { HotMeshConfig, HotMeshManifest } from '../../types/hotmesh';
|
|
7
|
-
import { JobMessageCallback } from '../../types/quorum';
|
|
7
|
+
import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
|
|
8
8
|
import { JobStatsInput, GetStatsOptions, IdsResponse, StatsResponse } from '../../types/stats';
|
|
9
9
|
import { StreamCode, StreamData, StreamDataResponse, StreamStatus } from '../../types/stream';
|
|
10
10
|
import { StringAnyType } from '../../types/serializer';
|
|
@@ -31,10 +31,10 @@ declare class HotMeshService {
|
|
|
31
31
|
punsub(wild: string): Promise<void>;
|
|
32
32
|
pubsub(topic: string, data?: JobData, context?: JobState | null, timeout?: number): Promise<JobOutput>;
|
|
33
33
|
add(streamData: StreamData | StreamDataResponse): Promise<string>;
|
|
34
|
+
rollCall(delay?: number): Promise<QuorumProfile[]>;
|
|
34
35
|
plan(path: string): Promise<HotMeshManifest>;
|
|
35
36
|
deploy(pathOrYAML: string): Promise<HotMeshManifest>;
|
|
36
37
|
activate(version: string, delay?: number): Promise<boolean>;
|
|
37
|
-
inventory(version: string, delay?: number): Promise<number>;
|
|
38
38
|
getStats(topic: string, query: JobStatsInput): Promise<StatsResponse>;
|
|
39
39
|
getStatus(jobId: string): Promise<JobStatus>;
|
|
40
40
|
getState(topic: string, jobId: string): Promise<JobOutput>;
|
|
@@ -90,6 +90,9 @@ class HotMeshService {
|
|
|
90
90
|
return await this.engine.add(streamData);
|
|
91
91
|
}
|
|
92
92
|
// ************* COMPILER METHODS *************
|
|
93
|
+
async rollCall(delay) {
|
|
94
|
+
return await this.quorum?.rollCall(delay);
|
|
95
|
+
}
|
|
93
96
|
async plan(path) {
|
|
94
97
|
return await this.engine?.plan(path);
|
|
95
98
|
}
|
|
@@ -100,10 +103,6 @@ class HotMeshService {
|
|
|
100
103
|
//activation is a quorum operation
|
|
101
104
|
return await this.quorum?.activate(version, delay);
|
|
102
105
|
}
|
|
103
|
-
async inventory(version, delay) {
|
|
104
|
-
//get count of all peers
|
|
105
|
-
return await this.quorum?.inventory(delay);
|
|
106
|
-
}
|
|
107
106
|
// ************* REPORTER METHODS *************
|
|
108
107
|
async getStats(topic, query) {
|
|
109
108
|
return await this.engine?.getStats(topic, query);
|
|
@@ -3,15 +3,15 @@ import { ILogger } from '../logger';
|
|
|
3
3
|
import { StoreService } from '../store';
|
|
4
4
|
import { SubService } from '../sub';
|
|
5
5
|
import { CacheMode } from '../../types/cache';
|
|
6
|
-
import { QuorumMessageCallback, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
|
|
7
|
-
import {
|
|
6
|
+
import { QuorumMessageCallback, QuorumProfile, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
|
|
7
|
+
import { HotMeshConfig } from '../../types/hotmesh';
|
|
8
8
|
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
9
9
|
declare class QuorumService {
|
|
10
10
|
namespace: string;
|
|
11
|
-
apps: HotMeshApps | null;
|
|
12
11
|
appId: string;
|
|
13
12
|
guid: string;
|
|
14
13
|
engine: EngineService;
|
|
14
|
+
profiles: QuorumProfile[];
|
|
15
15
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
16
16
|
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
17
17
|
logger: ILogger;
|
|
@@ -24,12 +24,12 @@ declare class QuorumService {
|
|
|
24
24
|
initStoreChannel(store: RedisClient): Promise<void>;
|
|
25
25
|
initSubChannel(sub: RedisClient): Promise<void>;
|
|
26
26
|
subscriptionHandler(): SubscriptionCallback;
|
|
27
|
-
sayPong(appId: string, guid: string, originator: string): Promise<void>;
|
|
28
|
-
requestQuorum(delay?: number): Promise<number>;
|
|
27
|
+
sayPong(appId: string, guid: string, originator: string, details?: boolean): Promise<void>;
|
|
28
|
+
requestQuorum(delay?: number, details?: boolean): Promise<number>;
|
|
29
29
|
pub(quorumMessage: ThrottleMessage): Promise<boolean>;
|
|
30
30
|
sub(callback: QuorumMessageCallback): Promise<void>;
|
|
31
31
|
unsub(callback: QuorumMessageCallback): Promise<void>;
|
|
32
|
-
|
|
32
|
+
rollCall(delay?: number): Promise<QuorumProfile[]>;
|
|
33
33
|
activate(version: string, delay?: number): Promise<boolean>;
|
|
34
34
|
}
|
|
35
35
|
export { QuorumService };
|
|
@@ -12,6 +12,7 @@ const redis_2 = require("../sub/clients/redis");
|
|
|
12
12
|
const QUORUM_DELAY = 250;
|
|
13
13
|
class QuorumService {
|
|
14
14
|
constructor() {
|
|
15
|
+
this.profiles = [];
|
|
15
16
|
this.cacheMode = 'cache';
|
|
16
17
|
this.untilVersion = null;
|
|
17
18
|
this.quorum = null;
|
|
@@ -68,10 +69,13 @@ class QuorumService {
|
|
|
68
69
|
self.engine.setCacheMode(message.cache_mode, message.until_version);
|
|
69
70
|
}
|
|
70
71
|
else if (message.type === 'ping') {
|
|
71
|
-
|
|
72
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
72
73
|
}
|
|
73
74
|
else if (message.type === 'pong' && self.guid === message.originator) {
|
|
74
75
|
self.quorum = self.quorum + 1;
|
|
76
|
+
if (message.profile) {
|
|
77
|
+
self.profiles.push(message.profile);
|
|
78
|
+
}
|
|
75
79
|
}
|
|
76
80
|
else if (message.type === 'throttle') {
|
|
77
81
|
self.engine.throttle(message.throttle);
|
|
@@ -91,13 +95,31 @@ class QuorumService {
|
|
|
91
95
|
}
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
|
-
async sayPong(appId, guid, originator) {
|
|
95
|
-
|
|
98
|
+
async sayPong(appId, guid, originator, details = false) {
|
|
99
|
+
let profile;
|
|
100
|
+
if (details) {
|
|
101
|
+
profile = {
|
|
102
|
+
engine_id: this.guid,
|
|
103
|
+
namespace: this.namespace,
|
|
104
|
+
app_id: this.appId,
|
|
105
|
+
stream: this.engine.stream.mintKey(key_1.KeyType.STREAMS, { appId: this.appId })
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
this.store.publish(key_1.KeyType.QUORUM, {
|
|
109
|
+
type: 'pong',
|
|
110
|
+
guid, originator,
|
|
111
|
+
profile,
|
|
112
|
+
}, appId);
|
|
96
113
|
}
|
|
97
|
-
async requestQuorum(delay = QUORUM_DELAY) {
|
|
114
|
+
async requestQuorum(delay = QUORUM_DELAY, details = false) {
|
|
98
115
|
const quorum = this.quorum;
|
|
99
116
|
this.quorum = 0;
|
|
100
|
-
|
|
117
|
+
this.profiles.length = 0;
|
|
118
|
+
await this.store.publish(key_1.KeyType.QUORUM, {
|
|
119
|
+
type: 'ping',
|
|
120
|
+
originator: this.guid,
|
|
121
|
+
details,
|
|
122
|
+
}, this.appId);
|
|
101
123
|
await (0, utils_1.sleepFor)(delay);
|
|
102
124
|
return quorum;
|
|
103
125
|
}
|
|
@@ -117,12 +139,26 @@ class QuorumService {
|
|
|
117
139
|
this.callbacks = this.callbacks.filter(cb => cb !== callback);
|
|
118
140
|
}
|
|
119
141
|
// ************* COMPILER METHODS *************
|
|
120
|
-
async
|
|
121
|
-
await this.requestQuorum(delay);
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
async rollCall(delay = QUORUM_DELAY) {
|
|
143
|
+
await this.requestQuorum(delay, true);
|
|
144
|
+
const targetStreams = [];
|
|
145
|
+
const multi = this.store.getMulti();
|
|
146
|
+
this.profiles.forEach((profile) => {
|
|
147
|
+
if (!targetStreams.includes(profile.stream)) {
|
|
148
|
+
targetStreams.push(profile.stream);
|
|
149
|
+
this.store.xlen(profile.stream, multi);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const stream_depths = await multi.exec();
|
|
153
|
+
this.profiles.forEach(async (profile) => {
|
|
154
|
+
const index = targetStreams.indexOf(profile.stream);
|
|
155
|
+
if (index != -1) {
|
|
156
|
+
profile.stream_depth = Array.isArray(stream_depths[index]) ?
|
|
157
|
+
stream_depths[index][1] :
|
|
158
|
+
stream_depths[index];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return this.profiles;
|
|
126
162
|
}
|
|
127
163
|
async activate(version, delay = QUORUM_DELAY) {
|
|
128
164
|
version = version.toString();
|
|
@@ -24,5 +24,6 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
|
|
|
24
24
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
25
25
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
26
26
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
27
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
27
28
|
}
|
|
28
29
|
export { IORedisStoreService };
|
|
@@ -105,5 +105,14 @@ class IORedisStoreService extends index_1.StoreService {
|
|
|
105
105
|
throw error;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
async xlen(key, multi) {
|
|
109
|
+
try {
|
|
110
|
+
return await (multi || this.redisClient).xlen(key);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
118
|
exports.IORedisStoreService = IORedisStoreService;
|
|
@@ -26,5 +26,6 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
|
|
|
26
26
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
27
27
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
28
28
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
29
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
29
30
|
}
|
|
30
31
|
export { RedisStoreService };
|
|
@@ -30,6 +30,7 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
30
30
|
rpush: 'RPUSH',
|
|
31
31
|
xack: 'XACK',
|
|
32
32
|
xdel: 'XDEL',
|
|
33
|
+
xlen: 'XLEN',
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
getMulti() {
|
|
@@ -141,5 +142,20 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
141
142
|
throw error;
|
|
142
143
|
}
|
|
143
144
|
}
|
|
145
|
+
async xlen(key, multi) {
|
|
146
|
+
try {
|
|
147
|
+
if (multi) {
|
|
148
|
+
multi.XLEN(key);
|
|
149
|
+
return multi;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
return await this.redisClient.XLEN(key);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
144
160
|
}
|
|
145
161
|
exports.RedisStoreService = RedisStoreService;
|
|
@@ -31,6 +31,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
31
31
|
abstract xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
32
32
|
abstract xack(key: string, group: string, id: string, multi?: U): Promise<number | U>;
|
|
33
33
|
abstract xdel(key: string, id: string, multi?: U): Promise<number | U>;
|
|
34
|
+
abstract xlen(key: string, multi?: U): Promise<number | U>;
|
|
34
35
|
constructor(redisClient: T);
|
|
35
36
|
init(namespace: string, appId: string, logger: ILogger): Promise<HotMeshApps>;
|
|
36
37
|
isSuccessful(result: any): boolean;
|
|
@@ -19,5 +19,6 @@ declare class IORedisStreamService extends StreamService<RedisClientType, RedisM
|
|
|
19
19
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
20
20
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
21
21
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
22
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
22
23
|
}
|
|
23
24
|
export { IORedisStreamService };
|
|
@@ -25,18 +25,18 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
25
25
|
try {
|
|
26
26
|
return (await this.redisClient.xgroup(command, key, groupName, id, mkStream)) === 'OK';
|
|
27
27
|
}
|
|
28
|
-
catch (
|
|
28
|
+
catch (error) {
|
|
29
29
|
this.logger.info(`Consumer group not created with MKSTREAM for key: ${key} and group: ${groupName}`);
|
|
30
|
-
throw
|
|
30
|
+
throw error;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
34
|
try {
|
|
35
35
|
return (await this.redisClient.xgroup(command, key, groupName, id)) === 'OK';
|
|
36
36
|
}
|
|
37
|
-
catch (
|
|
37
|
+
catch (error) {
|
|
38
38
|
this.logger.info(`Consumer group not created for key: ${key} and group: ${groupName}`);
|
|
39
|
-
throw
|
|
39
|
+
throw error;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -44,9 +44,9 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
44
44
|
try {
|
|
45
45
|
return await (multi || this.redisClient).xadd(key, id, messageId, messageValue);
|
|
46
46
|
}
|
|
47
|
-
catch (
|
|
48
|
-
this.logger.error(`Error publishing 'xadd'; key: ${key}`,
|
|
49
|
-
throw
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.logger.error(`Error publishing 'xadd'; key: ${key}`, { error });
|
|
49
|
+
throw error;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
async xreadgroup(command, groupName, consumerName, blockOption, blockTime, streamsOption, streamName, id) {
|
|
@@ -56,9 +56,9 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
56
56
|
// @ts-ignore
|
|
57
57
|
blockOption, blockTime, streamsOption, streamName, id);
|
|
58
58
|
}
|
|
59
|
-
catch (
|
|
60
|
-
this.logger.error(`Error reading stream data [Stream ${streamName}] [Group ${groupName}]`,
|
|
61
|
-
throw
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logger.error(`Error reading stream data [Stream ${streamName}] [Group ${groupName}]`, { error });
|
|
61
|
+
throw error;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
async xpending(key, group, start, end, count, consumer) {
|
|
@@ -75,40 +75,49 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
75
75
|
try {
|
|
76
76
|
return await this.redisClient.call('XPENDING', ...args);
|
|
77
77
|
}
|
|
78
|
-
catch (
|
|
79
|
-
this.logger.error('err, args',
|
|
78
|
+
catch (error) {
|
|
79
|
+
this.logger.error('err, args', { error }, args);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
catch (
|
|
83
|
-
this.logger.error(`Error in retrieving pending messages for [stream ${key}], [group ${group}]`,
|
|
84
|
-
throw
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.logger.error(`Error in retrieving pending messages for [stream ${key}], [group ${group}]`, { error });
|
|
84
|
+
throw error;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
async xclaim(key, group, consumer, minIdleTime, id, ...args) {
|
|
88
88
|
try {
|
|
89
89
|
return await this.redisClient.xclaim(key, group, consumer, minIdleTime, id, ...args);
|
|
90
90
|
}
|
|
91
|
-
catch (
|
|
92
|
-
this.logger.error(`Error in claiming message with id: ${id} in group: ${group} for key: ${key}`,
|
|
93
|
-
throw
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.logger.error(`Error in claiming message with id: ${id} in group: ${group} for key: ${key}`, { error });
|
|
93
|
+
throw error;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
async xack(key, group, id, multi) {
|
|
97
97
|
try {
|
|
98
98
|
return await (multi || this.redisClient).xack(key, group, id);
|
|
99
99
|
}
|
|
100
|
-
catch (
|
|
101
|
-
this.logger.error(`Error in acknowledging messages in group: ${group} for key: ${key}`,
|
|
102
|
-
throw
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.logger.error(`Error in acknowledging messages in group: ${group} for key: ${key}`, { error });
|
|
102
|
+
throw error;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
async xdel(key, id, multi) {
|
|
106
106
|
try {
|
|
107
107
|
return await (multi || this.redisClient).xdel(key, id);
|
|
108
108
|
}
|
|
109
|
-
catch (
|
|
110
|
-
this.logger.error(`Error in deleting messages with id: ${id} for key: ${key}`,
|
|
111
|
-
throw
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.logger.error(`Error in deleting messages with id: ${id} for key: ${key}`, { error });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async xlen(key, multi) {
|
|
115
|
+
try {
|
|
116
|
+
return await (multi || this.redisClient).xlen(key);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
120
|
+
throw error;
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
}
|
|
@@ -19,5 +19,6 @@ declare class RedisStreamService extends StreamService<RedisClientType, RedisMul
|
|
|
19
19
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
20
20
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
21
21
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
22
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
22
23
|
}
|
|
23
24
|
export { RedisStreamService };
|
|
@@ -115,5 +115,20 @@ class RedisStreamService extends index_1.StreamService {
|
|
|
115
115
|
throw err;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
async xlen(key, multi) {
|
|
119
|
+
try {
|
|
120
|
+
if (multi) {
|
|
121
|
+
multi.XLEN(key);
|
|
122
|
+
return multi;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
return await this.redisClient.XLEN(key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
118
133
|
}
|
|
119
134
|
exports.RedisStreamService = RedisStreamService;
|
|
@@ -17,5 +17,6 @@ declare abstract class StreamService<T, U> {
|
|
|
17
17
|
abstract xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
18
18
|
abstract xack(key: string, group: string, id: string, multi?: U): Promise<number | U>;
|
|
19
19
|
abstract xdel(key: string, id: string, multi?: U): Promise<number | U>;
|
|
20
|
+
abstract xlen(key: string, multi?: U): Promise<number | U>;
|
|
20
21
|
}
|
|
21
22
|
export { StreamService };
|
|
@@ -25,6 +25,7 @@ declare class WorkerService {
|
|
|
25
25
|
initStreamChannel(service: WorkerService, stream: RedisClient): Promise<void>;
|
|
26
26
|
initRouter(worker: HotMeshWorker, logger: ILogger): Router;
|
|
27
27
|
subscriptionHandler(): SubscriptionCallback;
|
|
28
|
+
sayPong(appId: string, guid: string, originator: string, details?: boolean): Promise<void>;
|
|
28
29
|
throttle(delayInMillis: number): Promise<void>;
|
|
29
30
|
}
|
|
30
31
|
export { WorkerService };
|
|
@@ -96,8 +96,32 @@ class WorkerService {
|
|
|
96
96
|
if (message.type === 'throttle') {
|
|
97
97
|
self.throttle(message.throttle);
|
|
98
98
|
}
|
|
99
|
+
else if (message.type === 'ping') {
|
|
100
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
101
|
+
}
|
|
99
102
|
};
|
|
100
103
|
}
|
|
104
|
+
async sayPong(appId, guid, originator, details = false) {
|
|
105
|
+
let profile;
|
|
106
|
+
if (details) {
|
|
107
|
+
const params = {
|
|
108
|
+
appId: this.appId,
|
|
109
|
+
topic: this.topic,
|
|
110
|
+
};
|
|
111
|
+
profile = {
|
|
112
|
+
engine_id: this.guid,
|
|
113
|
+
namespace: this.namespace,
|
|
114
|
+
app_id: this.appId,
|
|
115
|
+
worker_topic: this.topic,
|
|
116
|
+
stream: this.stream.mintKey(key_1.KeyType.STREAMS, params),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
this.store.publish(key_1.KeyType.QUORUM, {
|
|
120
|
+
type: 'pong',
|
|
121
|
+
guid, originator,
|
|
122
|
+
profile,
|
|
123
|
+
}, appId);
|
|
124
|
+
}
|
|
101
125
|
async throttle(delayInMillis) {
|
|
102
126
|
this.router.setThrottle(delayInMillis);
|
|
103
127
|
}
|
package/build/types/quorum.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { JobOutput } from "./job";
|
|
2
|
+
export interface QuorumProfile {
|
|
3
|
+
namespace: string;
|
|
4
|
+
app_id: string;
|
|
5
|
+
engine_id: string;
|
|
6
|
+
worker_topic?: string;
|
|
7
|
+
stream?: string;
|
|
8
|
+
stream_depth?: number;
|
|
9
|
+
}
|
|
2
10
|
export interface PingMessage {
|
|
3
11
|
type: 'ping';
|
|
4
12
|
originator: string;
|
|
13
|
+
details?: boolean;
|
|
5
14
|
}
|
|
6
15
|
export interface WorkMessage {
|
|
7
16
|
type: 'work';
|
|
@@ -13,8 +22,9 @@ export interface CronMessage {
|
|
|
13
22
|
}
|
|
14
23
|
export interface PongMessage {
|
|
15
24
|
type: 'pong';
|
|
16
|
-
originator: string;
|
|
17
25
|
guid: string;
|
|
26
|
+
originator: string;
|
|
27
|
+
profile?: QuorumProfile;
|
|
18
28
|
}
|
|
19
29
|
export interface ActivateMessage {
|
|
20
30
|
type: 'activate';
|
|
@@ -4,6 +4,7 @@ interface RedisMultiType {
|
|
|
4
4
|
XADD(key: string, id: string, fields: any): this;
|
|
5
5
|
XACK(key: string, group: string, id: string): this;
|
|
6
6
|
XDEL(key: string, id: string): this;
|
|
7
|
+
XLEN(key: string): this;
|
|
7
8
|
HDEL(key: string, itemId: string): this;
|
|
8
9
|
HGET(key: string, itemId: string): this;
|
|
9
10
|
HGETALL(key: string): this;
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
HotMeshConfig,
|
|
18
18
|
HotMeshManifest } from '../../types/hotmesh';
|
|
19
|
-
import { JobMessageCallback } from '../../types/quorum';
|
|
19
|
+
import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
|
|
20
20
|
import {
|
|
21
21
|
JobStatsInput,
|
|
22
22
|
GetStatsOptions,
|
|
@@ -137,6 +137,9 @@ class HotMeshService {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// ************* COMPILER METHODS *************
|
|
140
|
+
async rollCall(delay?: number): Promise<QuorumProfile[]> {
|
|
141
|
+
return await this.quorum?.rollCall(delay);
|
|
142
|
+
}
|
|
140
143
|
async plan(path: string): Promise<HotMeshManifest> {
|
|
141
144
|
return await this.engine?.plan(path);
|
|
142
145
|
}
|
|
@@ -147,10 +150,6 @@ class HotMeshService {
|
|
|
147
150
|
//activation is a quorum operation
|
|
148
151
|
return await this.quorum?.activate(version, delay);
|
|
149
152
|
}
|
|
150
|
-
async inventory(version: string, delay?: number): Promise<number> {
|
|
151
|
-
//get count of all peers
|
|
152
|
-
return await this.quorum?.inventory(delay);
|
|
153
|
-
}
|
|
154
153
|
|
|
155
154
|
// ************* REPORTER METHODS *************
|
|
156
155
|
async getStats(topic: string, query: JobStatsInput): Promise<StatsResponse> {
|
package/services/quorum/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { RedisClientType as IORedisClientType } from '../../types/ioredisclient'
|
|
|
14
14
|
import {
|
|
15
15
|
QuorumMessage,
|
|
16
16
|
QuorumMessageCallback,
|
|
17
|
+
QuorumProfile,
|
|
17
18
|
SubscriptionCallback,
|
|
18
19
|
ThrottleMessage
|
|
19
20
|
} from '../../types/quorum';
|
|
@@ -26,10 +27,10 @@ const QUORUM_DELAY = 250;
|
|
|
26
27
|
|
|
27
28
|
class QuorumService {
|
|
28
29
|
namespace: string;
|
|
29
|
-
apps: HotMeshApps | null;
|
|
30
30
|
appId: string;
|
|
31
31
|
guid: string;
|
|
32
32
|
engine: EngineService;
|
|
33
|
+
profiles: QuorumProfile[] = [];
|
|
33
34
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
34
35
|
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
35
36
|
logger: ILogger;
|
|
@@ -108,9 +109,12 @@ class QuorumService {
|
|
|
108
109
|
if (message.type === 'activate') {
|
|
109
110
|
self.engine.setCacheMode(message.cache_mode, message.until_version);
|
|
110
111
|
} else if (message.type === 'ping') {
|
|
111
|
-
|
|
112
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
112
113
|
} else if (message.type === 'pong' && self.guid === message.originator) {
|
|
113
114
|
self.quorum = self.quorum + 1;
|
|
115
|
+
if (message.profile) {
|
|
116
|
+
self.profiles.push(message.profile);
|
|
117
|
+
}
|
|
114
118
|
} else if (message.type === 'throttle') {
|
|
115
119
|
self.engine.throttle(message.throttle);
|
|
116
120
|
} else if (message.type === 'work') {
|
|
@@ -127,20 +131,38 @@ class QuorumService {
|
|
|
127
131
|
};
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
async sayPong(appId: string, guid: string, originator: string) {
|
|
134
|
+
async sayPong(appId: string, guid: string, originator: string, details = false) {
|
|
135
|
+
let profile: QuorumProfile;
|
|
136
|
+
if (details) {
|
|
137
|
+
profile = {
|
|
138
|
+
engine_id: this.guid,
|
|
139
|
+
namespace: this.namespace,
|
|
140
|
+
app_id: this.appId,
|
|
141
|
+
stream: this.engine.stream.mintKey(KeyType.STREAMS, { appId: this.appId })
|
|
142
|
+
};
|
|
143
|
+
}
|
|
131
144
|
this.store.publish(
|
|
132
145
|
KeyType.QUORUM,
|
|
133
|
-
{
|
|
146
|
+
{
|
|
147
|
+
type: 'pong',
|
|
148
|
+
guid, originator,
|
|
149
|
+
profile,
|
|
150
|
+
},
|
|
134
151
|
appId,
|
|
135
152
|
);
|
|
136
153
|
}
|
|
137
154
|
|
|
138
|
-
async requestQuorum(delay = QUORUM_DELAY): Promise<number> {
|
|
155
|
+
async requestQuorum(delay = QUORUM_DELAY, details = false): Promise<number> {
|
|
139
156
|
const quorum = this.quorum;
|
|
140
157
|
this.quorum = 0;
|
|
158
|
+
this.profiles.length = 0;
|
|
141
159
|
await this.store.publish(
|
|
142
160
|
KeyType.QUORUM,
|
|
143
|
-
{
|
|
161
|
+
{
|
|
162
|
+
type: 'ping',
|
|
163
|
+
originator: this.guid,
|
|
164
|
+
details,
|
|
165
|
+
},
|
|
144
166
|
this.appId,
|
|
145
167
|
);
|
|
146
168
|
await sleepFor(delay);
|
|
@@ -166,12 +188,26 @@ class QuorumService {
|
|
|
166
188
|
|
|
167
189
|
|
|
168
190
|
// ************* COMPILER METHODS *************
|
|
169
|
-
async
|
|
170
|
-
await this.requestQuorum(delay);
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
async rollCall(delay = QUORUM_DELAY): Promise<QuorumProfile[]> {
|
|
192
|
+
await this.requestQuorum(delay, true);
|
|
193
|
+
const targetStreams = [];
|
|
194
|
+
const multi = this.store.getMulti();
|
|
195
|
+
this.profiles.forEach((profile: QuorumProfile) => {
|
|
196
|
+
if (!targetStreams.includes(profile.stream)) {
|
|
197
|
+
targetStreams.push(profile.stream);
|
|
198
|
+
this.store.xlen(profile.stream, multi);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
const stream_depths = await multi.exec() as number[];
|
|
202
|
+
this.profiles.forEach(async (profile: QuorumProfile) => {
|
|
203
|
+
const index = targetStreams.indexOf(profile.stream);
|
|
204
|
+
if (index != -1) {
|
|
205
|
+
profile.stream_depth = Array.isArray(stream_depths[index]) ?
|
|
206
|
+
stream_depths[index][1] :
|
|
207
|
+
stream_depths[index];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return this.profiles;
|
|
175
211
|
}
|
|
176
212
|
async activate(version: string, delay = QUORUM_DELAY): Promise<boolean> {
|
|
177
213
|
version = version.toString();
|
|
@@ -131,6 +131,15 @@ class IORedisStoreService extends StoreService<RedisClientType, RedisMultiType>
|
|
|
131
131
|
throw error;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
136
|
+
try {
|
|
137
|
+
return await (multi || this.redisClient).xlen(key);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
export { IORedisStoreService };
|
|
@@ -42,6 +42,7 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
|
42
42
|
rpush: 'RPUSH',
|
|
43
43
|
xack: 'XACK',
|
|
44
44
|
xdel: 'XDEL',
|
|
45
|
+
xlen: 'XLEN',
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -167,6 +168,21 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
|
167
168
|
throw error;
|
|
168
169
|
}
|
|
169
170
|
}
|
|
171
|
+
|
|
172
|
+
async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
173
|
+
try {
|
|
174
|
+
if (multi) {
|
|
175
|
+
multi.XLEN(key);
|
|
176
|
+
return multi;
|
|
177
|
+
} else {
|
|
178
|
+
return await this.redisClient.XLEN(key);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
}
|
|
171
187
|
|
|
172
188
|
export { RedisStoreService };
|
package/services/store/index.ts
CHANGED
|
@@ -118,6 +118,10 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
118
118
|
id: string,
|
|
119
119
|
multi?: U
|
|
120
120
|
): Promise<number|U>;
|
|
121
|
+
abstract xlen(
|
|
122
|
+
key: string,
|
|
123
|
+
multi?: U
|
|
124
|
+
): Promise<number|U>;
|
|
121
125
|
|
|
122
126
|
constructor(redisClient: T) {
|
|
123
127
|
this.redisClient = redisClient;
|
|
@@ -33,16 +33,16 @@ class IORedisStreamService extends StreamService<RedisClientType, RedisMultiType
|
|
|
33
33
|
if (mkStream === 'MKSTREAM') {
|
|
34
34
|
try {
|
|
35
35
|
return (await this.redisClient.xgroup(command, key, groupName, id, mkStream)) === 'OK';
|
|
36
|
-
} catch (
|
|
36
|
+
} catch (error) {
|
|
37
37
|
this.logger.info(`Consumer group not created with MKSTREAM for key: ${key} and group: ${groupName}`);
|
|
38
|
-
throw
|
|
38
|
+
throw error;
|
|
39
39
|
}
|
|
40
40
|
} else {
|
|
41
41
|
try {
|
|
42
42
|
return (await this.redisClient.xgroup(command, key, groupName, id)) === 'OK';
|
|
43
|
-
} catch (
|
|
43
|
+
} catch (error) {
|
|
44
44
|
this.logger.info(`Consumer group not created for key: ${key} and group: ${groupName}`);
|
|
45
|
-
throw
|
|
45
|
+
throw error;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -50,9 +50,9 @@ class IORedisStreamService extends StreamService<RedisClientType, RedisMultiType
|
|
|
50
50
|
async xadd(key: string, id: string, messageId: string, messageValue: string, multi?: RedisMultiType): Promise<string | RedisMultiType> {
|
|
51
51
|
try {
|
|
52
52
|
return await (multi || this.redisClient).xadd(key, id, messageId, messageValue);
|
|
53
|
-
} catch (
|
|
54
|
-
this.logger.error(`Error publishing 'xadd'; key: ${key}`,
|
|
55
|
-
throw
|
|
53
|
+
} catch (error) {
|
|
54
|
+
this.logger.error(`Error publishing 'xadd'; key: ${key}`, { error });
|
|
55
|
+
throw error;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -79,9 +79,9 @@ class IORedisStreamService extends StreamService<RedisClientType, RedisMultiType
|
|
|
79
79
|
streamName,
|
|
80
80
|
id
|
|
81
81
|
);
|
|
82
|
-
} catch (
|
|
83
|
-
this.logger.error(`Error reading stream data [Stream ${streamName}] [Group ${groupName}]`,
|
|
84
|
-
throw
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.logger.error(`Error reading stream data [Stream ${streamName}] [Group ${groupName}]`, { error });
|
|
84
|
+
throw error;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -101,12 +101,12 @@ class IORedisStreamService extends StreamService<RedisClientType, RedisMultiType
|
|
|
101
101
|
if (consumer) args.push(consumer);
|
|
102
102
|
try {
|
|
103
103
|
return await this.redisClient.call('XPENDING', ...args) as [string, string, number, number][];
|
|
104
|
-
} catch (
|
|
105
|
-
this.logger.error('err, args',
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.logger.error('err, args', { error }, args);
|
|
106
106
|
}
|
|
107
|
-
} catch (
|
|
108
|
-
this.logger.error(`Error in retrieving pending messages for [stream ${key}], [group ${group}]`,
|
|
109
|
-
throw
|
|
107
|
+
} catch (error) {
|
|
108
|
+
this.logger.error(`Error in retrieving pending messages for [stream ${key}], [group ${group}]`, { error });
|
|
109
|
+
throw error;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -120,27 +120,36 @@ class IORedisStreamService extends StreamService<RedisClientType, RedisMultiType
|
|
|
120
120
|
): Promise<ReclaimedMessageType> {
|
|
121
121
|
try {
|
|
122
122
|
return await this.redisClient.xclaim(key, group, consumer, minIdleTime, id, ...args) as unknown as ReclaimedMessageType;
|
|
123
|
-
} catch (
|
|
124
|
-
this.logger.error(`Error in claiming message with id: ${id} in group: ${group} for key: ${key}`,
|
|
125
|
-
throw
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.logger.error(`Error in claiming message with id: ${id} in group: ${group} for key: ${key}`, { error });
|
|
125
|
+
throw error;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
async xack(key: string, group: string, id: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
130
130
|
try {
|
|
131
131
|
return await (multi || this.redisClient).xack(key, group, id);
|
|
132
|
-
} catch (
|
|
133
|
-
this.logger.error(`Error in acknowledging messages in group: ${group} for key: ${key}`,
|
|
134
|
-
throw
|
|
132
|
+
} catch (error) {
|
|
133
|
+
this.logger.error(`Error in acknowledging messages in group: ${group} for key: ${key}`, { error });
|
|
134
|
+
throw error;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
async xdel(key: string, id: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
139
139
|
try {
|
|
140
140
|
return await (multi || this.redisClient).xdel(key, id);
|
|
141
|
-
} catch (
|
|
142
|
-
this.logger.error(`Error in deleting messages with id: ${id} for key: ${key}`,
|
|
143
|
-
throw
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.logger.error(`Error in deleting messages with id: ${id} for key: ${key}`, { error });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
148
|
+
try {
|
|
149
|
+
return await (multi || this.redisClient).xlen(key);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
152
|
+
throw error;
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
}
|
|
@@ -139,6 +139,20 @@ class RedisStreamService extends StreamService<RedisClientType, RedisMultiType>
|
|
|
139
139
|
throw err;
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
144
|
+
try {
|
|
145
|
+
if (multi) {
|
|
146
|
+
multi.XLEN(key);
|
|
147
|
+
return multi;
|
|
148
|
+
} else {
|
|
149
|
+
return await this.redisClient.XLEN(key);
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
export { RedisStreamService };
|
package/services/stream/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ abstract class StreamService<T, U> {
|
|
|
52
52
|
...args: string[]): Promise<ReclaimedMessageType>;
|
|
53
53
|
abstract xack(key: string, group: string, id: string, multi?: U): Promise<number|U>;
|
|
54
54
|
abstract xdel(key: string, id: string, multi?: U): Promise<number|U>;
|
|
55
|
+
abstract xlen(key: string, multi?: U): Promise<number|U>;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export { StreamService };
|
package/services/worker/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { RedisClientType as IORedisClientType } from '../../types/ioredisclient'
|
|
|
14
14
|
import { HotMeshConfig, HotMeshWorker } from "../../types/hotmesh";
|
|
15
15
|
import {
|
|
16
16
|
QuorumMessage,
|
|
17
|
+
QuorumProfile,
|
|
17
18
|
SubscriptionCallback } from "../../types/quorum";
|
|
18
19
|
import { RedisClient, RedisMulti } from "../../types/redis";
|
|
19
20
|
import { RedisClientType } from '../../types/redisclient';
|
|
@@ -153,10 +154,39 @@ class WorkerService {
|
|
|
153
154
|
self.logger.debug('worker-event-received', { topic, type: message.type });
|
|
154
155
|
if (message.type === 'throttle') {
|
|
155
156
|
self.throttle(message.throttle);
|
|
157
|
+
} else if(message.type === 'ping') {
|
|
158
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
156
159
|
}
|
|
157
160
|
};
|
|
158
161
|
}
|
|
159
162
|
|
|
163
|
+
async sayPong(appId: string, guid: string, originator: string, details = false) {
|
|
164
|
+
let profile: QuorumProfile;
|
|
165
|
+
if (details) {
|
|
166
|
+
const params = {
|
|
167
|
+
appId: this.appId,
|
|
168
|
+
topic: this.topic,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
profile = {
|
|
172
|
+
engine_id: this.guid,
|
|
173
|
+
namespace: this.namespace,
|
|
174
|
+
app_id: this.appId,
|
|
175
|
+
worker_topic: this.topic,
|
|
176
|
+
stream: this.stream.mintKey(KeyType.STREAMS, params),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
this.store.publish(
|
|
180
|
+
KeyType.QUORUM,
|
|
181
|
+
{
|
|
182
|
+
type: 'pong',
|
|
183
|
+
guid, originator,
|
|
184
|
+
profile,
|
|
185
|
+
},
|
|
186
|
+
appId,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
160
190
|
async throttle(delayInMillis: number) {
|
|
161
191
|
this.router.setThrottle(delayInMillis);
|
|
162
192
|
}
|
package/types/quorum.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { JobOutput } from "./job";
|
|
2
2
|
|
|
3
|
-
//used for coordination like version activation
|
|
3
|
+
//used for coordination (like version activation)
|
|
4
|
+
|
|
5
|
+
export interface QuorumProfile {
|
|
6
|
+
namespace: string;
|
|
7
|
+
app_id: string;
|
|
8
|
+
engine_id: string;
|
|
9
|
+
worker_topic?: string;
|
|
10
|
+
stream?: string;
|
|
11
|
+
stream_depth?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
export interface PingMessage {
|
|
5
15
|
type: 'ping';
|
|
6
16
|
originator: string; //guid
|
|
17
|
+
details?: boolean; //if true, all endpoints will include their profile
|
|
7
18
|
}
|
|
8
19
|
|
|
9
20
|
export interface WorkMessage {
|
|
@@ -16,11 +27,11 @@ export interface CronMessage {
|
|
|
16
27
|
originator: string; //guid
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
//used for coordination like version activation
|
|
20
30
|
export interface PongMessage {
|
|
21
31
|
type: 'pong';
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
guid: string; //call initiator
|
|
33
|
+
originator: string; //clone of originator guid passed in ping
|
|
34
|
+
profile?: QuorumProfile; //contains details about the engine/worker
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
export interface ActivateMessage {
|
package/types/redisclient.ts
CHANGED
|
@@ -6,6 +6,7 @@ interface RedisMultiType {
|
|
|
6
6
|
XADD(key: string, id: string, fields: any): this;
|
|
7
7
|
XACK(key: string, group: string, id: string): this;
|
|
8
8
|
XDEL(key: string, id: string): this;
|
|
9
|
+
XLEN(key: string): this;
|
|
9
10
|
HDEL(key: string, itemId: string): this;
|
|
10
11
|
HGET(key: string, itemId: string): this;
|
|
11
12
|
HGETALL(key: string): this;
|