1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-17 19:41:06 +03:00
Files
node-redis/lib/cluster-slots.ts
leibale 38b2bd75fb spawn redis-servers for tests,
add some tests,
fix client auth on connect
2021-05-12 18:27:35 -04:00

113 lines
3.3 KiB
TypeScript

import calculateSlot from 'cluster-key-slot';
import RedisClient from './client.js';
import { RedisSocketOptions } from './socket.js';
import { RedisClusterNode } from './commands/CLUSTER_NODES.js';
import { RedisClusterOptions } from './cluster.js';
export default class RedisClusterSlots {
readonly #options: RedisClusterOptions;
readonly #clientByKey = new Map<string, RedisClient>();
readonly #slots: Array<RedisClient> = [];
constructor(options: RedisClusterOptions) {
this.#options = options;
}
async connect(): Promise<void> {
// TODO: if connected use a random client?
for (const rootNode of this.#options.rootNodes) {
try {
await this.#discoverNodes(rootNode);
} catch (err) {
// this.emit('error', err);
}
}
throw new Error('None of the root nodes was available');
}
async #discoverNodes(socketOptions: RedisSocketOptions) {
const client = RedisClient.create({
socket: socketOptions,
modules: this.#options?.modules
});
await client.connect();
try {
await this.#reset(await client.clusterNodes());
} finally {
await client.disconnect(); // TODO: catch error from disconnect?
}
}
async #reset(nodes: Array<RedisClusterNode>): Promise<void> {
// Override this.#slots and add not existing clients to this.#clientByKey
const promises = [],
clientsInUse = new Set();
for (const {url, slots} of nodes) {
clientsInUse.add(url);
let client = this.#clientByKey.get(url);
if (!client) {
// TODO: client configuration
client = RedisClient.create();
promises.push(client.connect());
}
for (const slot of slots) {
for (let i = slot.from; i < slot.to; i++) {
this.#slots[i] = client;
}
}
}
// Remove unused clients from this.#clientBykey using clientsInUse
for (const [key, client] of this.#clientByKey.entries()) {
if (clientsInUse.has(key)) continue;
// TODO: ignore error from `.disconnect`?
promises.push(client.disconnect());
this.#clientByKey.delete(key);
}
}
#getSlotClient(slot: number): RedisClient {
return this.#slots[slot];
}
#randomClientIterator?: IterableIterator<RedisClient>;
#getRandomClient(): RedisClient {
if (!this.#clientByKey.size) {
throw new Error('Cluster is not connected');
}
if (!this.#randomClientIterator) {
this.#randomClientIterator = this.#clientByKey.values();
}
const {done, value} = this.#randomClientIterator.next();
if (done) {
this.#randomClientIterator = undefined;
return this.#getRandomClient();
}
return value;
}
getClient(firstKey?: string): RedisClient {
if (!firstKey) {
return this.#getRandomClient();
}
return this.#getSlotClient(calculateSlot(firstKey));
}
async disconnect(): Promise<void> {
await Promise.all(
[...this.#clientByKey.values()].map(client => client.disconnect())
);
}
}