ioredis-om 5.10.2
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/LICENSE +21 -0
- package/README.md +1571 -0
- package/built/Command.d.ts +166 -0
- package/built/Command.js +450 -0
- package/built/DataHandler.d.ts +37 -0
- package/built/DataHandler.js +224 -0
- package/built/Pipeline.d.ts +31 -0
- package/built/Pipeline.js +342 -0
- package/built/Redis.d.ts +243 -0
- package/built/Redis.js +800 -0
- package/built/ScanStream.d.ts +23 -0
- package/built/ScanStream.js +51 -0
- package/built/Script.d.ts +11 -0
- package/built/Script.js +62 -0
- package/built/SubscriptionSet.d.ts +14 -0
- package/built/SubscriptionSet.js +41 -0
- package/built/autoPipelining.d.ts +8 -0
- package/built/autoPipelining.js +167 -0
- package/built/cluster/ClusterOptions.d.ts +172 -0
- package/built/cluster/ClusterOptions.js +22 -0
- package/built/cluster/ClusterSubscriber.d.ts +29 -0
- package/built/cluster/ClusterSubscriber.js +223 -0
- package/built/cluster/ClusterSubscriberGroup.d.ts +108 -0
- package/built/cluster/ClusterSubscriberGroup.js +373 -0
- package/built/cluster/ConnectionPool.d.ts +37 -0
- package/built/cluster/ConnectionPool.js +154 -0
- package/built/cluster/DelayQueue.d.ts +20 -0
- package/built/cluster/DelayQueue.js +53 -0
- package/built/cluster/ShardedSubscriber.d.ts +36 -0
- package/built/cluster/ShardedSubscriber.js +147 -0
- package/built/cluster/index.d.ts +163 -0
- package/built/cluster/index.js +937 -0
- package/built/cluster/util.d.ts +25 -0
- package/built/cluster/util.js +100 -0
- package/built/connectors/AbstractConnector.d.ts +12 -0
- package/built/connectors/AbstractConnector.js +26 -0
- package/built/connectors/ConnectorConstructor.d.ts +5 -0
- package/built/connectors/ConnectorConstructor.js +2 -0
- package/built/connectors/SentinelConnector/FailoverDetector.d.ts +11 -0
- package/built/connectors/SentinelConnector/FailoverDetector.js +45 -0
- package/built/connectors/SentinelConnector/SentinelIterator.d.ts +13 -0
- package/built/connectors/SentinelConnector/SentinelIterator.js +37 -0
- package/built/connectors/SentinelConnector/index.d.ts +72 -0
- package/built/connectors/SentinelConnector/index.js +305 -0
- package/built/connectors/SentinelConnector/types.d.ts +21 -0
- package/built/connectors/SentinelConnector/types.js +2 -0
- package/built/connectors/StandaloneConnector.d.ts +17 -0
- package/built/connectors/StandaloneConnector.js +69 -0
- package/built/connectors/index.d.ts +3 -0
- package/built/connectors/index.js +7 -0
- package/built/constants/TLSProfiles.d.ts +9 -0
- package/built/constants/TLSProfiles.js +149 -0
- package/built/errors/ClusterAllFailedError.d.ts +7 -0
- package/built/errors/ClusterAllFailedError.js +15 -0
- package/built/errors/MaxRetriesPerRequestError.d.ts +5 -0
- package/built/errors/MaxRetriesPerRequestError.js +14 -0
- package/built/errors/index.d.ts +2 -0
- package/built/errors/index.js +5 -0
- package/built/index.d.ts +44 -0
- package/built/index.js +62 -0
- package/built/redis/RedisOptions.d.ts +197 -0
- package/built/redis/RedisOptions.js +58 -0
- package/built/redis/event_handler.d.ts +4 -0
- package/built/redis/event_handler.js +315 -0
- package/built/tracing.d.ts +26 -0
- package/built/tracing.js +96 -0
- package/built/transaction.d.ts +13 -0
- package/built/transaction.js +100 -0
- package/built/types.d.ts +33 -0
- package/built/types.js +2 -0
- package/built/utils/Commander.d.ts +50 -0
- package/built/utils/Commander.js +117 -0
- package/built/utils/RedisCommander.d.ts +8950 -0
- package/built/utils/RedisCommander.js +7 -0
- package/built/utils/applyMixin.d.ts +3 -0
- package/built/utils/applyMixin.js +8 -0
- package/built/utils/argumentParsers.d.ts +14 -0
- package/built/utils/argumentParsers.js +74 -0
- package/built/utils/debug.d.ts +16 -0
- package/built/utils/debug.js +95 -0
- package/built/utils/index.d.ts +124 -0
- package/built/utils/index.js +332 -0
- package/built/utils/lodash.d.ts +4 -0
- package/built/utils/lodash.js +9 -0
- package/package.json +103 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const util_1 = require("./util");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const Redis_1 = require("../Redis");
|
|
6
|
+
const debug = (0, utils_1.Debug)("cluster:subscriber");
|
|
7
|
+
class ClusterSubscriber {
|
|
8
|
+
constructor(connectionPool, emitter, isSharded = false) {
|
|
9
|
+
this.connectionPool = connectionPool;
|
|
10
|
+
this.emitter = emitter;
|
|
11
|
+
this.isSharded = isSharded;
|
|
12
|
+
this.started = false;
|
|
13
|
+
//There is only one connection for the entire pool
|
|
14
|
+
this.subscriber = null;
|
|
15
|
+
//The slot range for which this subscriber is responsible
|
|
16
|
+
this.slotRange = [];
|
|
17
|
+
this.onSubscriberEnd = () => {
|
|
18
|
+
if (!this.started) {
|
|
19
|
+
debug("subscriber has disconnected, but ClusterSubscriber is not started, so not reconnecting.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// If the subscriber closes whilst it's still the active connection,
|
|
23
|
+
// we might as well try to connecting to a new node if possible to
|
|
24
|
+
// minimise the number of missed publishes.
|
|
25
|
+
debug("subscriber has disconnected, selecting a new one...");
|
|
26
|
+
this.selectSubscriber();
|
|
27
|
+
};
|
|
28
|
+
// If the current node we're using as the subscriber disappears
|
|
29
|
+
// from the node pool for some reason, we will select a new one
|
|
30
|
+
// to connect to.
|
|
31
|
+
// Note that this event is only triggered if the connection to
|
|
32
|
+
// the node has been used; cluster subscriptions are setup with
|
|
33
|
+
// lazyConnect = true. It's possible for the subscriber node to
|
|
34
|
+
// disappear without this method being called!
|
|
35
|
+
// See https://github.com/luin/ioredis/pull/1589
|
|
36
|
+
this.connectionPool.on("-node", (_, key) => {
|
|
37
|
+
if (!this.started || !this.subscriber) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if ((0, util_1.getNodeKey)(this.subscriber.options) === key) {
|
|
41
|
+
debug("subscriber has left, selecting a new one...");
|
|
42
|
+
this.selectSubscriber();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
this.connectionPool.on("+node", () => {
|
|
46
|
+
if (!this.started || this.subscriber) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
debug("a new node is discovered and there is no subscriber, selecting a new one...");
|
|
50
|
+
this.selectSubscriber();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
getInstance() {
|
|
54
|
+
return this.subscriber;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Associate this subscriber to a specific slot range.
|
|
58
|
+
*
|
|
59
|
+
* Returns the range or an empty array if the slot range couldn't be associated.
|
|
60
|
+
*
|
|
61
|
+
* BTW: This is more for debugging and testing purposes.
|
|
62
|
+
*
|
|
63
|
+
* @param range
|
|
64
|
+
*/
|
|
65
|
+
associateSlotRange(range) {
|
|
66
|
+
if (this.isSharded) {
|
|
67
|
+
this.slotRange = range;
|
|
68
|
+
}
|
|
69
|
+
return this.slotRange;
|
|
70
|
+
}
|
|
71
|
+
start() {
|
|
72
|
+
this.started = true;
|
|
73
|
+
this.selectSubscriber();
|
|
74
|
+
debug("started");
|
|
75
|
+
}
|
|
76
|
+
stop() {
|
|
77
|
+
this.started = false;
|
|
78
|
+
if (this.subscriber) {
|
|
79
|
+
this.subscriber.disconnect();
|
|
80
|
+
this.subscriber = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
isStarted() {
|
|
84
|
+
return this.started;
|
|
85
|
+
}
|
|
86
|
+
selectSubscriber() {
|
|
87
|
+
const lastActiveSubscriber = this.lastActiveSubscriber;
|
|
88
|
+
// Disconnect the previous subscriber even if there
|
|
89
|
+
// will not be a new one.
|
|
90
|
+
if (lastActiveSubscriber) {
|
|
91
|
+
lastActiveSubscriber.off("end", this.onSubscriberEnd);
|
|
92
|
+
lastActiveSubscriber.disconnect();
|
|
93
|
+
}
|
|
94
|
+
if (this.subscriber) {
|
|
95
|
+
this.subscriber.off("end", this.onSubscriberEnd);
|
|
96
|
+
this.subscriber.disconnect();
|
|
97
|
+
}
|
|
98
|
+
const sampleNode = (0, utils_1.sample)(this.connectionPool.getNodes());
|
|
99
|
+
if (!sampleNode) {
|
|
100
|
+
debug("selecting subscriber failed since there is no node discovered in the cluster yet");
|
|
101
|
+
this.subscriber = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const { options } = sampleNode;
|
|
105
|
+
debug("selected a subscriber %s:%s", options.host, options.port);
|
|
106
|
+
/*
|
|
107
|
+
* Create a specialized Redis connection for the subscription.
|
|
108
|
+
* Note that auto reconnection is enabled here.
|
|
109
|
+
*
|
|
110
|
+
* `enableReadyCheck` is also enabled because although subscription is allowed
|
|
111
|
+
* while redis is loading data from the disk, we can check if the password
|
|
112
|
+
* provided for the subscriber is correct, and if not, the current subscriber
|
|
113
|
+
* will be disconnected and a new subscriber will be selected.
|
|
114
|
+
*/
|
|
115
|
+
let connectionPrefix = "subscriber";
|
|
116
|
+
if (this.isSharded)
|
|
117
|
+
connectionPrefix = "ssubscriber";
|
|
118
|
+
this.subscriber = new Redis_1.default({
|
|
119
|
+
port: options.port,
|
|
120
|
+
host: options.host,
|
|
121
|
+
username: options.username,
|
|
122
|
+
password: options.password,
|
|
123
|
+
enableReadyCheck: true,
|
|
124
|
+
connectionName: (0, util_1.getConnectionName)(connectionPrefix, options.connectionName),
|
|
125
|
+
lazyConnect: true,
|
|
126
|
+
tls: options.tls,
|
|
127
|
+
// Don't try to reconnect the subscriber connection. If the connection fails
|
|
128
|
+
// we will get an end event (handled below), at which point we'll pick a new
|
|
129
|
+
// node from the pool and try to connect to that as the subscriber connection.
|
|
130
|
+
retryStrategy: null,
|
|
131
|
+
});
|
|
132
|
+
// Ignore the errors since they're handled in the connection pool.
|
|
133
|
+
this.subscriber.on("error", utils_1.noop);
|
|
134
|
+
this.subscriber.on("moved", () => {
|
|
135
|
+
this.emitter.emit("forceRefresh");
|
|
136
|
+
});
|
|
137
|
+
// The node we lost connection to may not come back up in a
|
|
138
|
+
// reasonable amount of time (e.g. a slave that's taken down
|
|
139
|
+
// for maintainence), we could potentially miss many published
|
|
140
|
+
// messages so we should reconnect as quickly as possible, to
|
|
141
|
+
// a different node if needed.
|
|
142
|
+
this.subscriber.once("end", this.onSubscriberEnd);
|
|
143
|
+
// Re-subscribe previous channels
|
|
144
|
+
const previousChannels = { subscribe: [], psubscribe: [], ssubscribe: [] };
|
|
145
|
+
if (lastActiveSubscriber) {
|
|
146
|
+
const condition = lastActiveSubscriber.condition || lastActiveSubscriber.prevCondition;
|
|
147
|
+
if (condition && condition.subscriber) {
|
|
148
|
+
previousChannels.subscribe = condition.subscriber.channels("subscribe");
|
|
149
|
+
previousChannels.psubscribe =
|
|
150
|
+
condition.subscriber.channels("psubscribe");
|
|
151
|
+
previousChannels.ssubscribe =
|
|
152
|
+
condition.subscriber.channels("ssubscribe");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (previousChannels.subscribe.length ||
|
|
156
|
+
previousChannels.psubscribe.length ||
|
|
157
|
+
previousChannels.ssubscribe.length) {
|
|
158
|
+
let pending = 0;
|
|
159
|
+
for (const type of ["subscribe", "psubscribe", "ssubscribe"]) {
|
|
160
|
+
const channels = previousChannels[type];
|
|
161
|
+
if (channels.length == 0) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
debug("%s %d channels", type, channels.length);
|
|
165
|
+
if (type === "ssubscribe") {
|
|
166
|
+
for (const channel of channels) {
|
|
167
|
+
pending += 1;
|
|
168
|
+
this.subscriber[type](channel)
|
|
169
|
+
.then(() => {
|
|
170
|
+
if (!--pending) {
|
|
171
|
+
this.lastActiveSubscriber = this.subscriber;
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.catch(() => {
|
|
175
|
+
// TODO: should probably disconnect the subscriber and try again.
|
|
176
|
+
debug("failed to ssubscribe to channel: %s", channel);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
pending += 1;
|
|
182
|
+
this.subscriber[type](channels)
|
|
183
|
+
.then(() => {
|
|
184
|
+
if (!--pending) {
|
|
185
|
+
this.lastActiveSubscriber = this.subscriber;
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
.catch(() => {
|
|
189
|
+
// TODO: should probably disconnect the subscriber and try again.
|
|
190
|
+
debug("failed to %s %d channels", type, channels.length);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.lastActiveSubscriber = this.subscriber;
|
|
197
|
+
}
|
|
198
|
+
for (const event of [
|
|
199
|
+
"message",
|
|
200
|
+
"messageBuffer",
|
|
201
|
+
]) {
|
|
202
|
+
this.subscriber.on(event, (arg1, arg2) => {
|
|
203
|
+
this.emitter.emit(event, arg1, arg2);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
for (const event of ["pmessage", "pmessageBuffer"]) {
|
|
207
|
+
this.subscriber.on(event, (arg1, arg2, arg3) => {
|
|
208
|
+
this.emitter.emit(event, arg1, arg2, arg3);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (this.isSharded == true) {
|
|
212
|
+
for (const event of [
|
|
213
|
+
"smessage",
|
|
214
|
+
"smessageBuffer",
|
|
215
|
+
]) {
|
|
216
|
+
this.subscriber.on(event, (arg1, arg2) => {
|
|
217
|
+
this.emitter.emit(event, arg1, arg2);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.default = ClusterSubscriber;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import * as EventEmitter from "events";
|
|
3
|
+
import ShardedSubscriber from "./ShardedSubscriber";
|
|
4
|
+
import { ClusterOptions } from "./ClusterOptions";
|
|
5
|
+
/**
|
|
6
|
+
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
|
|
7
|
+
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
|
|
8
|
+
* messages between shards. Sharded PubSub removes this limitation by making each shard
|
|
9
|
+
* responsible for its own messages.
|
|
10
|
+
*
|
|
11
|
+
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
|
|
12
|
+
* sharded PubSub support while keeping the public API backward compatible.
|
|
13
|
+
*/
|
|
14
|
+
export default class ClusterSubscriberGroup {
|
|
15
|
+
private readonly subscriberGroupEmitter;
|
|
16
|
+
private readonly options;
|
|
17
|
+
private shardedSubscribers;
|
|
18
|
+
private clusterSlots;
|
|
19
|
+
private subscriberToSlotsIndex;
|
|
20
|
+
private channels;
|
|
21
|
+
private failedAttemptsByNode;
|
|
22
|
+
private isResetting;
|
|
23
|
+
private pendingReset;
|
|
24
|
+
private static readonly MAX_RETRY_ATTEMPTS;
|
|
25
|
+
private static readonly MAX_BACKOFF_MS;
|
|
26
|
+
private static readonly BASE_BACKOFF_MS;
|
|
27
|
+
/**
|
|
28
|
+
* Register callbacks
|
|
29
|
+
*
|
|
30
|
+
* @param cluster
|
|
31
|
+
*/
|
|
32
|
+
constructor(subscriberGroupEmitter: EventEmitter, options: ClusterOptions);
|
|
33
|
+
/**
|
|
34
|
+
* Get the responsible subscriber.
|
|
35
|
+
*
|
|
36
|
+
* @param slot
|
|
37
|
+
*/
|
|
38
|
+
getResponsibleSubscriber(slot: number): ShardedSubscriber | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Adds a channel for which this subscriber group is responsible
|
|
41
|
+
*
|
|
42
|
+
* @param channels
|
|
43
|
+
*/
|
|
44
|
+
addChannels(channels: (string | Buffer)[]): number;
|
|
45
|
+
/**
|
|
46
|
+
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
|
|
47
|
+
* @param channels
|
|
48
|
+
*/
|
|
49
|
+
removeChannels(channels: (string | Buffer)[]): number;
|
|
50
|
+
/**
|
|
51
|
+
* Disconnect all subscribers and clear some of the internal state.
|
|
52
|
+
*/
|
|
53
|
+
stop(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Start all not yet started subscribers
|
|
56
|
+
*/
|
|
57
|
+
start(): Promise<any[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
|
|
60
|
+
*/
|
|
61
|
+
reset(clusterSlots: string[][], clusterNodes: any[]): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Refreshes the subscriber-related slot ranges
|
|
64
|
+
*
|
|
65
|
+
* Returns false if no refresh was needed
|
|
66
|
+
*
|
|
67
|
+
* @param targetSlots
|
|
68
|
+
*/
|
|
69
|
+
private _refreshSlots;
|
|
70
|
+
/**
|
|
71
|
+
* Resubscribes to the previous channels
|
|
72
|
+
*
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
private _resubscribe;
|
|
76
|
+
/**
|
|
77
|
+
* Deep equality of the cluster slots objects
|
|
78
|
+
*
|
|
79
|
+
* @param other
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
private _slotsAreEqual;
|
|
83
|
+
/**
|
|
84
|
+
* Checks if any subscribers are in an unhealthy state.
|
|
85
|
+
*
|
|
86
|
+
* A subscriber is considered unhealthy if:
|
|
87
|
+
* - It exists but is not started (failed/disconnected)
|
|
88
|
+
* - It's missing entirely for a node that should have one
|
|
89
|
+
*
|
|
90
|
+
* @returns true if any subscribers need to be recreated
|
|
91
|
+
*/
|
|
92
|
+
private hasUnhealthySubscribers;
|
|
93
|
+
/**
|
|
94
|
+
* Handles failed subscriber connections by emitting an event to refresh the slots cache
|
|
95
|
+
* after a backoff period.
|
|
96
|
+
*
|
|
97
|
+
* @param error
|
|
98
|
+
* @param nodeKey
|
|
99
|
+
*/
|
|
100
|
+
private handleSubscriberConnectFailed;
|
|
101
|
+
/**
|
|
102
|
+
* Handles successful subscriber connections by resetting the failed attempts counter.
|
|
103
|
+
*
|
|
104
|
+
* @param nodeKey
|
|
105
|
+
*/
|
|
106
|
+
private handleSubscriberConnectSucceeded;
|
|
107
|
+
private shouldStartSubscriber;
|
|
108
|
+
}
|