You've already forked node-redis
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:
@@ -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 => {
|
||||
|
Reference in New Issue
Block a user