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,25 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { SrvRecord } from "dns";
|
|
3
|
+
export declare type NodeKey = string;
|
|
4
|
+
export declare type NodeRole = "master" | "slave" | "all";
|
|
5
|
+
export interface RedisOptions {
|
|
6
|
+
port: number;
|
|
7
|
+
host: string;
|
|
8
|
+
username?: string | undefined;
|
|
9
|
+
password?: string | undefined;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
export interface SrvRecordsGroup {
|
|
13
|
+
totalWeight: number;
|
|
14
|
+
records: SrvRecord[];
|
|
15
|
+
}
|
|
16
|
+
export interface GroupedSrvRecords {
|
|
17
|
+
[key: number]: SrvRecordsGroup;
|
|
18
|
+
}
|
|
19
|
+
export declare function getNodeKey(node: RedisOptions): NodeKey;
|
|
20
|
+
export declare function nodeKeyToRedisOptions(nodeKey: NodeKey): RedisOptions;
|
|
21
|
+
export declare function normalizeNodeOptions(nodes: Array<string | number | object>): RedisOptions[];
|
|
22
|
+
export declare function getUniqueHostnamesFromOptions(nodes: RedisOptions[]): string[];
|
|
23
|
+
export declare function groupSrvRecords(records: SrvRecord[]): GroupedSrvRecords;
|
|
24
|
+
export declare function weightSrvRecords(recordsGroup: SrvRecordsGroup): SrvRecord;
|
|
25
|
+
export declare function getConnectionName(component: any, nodeConnectionName: any): string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getConnectionName = exports.weightSrvRecords = exports.groupSrvRecords = exports.getUniqueHostnamesFromOptions = exports.normalizeNodeOptions = exports.nodeKeyToRedisOptions = exports.getNodeKey = void 0;
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const net_1 = require("net");
|
|
6
|
+
function getNodeKey(node) {
|
|
7
|
+
node.port = node.port || 6379;
|
|
8
|
+
node.host = node.host || "127.0.0.1";
|
|
9
|
+
return node.host + ":" + node.port;
|
|
10
|
+
}
|
|
11
|
+
exports.getNodeKey = getNodeKey;
|
|
12
|
+
function nodeKeyToRedisOptions(nodeKey) {
|
|
13
|
+
const portIndex = nodeKey.lastIndexOf(":");
|
|
14
|
+
if (portIndex === -1) {
|
|
15
|
+
throw new Error(`Invalid node key ${nodeKey}`);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
host: nodeKey.slice(0, portIndex),
|
|
19
|
+
port: Number(nodeKey.slice(portIndex + 1)),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
exports.nodeKeyToRedisOptions = nodeKeyToRedisOptions;
|
|
23
|
+
function normalizeNodeOptions(nodes) {
|
|
24
|
+
return nodes.map((node) => {
|
|
25
|
+
const options = {};
|
|
26
|
+
if (typeof node === "object") {
|
|
27
|
+
Object.assign(options, node);
|
|
28
|
+
}
|
|
29
|
+
else if (typeof node === "string") {
|
|
30
|
+
Object.assign(options, (0, utils_1.parseURL)(node));
|
|
31
|
+
}
|
|
32
|
+
else if (typeof node === "number") {
|
|
33
|
+
options.port = node;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
throw new Error("Invalid argument " + node);
|
|
37
|
+
}
|
|
38
|
+
if (typeof options.port === "string") {
|
|
39
|
+
options.port = parseInt(options.port, 10);
|
|
40
|
+
}
|
|
41
|
+
// Cluster mode only support db 0
|
|
42
|
+
delete options.db;
|
|
43
|
+
if (!options.port) {
|
|
44
|
+
options.port = 6379;
|
|
45
|
+
}
|
|
46
|
+
if (!options.host) {
|
|
47
|
+
options.host = "127.0.0.1";
|
|
48
|
+
}
|
|
49
|
+
return (0, utils_1.resolveTLSProfile)(options);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
exports.normalizeNodeOptions = normalizeNodeOptions;
|
|
53
|
+
function getUniqueHostnamesFromOptions(nodes) {
|
|
54
|
+
const uniqueHostsMap = {};
|
|
55
|
+
nodes.forEach((node) => {
|
|
56
|
+
uniqueHostsMap[node.host] = true;
|
|
57
|
+
});
|
|
58
|
+
return Object.keys(uniqueHostsMap).filter((host) => !(0, net_1.isIP)(host));
|
|
59
|
+
}
|
|
60
|
+
exports.getUniqueHostnamesFromOptions = getUniqueHostnamesFromOptions;
|
|
61
|
+
function groupSrvRecords(records) {
|
|
62
|
+
const recordsByPriority = {};
|
|
63
|
+
for (const record of records) {
|
|
64
|
+
if (!recordsByPriority.hasOwnProperty(record.priority)) {
|
|
65
|
+
recordsByPriority[record.priority] = {
|
|
66
|
+
totalWeight: record.weight,
|
|
67
|
+
records: [record],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
recordsByPriority[record.priority].totalWeight += record.weight;
|
|
72
|
+
recordsByPriority[record.priority].records.push(record);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return recordsByPriority;
|
|
76
|
+
}
|
|
77
|
+
exports.groupSrvRecords = groupSrvRecords;
|
|
78
|
+
function weightSrvRecords(recordsGroup) {
|
|
79
|
+
if (recordsGroup.records.length === 1) {
|
|
80
|
+
recordsGroup.totalWeight = 0;
|
|
81
|
+
return recordsGroup.records.shift();
|
|
82
|
+
}
|
|
83
|
+
// + `recordsGroup.records.length` to support `weight` 0
|
|
84
|
+
const random = Math.floor(Math.random() * (recordsGroup.totalWeight + recordsGroup.records.length));
|
|
85
|
+
let total = 0;
|
|
86
|
+
for (const [i, record] of recordsGroup.records.entries()) {
|
|
87
|
+
total += 1 + record.weight;
|
|
88
|
+
if (total > random) {
|
|
89
|
+
recordsGroup.totalWeight -= record.weight;
|
|
90
|
+
recordsGroup.records.splice(i, 1);
|
|
91
|
+
return record;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.weightSrvRecords = weightSrvRecords;
|
|
96
|
+
function getConnectionName(component, nodeConnectionName) {
|
|
97
|
+
const prefix = `ioredis-om-cluster(${component})`;
|
|
98
|
+
return nodeConnectionName ? `${prefix}:${nodeConnectionName}` : prefix;
|
|
99
|
+
}
|
|
100
|
+
exports.getConnectionName = getConnectionName;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NetStream } from "../types";
|
|
2
|
+
export declare type ErrorEmitter = (type: string, err: Error) => void;
|
|
3
|
+
export default abstract class AbstractConnector {
|
|
4
|
+
firstError?: Error;
|
|
5
|
+
protected connecting: boolean;
|
|
6
|
+
protected stream: NetStream;
|
|
7
|
+
private disconnectTimeout;
|
|
8
|
+
constructor(disconnectTimeout: number);
|
|
9
|
+
check(info: any): boolean;
|
|
10
|
+
disconnect(): void;
|
|
11
|
+
abstract connect(_: ErrorEmitter): Promise<NetStream>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const debug = (0, utils_1.Debug)("AbstractConnector");
|
|
5
|
+
class AbstractConnector {
|
|
6
|
+
constructor(disconnectTimeout) {
|
|
7
|
+
this.connecting = false;
|
|
8
|
+
this.disconnectTimeout = disconnectTimeout;
|
|
9
|
+
}
|
|
10
|
+
check(info) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
disconnect() {
|
|
14
|
+
this.connecting = false;
|
|
15
|
+
if (this.stream) {
|
|
16
|
+
const stream = this.stream; // Make sure callbacks refer to the same instance
|
|
17
|
+
const timeout = setTimeout(() => {
|
|
18
|
+
debug("stream %s:%s still open, destroying it", stream.remoteAddress, stream.remotePort);
|
|
19
|
+
stream.destroy();
|
|
20
|
+
}, this.disconnectTimeout);
|
|
21
|
+
stream.on("close", () => clearTimeout(timeout));
|
|
22
|
+
stream.end();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.default = AbstractConnector;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SentinelConnector from "./index";
|
|
2
|
+
import { Sentinel } from "./types";
|
|
3
|
+
export declare class FailoverDetector {
|
|
4
|
+
private connector;
|
|
5
|
+
private sentinels;
|
|
6
|
+
private isDisconnected;
|
|
7
|
+
constructor(connector: SentinelConnector, sentinels: Sentinel[]);
|
|
8
|
+
cleanup(): void;
|
|
9
|
+
subscribe(): Promise<void>;
|
|
10
|
+
private disconnect;
|
|
11
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FailoverDetector = void 0;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const debug = (0, utils_1.Debug)("FailoverDetector");
|
|
6
|
+
const CHANNEL_NAME = "+switch-master";
|
|
7
|
+
class FailoverDetector {
|
|
8
|
+
// sentinels can't be used for regular commands after this
|
|
9
|
+
constructor(connector, sentinels) {
|
|
10
|
+
this.isDisconnected = false;
|
|
11
|
+
this.connector = connector;
|
|
12
|
+
this.sentinels = sentinels;
|
|
13
|
+
}
|
|
14
|
+
cleanup() {
|
|
15
|
+
this.isDisconnected = true;
|
|
16
|
+
for (const sentinel of this.sentinels) {
|
|
17
|
+
sentinel.client.disconnect();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async subscribe() {
|
|
21
|
+
debug("Starting FailoverDetector");
|
|
22
|
+
const promises = [];
|
|
23
|
+
for (const sentinel of this.sentinels) {
|
|
24
|
+
const promise = sentinel.client.subscribe(CHANNEL_NAME).catch((err) => {
|
|
25
|
+
debug("Failed to subscribe to failover messages on sentinel %s:%s (%s)", sentinel.address.host || "127.0.0.1", sentinel.address.port || 26739, err.message);
|
|
26
|
+
});
|
|
27
|
+
promises.push(promise);
|
|
28
|
+
sentinel.client.on("message", (channel) => {
|
|
29
|
+
if (!this.isDisconnected && channel === CHANNEL_NAME) {
|
|
30
|
+
this.disconnect();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
await Promise.all(promises);
|
|
35
|
+
}
|
|
36
|
+
disconnect() {
|
|
37
|
+
// Avoid disconnecting more than once per failover.
|
|
38
|
+
// A new FailoverDetector will be created after reconnecting.
|
|
39
|
+
this.isDisconnected = true;
|
|
40
|
+
debug("Failover detected, disconnecting");
|
|
41
|
+
// Will call this.cleanup()
|
|
42
|
+
this.connector.disconnect();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.FailoverDetector = FailoverDetector;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SentinelAddress } from "./types";
|
|
2
|
+
export default class SentinelIterator implements Iterator<Partial<SentinelAddress>> {
|
|
3
|
+
private cursor;
|
|
4
|
+
private sentinels;
|
|
5
|
+
constructor(sentinels: Array<Partial<SentinelAddress>>);
|
|
6
|
+
next(): {
|
|
7
|
+
done: boolean;
|
|
8
|
+
value: Partial<SentinelAddress>;
|
|
9
|
+
};
|
|
10
|
+
reset(moveCurrentEndpointToFirst: boolean): void;
|
|
11
|
+
add(sentinel: SentinelAddress): boolean;
|
|
12
|
+
toString(): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function isSentinelEql(a, b) {
|
|
4
|
+
return ((a.host || "127.0.0.1") === (b.host || "127.0.0.1") &&
|
|
5
|
+
(a.port || 26379) === (b.port || 26379));
|
|
6
|
+
}
|
|
7
|
+
class SentinelIterator {
|
|
8
|
+
constructor(sentinels) {
|
|
9
|
+
this.cursor = 0;
|
|
10
|
+
this.sentinels = sentinels.slice(0);
|
|
11
|
+
}
|
|
12
|
+
next() {
|
|
13
|
+
const done = this.cursor >= this.sentinels.length;
|
|
14
|
+
return { done, value: done ? undefined : this.sentinels[this.cursor++] };
|
|
15
|
+
}
|
|
16
|
+
reset(moveCurrentEndpointToFirst) {
|
|
17
|
+
if (moveCurrentEndpointToFirst &&
|
|
18
|
+
this.sentinels.length > 1 &&
|
|
19
|
+
this.cursor !== 1) {
|
|
20
|
+
this.sentinels.unshift(...this.sentinels.splice(this.cursor - 1));
|
|
21
|
+
}
|
|
22
|
+
this.cursor = 0;
|
|
23
|
+
}
|
|
24
|
+
add(sentinel) {
|
|
25
|
+
for (let i = 0; i < this.sentinels.length; i++) {
|
|
26
|
+
if (isSentinelEql(sentinel, this.sentinels[i])) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
this.sentinels.push(sentinel);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
toString() {
|
|
34
|
+
return `${JSON.stringify(this.sentinels)} @${this.cursor}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = SentinelIterator;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { NatMap } from "../../cluster/ClusterOptions";
|
|
4
|
+
import { ConnectionOptions } from "tls";
|
|
5
|
+
import SentinelIterator from "./SentinelIterator";
|
|
6
|
+
import { SentinelAddress } from "./types";
|
|
7
|
+
import AbstractConnector, { ErrorEmitter } from "../AbstractConnector";
|
|
8
|
+
import { NetStream } from "../../types";
|
|
9
|
+
interface AddressFromResponse {
|
|
10
|
+
port: string;
|
|
11
|
+
ip: string;
|
|
12
|
+
flags?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
declare type PreferredSlaves = ((slaves: AddressFromResponse[]) => AddressFromResponse | null) | Array<{
|
|
15
|
+
port: string;
|
|
16
|
+
ip: string;
|
|
17
|
+
prio?: number | undefined;
|
|
18
|
+
}> | {
|
|
19
|
+
port: string;
|
|
20
|
+
ip: string;
|
|
21
|
+
prio?: number | undefined;
|
|
22
|
+
};
|
|
23
|
+
export { SentinelAddress, SentinelIterator };
|
|
24
|
+
export interface SentinelConnectionOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Master group name of the Sentinel
|
|
27
|
+
*/
|
|
28
|
+
name?: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* @default "master"
|
|
31
|
+
*/
|
|
32
|
+
role?: "master" | "slave" | undefined;
|
|
33
|
+
tls?: ConnectionOptions | undefined;
|
|
34
|
+
sentinelUsername?: string | undefined;
|
|
35
|
+
sentinelPassword?: string | undefined;
|
|
36
|
+
sentinels?: Array<Partial<SentinelAddress>> | undefined;
|
|
37
|
+
sentinelRetryStrategy?: ((retryAttempts: number) => number | void | null) | undefined;
|
|
38
|
+
sentinelReconnectStrategy?: ((retryAttempts: number) => number | void | null) | undefined;
|
|
39
|
+
preferredSlaves?: PreferredSlaves | undefined;
|
|
40
|
+
connectTimeout?: number | undefined;
|
|
41
|
+
disconnectTimeout?: number | undefined;
|
|
42
|
+
sentinelCommandTimeout?: number | undefined;
|
|
43
|
+
enableTLSForSentinelMode?: boolean | undefined;
|
|
44
|
+
sentinelTLS?: ConnectionOptions | undefined;
|
|
45
|
+
natMap?: NatMap | undefined;
|
|
46
|
+
updateSentinels?: boolean | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* @default 10
|
|
49
|
+
*/
|
|
50
|
+
sentinelMaxConnections?: number | undefined;
|
|
51
|
+
failoverDetector?: boolean | undefined;
|
|
52
|
+
}
|
|
53
|
+
export default class SentinelConnector extends AbstractConnector {
|
|
54
|
+
protected options: SentinelConnectionOptions;
|
|
55
|
+
emitter: EventEmitter | null;
|
|
56
|
+
protected sentinelIterator: SentinelIterator;
|
|
57
|
+
private retryAttempts;
|
|
58
|
+
private failoverDetector;
|
|
59
|
+
constructor(options: SentinelConnectionOptions);
|
|
60
|
+
check(info: {
|
|
61
|
+
role?: string;
|
|
62
|
+
}): boolean;
|
|
63
|
+
disconnect(): void;
|
|
64
|
+
connect(eventEmitter: ErrorEmitter): Promise<NetStream>;
|
|
65
|
+
private updateSentinels;
|
|
66
|
+
private resolveMaster;
|
|
67
|
+
private resolveSlave;
|
|
68
|
+
private sentinelNatResolve;
|
|
69
|
+
private connectToSentinel;
|
|
70
|
+
private resolve;
|
|
71
|
+
private initFailoverDetector;
|
|
72
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SentinelIterator = void 0;
|
|
4
|
+
const net_1 = require("net");
|
|
5
|
+
const utils_1 = require("../../utils");
|
|
6
|
+
const tls_1 = require("tls");
|
|
7
|
+
const SentinelIterator_1 = require("./SentinelIterator");
|
|
8
|
+
exports.SentinelIterator = SentinelIterator_1.default;
|
|
9
|
+
const AbstractConnector_1 = require("../AbstractConnector");
|
|
10
|
+
const Redis_1 = require("../../Redis");
|
|
11
|
+
const FailoverDetector_1 = require("./FailoverDetector");
|
|
12
|
+
const debug = (0, utils_1.Debug)("SentinelConnector");
|
|
13
|
+
class SentinelConnector extends AbstractConnector_1.default {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
super(options.disconnectTimeout);
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.emitter = null;
|
|
18
|
+
this.failoverDetector = null;
|
|
19
|
+
if (!this.options.sentinels.length) {
|
|
20
|
+
throw new Error("Requires at least one sentinel to connect to.");
|
|
21
|
+
}
|
|
22
|
+
if (!this.options.name) {
|
|
23
|
+
throw new Error("Requires the name of master.");
|
|
24
|
+
}
|
|
25
|
+
this.sentinelIterator = new SentinelIterator_1.default(this.options.sentinels);
|
|
26
|
+
}
|
|
27
|
+
check(info) {
|
|
28
|
+
const roleMatches = !info.role || this.options.role === info.role;
|
|
29
|
+
if (!roleMatches) {
|
|
30
|
+
debug("role invalid, expected %s, but got %s", this.options.role, info.role);
|
|
31
|
+
// Start from the next item.
|
|
32
|
+
// Note that `reset` will move the cursor to the previous element,
|
|
33
|
+
// so we advance two steps here.
|
|
34
|
+
this.sentinelIterator.next();
|
|
35
|
+
this.sentinelIterator.next();
|
|
36
|
+
this.sentinelIterator.reset(true);
|
|
37
|
+
}
|
|
38
|
+
return roleMatches;
|
|
39
|
+
}
|
|
40
|
+
disconnect() {
|
|
41
|
+
super.disconnect();
|
|
42
|
+
if (this.failoverDetector) {
|
|
43
|
+
this.failoverDetector.cleanup();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
connect(eventEmitter) {
|
|
47
|
+
this.connecting = true;
|
|
48
|
+
this.retryAttempts = 0;
|
|
49
|
+
let lastError;
|
|
50
|
+
const connectToNext = async () => {
|
|
51
|
+
const endpoint = this.sentinelIterator.next();
|
|
52
|
+
if (endpoint.done) {
|
|
53
|
+
this.sentinelIterator.reset(false);
|
|
54
|
+
const retryDelay = typeof this.options.sentinelRetryStrategy === "function"
|
|
55
|
+
? this.options.sentinelRetryStrategy(++this.retryAttempts)
|
|
56
|
+
: null;
|
|
57
|
+
let errorMsg = typeof retryDelay !== "number"
|
|
58
|
+
? "All sentinels are unreachable and retry is disabled."
|
|
59
|
+
: `All sentinels are unreachable. Retrying from scratch after ${retryDelay}ms.`;
|
|
60
|
+
if (lastError) {
|
|
61
|
+
errorMsg += ` Last error: ${lastError.message}`;
|
|
62
|
+
}
|
|
63
|
+
debug(errorMsg);
|
|
64
|
+
const error = new Error(errorMsg);
|
|
65
|
+
if (typeof retryDelay === "number") {
|
|
66
|
+
eventEmitter("error", error);
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
68
|
+
return connectToNext();
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let resolved = null;
|
|
75
|
+
let err = null;
|
|
76
|
+
try {
|
|
77
|
+
resolved = await this.resolve(endpoint.value);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
err = error;
|
|
81
|
+
}
|
|
82
|
+
if (!this.connecting) {
|
|
83
|
+
throw new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG);
|
|
84
|
+
}
|
|
85
|
+
const endpointAddress = endpoint.value.host + ":" + endpoint.value.port;
|
|
86
|
+
if (resolved) {
|
|
87
|
+
debug("resolved: %s:%s from sentinel %s", resolved.host, resolved.port, endpointAddress);
|
|
88
|
+
if (this.options.enableTLSForSentinelMode && this.options.tls) {
|
|
89
|
+
Object.assign(resolved, this.options.tls);
|
|
90
|
+
this.stream = (0, tls_1.connect)(resolved);
|
|
91
|
+
this.stream.once("secureConnect", this.initFailoverDetector.bind(this));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
this.stream = (0, net_1.createConnection)(resolved);
|
|
95
|
+
this.stream.once("connect", this.initFailoverDetector.bind(this));
|
|
96
|
+
}
|
|
97
|
+
this.stream.once("error", (err) => {
|
|
98
|
+
this.firstError = err;
|
|
99
|
+
});
|
|
100
|
+
return this.stream;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const errorMsg = err
|
|
104
|
+
? "failed to connect to sentinel " +
|
|
105
|
+
endpointAddress +
|
|
106
|
+
" because " +
|
|
107
|
+
err.message
|
|
108
|
+
: "connected to sentinel " +
|
|
109
|
+
endpointAddress +
|
|
110
|
+
" successfully, but got an invalid reply: " +
|
|
111
|
+
resolved;
|
|
112
|
+
debug(errorMsg);
|
|
113
|
+
eventEmitter("sentinelError", new Error(errorMsg));
|
|
114
|
+
if (err) {
|
|
115
|
+
lastError = err;
|
|
116
|
+
}
|
|
117
|
+
return connectToNext();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return connectToNext();
|
|
121
|
+
}
|
|
122
|
+
async updateSentinels(client) {
|
|
123
|
+
if (!this.options.updateSentinels) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const result = await client.sentinel("sentinels", this.options.name);
|
|
127
|
+
if (!Array.isArray(result)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
result
|
|
131
|
+
.map(utils_1.packObject)
|
|
132
|
+
.forEach((sentinel) => {
|
|
133
|
+
const flags = sentinel.flags ? sentinel.flags.split(",") : [];
|
|
134
|
+
if (flags.indexOf("disconnected") === -1 &&
|
|
135
|
+
sentinel.ip &&
|
|
136
|
+
sentinel.port) {
|
|
137
|
+
const endpoint = this.sentinelNatResolve(addressResponseToAddress(sentinel));
|
|
138
|
+
if (this.sentinelIterator.add(endpoint)) {
|
|
139
|
+
debug("adding sentinel %s:%s", endpoint.host, endpoint.port);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
debug("Updated internal sentinels: %s", this.sentinelIterator);
|
|
144
|
+
}
|
|
145
|
+
async resolveMaster(client) {
|
|
146
|
+
const result = await client.sentinel("get-master-addr-by-name", this.options.name);
|
|
147
|
+
await this.updateSentinels(client);
|
|
148
|
+
return this.sentinelNatResolve(Array.isArray(result)
|
|
149
|
+
? { host: result[0], port: Number(result[1]) }
|
|
150
|
+
: null);
|
|
151
|
+
}
|
|
152
|
+
async resolveSlave(client) {
|
|
153
|
+
const result = await client.sentinel("slaves", this.options.name);
|
|
154
|
+
if (!Array.isArray(result)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const availableSlaves = result
|
|
158
|
+
.map(utils_1.packObject)
|
|
159
|
+
.filter((slave) => slave.flags && !slave.flags.match(/(disconnected|s_down|o_down)/));
|
|
160
|
+
return this.sentinelNatResolve(selectPreferredSentinel(availableSlaves, this.options.preferredSlaves));
|
|
161
|
+
}
|
|
162
|
+
sentinelNatResolve(item) {
|
|
163
|
+
if (!item || !this.options.natMap)
|
|
164
|
+
return item;
|
|
165
|
+
const key = `${item.host}:${item.port}`;
|
|
166
|
+
let result = item;
|
|
167
|
+
if (typeof this.options.natMap === "function") {
|
|
168
|
+
result = this.options.natMap(key) || item;
|
|
169
|
+
}
|
|
170
|
+
else if (typeof this.options.natMap === "object") {
|
|
171
|
+
result = this.options.natMap[key] || item;
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
connectToSentinel(endpoint, options) {
|
|
176
|
+
const redis = new Redis_1.default({
|
|
177
|
+
port: endpoint.port || 26379,
|
|
178
|
+
host: endpoint.host,
|
|
179
|
+
username: this.options.sentinelUsername || null,
|
|
180
|
+
password: this.options.sentinelPassword || null,
|
|
181
|
+
family: endpoint.family ||
|
|
182
|
+
// @ts-expect-error
|
|
183
|
+
("path" in this.options && this.options.path
|
|
184
|
+
? undefined
|
|
185
|
+
: // @ts-expect-error
|
|
186
|
+
this.options.family),
|
|
187
|
+
tls: this.options.sentinelTLS,
|
|
188
|
+
retryStrategy: null,
|
|
189
|
+
enableReadyCheck: false,
|
|
190
|
+
connectTimeout: this.options.connectTimeout,
|
|
191
|
+
commandTimeout: this.options.sentinelCommandTimeout,
|
|
192
|
+
...options,
|
|
193
|
+
});
|
|
194
|
+
// @ts-expect-error
|
|
195
|
+
return redis;
|
|
196
|
+
}
|
|
197
|
+
async resolve(endpoint) {
|
|
198
|
+
const client = this.connectToSentinel(endpoint);
|
|
199
|
+
// ignore the errors since resolve* methods will handle them
|
|
200
|
+
client.on("error", noop);
|
|
201
|
+
try {
|
|
202
|
+
if (this.options.role === "slave") {
|
|
203
|
+
return await this.resolveSlave(client);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
return await this.resolveMaster(client);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
client.disconnect();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async initFailoverDetector() {
|
|
214
|
+
var _a;
|
|
215
|
+
if (!this.options.failoverDetector) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Move the current sentinel to the first position
|
|
219
|
+
this.sentinelIterator.reset(true);
|
|
220
|
+
const sentinels = [];
|
|
221
|
+
// In case of a large amount of sentinels, limit the number of concurrent connections
|
|
222
|
+
while (sentinels.length < this.options.sentinelMaxConnections) {
|
|
223
|
+
const { done, value } = this.sentinelIterator.next();
|
|
224
|
+
if (done) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
const client = this.connectToSentinel(value, {
|
|
228
|
+
lazyConnect: true,
|
|
229
|
+
retryStrategy: this.options.sentinelReconnectStrategy,
|
|
230
|
+
});
|
|
231
|
+
client.on("reconnecting", () => {
|
|
232
|
+
var _a;
|
|
233
|
+
// Tests listen to this event
|
|
234
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit("sentinelReconnecting");
|
|
235
|
+
});
|
|
236
|
+
sentinels.push({ address: value, client });
|
|
237
|
+
}
|
|
238
|
+
this.sentinelIterator.reset(false);
|
|
239
|
+
if (this.failoverDetector) {
|
|
240
|
+
// Clean up previous detector
|
|
241
|
+
this.failoverDetector.cleanup();
|
|
242
|
+
}
|
|
243
|
+
this.failoverDetector = new FailoverDetector_1.FailoverDetector(this, sentinels);
|
|
244
|
+
await this.failoverDetector.subscribe();
|
|
245
|
+
// Tests listen to this event
|
|
246
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit("failoverSubscribed");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
exports.default = SentinelConnector;
|
|
250
|
+
function selectPreferredSentinel(availableSlaves, preferredSlaves) {
|
|
251
|
+
if (availableSlaves.length === 0) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
let selectedSlave;
|
|
255
|
+
if (typeof preferredSlaves === "function") {
|
|
256
|
+
selectedSlave = preferredSlaves(availableSlaves);
|
|
257
|
+
}
|
|
258
|
+
else if (preferredSlaves !== null && typeof preferredSlaves === "object") {
|
|
259
|
+
const preferredSlavesArray = Array.isArray(preferredSlaves)
|
|
260
|
+
? preferredSlaves
|
|
261
|
+
: [preferredSlaves];
|
|
262
|
+
// sort by priority
|
|
263
|
+
preferredSlavesArray.sort((a, b) => {
|
|
264
|
+
// default the priority to 1
|
|
265
|
+
if (!a.prio) {
|
|
266
|
+
a.prio = 1;
|
|
267
|
+
}
|
|
268
|
+
if (!b.prio) {
|
|
269
|
+
b.prio = 1;
|
|
270
|
+
}
|
|
271
|
+
// lowest priority first
|
|
272
|
+
if (a.prio < b.prio) {
|
|
273
|
+
return -1;
|
|
274
|
+
}
|
|
275
|
+
if (a.prio > b.prio) {
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
});
|
|
280
|
+
// loop over preferred slaves and return the first match
|
|
281
|
+
for (let p = 0; p < preferredSlavesArray.length; p++) {
|
|
282
|
+
for (let a = 0; a < availableSlaves.length; a++) {
|
|
283
|
+
const slave = availableSlaves[a];
|
|
284
|
+
if (slave.ip === preferredSlavesArray[p].ip) {
|
|
285
|
+
if (slave.port === preferredSlavesArray[p].port) {
|
|
286
|
+
selectedSlave = slave;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (selectedSlave) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// if none of the preferred slaves are available, a random available slave is returned
|
|
297
|
+
if (!selectedSlave) {
|
|
298
|
+
selectedSlave = (0, utils_1.sample)(availableSlaves);
|
|
299
|
+
}
|
|
300
|
+
return addressResponseToAddress(selectedSlave);
|
|
301
|
+
}
|
|
302
|
+
function addressResponseToAddress(input) {
|
|
303
|
+
return { host: input.ip, port: Number(input.port) };
|
|
304
|
+
}
|
|
305
|
+
function noop() { }
|