1
0
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:
leibale
2021-12-30 17:12:13 -05:00
parent 29ff6c8a36
commit 8f88eb289b

View File

@@ -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)
); );
}
async #execute<Reply>(
firstKey: RedisCommandArgument | undefined,
isReadonly: boolean | undefined,
executor: (client: RedisClientType<M, S>) => Promise<Reply>
): Promise<Reply> {
const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16;
let client = this.#slots.getClient(firstKey, isReadonly);
for (let i = 0;; i++) {
try { try {
return await client.executeScript(script, redisArgs, options); return await executor(client);
} catch (err: any) { } catch (err) {
const shouldRetry = await this.#handleCommandError(err, client, redirections); if (++i > maxCommandRedirections || !(err instanceof Error)) {
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>> {
if (redirections > (this.#options.maxCommandRedirections ?? 16)) {
throw err; throw err;
} }
if (err.message.startsWith('ASK')) { if (err.message.startsWith('ASK')) {
const url = err.message.substring(err.message.lastIndexOf(' ') + 1); const url = err.message.substring(err.message.lastIndexOf(' ') + 1);
let node = this.#slots.getNodeByUrl(url); if (this.#slots.getNodeByUrl(url)?.client === client) {
if (!node) { await client.asking();
await this.#slots.rediscover(client); continue;
node = this.#slots.getNodeByUrl(url); }
if (!node) { await this.#slots.rediscover(client);
const redirectTo = this.#slots.getNodeByUrl(url);
if (!redirectTo) {
throw new Error(`Cannot find node ${url}`); throw new Error(`Cannot find node ${url}`);
} }
}
await node.client.asking(); await redirectTo.client.asking();
return node.client; client = redirectTo.client;
continue;
} else if (err.message.startsWith('MOVED')) { } else if (err.message.startsWith('MOVED')) {
await this.#slots.rediscover(client); await this.#slots.rediscover(client);
return true; client = this.#slots.getClient(firstKey, isReadonly);
continue;
} }
throw err; 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
); );