Review funkt
This commit is contained in:
227
node_modules/ioredis/built/cluster/ClusterOptions.d.ts
generated
vendored
Normal file
227
node_modules/ioredis/built/cluster/ClusterOptions.d.ts
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { SrvRecord } from "dns";
|
||||
import { RedisOptions, RetryStrategy } from "../redis/RedisOptions";
|
||||
import { ReplyMappingMode } from "../types";
|
||||
import { CommanderOptions } from "../utils/Commander";
|
||||
import { NodeRole } from "./util";
|
||||
import type { HimportFieldset } from "../himport/types";
|
||||
export type DNSResolveSrvFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, records?: SrvRecord[]) => void) => void;
|
||||
export type DNSLookupFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, address: string, family?: number) => void) => void;
|
||||
export type NatMapFunction = (key: string) => {
|
||||
host: string;
|
||||
port: number;
|
||||
} | null;
|
||||
export type NatMap = {
|
||||
[key: string]: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
} | NatMapFunction;
|
||||
export type ClusterNodeRetryStrategy = RetryStrategy;
|
||||
/**
|
||||
* Options for Cluster constructor
|
||||
*/
|
||||
export interface ClusterOptions extends CommanderOptions {
|
||||
/**
|
||||
* See "Quick Start" section.
|
||||
*
|
||||
* @default (times) => Math.min(100 + times * 2, 2000)
|
||||
*/
|
||||
clusterRetryStrategy?: ((times: number, reason?: Error) => number | void | null) | null | undefined;
|
||||
/**
|
||||
* See Redis class.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableOfflineQueue?: boolean | undefined;
|
||||
/**
|
||||
* When enabled, ioredis only emits "ready" event when `CLUSTER INFO`
|
||||
* command reporting the cluster is ready for handling commands.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableReadyCheck?: boolean | undefined;
|
||||
/**
|
||||
* Scale reads to the node with the specified role.
|
||||
*
|
||||
* @default "master"
|
||||
*/
|
||||
scaleReads?: NodeRole | Function | undefined;
|
||||
/**
|
||||
* When a MOVED or ASK error is received, client will redirect the
|
||||
* command to another node.
|
||||
* This option limits the max redirections allowed to send a command.
|
||||
*
|
||||
* @default 16
|
||||
*/
|
||||
maxRedirections?: number | undefined;
|
||||
/**
|
||||
* When an error is received when sending a command (e.g.
|
||||
* "Connection is closed." when the target Redis node is down), client will retry
|
||||
* if `retryDelayOnFailover` is valid delay time (in ms).
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
retryDelayOnFailover?: number | undefined;
|
||||
/**
|
||||
* When a CLUSTERDOWN error is received, client will retry
|
||||
* if `retryDelayOnClusterDown` is valid delay time (in ms).
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
retryDelayOnClusterDown?: number | undefined;
|
||||
/**
|
||||
* When a TRYAGAIN error is received, client will retry
|
||||
* if `retryDelayOnTryAgain` is valid delay time (in ms).
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
retryDelayOnTryAgain?: number | undefined;
|
||||
/**
|
||||
* By default, this value is 0, which means when a `MOVED` error is received,
|
||||
* the client will resend the command instantly to the node returned together with
|
||||
* the `MOVED` error. However, sometimes it takes time for a cluster to become
|
||||
* state stabilized after a failover, so adding a delay before resending can
|
||||
* prevent a ping pong effect.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
retryDelayOnMoved?: number | undefined;
|
||||
/**
|
||||
* The milliseconds before a timeout occurs while refreshing
|
||||
* slots from the cluster.
|
||||
*
|
||||
* @default 1000
|
||||
*/
|
||||
slotsRefreshTimeout?: number | undefined;
|
||||
/**
|
||||
* The milliseconds between every automatic slots refresh.
|
||||
*
|
||||
* @default 5000
|
||||
*/
|
||||
slotsRefreshInterval?: number | undefined;
|
||||
/**
|
||||
* Use sharded subscribers instead of a single subscriber.
|
||||
*
|
||||
* If sharded subscribers are used, then one additional subscriber connection per master node
|
||||
* is established. If you don't plan to use SPUBLISH/SSUBSCRIBE, then this should be disabled.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
shardedSubscribers?: boolean | undefined;
|
||||
/**
|
||||
* When a cluster node connection is closed, this function will be called
|
||||
* to determine the retry delay (in ms). Returning `null` or a non-number
|
||||
* disables reconnection for that node.
|
||||
*
|
||||
* By default this is `null`, meaning cluster nodes will NOT automatically
|
||||
* reconnect — the cluster relies on `MOVED` errors to refresh topology.
|
||||
* Set this to enable reconnection, e.g. for replica nodes that restart
|
||||
* without any slot changes.
|
||||
*
|
||||
* @example
|
||||
* clusterNodeRetryStrategy: (times) => Math.min(times * 100, 3000)
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
clusterNodeRetryStrategy?: ClusterNodeRetryStrategy;
|
||||
/**
|
||||
* Passed to the constructor of `Redis`
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
redisOptions?: Omit<RedisOptions, "port" | "host" | "path" | "sentinels" | "retryStrategy" | "enableOfflineQueue" | "readOnly" | "himportFieldsets"> | undefined;
|
||||
/**
|
||||
* By default, When a new Cluster instance is created,
|
||||
* it will connect to the Redis cluster automatically.
|
||||
* If you want to keep the instance disconnected until the first command is called,
|
||||
* set this option to `true`.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
lazyConnect?: boolean | undefined;
|
||||
/**
|
||||
* Discover nodes using SRV records
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
useSRVRecords?: boolean | undefined;
|
||||
/**
|
||||
* SRV records will be resolved via this function.
|
||||
*
|
||||
* You may provide a custom `resolveSrv` function when you want to customize
|
||||
* the cache behavior of the default function.
|
||||
*
|
||||
* @default require('dns').resolveSrv
|
||||
*/
|
||||
resolveSrv?: DNSResolveSrvFunction | undefined;
|
||||
/**
|
||||
* Hostnames will be resolved to IP addresses via this function.
|
||||
* This is needed when the addresses of startup nodes are hostnames instead
|
||||
* of IPs.
|
||||
*
|
||||
* You may provide a custom `lookup` function when you want to customize
|
||||
* the cache behavior of the default function.
|
||||
*
|
||||
* @default require('dns').lookup
|
||||
*/
|
||||
dnsLookup?: DNSLookupFunction | undefined;
|
||||
natMap?: NatMap | undefined;
|
||||
/**
|
||||
* See Redis class.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableAutoPipelining?: boolean | undefined;
|
||||
/**
|
||||
* See Redis class.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
autoPipeliningIgnoredCommands?: string[] | undefined;
|
||||
/**
|
||||
* Custom LUA commands
|
||||
*/
|
||||
scripts?: Record<string, {
|
||||
lua: string;
|
||||
numberOfKeys?: number;
|
||||
readOnly?: boolean;
|
||||
}> | undefined;
|
||||
/**
|
||||
* Managed-fieldset support is experimental and requires Redis 8.10 or newer.
|
||||
*
|
||||
* Long-lived HIMPORT fieldsets managed across all current and future master
|
||||
* connections for the lifetime of this Cluster client.
|
||||
* Configure this option at the top level, not under `redisOptions`.
|
||||
*
|
||||
* When a managed `HIMPORT SET` needs fieldset preparation or recovery,
|
||||
* later commands issued on this Cluster client may be sent before that SET
|
||||
* resumes. Await the SET before issuing commands that depend on its write.
|
||||
*
|
||||
* Explicit pipelines containing a managed `HIMPORT SET` wait for required
|
||||
* fieldset preparation on the selected master before the batch is sent.
|
||||
*
|
||||
* Background preparation failures do not prevent the connection from
|
||||
* becoming ready and are reported through the `node error` event. A
|
||||
* dependent managed `HIMPORT SET` retries preparation and rejects if
|
||||
* recovery fails.
|
||||
*
|
||||
* Direct `HIMPORT PREPARE`, `DISCARD`, and `DISCARDALL` calls fan out to all
|
||||
* current masters. Within an explicit pipeline, these commands remain
|
||||
* connection-affine and are not managed.
|
||||
*
|
||||
* Use explicit HIMPORT commands on a separate unconfigured client for
|
||||
* bounded, manually managed batches.
|
||||
*
|
||||
* @default undefined
|
||||
* @experimental
|
||||
*/
|
||||
himportFieldsets?: readonly HimportFieldset[] | undefined;
|
||||
}
|
||||
export type ClusterOptionsWithReplyMapping<Mapping extends ReplyMappingMode> = ClusterOptions & {
|
||||
redisOptions?: ClusterOptions["redisOptions"] & {
|
||||
replyMapping?: Mapping;
|
||||
};
|
||||
};
|
||||
export declare const DEFAULT_CLUSTER_OPTIONS: ClusterOptions;
|
||||
23
node_modules/ioredis/built/cluster/ClusterOptions.js
generated
vendored
Normal file
23
node_modules/ioredis/built/cluster/ClusterOptions.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DEFAULT_CLUSTER_OPTIONS = void 0;
|
||||
const dns_1 = require("dns");
|
||||
exports.DEFAULT_CLUSTER_OPTIONS = {
|
||||
clusterRetryStrategy: (times) => Math.min(100 + times * 2, 2000),
|
||||
clusterNodeRetryStrategy: null,
|
||||
enableOfflineQueue: true,
|
||||
enableReadyCheck: true,
|
||||
scaleReads: "master",
|
||||
maxRedirections: 16,
|
||||
retryDelayOnMoved: 0,
|
||||
retryDelayOnFailover: 100,
|
||||
retryDelayOnClusterDown: 100,
|
||||
retryDelayOnTryAgain: 100,
|
||||
slotsRefreshTimeout: 1000,
|
||||
useSRVRecords: false,
|
||||
resolveSrv: dns_1.resolveSrv,
|
||||
dnsLookup: dns_1.lookup,
|
||||
enableAutoPipelining: false,
|
||||
autoPipeliningIgnoredCommands: [],
|
||||
shardedSubscribers: false,
|
||||
};
|
||||
29
node_modules/ioredis/built/cluster/ClusterSubscriber.d.ts
generated
vendored
Normal file
29
node_modules/ioredis/built/cluster/ClusterSubscriber.d.ts
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/// <reference types="node" />
|
||||
import { EventEmitter } from "events";
|
||||
import ConnectionPool from "./ConnectionPool";
|
||||
export default class ClusterSubscriber {
|
||||
private connectionPool;
|
||||
private emitter;
|
||||
private isSharded;
|
||||
private started;
|
||||
private subscriber;
|
||||
private lastActiveSubscriber;
|
||||
private slotRange;
|
||||
constructor(connectionPool: ConnectionPool, emitter: EventEmitter, isSharded?: boolean);
|
||||
getInstance(): any;
|
||||
/**
|
||||
* Associate this subscriber to a specific slot range.
|
||||
*
|
||||
* Returns the range or an empty array if the slot range couldn't be associated.
|
||||
*
|
||||
* BTW: This is more for debugging and testing purposes.
|
||||
*
|
||||
* @param range
|
||||
*/
|
||||
associateSlotRange(range: number[]): number[];
|
||||
start(): void;
|
||||
stop(): void;
|
||||
isStarted(): boolean;
|
||||
private onSubscriberEnd;
|
||||
private selectSubscriber;
|
||||
}
|
||||
225
node_modules/ioredis/built/cluster/ClusterSubscriber.js
generated
vendored
Normal file
225
node_modules/ioredis/built/cluster/ClusterSubscriber.js
generated
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const util_1 = require("./util");
|
||||
const utils_1 = require("../utils");
|
||||
const Redis_1 = require("../Redis");
|
||||
const debug = (0, utils_1.Debug)("cluster:subscriber");
|
||||
class ClusterSubscriber {
|
||||
constructor(connectionPool, emitter, isSharded = false) {
|
||||
this.connectionPool = connectionPool;
|
||||
this.emitter = emitter;
|
||||
this.isSharded = isSharded;
|
||||
this.started = false;
|
||||
//There is only one connection for the entire pool
|
||||
this.subscriber = null;
|
||||
//The slot range for which this subscriber is responsible
|
||||
this.slotRange = [];
|
||||
this.onSubscriberEnd = () => {
|
||||
if (!this.started) {
|
||||
debug("subscriber has disconnected, but ClusterSubscriber is not started, so not reconnecting.");
|
||||
return;
|
||||
}
|
||||
// If the subscriber closes whilst it's still the active connection,
|
||||
// we might as well try to connecting to a new node if possible to
|
||||
// minimise the number of missed publishes.
|
||||
debug("subscriber has disconnected, selecting a new one...");
|
||||
this.selectSubscriber();
|
||||
};
|
||||
// If the current node we're using as the subscriber disappears
|
||||
// from the node pool for some reason, we will select a new one
|
||||
// to connect to.
|
||||
// Note that this event is only triggered if the connection to
|
||||
// the node has been used; cluster subscriptions are setup with
|
||||
// lazyConnect = true. It's possible for the subscriber node to
|
||||
// disappear without this method being called!
|
||||
// See https://github.com/luin/ioredis/pull/1589
|
||||
this.connectionPool.on("-node", (_, key) => {
|
||||
if (!this.started || !this.subscriber) {
|
||||
return;
|
||||
}
|
||||
if ((0, util_1.getNodeKey)(this.subscriber.options) === key) {
|
||||
debug("subscriber has left, selecting a new one...");
|
||||
this.selectSubscriber();
|
||||
}
|
||||
});
|
||||
this.connectionPool.on("+node", () => {
|
||||
if (!this.started || this.subscriber) {
|
||||
return;
|
||||
}
|
||||
debug("a new node is discovered and there is no subscriber, selecting a new one...");
|
||||
this.selectSubscriber();
|
||||
});
|
||||
}
|
||||
getInstance() {
|
||||
return this.subscriber;
|
||||
}
|
||||
/**
|
||||
* Associate this subscriber to a specific slot range.
|
||||
*
|
||||
* Returns the range or an empty array if the slot range couldn't be associated.
|
||||
*
|
||||
* BTW: This is more for debugging and testing purposes.
|
||||
*
|
||||
* @param range
|
||||
*/
|
||||
associateSlotRange(range) {
|
||||
if (this.isSharded) {
|
||||
this.slotRange = range;
|
||||
}
|
||||
return this.slotRange;
|
||||
}
|
||||
start() {
|
||||
this.started = true;
|
||||
this.selectSubscriber();
|
||||
debug("started");
|
||||
}
|
||||
stop() {
|
||||
this.started = false;
|
||||
if (this.subscriber) {
|
||||
this.subscriber.disconnect();
|
||||
this.subscriber = null;
|
||||
}
|
||||
}
|
||||
isStarted() {
|
||||
return this.started;
|
||||
}
|
||||
selectSubscriber() {
|
||||
const lastActiveSubscriber = this.lastActiveSubscriber;
|
||||
// Disconnect the previous subscriber even if there
|
||||
// will not be a new one.
|
||||
if (lastActiveSubscriber) {
|
||||
lastActiveSubscriber.off("end", this.onSubscriberEnd);
|
||||
lastActiveSubscriber.disconnect();
|
||||
}
|
||||
if (this.subscriber) {
|
||||
this.subscriber.off("end", this.onSubscriberEnd);
|
||||
this.subscriber.disconnect();
|
||||
}
|
||||
const sampleNode = (0, utils_1.sample)(this.connectionPool.getNodes());
|
||||
if (!sampleNode) {
|
||||
debug("selecting subscriber failed since there is no node discovered in the cluster yet");
|
||||
this.subscriber = null;
|
||||
return;
|
||||
}
|
||||
const { options } = sampleNode;
|
||||
debug("selected a subscriber %s:%s", options.host, options.port);
|
||||
/*
|
||||
* Create a specialized Redis connection for the subscription.
|
||||
* Note that auto reconnection is enabled here.
|
||||
*
|
||||
* `enableReadyCheck` is also enabled because although subscription is allowed
|
||||
* while redis is loading data from the disk, we can check if the password
|
||||
* provided for the subscriber is correct, and if not, the current subscriber
|
||||
* will be disconnected and a new subscriber will be selected.
|
||||
*/
|
||||
let connectionPrefix = "subscriber";
|
||||
if (this.isSharded)
|
||||
connectionPrefix = "ssubscriber";
|
||||
this.subscriber = new Redis_1.default({
|
||||
port: options.port,
|
||||
host: options.host,
|
||||
username: options.username,
|
||||
password: options.password,
|
||||
enableReadyCheck: true,
|
||||
connectionName: (0, util_1.getConnectionName)(connectionPrefix, options.connectionName),
|
||||
lazyConnect: true,
|
||||
tls: options.tls,
|
||||
protocol: options.protocol,
|
||||
replyMapping: options.replyMapping,
|
||||
// Don't try to reconnect the subscriber connection. If the connection fails
|
||||
// we will get an end event (handled below), at which point we'll pick a new
|
||||
// node from the pool and try to connect to that as the subscriber connection.
|
||||
retryStrategy: null,
|
||||
});
|
||||
// Ignore the errors since they're handled in the connection pool.
|
||||
this.subscriber.on("error", utils_1.noop);
|
||||
this.subscriber.on("moved", () => {
|
||||
this.emitter.emit("forceRefresh");
|
||||
});
|
||||
// The node we lost connection to may not come back up in a
|
||||
// reasonable amount of time (e.g. a slave that's taken down
|
||||
// for maintainence), we could potentially miss many published
|
||||
// messages so we should reconnect as quickly as possible, to
|
||||
// a different node if needed.
|
||||
this.subscriber.once("end", this.onSubscriberEnd);
|
||||
// Re-subscribe previous channels
|
||||
const previousChannels = { subscribe: [], psubscribe: [], ssubscribe: [] };
|
||||
if (lastActiveSubscriber) {
|
||||
const condition = lastActiveSubscriber.condition || lastActiveSubscriber.prevCondition;
|
||||
if (condition && condition.subscriber) {
|
||||
previousChannels.subscribe = condition.subscriber.channels("subscribe");
|
||||
previousChannels.psubscribe =
|
||||
condition.subscriber.channels("psubscribe");
|
||||
previousChannels.ssubscribe =
|
||||
condition.subscriber.channels("ssubscribe");
|
||||
}
|
||||
}
|
||||
if (previousChannels.subscribe.length ||
|
||||
previousChannels.psubscribe.length ||
|
||||
previousChannels.ssubscribe.length) {
|
||||
let pending = 0;
|
||||
for (const type of ["subscribe", "psubscribe", "ssubscribe"]) {
|
||||
const channels = previousChannels[type];
|
||||
if (channels.length == 0) {
|
||||
continue;
|
||||
}
|
||||
debug("%s %d channels", type, channels.length);
|
||||
if (type === "ssubscribe") {
|
||||
for (const channel of channels) {
|
||||
pending += 1;
|
||||
this.subscriber[type](channel)
|
||||
.then(() => {
|
||||
if (!--pending) {
|
||||
this.lastActiveSubscriber = this.subscriber;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO: should probably disconnect the subscriber and try again.
|
||||
debug("failed to ssubscribe to channel: %s", channel);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
pending += 1;
|
||||
this.subscriber[type](channels)
|
||||
.then(() => {
|
||||
if (!--pending) {
|
||||
this.lastActiveSubscriber = this.subscriber;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO: should probably disconnect the subscriber and try again.
|
||||
debug("failed to %s %d channels", type, channels.length);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.lastActiveSubscriber = this.subscriber;
|
||||
}
|
||||
for (const event of [
|
||||
"message",
|
||||
"messageBuffer",
|
||||
]) {
|
||||
this.subscriber.on(event, (arg1, arg2) => {
|
||||
this.emitter.emit(event, arg1, arg2);
|
||||
});
|
||||
}
|
||||
for (const event of ["pmessage", "pmessageBuffer"]) {
|
||||
this.subscriber.on(event, (arg1, arg2, arg3) => {
|
||||
this.emitter.emit(event, arg1, arg2, arg3);
|
||||
});
|
||||
}
|
||||
if (this.isSharded == true) {
|
||||
for (const event of [
|
||||
"smessage",
|
||||
"smessageBuffer",
|
||||
]) {
|
||||
this.subscriber.on(event, (arg1, arg2) => {
|
||||
this.emitter.emit(event, arg1, arg2);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = ClusterSubscriber;
|
||||
110
node_modules/ioredis/built/cluster/ClusterSubscriberGroup.d.ts
generated
vendored
Normal file
110
node_modules/ioredis/built/cluster/ClusterSubscriberGroup.d.ts
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import * as EventEmitter from "events";
|
||||
import ShardedSubscriber from "./ShardedSubscriber";
|
||||
import { ClusterOptions } from "./ClusterOptions";
|
||||
/**
|
||||
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
|
||||
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
|
||||
* messages between shards. Sharded PubSub removes this limitation by making each shard
|
||||
* responsible for its own messages.
|
||||
*
|
||||
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
|
||||
* sharded PubSub support while keeping the public API backward compatible.
|
||||
*/
|
||||
export default class ClusterSubscriberGroup {
|
||||
private readonly subscriberGroupEmitter;
|
||||
private readonly options;
|
||||
private shardedSubscribers;
|
||||
private clusterSlots;
|
||||
private subscriberToSlotsIndex;
|
||||
private channels;
|
||||
private failedAttemptsByNode;
|
||||
private isResetting;
|
||||
private pendingReset;
|
||||
private static readonly MAX_RETRY_ATTEMPTS;
|
||||
private static readonly MAX_BACKOFF_MS;
|
||||
private static readonly BASE_BACKOFF_MS;
|
||||
/**
|
||||
* Register callbacks
|
||||
*
|
||||
* @param cluster
|
||||
*/
|
||||
constructor(subscriberGroupEmitter: EventEmitter, options: ClusterOptions);
|
||||
/**
|
||||
* Get the responsible subscriber.
|
||||
*
|
||||
* @param slot
|
||||
*/
|
||||
getResponsibleSubscriber(slot: number): ShardedSubscriber | undefined;
|
||||
/**
|
||||
* Adds a channel for which this subscriber group is responsible
|
||||
*
|
||||
* @param channels
|
||||
*/
|
||||
addChannels(channels: (string | Buffer)[]): number;
|
||||
/**
|
||||
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
|
||||
* @param channels
|
||||
*/
|
||||
removeChannels(channels: (string | Buffer)[]): number;
|
||||
/**
|
||||
* Disconnect all subscribers and clear some of the internal state.
|
||||
*/
|
||||
stop(): void;
|
||||
/**
|
||||
* Start all not yet started subscribers
|
||||
*/
|
||||
start(): Promise<any[]>;
|
||||
/**
|
||||
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
|
||||
*/
|
||||
reset(clusterSlots: string[][], clusterNodes: any[]): Promise<void>;
|
||||
/**
|
||||
* Refreshes the subscriber-related slot ranges
|
||||
*
|
||||
* Returns false if no refresh was needed
|
||||
*
|
||||
* @param targetSlots
|
||||
*/
|
||||
private _refreshSlots;
|
||||
/**
|
||||
* Resubscribes to the previous channels
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _resubscribe;
|
||||
/**
|
||||
* Deep equality of the cluster slots objects
|
||||
*
|
||||
* @param other
|
||||
* @private
|
||||
*/
|
||||
private _slotsAreEqual;
|
||||
/**
|
||||
* Checks if any subscribers are in an unhealthy state.
|
||||
*
|
||||
* A subscriber is considered unhealthy if:
|
||||
* - It exists but is not started (failed/disconnected)
|
||||
* - It's missing entirely for a node that should have one
|
||||
*
|
||||
* @returns true if any subscribers need to be recreated
|
||||
*/
|
||||
private hasUnhealthySubscribers;
|
||||
/**
|
||||
* Handles failed subscriber connections by emitting an event to refresh the slots cache
|
||||
* after a backoff period.
|
||||
*
|
||||
* @param error
|
||||
* @param nodeKey
|
||||
*/
|
||||
private handleSubscriberConnectFailed;
|
||||
/**
|
||||
* Handles successful subscriber connections by resetting the failed attempts counter.
|
||||
*
|
||||
* @param nodeKey
|
||||
*/
|
||||
private handleSubscriberConnectSucceeded;
|
||||
private shouldStartSubscriber;
|
||||
}
|
||||
373
node_modules/ioredis/built/cluster/ClusterSubscriberGroup.js
generated
vendored
Normal file
373
node_modules/ioredis/built/cluster/ClusterSubscriberGroup.js
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils_1 = require("../utils");
|
||||
const util_1 = require("./util");
|
||||
const calculateSlot = require("cluster-key-slot");
|
||||
const ShardedSubscriber_1 = require("./ShardedSubscriber");
|
||||
const debug = (0, utils_1.Debug)("cluster:subscriberGroup");
|
||||
/**
|
||||
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
|
||||
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
|
||||
* messages between shards. Sharded PubSub removes this limitation by making each shard
|
||||
* responsible for its own messages.
|
||||
*
|
||||
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
|
||||
* sharded PubSub support while keeping the public API backward compatible.
|
||||
*/
|
||||
class ClusterSubscriberGroup {
|
||||
/**
|
||||
* Register callbacks
|
||||
*
|
||||
* @param cluster
|
||||
*/
|
||||
constructor(subscriberGroupEmitter, options) {
|
||||
this.subscriberGroupEmitter = subscriberGroupEmitter;
|
||||
this.options = options;
|
||||
this.shardedSubscribers = new Map();
|
||||
this.clusterSlots = [];
|
||||
// Simple [min, max] slot ranges aren't enough because you can migrate single slots
|
||||
this.subscriberToSlotsIndex = new Map();
|
||||
this.channels = new Map();
|
||||
this.failedAttemptsByNode = new Map();
|
||||
// Only latest pending reset kept; throttled by refreshSlotsCache's isRefreshing + backoff delay
|
||||
this.isResetting = false;
|
||||
this.pendingReset = null;
|
||||
/**
|
||||
* Handles failed subscriber connections by emitting an event to refresh the slots cache
|
||||
* after a backoff period.
|
||||
*
|
||||
* @param error
|
||||
* @param nodeKey
|
||||
*/
|
||||
this.handleSubscriberConnectFailed = (error, nodeKey) => {
|
||||
const currentAttempts = this.failedAttemptsByNode.get(nodeKey) || 0;
|
||||
const failedAttempts = currentAttempts + 1;
|
||||
this.failedAttemptsByNode.set(nodeKey, failedAttempts);
|
||||
const attempts = Math.min(failedAttempts, ClusterSubscriberGroup.MAX_RETRY_ATTEMPTS);
|
||||
const backoff = Math.min(ClusterSubscriberGroup.BASE_BACKOFF_MS * 2 ** attempts, ClusterSubscriberGroup.MAX_BACKOFF_MS);
|
||||
const jitter = Math.floor((Math.random() - 0.5) * (backoff * 0.5));
|
||||
const delay = Math.max(0, backoff + jitter);
|
||||
debug("Failed to connect subscriber for %s. Refreshing slots in %dms", nodeKey, delay);
|
||||
this.subscriberGroupEmitter.emit("subscriberConnectFailed", {
|
||||
delay,
|
||||
error,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Handles successful subscriber connections by resetting the failed attempts counter.
|
||||
*
|
||||
* @param nodeKey
|
||||
*/
|
||||
this.handleSubscriberConnectSucceeded = (nodeKey) => {
|
||||
this.failedAttemptsByNode.delete(nodeKey);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the responsible subscriber.
|
||||
*
|
||||
* @param slot
|
||||
*/
|
||||
getResponsibleSubscriber(slot) {
|
||||
const nodeKey = this.clusterSlots[slot][0];
|
||||
const sub = this.shardedSubscribers.get(nodeKey);
|
||||
if (sub && sub.subscriberStatus === "idle") {
|
||||
sub
|
||||
.start()
|
||||
.then(() => {
|
||||
this.handleSubscriberConnectSucceeded(sub.getNodeKey());
|
||||
})
|
||||
.catch((err) => {
|
||||
this.handleSubscriberConnectFailed(err, sub.getNodeKey());
|
||||
});
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
/**
|
||||
* Adds a channel for which this subscriber group is responsible
|
||||
*
|
||||
* @param channels
|
||||
*/
|
||||
addChannels(channels) {
|
||||
const slot = calculateSlot(channels[0]);
|
||||
// Check if the all channels belong to the same slot and otherwise reject the operation
|
||||
for (const c of channels) {
|
||||
if (calculateSlot(c) !== slot) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
const currChannels = this.channels.get(slot);
|
||||
if (!currChannels) {
|
||||
this.channels.set(slot, channels);
|
||||
}
|
||||
else {
|
||||
this.channels.set(slot, currChannels.concat(channels));
|
||||
}
|
||||
return Array.from(this.channels.values()).reduce((sum, array) => sum + array.length, 0);
|
||||
}
|
||||
/**
|
||||
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
|
||||
* @param channels
|
||||
*/
|
||||
removeChannels(channels) {
|
||||
const slot = calculateSlot(channels[0]);
|
||||
// Check if the all channels belong to the same slot and otherwise reject the operation
|
||||
for (const c of channels) {
|
||||
if (calculateSlot(c) !== slot) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
const slotChannels = this.channels.get(slot);
|
||||
if (slotChannels) {
|
||||
const updatedChannels = slotChannels.filter((c) => !channels.includes(c));
|
||||
this.channels.set(slot, updatedChannels);
|
||||
}
|
||||
return Array.from(this.channels.values()).reduce((sum, array) => sum + array.length, 0);
|
||||
}
|
||||
/**
|
||||
* Disconnect all subscribers and clear some of the internal state.
|
||||
*/
|
||||
stop() {
|
||||
for (const s of this.shardedSubscribers.values()) {
|
||||
s.stop();
|
||||
}
|
||||
// Clear subscriber instances and pending operations.
|
||||
// Channels are preserved for resubscription on reconnect.
|
||||
this.pendingReset = null;
|
||||
this.shardedSubscribers.clear();
|
||||
this.subscriberToSlotsIndex.clear();
|
||||
}
|
||||
/**
|
||||
* Start all not yet started subscribers
|
||||
*/
|
||||
start() {
|
||||
const startPromises = [];
|
||||
for (const s of this.shardedSubscribers.values()) {
|
||||
if (this.shouldStartSubscriber(s)) {
|
||||
startPromises.push(s
|
||||
.start()
|
||||
.then(() => {
|
||||
this.handleSubscriberConnectSucceeded(s.getNodeKey());
|
||||
})
|
||||
.catch((err) => {
|
||||
this.handleSubscriberConnectFailed(err, s.getNodeKey());
|
||||
}));
|
||||
this.subscriberGroupEmitter.emit("+subscriber");
|
||||
}
|
||||
}
|
||||
return Promise.all(startPromises);
|
||||
}
|
||||
/**
|
||||
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
|
||||
*/
|
||||
async reset(clusterSlots, clusterNodes) {
|
||||
if (this.isResetting) {
|
||||
this.pendingReset = { slots: clusterSlots, nodes: clusterNodes };
|
||||
return;
|
||||
}
|
||||
this.isResetting = true;
|
||||
try {
|
||||
const hasTopologyChanged = this._refreshSlots(clusterSlots);
|
||||
const hasFailedSubscribers = this.hasUnhealthySubscribers();
|
||||
if (!hasTopologyChanged && !hasFailedSubscribers) {
|
||||
debug("No topology change detected or failed subscribers. Skipping reset.");
|
||||
return;
|
||||
}
|
||||
// For each of the sharded subscribers
|
||||
for (const [nodeKey, shardedSubscriber] of this.shardedSubscribers) {
|
||||
if (
|
||||
// If the subscriber is still responsible for a slot range and is healthy then keep it
|
||||
this.subscriberToSlotsIndex.has(nodeKey) &&
|
||||
shardedSubscriber.isHealthy()) {
|
||||
debug("Skipping deleting subscriber for %s", nodeKey);
|
||||
continue;
|
||||
}
|
||||
debug("Removing subscriber for %s", nodeKey);
|
||||
// Otherwise stop the subscriber and remove it
|
||||
shardedSubscriber.stop();
|
||||
this.shardedSubscribers.delete(nodeKey);
|
||||
this.subscriberGroupEmitter.emit("-subscriber");
|
||||
}
|
||||
const startPromises = [];
|
||||
// For each node in slots cache
|
||||
for (const [nodeKey, _] of this.subscriberToSlotsIndex) {
|
||||
const existingSubscriber = this.shardedSubscribers.get(nodeKey);
|
||||
// If we already have a subscriber for this node, only ensure it is healthy
|
||||
// when it now owns slots with active channel subscriptions.
|
||||
if (existingSubscriber && existingSubscriber.isHealthy()) {
|
||||
debug("Skipping creating new subscriber for %s", nodeKey);
|
||||
if (!existingSubscriber.isStarted() &&
|
||||
this.shouldStartSubscriber(existingSubscriber)) {
|
||||
startPromises.push(existingSubscriber
|
||||
.start()
|
||||
.then(() => {
|
||||
this.handleSubscriberConnectSucceeded(nodeKey);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.handleSubscriberConnectFailed(error, nodeKey);
|
||||
}));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// If we have an existing subscriber but it is not healthy, stop it
|
||||
if (existingSubscriber && !existingSubscriber.isHealthy()) {
|
||||
debug("Replacing subscriber for %s", nodeKey);
|
||||
existingSubscriber.stop();
|
||||
this.shardedSubscribers.delete(nodeKey);
|
||||
this.subscriberGroupEmitter.emit("-subscriber");
|
||||
}
|
||||
debug("Creating new subscriber for %s", nodeKey);
|
||||
// Otherwise create a new subscriber
|
||||
const redis = clusterNodes.find((node) => {
|
||||
return (0, util_1.getNodeKey)(node.options) === nodeKey;
|
||||
});
|
||||
if (!redis) {
|
||||
debug("Failed to find node for key %s", nodeKey);
|
||||
continue;
|
||||
}
|
||||
const sub = new ShardedSubscriber_1.default(this.subscriberGroupEmitter, redis.options, this.options.redisOptions);
|
||||
this.shardedSubscribers.set(nodeKey, sub);
|
||||
if (this.shouldStartSubscriber(sub)) {
|
||||
startPromises.push(sub
|
||||
.start()
|
||||
.then(() => {
|
||||
this.handleSubscriberConnectSucceeded(nodeKey);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.handleSubscriberConnectFailed(error, nodeKey);
|
||||
}));
|
||||
}
|
||||
this.subscriberGroupEmitter.emit("+subscriber");
|
||||
}
|
||||
// It's vital to await the start promises before resubscribing
|
||||
// Otherwise we might try to resubscribe to a subscriber that is not yet connected
|
||||
// This can cause a race condition
|
||||
await Promise.all(startPromises);
|
||||
this._resubscribe();
|
||||
this.subscriberGroupEmitter.emit("subscribersReady");
|
||||
}
|
||||
finally {
|
||||
this.isResetting = false;
|
||||
if (this.pendingReset) {
|
||||
const { slots, nodes } = this.pendingReset;
|
||||
this.pendingReset = null;
|
||||
await this.reset(slots, nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Refreshes the subscriber-related slot ranges
|
||||
*
|
||||
* Returns false if no refresh was needed
|
||||
*
|
||||
* @param targetSlots
|
||||
*/
|
||||
_refreshSlots(targetSlots) {
|
||||
//If there was an actual change, then reassign the slot ranges
|
||||
// Also rebuild if subscriberToSlotsIndex is empty (e.g., after stop() was called)
|
||||
if (this._slotsAreEqual(targetSlots) && this.subscriberToSlotsIndex.size > 0) {
|
||||
debug("Nothing to refresh because the new cluster map is equal to the previous one.");
|
||||
return false;
|
||||
}
|
||||
debug("Refreshing the slots of the subscriber group.");
|
||||
//Rebuild the slots index
|
||||
this.subscriberToSlotsIndex = new Map();
|
||||
for (let slot = 0; slot < targetSlots.length; slot++) {
|
||||
const node = targetSlots[slot][0];
|
||||
if (!this.subscriberToSlotsIndex.has(node)) {
|
||||
this.subscriberToSlotsIndex.set(node, []);
|
||||
}
|
||||
this.subscriberToSlotsIndex.get(node).push(Number(slot));
|
||||
}
|
||||
//Update the cached slots map
|
||||
this.clusterSlots = JSON.parse(JSON.stringify(targetSlots));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Resubscribes to the previous channels
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_resubscribe() {
|
||||
if (this.shardedSubscribers) {
|
||||
this.shardedSubscribers.forEach((s, nodeKey) => {
|
||||
const subscriberSlots = this.subscriberToSlotsIndex.get(nodeKey);
|
||||
if (subscriberSlots) {
|
||||
//Resubscribe on the underlying connection
|
||||
subscriberSlots.forEach((ss) => {
|
||||
//Might return null if being disconnected
|
||||
const redis = s.getInstance();
|
||||
const channels = this.channels.get(ss);
|
||||
if (channels && channels.length > 0) {
|
||||
if (!redis || redis.status === "end") {
|
||||
return;
|
||||
}
|
||||
if (redis.status === "ready") {
|
||||
redis.ssubscribe(...channels).catch((err) => {
|
||||
// TODO: Should we emit an error event here?
|
||||
debug("Failed to ssubscribe on node %s: %s", nodeKey, err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
redis.once("ready", () => {
|
||||
redis.ssubscribe(...channels).catch((err) => {
|
||||
// TODO: Should we emit an error event here?
|
||||
debug("Failed to ssubscribe on node %s: %s", nodeKey, err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Deep equality of the cluster slots objects
|
||||
*
|
||||
* @param other
|
||||
* @private
|
||||
*/
|
||||
_slotsAreEqual(other) {
|
||||
if (this.clusterSlots === undefined) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return JSON.stringify(this.clusterSlots) === JSON.stringify(other);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if any subscribers are in an unhealthy state.
|
||||
*
|
||||
* A subscriber is considered unhealthy if:
|
||||
* - It exists but is not started (failed/disconnected)
|
||||
* - It's missing entirely for a node that should have one
|
||||
*
|
||||
* @returns true if any subscribers need to be recreated
|
||||
*/
|
||||
hasUnhealthySubscribers() {
|
||||
const hasFailedSubscribers = Array.from(this.shardedSubscribers.values()).some((sub) => !sub.isHealthy());
|
||||
const hasMissingSubscribers = Array.from(this.subscriberToSlotsIndex.keys()).some((nodeKey) => !this.shardedSubscribers.has(nodeKey));
|
||||
return hasFailedSubscribers || hasMissingSubscribers;
|
||||
}
|
||||
shouldStartSubscriber(sub) {
|
||||
if (sub.isStarted()) {
|
||||
return false;
|
||||
}
|
||||
if (!sub.isLazyConnect()) {
|
||||
return true;
|
||||
}
|
||||
const subscriberSlots = this.subscriberToSlotsIndex.get(sub.getNodeKey());
|
||||
if (!subscriberSlots) {
|
||||
return false;
|
||||
}
|
||||
return subscriberSlots.some((slot) => {
|
||||
const channels = this.channels.get(slot);
|
||||
return Boolean(channels && channels.length > 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Retry strategy
|
||||
ClusterSubscriberGroup.MAX_RETRY_ATTEMPTS = 10;
|
||||
ClusterSubscriberGroup.MAX_BACKOFF_MS = 2000;
|
||||
ClusterSubscriberGroup.BASE_BACKOFF_MS = 100;
|
||||
exports.default = ClusterSubscriberGroup;
|
||||
51
node_modules/ioredis/built/cluster/ConnectionPool.d.ts
generated
vendored
Normal file
51
node_modules/ioredis/built/cluster/ConnectionPool.d.ts
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/// <reference types="node" />
|
||||
import { EventEmitter } from "events";
|
||||
import { RedisOptions, NodeKey, NodeRole } from "./util";
|
||||
import { ClusterOptions, ClusterNodeRetryStrategy } from "./ClusterOptions";
|
||||
import Redis from "../Redis";
|
||||
export interface ConnectionPoolHooks {
|
||||
onCreate?: (redis: Redis, readOnly: boolean) => void;
|
||||
onRoleChange?: (redis: Redis, key: NodeKey, readOnly: boolean) => void;
|
||||
onRemove?: (redis: Redis) => void;
|
||||
}
|
||||
export default class ConnectionPool extends EventEmitter {
|
||||
private redisOptions;
|
||||
private clusterNodeRetryStrategy;
|
||||
private hooks;
|
||||
private nodes;
|
||||
private specifiedOptions;
|
||||
constructor(redisOptions: NonNullable<ClusterOptions["redisOptions"]>, clusterNodeRetryStrategy?: ClusterNodeRetryStrategy, hooks?: ConnectionPoolHooks);
|
||||
getNodes(role?: NodeRole): Redis[];
|
||||
getInstanceByKey(key: NodeKey): Redis;
|
||||
getSampleInstance(role: NodeRole): Redis;
|
||||
/**
|
||||
* Add a master node to the pool
|
||||
* @param node
|
||||
*/
|
||||
addMasterNode(node: RedisOptions): boolean;
|
||||
/**
|
||||
* Creates a Redis connection instance from the node options
|
||||
* @param node
|
||||
* @param readOnly
|
||||
*/
|
||||
createRedisFromOptions(node: RedisOptions, readOnly: boolean): Redis<import("../types").ReplyMappingMode>;
|
||||
/**
|
||||
* Find or create a connection to the node
|
||||
*/
|
||||
findOrCreate(node: RedisOptions, readOnly?: boolean): Redis;
|
||||
/**
|
||||
* Replace the connection to the node with a fresh one. Used when the
|
||||
* existing connection is known to be stale, e.g. after a MOVED redirect
|
||||
* that points back at the node it came from.
|
||||
*/
|
||||
recreate(node: RedisOptions, readOnly?: boolean): Redis;
|
||||
/**
|
||||
* Reset the pool with a set of nodes.
|
||||
* The old node will be removed.
|
||||
*/
|
||||
reset(nodes: RedisOptions[]): void;
|
||||
/**
|
||||
* Remove a node from the pool.
|
||||
*/
|
||||
private removeNode;
|
||||
}
|
||||
190
node_modules/ioredis/built/cluster/ConnectionPool.js
generated
vendored
Normal file
190
node_modules/ioredis/built/cluster/ConnectionPool.js
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const events_1 = require("events");
|
||||
const utils_1 = require("../utils");
|
||||
const util_1 = require("./util");
|
||||
const Redis_1 = require("../Redis");
|
||||
const debug = (0, utils_1.Debug)("cluster:connectionPool");
|
||||
class ConnectionPool extends events_1.EventEmitter {
|
||||
constructor(redisOptions, clusterNodeRetryStrategy = null, hooks = {}) {
|
||||
super();
|
||||
this.redisOptions = redisOptions;
|
||||
this.clusterNodeRetryStrategy = clusterNodeRetryStrategy;
|
||||
this.hooks = hooks;
|
||||
// master + slave = all
|
||||
this.nodes = {
|
||||
all: {},
|
||||
master: {},
|
||||
slave: {},
|
||||
};
|
||||
this.specifiedOptions = {};
|
||||
}
|
||||
getNodes(role = "all") {
|
||||
const nodes = this.nodes[role];
|
||||
return Object.keys(nodes).map((key) => nodes[key]);
|
||||
}
|
||||
getInstanceByKey(key) {
|
||||
return this.nodes.all[key];
|
||||
}
|
||||
getSampleInstance(role) {
|
||||
const keys = Object.keys(this.nodes[role]);
|
||||
const sampleKey = (0, utils_1.sample)(keys);
|
||||
return this.nodes[role][sampleKey];
|
||||
}
|
||||
/**
|
||||
* Add a master node to the pool
|
||||
* @param node
|
||||
*/
|
||||
addMasterNode(node) {
|
||||
const key = (0, util_1.getNodeKey)(node.options);
|
||||
const redis = this.createRedisFromOptions(node, node.options.readOnly);
|
||||
//Master nodes aren't read-only
|
||||
if (!node.options.readOnly) {
|
||||
this.nodes.all[key] = redis;
|
||||
this.nodes.master[key] = redis;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Creates a Redis connection instance from the node options
|
||||
* @param node
|
||||
* @param readOnly
|
||||
*/
|
||||
createRedisFromOptions(node, readOnly) {
|
||||
const redis = new Redis_1.default((0, utils_1.defaults)({
|
||||
// By default, never try to reconnect when a node is lost,
|
||||
// instead, waiting for a `MOVED` error and fetching slots again.
|
||||
// When `clusterNodeRetryStrategy` is set, use it to allow
|
||||
// reconnection (e.g. for replica nodes that restart without
|
||||
// any slot changes).
|
||||
retryStrategy: typeof this.clusterNodeRetryStrategy === "function"
|
||||
? this.clusterNodeRetryStrategy
|
||||
: null,
|
||||
// Offline queue should be enabled so that
|
||||
// we don't need to wait for the `ready` event
|
||||
// before sending commands to the node.
|
||||
enableOfflineQueue: true,
|
||||
readOnly: readOnly,
|
||||
}, node, this.redisOptions, { lazyConnect: true }));
|
||||
this.hooks.onCreate?.(redis, readOnly);
|
||||
return redis;
|
||||
}
|
||||
/**
|
||||
* Find or create a connection to the node
|
||||
*/
|
||||
findOrCreate(node, readOnly = false) {
|
||||
const key = (0, util_1.getNodeKey)(node);
|
||||
readOnly = Boolean(readOnly);
|
||||
if (this.specifiedOptions[key]) {
|
||||
Object.assign(node, this.specifiedOptions[key]);
|
||||
}
|
||||
else {
|
||||
this.specifiedOptions[key] = node;
|
||||
}
|
||||
let redis;
|
||||
if (this.nodes.all[key]) {
|
||||
redis = this.nodes.all[key];
|
||||
if (redis.options.readOnly !== readOnly) {
|
||||
redis.options.readOnly = readOnly;
|
||||
debug("Change role of %s to %s", key, readOnly ? "slave" : "master");
|
||||
redis[readOnly ? "readonly" : "readwrite"]().catch(utils_1.noop);
|
||||
if (readOnly) {
|
||||
delete this.nodes.master[key];
|
||||
this.nodes.slave[key] = redis;
|
||||
}
|
||||
else {
|
||||
delete this.nodes.slave[key];
|
||||
this.nodes.master[key] = redis;
|
||||
}
|
||||
this.hooks.onRoleChange?.(redis, key, readOnly);
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("Connecting to %s as %s", key, readOnly ? "slave" : "master");
|
||||
redis = this.createRedisFromOptions(node, readOnly);
|
||||
this.nodes.all[key] = redis;
|
||||
this.nodes[readOnly ? "slave" : "master"][key] = redis;
|
||||
redis.once("end", () => {
|
||||
if (this.nodes.all[key] && this.nodes.all[key] !== redis) {
|
||||
// The node was recreated (see `recreate`); leave the new one alone.
|
||||
return;
|
||||
}
|
||||
this.removeNode(key);
|
||||
this.emit("-node", redis, key);
|
||||
if (!Object.keys(this.nodes.all).length) {
|
||||
this.emit("drain");
|
||||
}
|
||||
});
|
||||
this.emit("+node", redis, key);
|
||||
redis.on("error", (error) => {
|
||||
this.emit("nodeError", error, key);
|
||||
});
|
||||
}
|
||||
return redis;
|
||||
}
|
||||
/**
|
||||
* Replace the connection to the node with a fresh one. Used when the
|
||||
* existing connection is known to be stale, e.g. after a MOVED redirect
|
||||
* that points back at the node it came from.
|
||||
*/
|
||||
recreate(node, readOnly = false) {
|
||||
const key = (0, util_1.getNodeKey)(node);
|
||||
const existing = this.nodes.all[key];
|
||||
if (existing) {
|
||||
debug("Recreating connection to %s", key);
|
||||
// Remove before disconnecting so the "end" event of the stale
|
||||
// instance doesn't tear down the replacement.
|
||||
this.removeNode(key);
|
||||
existing.disconnect();
|
||||
}
|
||||
const redis = this.findOrCreate(node, readOnly);
|
||||
if (existing) {
|
||||
// Emit after the replacement is in place so listeners (e.g.
|
||||
// ClusterSubscriber) never observe the pool without the node.
|
||||
this.emit("-node", existing, key);
|
||||
}
|
||||
return redis;
|
||||
}
|
||||
/**
|
||||
* Reset the pool with a set of nodes.
|
||||
* The old node will be removed.
|
||||
*/
|
||||
reset(nodes) {
|
||||
debug("Reset with %O", nodes);
|
||||
const newNodes = {};
|
||||
nodes.forEach((node) => {
|
||||
const key = (0, util_1.getNodeKey)(node);
|
||||
// Don't override the existing (master) node
|
||||
// when the current one is slave.
|
||||
if (!(node.readOnly && newNodes[key])) {
|
||||
newNodes[key] = node;
|
||||
}
|
||||
});
|
||||
Object.keys(this.nodes.all).forEach((key) => {
|
||||
if (!newNodes[key]) {
|
||||
debug("Disconnect %s because the node does not hold any slot", key);
|
||||
this.nodes.all[key].disconnect();
|
||||
this.removeNode(key);
|
||||
}
|
||||
});
|
||||
Object.keys(newNodes).forEach((key) => {
|
||||
const node = newNodes[key];
|
||||
this.findOrCreate(node, node.readOnly);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Remove a node from the pool.
|
||||
*/
|
||||
removeNode(key) {
|
||||
const { nodes } = this;
|
||||
if (nodes.all[key]) {
|
||||
debug("Remove %s from the pool", key);
|
||||
this.hooks.onRemove?.(nodes.all[key]);
|
||||
delete nodes.all[key];
|
||||
}
|
||||
delete nodes.master[key];
|
||||
delete nodes.slave[key];
|
||||
}
|
||||
}
|
||||
exports.default = ConnectionPool;
|
||||
20
node_modules/ioredis/built/cluster/DelayQueue.d.ts
generated
vendored
Normal file
20
node_modules/ioredis/built/cluster/DelayQueue.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export interface DelayQueueOptions {
|
||||
callback?: Function;
|
||||
timeout: number;
|
||||
}
|
||||
/**
|
||||
* Queue that runs items after specified duration
|
||||
*/
|
||||
export default class DelayQueue {
|
||||
private queues;
|
||||
private timeouts;
|
||||
/**
|
||||
* Add a new item to the queue
|
||||
*
|
||||
* @param bucket bucket name
|
||||
* @param item function that will run later
|
||||
* @param options
|
||||
*/
|
||||
push(bucket: string, item: Function, options: DelayQueueOptions): void;
|
||||
private execute;
|
||||
}
|
||||
53
node_modules/ioredis/built/cluster/DelayQueue.js
generated
vendored
Normal file
53
node_modules/ioredis/built/cluster/DelayQueue.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils_1 = require("../utils");
|
||||
const Deque = require("denque");
|
||||
const debug = (0, utils_1.Debug)("delayqueue");
|
||||
/**
|
||||
* Queue that runs items after specified duration
|
||||
*/
|
||||
class DelayQueue {
|
||||
constructor() {
|
||||
this.queues = {};
|
||||
this.timeouts = {};
|
||||
}
|
||||
/**
|
||||
* Add a new item to the queue
|
||||
*
|
||||
* @param bucket bucket name
|
||||
* @param item function that will run later
|
||||
* @param options
|
||||
*/
|
||||
push(bucket, item, options) {
|
||||
const callback = options.callback || process.nextTick;
|
||||
if (!this.queues[bucket]) {
|
||||
this.queues[bucket] = new Deque();
|
||||
}
|
||||
const queue = this.queues[bucket];
|
||||
queue.push(item);
|
||||
if (!this.timeouts[bucket]) {
|
||||
this.timeouts[bucket] = setTimeout(() => {
|
||||
callback(() => {
|
||||
this.timeouts[bucket] = null;
|
||||
this.execute(bucket);
|
||||
});
|
||||
}, options.timeout);
|
||||
}
|
||||
}
|
||||
execute(bucket) {
|
||||
const queue = this.queues[bucket];
|
||||
if (!queue) {
|
||||
return;
|
||||
}
|
||||
const { length } = queue;
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
debug("send %d commands in %s queue", length, bucket);
|
||||
this.queues[bucket] = null;
|
||||
while (queue.length > 0) {
|
||||
queue.shift()();
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = DelayQueue;
|
||||
36
node_modules/ioredis/built/cluster/ShardedSubscriber.d.ts
generated
vendored
Normal file
36
node_modules/ioredis/built/cluster/ShardedSubscriber.d.ts
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/// <reference types="node" />
|
||||
import EventEmitter = require("events");
|
||||
import { RedisOptions } from "./util";
|
||||
import Redis from "../Redis";
|
||||
import { ClusterOptions } from "./ClusterOptions";
|
||||
declare const SubscriberStatus: {
|
||||
readonly IDLE: "idle";
|
||||
readonly STARTING: "starting";
|
||||
readonly CONNECTED: "connected";
|
||||
readonly STOPPING: "stopping";
|
||||
readonly ENDED: "ended";
|
||||
};
|
||||
type SubscriberStatus = typeof SubscriberStatus[keyof typeof SubscriberStatus];
|
||||
export default class ShardedSubscriber {
|
||||
private readonly emitter;
|
||||
private readonly nodeKey;
|
||||
private status;
|
||||
private instance;
|
||||
private connectPromise;
|
||||
private lazyConnect;
|
||||
private readonly messageListeners;
|
||||
constructor(emitter: EventEmitter, options: RedisOptions, redisOptions?: ClusterOptions["redisOptions"]);
|
||||
start(): Promise<void>;
|
||||
stop(): void;
|
||||
isStarted(): boolean;
|
||||
get subscriberStatus(): SubscriberStatus;
|
||||
isHealthy(): boolean;
|
||||
getInstance(): Redis | null;
|
||||
getNodeKey(): string;
|
||||
isLazyConnect(): boolean;
|
||||
private onEnd;
|
||||
private onError;
|
||||
private onMoved;
|
||||
private updateStatus;
|
||||
}
|
||||
export {};
|
||||
146
node_modules/ioredis/built/cluster/ShardedSubscriber.js
generated
vendored
Normal file
146
node_modules/ioredis/built/cluster/ShardedSubscriber.js
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const util_1 = require("./util");
|
||||
const utils_1 = require("../utils");
|
||||
const Redis_1 = require("../Redis");
|
||||
const debug = (0, utils_1.Debug)("cluster:subscriberGroup:shardedSubscriber");
|
||||
const SubscriberStatus = {
|
||||
IDLE: "idle",
|
||||
STARTING: "starting",
|
||||
CONNECTED: "connected",
|
||||
STOPPING: "stopping",
|
||||
ENDED: "ended",
|
||||
};
|
||||
const ALLOWED_STATUS_UPDATES = {
|
||||
[SubscriberStatus.IDLE]: [
|
||||
SubscriberStatus.STARTING,
|
||||
SubscriberStatus.STOPPING,
|
||||
SubscriberStatus.ENDED,
|
||||
],
|
||||
[SubscriberStatus.STARTING]: [
|
||||
SubscriberStatus.CONNECTED,
|
||||
SubscriberStatus.STOPPING,
|
||||
SubscriberStatus.ENDED,
|
||||
],
|
||||
[SubscriberStatus.CONNECTED]: [
|
||||
SubscriberStatus.STOPPING,
|
||||
SubscriberStatus.ENDED,
|
||||
],
|
||||
[SubscriberStatus.STOPPING]: [SubscriberStatus.ENDED],
|
||||
[SubscriberStatus.ENDED]: [],
|
||||
};
|
||||
class ShardedSubscriber {
|
||||
constructor(emitter, options, redisOptions) {
|
||||
this.emitter = emitter;
|
||||
this.status = SubscriberStatus.IDLE;
|
||||
this.instance = null;
|
||||
this.connectPromise = null;
|
||||
// Store listener references for cleanup
|
||||
this.messageListeners = new Map();
|
||||
this.onEnd = () => {
|
||||
this.updateStatus(SubscriberStatus.ENDED);
|
||||
this.emitter.emit("-node", this.instance, this.nodeKey);
|
||||
};
|
||||
this.onError = (error) => {
|
||||
this.emitter.emit("nodeError", error, this.nodeKey);
|
||||
};
|
||||
this.onMoved = () => {
|
||||
this.emitter.emit("moved");
|
||||
};
|
||||
this.instance = new Redis_1.default((0, utils_1.defaults)({
|
||||
enableReadyCheck: false,
|
||||
enableOfflineQueue: true,
|
||||
connectionName: (0, util_1.getConnectionName)("ssubscriber", options.connectionName),
|
||||
/**
|
||||
* Disable auto reconnection for subscribers.
|
||||
* The ClusterSubscriberGroup will handle the reconnection.
|
||||
*/
|
||||
retryStrategy: null,
|
||||
lazyConnect: true,
|
||||
}, options, redisOptions));
|
||||
this.lazyConnect = redisOptions?.lazyConnect ?? true;
|
||||
this.nodeKey = (0, util_1.getNodeKey)(options);
|
||||
// Register listeners
|
||||
this.instance.on("end", this.onEnd);
|
||||
this.instance.on("error", this.onError);
|
||||
this.instance.on("moved", this.onMoved);
|
||||
for (const event of ["smessage", "smessageBuffer"]) {
|
||||
const listener = (...args) => {
|
||||
this.emitter.emit(event, ...args);
|
||||
};
|
||||
this.messageListeners.set(event, listener);
|
||||
this.instance.on(event, listener);
|
||||
}
|
||||
}
|
||||
async start() {
|
||||
if (this.connectPromise) {
|
||||
return this.connectPromise;
|
||||
}
|
||||
if (this.status === SubscriberStatus.STARTING ||
|
||||
this.status === SubscriberStatus.CONNECTED) {
|
||||
return;
|
||||
}
|
||||
if (this.status === SubscriberStatus.ENDED || !this.instance) {
|
||||
throw new Error(`Sharded subscriber ${this.nodeKey} cannot be restarted once ended.`);
|
||||
}
|
||||
this.updateStatus(SubscriberStatus.STARTING);
|
||||
this.connectPromise = this.instance.connect();
|
||||
try {
|
||||
await this.connectPromise;
|
||||
this.updateStatus(SubscriberStatus.CONNECTED);
|
||||
}
|
||||
catch (err) {
|
||||
this.updateStatus(SubscriberStatus.ENDED);
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
this.connectPromise = null;
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
this.updateStatus(SubscriberStatus.STOPPING);
|
||||
if (this.instance) {
|
||||
this.instance.disconnect();
|
||||
this.instance.removeAllListeners();
|
||||
this.messageListeners.clear();
|
||||
this.instance = null;
|
||||
}
|
||||
this.updateStatus(SubscriberStatus.ENDED);
|
||||
debug("stopped %s", this.nodeKey);
|
||||
}
|
||||
isStarted() {
|
||||
return [
|
||||
SubscriberStatus.CONNECTED,
|
||||
SubscriberStatus.STARTING,
|
||||
].includes(this.status);
|
||||
}
|
||||
get subscriberStatus() {
|
||||
return this.status;
|
||||
}
|
||||
isHealthy() {
|
||||
return ((this.status === SubscriberStatus.IDLE ||
|
||||
this.status === SubscriberStatus.CONNECTED ||
|
||||
this.status === SubscriberStatus.STARTING) &&
|
||||
this.instance !== null);
|
||||
}
|
||||
getInstance() {
|
||||
return this.instance;
|
||||
}
|
||||
getNodeKey() {
|
||||
return this.nodeKey;
|
||||
}
|
||||
isLazyConnect() {
|
||||
return this.lazyConnect;
|
||||
}
|
||||
updateStatus(nextStatus) {
|
||||
if (this.status === nextStatus) {
|
||||
return;
|
||||
}
|
||||
if (!ALLOWED_STATUS_UPDATES[this.status].includes(nextStatus)) {
|
||||
debug("Invalid status transition for %s: %s -> %s", this.nodeKey, this.status, nextStatus);
|
||||
return;
|
||||
}
|
||||
this.status = nextStatus;
|
||||
}
|
||||
}
|
||||
exports.default = ShardedSubscriber;
|
||||
166
node_modules/ioredis/built/cluster/index.d.ts
generated
vendored
Normal file
166
node_modules/ioredis/built/cluster/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
/// <reference types="node" />
|
||||
import { EventEmitter } from "events";
|
||||
import Command from "../Command";
|
||||
import Redis from "../Redis";
|
||||
import ScanStream from "../ScanStream";
|
||||
import { Transaction } from "../transaction";
|
||||
import { Callback, ReplyMappingFromClusterOptions, ReplyMappingMode, ScanStreamOptions, WriteableStream } from "../types";
|
||||
import Commander from "../utils/Commander";
|
||||
import { ClusterOptions, ClusterOptionsWithReplyMapping } from "./ClusterOptions";
|
||||
import { NodeKey, NodeRole } from "./util";
|
||||
export type ClusterNode = string | number | {
|
||||
host?: string | undefined;
|
||||
port?: number | undefined;
|
||||
};
|
||||
export type ClusterStatus = "end" | "close" | "wait" | "connecting" | "connect" | "ready" | "reconnecting" | "disconnecting";
|
||||
/**
|
||||
* Client for the official Redis Cluster
|
||||
*/
|
||||
declare class Cluster<ReplyMapping extends ReplyMappingMode = "legacy"> extends Commander<{
|
||||
type: "default";
|
||||
mapping: ReplyMapping extends "resp3" ? "resp3" : "resp2";
|
||||
}> {
|
||||
options: ClusterOptions;
|
||||
slots: NodeKey[][];
|
||||
status: ClusterStatus;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
_groupsIds: {
|
||||
[key: string]: number;
|
||||
};
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
_groupsBySlot: number[];
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
isCluster: boolean;
|
||||
private startupNodes;
|
||||
private connectionPool;
|
||||
private manuallyClosing;
|
||||
private retryAttempts;
|
||||
private delayQueue;
|
||||
private offlineQueue;
|
||||
private subscriber;
|
||||
private shardedSubscribers;
|
||||
private slotsTimer;
|
||||
private reconnectTimeout;
|
||||
private isRefreshing;
|
||||
private _refreshSlotsCacheCallbacks;
|
||||
private _autoPipelines;
|
||||
private _runningAutoPipelines;
|
||||
private _readyDelayedCallbacks;
|
||||
private subscriberGroupEmitter;
|
||||
/**
|
||||
* Every time Cluster#connect() is called, this value will be
|
||||
* auto-incrementing. The purpose of this value is used for
|
||||
* discarding previous connect attampts when creating a new
|
||||
* connection.
|
||||
*/
|
||||
private connectionEpoch;
|
||||
/**
|
||||
* Creates an instance of Cluster.
|
||||
*/
|
||||
constructor(startupNodes: ClusterNode[], options?: ClusterOptionsWithReplyMapping<ReplyMapping>);
|
||||
/**
|
||||
* Connect to a cluster
|
||||
*/
|
||||
connect(): Promise<void>;
|
||||
/**
|
||||
* Disconnect from every node in the cluster.
|
||||
*/
|
||||
disconnect(reconnect?: boolean): void;
|
||||
/**
|
||||
* Quit the cluster gracefully.
|
||||
*/
|
||||
quit(callback?: Callback<"OK">): Promise<"OK">;
|
||||
/**
|
||||
* Create a new instance with the same startup nodes and options as the current one.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var cluster = new Redis.Cluster([{ host: "127.0.0.1", port: "30001" }]);
|
||||
* var anotherCluster = cluster.duplicate();
|
||||
* ```
|
||||
*/
|
||||
duplicate<OverrideOptions extends ClusterOptionsWithReplyMapping<ReplyMappingMode> | undefined = undefined>(overrideStartupNodes?: ClusterNode[], overrideOptions?: OverrideOptions): Cluster<ReplyMappingFromClusterOptions<ReplyMapping, OverrideOptions>>;
|
||||
/**
|
||||
* Get nodes with the specified role
|
||||
*/
|
||||
nodes(role?: NodeRole): Redis[];
|
||||
/**
|
||||
* This is needed in order not to install a listener for each auto pipeline
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
delayUntilReady(callback: Callback): void;
|
||||
/**
|
||||
* Get the number of commands queued in automatic pipelines.
|
||||
*
|
||||
* This is not available (and returns 0) until the cluster is connected and slots information have been received.
|
||||
*/
|
||||
get autoPipelineQueueSize(): number;
|
||||
/**
|
||||
* Refresh the slot cache
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
refreshSlotsCache(callback?: Callback<void>): void;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
sendCommand(command: Command, stream?: WriteableStream, node?: any): unknown;
|
||||
sscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||
sscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||
hscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||
hscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||
zscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||
zscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
handleError(error: Error, ttl: {
|
||||
value?: any;
|
||||
}, handlers: any): void;
|
||||
private resetOfflineQueue;
|
||||
private clearNodesRefreshInterval;
|
||||
private resetNodesRefreshInterval;
|
||||
/**
|
||||
* Change cluster instance's status
|
||||
*/
|
||||
private setStatus;
|
||||
/**
|
||||
* Called when closed to check whether a reconnection should be made
|
||||
*/
|
||||
private handleCloseEvent;
|
||||
/**
|
||||
* Flush offline queue with error.
|
||||
*/
|
||||
private flushQueue;
|
||||
private executeOfflineCommands;
|
||||
private natMapper;
|
||||
private getInfoFromNode;
|
||||
private invokeReadyDelayedCallbacks;
|
||||
/**
|
||||
* Check whether Cluster is able to process commands
|
||||
*/
|
||||
private readyCheck;
|
||||
private resolveSrv;
|
||||
private dnsLookup;
|
||||
/**
|
||||
* Normalize startup nodes, and resolving hostnames to IPs.
|
||||
*
|
||||
* This process happens every time when #connect() is called since
|
||||
* #startupNodes and DNS records may chanage.
|
||||
*/
|
||||
private resolveStartupNodeHostnames;
|
||||
private createScanStream;
|
||||
private createShardedSubscriberGroup;
|
||||
}
|
||||
interface Cluster<ReplyMapping extends "legacy" | "resp3" = "legacy"> extends EventEmitter {
|
||||
}
|
||||
interface Cluster<ReplyMapping extends "legacy" | "resp3" = "legacy"> extends Transaction<ReplyMapping extends "resp3" ? "resp3" : "resp2"> {
|
||||
}
|
||||
export default Cluster;
|
||||
1049
node_modules/ioredis/built/cluster/index.js
generated
vendored
Normal file
1049
node_modules/ioredis/built/cluster/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
27
node_modules/ioredis/built/cluster/util.d.ts
generated
vendored
Normal file
27
node_modules/ioredis/built/cluster/util.d.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/// <reference types="node" />
|
||||
import type Redis from "../Redis";
|
||||
import { SrvRecord } from "dns";
|
||||
export type NodeKey = string;
|
||||
export type NodeRole = "master" | "slave" | "all";
|
||||
export interface RedisOptions {
|
||||
port: number;
|
||||
host: string;
|
||||
username?: string | undefined;
|
||||
password?: string | undefined;
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface SrvRecordsGroup {
|
||||
totalWeight: number;
|
||||
records: SrvRecord[];
|
||||
}
|
||||
export interface GroupedSrvRecords {
|
||||
[key: number]: SrvRecordsGroup;
|
||||
}
|
||||
export declare function waitForRedisReady(redis: Redis): Promise<void>;
|
||||
export declare function getNodeKey(node: RedisOptions): NodeKey;
|
||||
export declare function nodeKeyToRedisOptions(nodeKey: NodeKey): RedisOptions;
|
||||
export declare function normalizeNodeOptions(nodes: Array<string | number | object>): RedisOptions[];
|
||||
export declare function getUniqueHostnamesFromOptions(nodes: RedisOptions[]): string[];
|
||||
export declare function groupSrvRecords(records: SrvRecord[]): GroupedSrvRecords;
|
||||
export declare function weightSrvRecords(recordsGroup: SrvRecordsGroup): SrvRecord;
|
||||
export declare function getConnectionName(component: any, nodeConnectionName: any): string;
|
||||
124
node_modules/ioredis/built/cluster/util.js
generated
vendored
Normal file
124
node_modules/ioredis/built/cluster/util.js
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getConnectionName = exports.weightSrvRecords = exports.groupSrvRecords = exports.getUniqueHostnamesFromOptions = exports.normalizeNodeOptions = exports.nodeKeyToRedisOptions = exports.getNodeKey = exports.waitForRedisReady = void 0;
|
||||
const utils_1 = require("../utils");
|
||||
const net_1 = require("net");
|
||||
function waitForRedisReady(redis) {
|
||||
if (redis.status === "ready") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (redis.status === "wait") {
|
||||
return redis.connect();
|
||||
}
|
||||
if (redis.status === "end") {
|
||||
return Promise.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const onReady = () => {
|
||||
redis.removeListener("end", onEnd);
|
||||
resolve();
|
||||
};
|
||||
const onEnd = () => {
|
||||
redis.removeListener("ready", onReady);
|
||||
reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||
};
|
||||
redis.once("ready", onReady);
|
||||
redis.once("end", onEnd);
|
||||
});
|
||||
}
|
||||
exports.waitForRedisReady = waitForRedisReady;
|
||||
function getNodeKey(node) {
|
||||
node.port = node.port || 6379;
|
||||
node.host = node.host || "127.0.0.1";
|
||||
return node.host + ":" + node.port;
|
||||
}
|
||||
exports.getNodeKey = getNodeKey;
|
||||
function nodeKeyToRedisOptions(nodeKey) {
|
||||
const portIndex = nodeKey.lastIndexOf(":");
|
||||
if (portIndex === -1) {
|
||||
throw new Error(`Invalid node key ${nodeKey}`);
|
||||
}
|
||||
return {
|
||||
host: nodeKey.slice(0, portIndex),
|
||||
port: Number(nodeKey.slice(portIndex + 1)),
|
||||
};
|
||||
}
|
||||
exports.nodeKeyToRedisOptions = nodeKeyToRedisOptions;
|
||||
function normalizeNodeOptions(nodes) {
|
||||
return nodes.map((node) => {
|
||||
const options = {};
|
||||
if (typeof node === "object") {
|
||||
Object.assign(options, node);
|
||||
}
|
||||
else if (typeof node === "string") {
|
||||
Object.assign(options, (0, utils_1.parseURL)(node));
|
||||
}
|
||||
else if (typeof node === "number") {
|
||||
options.port = node;
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid argument " + node);
|
||||
}
|
||||
if (typeof options.port === "string") {
|
||||
options.port = parseInt(options.port, 10);
|
||||
}
|
||||
// Cluster mode only support db 0
|
||||
delete options.db;
|
||||
if (!options.port) {
|
||||
options.port = 6379;
|
||||
}
|
||||
if (!options.host) {
|
||||
options.host = "127.0.0.1";
|
||||
}
|
||||
return (0, utils_1.resolveTLSProfile)(options);
|
||||
});
|
||||
}
|
||||
exports.normalizeNodeOptions = normalizeNodeOptions;
|
||||
function getUniqueHostnamesFromOptions(nodes) {
|
||||
const uniqueHostsMap = {};
|
||||
nodes.forEach((node) => {
|
||||
uniqueHostsMap[node.host] = true;
|
||||
});
|
||||
return Object.keys(uniqueHostsMap).filter((host) => !(0, net_1.isIP)(host));
|
||||
}
|
||||
exports.getUniqueHostnamesFromOptions = getUniqueHostnamesFromOptions;
|
||||
function groupSrvRecords(records) {
|
||||
const recordsByPriority = {};
|
||||
for (const record of records) {
|
||||
if (!recordsByPriority.hasOwnProperty(record.priority)) {
|
||||
recordsByPriority[record.priority] = {
|
||||
totalWeight: record.weight,
|
||||
records: [record],
|
||||
};
|
||||
}
|
||||
else {
|
||||
recordsByPriority[record.priority].totalWeight += record.weight;
|
||||
recordsByPriority[record.priority].records.push(record);
|
||||
}
|
||||
}
|
||||
return recordsByPriority;
|
||||
}
|
||||
exports.groupSrvRecords = groupSrvRecords;
|
||||
function weightSrvRecords(recordsGroup) {
|
||||
if (recordsGroup.records.length === 1) {
|
||||
recordsGroup.totalWeight = 0;
|
||||
return recordsGroup.records.shift();
|
||||
}
|
||||
// + `recordsGroup.records.length` to support `weight` 0
|
||||
const random = Math.floor(Math.random() * (recordsGroup.totalWeight + recordsGroup.records.length));
|
||||
let total = 0;
|
||||
for (const [i, record] of recordsGroup.records.entries()) {
|
||||
total += 1 + record.weight;
|
||||
if (total > random) {
|
||||
recordsGroup.totalWeight -= record.weight;
|
||||
recordsGroup.records.splice(i, 1);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.weightSrvRecords = weightSrvRecords;
|
||||
function getConnectionName(component, nodeConnectionName) {
|
||||
const prefix = `ioredis-cluster(${component})`;
|
||||
return nodeConnectionName ? `${prefix}:${nodeConnectionName}` : prefix;
|
||||
}
|
||||
exports.getConnectionName = getConnectionName;
|
||||
Reference in New Issue
Block a user