1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

Add support for sharded PubSub (#2373)

* refactor pubsub, add support for sharded pub sub

* run tests in redis 7 only, fix PUBSUB SHARDCHANNELS test

* add some comments and fix some bugs

* PubSubType, not PubSubTypes 🤦‍♂️

* remove test.txt

* fix some bugs, add tests

* add some tests

* fix #2345 - allow PING in PubSub mode (remove client side validation)

* remove .only

* revert changes in cluster/index.ts

* fix tests minimum version

* handle server sunsubscribe

* add 'sharded-channel-moved' event to docs, improve the events section in the main README (fix #2302)

* exit "resubscribe" if pubsub not active

* Update commands-queue.ts

* Release client@1.5.0-rc.0

* WIP

* use `node:util` instead of `node:util/types` (to support node 14)

* run PubSub resharding test with Redis 7+

* fix inconsistency in live resharding test

* add some tests

* fix iterateAllNodes when starting from a replica

* fix iterateAllNodes random

* fix slotNodesIterator

* fix slotNodesIterator

* clear pubSubNode when node in use

* wait for all nodes cluster state to be ok before testing

* `cluster.minimizeConections` tests

* `client.reconnectStrategry = false | 0` tests

* sharded pubsub + cluster 🎉

* add minimum version to sharded pubsub tests

* add cluster sharded pubsub live reshard test, use stable dockers for tests, make sure to close pubsub clients when a node disconnects from the cluster

* fix "ssubscribe & sunsubscribe" test

* lock search docker to 2.4.9

* change numberOfMasters default to 2

* use edge for bloom

* add tests

* add back getMasters and getSlotMaster as deprecated functions

* add some tests

* fix reconnect strategy + docs

* sharded pubsub docs

* Update pub-sub.md

* some jsdoc, docs, cluster topology test

* clean pub-sub docs

Co-authored-by: Simon Prickett <simon@redislabs.com>

* reconnect startegy docs and bug fix

Co-authored-by: Simon Prickett <simon@redislabs.com>

* refine jsdoc and some docs

Co-authored-by: Simon Prickett <simon@redislabs.com>

* I'm stupid

* fix cluster topology test

* fix cluster topology test

* Update README.md

* Update clustering.md

* Update pub-sub.md

Co-authored-by: Simon Prickett <simon@redislabs.com>
This commit is contained in:
Leibale Eidelman
2023-01-25 11:00:39 -05:00
committed by GitHub
parent e75a5db3e4
commit 3b1bad2296
41 changed files with 2415 additions and 768 deletions

View File

@@ -2,14 +2,20 @@ import { strict as assert } from 'assert';
import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
import RedisClient, { RedisClientType } from '.';
import { RedisClientMultiCommandType } from './multi-command';
import { RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands';
import { AbortError, ClientClosedError, ClientOfflineError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors';
import { RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands';
import { AbortError, ClientClosedError, ClientOfflineError, ConnectionTimeoutError, DisconnectsClientError, ErrorReply, SocketClosedUnexpectedlyError, WatchError } from '../errors';
import { defineScript } from '../lua-script';
import { spy } from 'sinon';
import { once } from 'events';
import { ClientKillFilters } from '../commands/CLIENT_KILL';
import { ClusterSlotStates } from '../commands/CLUSTER_SETSLOT';
import { promisify } from 'util';
// We need to use 'require', because it's not possible with Typescript to import
// function that are exported as 'module.exports = function`, without esModuleInterop
// set to true.
const calculateSlot = require('cluster-key-slot');
export const SQUARE_SCRIPT = defineScript({
SCRIPT: 'return ARGV[1] * ARGV[1];',
NUMBER_OF_KEYS: 0,
@@ -817,7 +823,34 @@ describe('Client', () => {
}
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('should be able to quit in PubSub mode', async client => {
testUtils.testWithClient('should be able to PING in PubSub mode', async client => {
await client.connect();
try {
await client.subscribe('channel', () => {
// noop
});
const [string, buffer, customString, customBuffer] = await Promise.all([
client.ping(),
client.ping(client.commandOptions({ returnBuffers: true })),
client.ping('custom'),
client.ping(client.commandOptions({ returnBuffers: true }), 'custom')
]);
assert.equal(string, 'pong');
assert.deepEqual(buffer, Buffer.from('pong'));
assert.equal(customString, 'custom');
assert.deepEqual(customBuffer, Buffer.from('custom'));
} finally {
await client.disconnect();
}
}, {
...GLOBAL.SERVERS.OPEN,
disableClientSetup: true
});
testUtils.testWithClient('should be able to QUIT in PubSub mode', async client => {
await client.subscribe('channel', () => {
// noop
});
@@ -826,6 +859,122 @@ describe('Client', () => {
assert.equal(client.isOpen, false);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('should reject GET in PubSub mode', async client => {
await client.connect();
try {
await client.subscribe('channel', () => {
// noop
});
await assert.rejects(client.get('key'), ErrorReply);
} finally {
await client.disconnect();
}
}, {
...GLOBAL.SERVERS.OPEN,
disableClientSetup: true
});
describe('shareded PubSub', () => {
testUtils.isVersionGreaterThanHook([7]);
testUtils.testWithClient('should be able to receive messages', async publisher => {
const subscriber = publisher.duplicate();
await subscriber.connect();
try {
const listener = spy();
await subscriber.sSubscribe('channel', listener);
await Promise.all([
waitTillBeenCalled(listener),
publisher.sPublish('channel', 'message')
]);
assert.ok(listener.calledOnceWithExactly('message', 'channel'));
await subscriber.sUnsubscribe();
// should be able to send commands
await assert.doesNotReject(subscriber.ping());
} finally {
await subscriber.disconnect();
}
}, {
...GLOBAL.SERVERS.OPEN
});
testUtils.testWithClient('should emit sharded-channel-moved event', async publisher => {
await publisher.clusterAddSlotsRange({ start: 0, end: 16383 });
const subscriber = publisher.duplicate();
await subscriber.connect();
try {
await subscriber.sSubscribe('channel', () => {});
await Promise.all([
publisher.clusterSetSlot(
calculateSlot('channel'),
ClusterSlotStates.NODE,
await publisher.clusterMyId()
),
once(subscriber, 'sharded-channel-moved')
]);
assert.equal(
await subscriber.ping(),
'PONG'
);
} finally {
await subscriber.disconnect();
}
}, {
serverArguments: ['--cluster-enabled', 'yes']
});
});
testUtils.testWithClient('should handle errors in SUBSCRIBE', async publisher => {
const subscriber = publisher.duplicate();
await subscriber.connect();
try {
const listener1 = spy();
await subscriber.subscribe('1', listener1);
await publisher.aclSetUser('default', 'resetchannels');
const listener2 = spy();
await assert.rejects(subscriber.subscribe('2', listener2));
await Promise.all([
waitTillBeenCalled(listener1),
publisher.aclSetUser('default', 'allchannels'),
publisher.publish('1', 'message'),
]);
assert.ok(listener1.calledOnceWithExactly('message', '1'));
await subscriber.subscribe('2', listener2);
await Promise.all([
waitTillBeenCalled(listener2),
publisher.publish('2', 'message'),
]);
assert.ok(listener2.calledOnceWithExactly('message', '2'));
} finally {
await subscriber.disconnect();
}
}, {
// this test change ACL rules, running in isolated server
serverArguments: [],
minimumDockerVersion: [6 ,2] // ACL PubSub rules were added in Redis 6.2
});
});
testUtils.testWithClient('ConnectionTimeoutError', async client => {