You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-07 13:22:56 +03:00
fix ASK and MOVED errors in multi as well
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import COMMANDS from './commands';
|
import COMMANDS from './commands';
|
||||||
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
|
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
|
||||||
import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client';
|
import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client';
|
||||||
import RedisClusterSlots, { ClusterNode } from './cluster-slots';
|
import RedisClusterSlots, { ClusterNode } from './cluster-slots';
|
||||||
import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander';
|
import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander';
|
||||||
@@ -82,27 +82,17 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendCommand<C extends RedisCommand>(
|
async sendCommand<T = RedisCommandRawReply>(
|
||||||
firstKey: RedisCommandArgument | undefined,
|
firstKey: RedisCommandArgument | undefined,
|
||||||
isReadonly: boolean | undefined,
|
isReadonly: boolean | undefined,
|
||||||
args: RedisCommandArguments,
|
args: RedisCommandArguments,
|
||||||
options?: ClientCommandOptions,
|
options?: ClientCommandOptions
|
||||||
redirections = 0
|
): Promise<T> {
|
||||||
): Promise<RedisCommandReply<C>> {
|
return this.#execute(
|
||||||
const client = this.#slots.getClient(firstKey, isReadonly);
|
firstKey,
|
||||||
|
isReadonly,
|
||||||
try {
|
client => client.sendCommand<T>(args, options)
|
||||||
return await client.sendCommand(args, options);
|
);
|
||||||
} catch (err: any) {
|
|
||||||
const shouldRetry = await this.#handleCommandError(err, client, redirections);
|
|
||||||
if (shouldRetry === true) {
|
|
||||||
return this.sendCommand(firstKey, isReadonly, args, options, redirections + 1);
|
|
||||||
} else if (shouldRetry) {
|
|
||||||
return shouldRetry.sendCommand(args, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> {
|
async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> {
|
||||||
@@ -124,61 +114,65 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
|
|||||||
script: RedisScript,
|
script: RedisScript,
|
||||||
originalArgs: Array<unknown>,
|
originalArgs: Array<unknown>,
|
||||||
redisArgs: RedisCommandArguments,
|
redisArgs: RedisCommandArguments,
|
||||||
options?: ClientCommandOptions,
|
options?: ClientCommandOptions
|
||||||
redirections = 0
|
|
||||||
): Promise<RedisCommandReply<typeof script>> {
|
): Promise<RedisCommandReply<typeof script>> {
|
||||||
const client = this.#slots.getClient(
|
return this.#execute(
|
||||||
RedisCluster.extractFirstKey(script, originalArgs, redisArgs),
|
RedisCluster.extractFirstKey(script, originalArgs, redisArgs),
|
||||||
script.IS_READ_ONLY
|
script.IS_READ_ONLY,
|
||||||
|
client => client.executeScript(script, redisArgs, options)
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
|
||||||
return await client.executeScript(script, redisArgs, options);
|
|
||||||
} catch (err: any) {
|
|
||||||
const shouldRetry = await this.#handleCommandError(err, client, redirections);
|
|
||||||
if (shouldRetry === true) {
|
|
||||||
return this.executeScript(script, originalArgs, redisArgs, options, redirections + 1);
|
|
||||||
} else if (shouldRetry) {
|
|
||||||
return shouldRetry.executeScript(script, redisArgs, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async #handleCommandError(err: Error, client: RedisClientType<M, S>, redirections: number): Promise<boolean | RedisClientType<M, S>> {
|
async #execute<Reply>(
|
||||||
if (redirections > (this.#options.maxCommandRedirections ?? 16)) {
|
firstKey: RedisCommandArgument | undefined,
|
||||||
throw err;
|
isReadonly: boolean | undefined,
|
||||||
}
|
executor: (client: RedisClientType<M, S>) => Promise<Reply>
|
||||||
|
): Promise<Reply> {
|
||||||
if (err.message.startsWith('ASK')) {
|
const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16;
|
||||||
const url = err.message.substring(err.message.lastIndexOf(' ') + 1);
|
let client = this.#slots.getClient(firstKey, isReadonly);
|
||||||
let node = this.#slots.getNodeByUrl(url);
|
for (let i = 0;; i++) {
|
||||||
if (!node) {
|
try {
|
||||||
await this.#slots.rediscover(client);
|
return await executor(client);
|
||||||
node = this.#slots.getNodeByUrl(url);
|
} catch (err) {
|
||||||
|
if (++i > maxCommandRedirections || !(err instanceof Error)) {
|
||||||
if (!node) {
|
throw err;
|
||||||
throw new Error(`Cannot find node ${url}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err.message.startsWith('ASK')) {
|
||||||
|
const url = err.message.substring(err.message.lastIndexOf(' ') + 1);
|
||||||
|
if (this.#slots.getNodeByUrl(url)?.client === client) {
|
||||||
|
await client.asking();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#slots.rediscover(client);
|
||||||
|
const redirectTo = this.#slots.getNodeByUrl(url);
|
||||||
|
if (!redirectTo) {
|
||||||
|
throw new Error(`Cannot find node ${url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await redirectTo.client.asking();
|
||||||
|
client = redirectTo.client;
|
||||||
|
continue;
|
||||||
|
} else if (err.message.startsWith('MOVED')) {
|
||||||
|
await this.#slots.rediscover(client);
|
||||||
|
client = this.#slots.getClient(firstKey, isReadonly);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
await node.client.asking();
|
|
||||||
return node.client;
|
|
||||||
} else if (err.message.startsWith('MOVED')) {
|
|
||||||
await this.#slots.rediscover(client);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, S> {
|
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, S> {
|
||||||
return new this.#Multi(
|
return new this.#Multi(
|
||||||
async (commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
|
(commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
|
||||||
return this.#slots
|
return this.#execute(
|
||||||
.getClient(firstKey)
|
firstKey,
|
||||||
.multiExecutor(commands, chainId);
|
false,
|
||||||
|
client => client.multiExecutor(commands, chainId)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
routing
|
routing
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user