1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-17 19:41:06 +03:00

better cluster nodes discorvery strategy after MOVED error, add PubSub test

This commit is contained in:
leibale
2021-06-11 17:29:20 -04:00
parent 71242304bc
commit c29c1bb7d2
7 changed files with 312 additions and 9 deletions

View File

@@ -4,6 +4,7 @@ import { itWithClient, TEST_REDIS_SERVERS, TestRedisServers } from './test-utils
import RedisClient from './client';
import { AbortError } from './errors';
import { defineScript } from './lua-script';
import { spy } from 'sinon';
describe('Client', () => {
describe('authentication', () => {
@@ -342,4 +343,50 @@ describe('Client', () => {
keys.sort()
);
});
itWithClient(TestRedisServers.OPEN, 'PubSub', async publisher => {
const subscriber = publisher.duplicate();
await subscriber.connect();
try {
const channelListener1 = spy(),
channelListener2 = spy(),
patternListener = spy();
await Promise.all([
subscriber.subscribe('channel', channelListener1),
subscriber.subscribe('channel', channelListener2),
subscriber.pSubscribe('channel*', patternListener)
]);
await publisher.publish('channel', 'message');
assert.ok(channelListener1.calledOnceWithExactly('message', 'channel'));
assert.ok(channelListener2.calledOnceWithExactly('message', 'channel'));
assert.ok(patternListener.calledOnceWithExactly('message', 'channel'));
await subscriber.unsubscribe('channel', channelListener1);
await publisher.publish('channel', 'message');
assert.ok(channelListener1.calledOnce);
assert.ok(channelListener2.calledTwice);
assert.ok(channelListener2.secondCall.calledWithExactly('message', 'channel'));
assert.ok(patternListener.calledTwice);
assert.ok(patternListener.secondCall.calledWithExactly('message', 'channel'));
await subscriber.unsubscribe('channel');
await publisher.publish('channel', 'message');
assert.ok(channelListener1.calledOnce);
assert.ok(channelListener2.calledTwice);
assert.ok(patternListener.calledThrice);
assert.ok(patternListener.thirdCall.calledWithExactly('message', 'channel'));
await subscriber.pUnsubscribe();
await publisher.publish('channel', 'message');
assert.ok(channelListener1.calledOnce);
assert.ok(channelListener2.calledTwice);
assert.ok(patternListener.calledThrice);
} finally {
await subscriber.disconnect();
}
});
});

View File

@@ -306,19 +306,19 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
return promise;
}
UNSUBSCRIBE(channels: string | Array<string>, listener?: PubSubListener): Promise<void> {
UNSUBSCRIBE(channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
return this.#unsubscribe(PubSubUnsubscribeCommands.UNSUBSCRIBE, channels, listener);
}
unsubscribe = this.UNSUBSCRIBE;
PUNSUBSCRIBE(patterns: string | Array<string>, listener?: PubSubListener): Promise<void> {
PUNSUBSCRIBE(patterns?: string | Array<string>, listener?: PubSubListener): Promise<void> {
return this.#unsubscribe(PubSubUnsubscribeCommands.PUNSUBSCRIBE, patterns, listener);
}
pUnsubscribe = this.PUNSUBSCRIBE;
#unsubscribe(command: PubSubUnsubscribeCommands, channels: string | Array<string>, listener?: PubSubListener): Promise<void> {
#unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
const promise = this.#queue.unsubscribe(command, channels, listener);
this.#tick();
return promise;

View File

@@ -32,9 +32,17 @@ export default class RedisClusterSlots {
throw new Error('None of the root nodes is available');
}
async discover(): Promise<void> {
// TODO: shuffle?
async discover(startWith: RedisClient): Promise<void> {
try {
await this.#discoverNodes(startWith.options?.socket);
return;
} catch (err) {
// this.emit('error', err);
}
for (const client of this.#clientByKey.values()) {
if (client === startWith) continue;
try {
await this.#discoverNodes(client.options?.socket);
return;

View File

@@ -61,7 +61,7 @@ export default class RedisCluster {
if (err.message.startsWith('ASK')) {
// TODO
} else if (err.message.startsWith('MOVED')) {
await this.#slots.discover();
await this.#slots.discover(client);
if (redirections < (this.#options.maxCommandRedirections ?? 16)) {
return this.sendCommand(args, firstKeyIndex, isReadonly, redirections + 1);

View File

@@ -204,9 +204,15 @@ export default class RedisCommandsQueue {
return this.#pushPubSubCommand(command, channelsToSubscribe);
}
unsubscribe(command: PubSubUnsubscribeCommands, channels: string | Array<string>, listener?: PubSubListener) {
const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns,
channelsToUnsubscribe = [];
unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener) {
const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns;
if (!channels) {
const keys = [...listeners.keys()];
listeners.clear();
return this.#pushPubSubCommand(command, keys);
}
const channelsToUnsubscribe = [];
for (const channel of (Array.isArray(channels) ? channels : [channels])) {
const set = listeners.get(channel);
if (!set) continue;