Sitelet https://github.com/redis/node-redis/blob/master/docs/clustering.md
Skip to content

Latest commit

 

History

History
199 lines (151 loc) · 12.6 KB

File metadata and controls

199 lines (151 loc) · 12.6 KB

Clustering

Basic Example

Connecting to a cluster is a bit different. Create the client by specifying some (or all) of the nodes in your cluster and then use it like a regular client instance:

import { createCluster } from 'redis';

const cluster = await createCluster({
    rootNodes: [{
      url: 'redis://10.0.0.1:30001'
    }, {
      url: 'redis://10.0.0.2:30002'
    }]
  })
  .on('error', err => console.log('Redis Cluster Error', err))
  .connect();

await cluster.set('key', 'value');
const value = await cluster.get('key');
await cluster.close();

createCluster configuration

See the client configuration page for the rootNodes and defaults configuration schemas.

Property Default Description
rootNodes An array of root nodes that are part of the cluster, which will be used to get the cluster topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster: 3 should be enough to reliably connect and obtain the cluster configuration from the server
defaults The default configuration values for every client in the cluster. Use this for example when specifying an ACL user to connect with
useReplicas false When true, distribute load by executing readonly commands (such as GET, GEOSEARCH, etc.) across all cluster nodes. When false, only use master nodes
minimizeConnections false When true, .connect() will only discover the cluster topology, without actually connecting to all the nodes. Useful for short-term or Pub/Sub-only connections.
maxCommandRedirections 16 The maximum number of times a command will be redirected due to MOVED or ASK errors
nodeAddressMap Defines the node address mapping
keyPrefix Prefix prepended to every key sent to Redis (ioredis-compatible). See Key Prefixing.
commandOptions Default Command Options (e.g. timeout, typeMapping) for commands executed on the cluster
clientSideCache Client Side Caching configuration for the cluster (RESP3 only; shared across cluster nodes). See V5 docs
topologyRefreshOnReconnectionAttemptStrategy 5000 Delay in ms after a node reconnection attempt before background cluster topology refreshes are triggered. Set to false or 0 to disable, or provide a dynamic strategy function
modules Included Redis Modules
scripts Script definitions (see Lua Scripts)
functions Function definitions (see Functions)

Usage

Most redis commands are the same as with individual clients.

Unsupported Redis Commands

If you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use .sendCommand().

When clustering, sendCommand takes 3 arguments to help with routing to the correct redis node:

  • firstKey: the key that is being operated on, or undefined to route to a random node.
  • isReadOnly: determines if the command needs to go to the master or may go to a replica.
  • args: the command and all arguments (including the key), as an array of strings.
await cluster.sendCommand("key", false, ["SET", "key", "value", "NX"]); // 'OK'

await cluster.sendCommand("key", true, ["HGETALL", "key"]); // ['key1', 'field1', 'key2', 'field2']

Root nodes vs. defaults

The configuration in rootNodes is only used for the connections that discover the cluster topology. It is not inherited by the connections the cluster then makes to the discovered nodes — those are created from the defaults option (plus the discovered host and port).

This means that any setting that should apply to every connection in the cluster — credentials, TLS, timeouts, etc. — must be specified via defaults, even if it is already present in a root node URL or configuration.

Auth with password and username

Specifying the password in the URL or a root node will only affect the connection used for topology discovery. In case you want to set the password for all the connections being created from a cluster instance, use the defaults option.

createCluster({
  rootNodes: [{
    url: 'redis://10.0.0.1:30001'
  }, {
    url: 'redis://10.0.0.2:30002'
  }],
  defaults: {
    username: 'username',
    password: 'password'
  }
});

TLS

Likewise, TLS options specified in the URL (rediss://) or in a root node only affect the topology discovery connection. If your cluster requires TLS (e.g. AWS ElastiCache with in-transit encryption enabled), enable it via defaults.socket, otherwise the connections to the discovered nodes will be attempted in plaintext and can hang without an error:

createCluster({
  rootNodes: [{
    url: 'rediss://external-host.io:30001'
  }],
  defaults: {
    socket: {
      tls: true
    }
  }
});

Node Address Map

A mapping between the addresses in the cluster (see CLUSTER SHARDS) and the addresses the client should connect to. Useful when the cluster is running on a different network to the client.

const rootNodes = [{
  url: 'external-host-1.io:30001'
}, {
  url: 'external-host-2.io:30002'
}];

// Use either a static mapping:
createCluster({
  rootNodes,
  nodeAddressMap: {
    '10.0.0.1:30001': {
      host: 'external-host.io',
      port: 30001
    },
    '10.0.0.2:30002': {
      host: 'external-host.io',
      port: 30002
    }
  }
});

// or create the mapping dynamically, as a function:
createCluster({
  rootNodes,
  nodeAddressMap(address) {
    const indexOfDash = address.lastIndexOf('-'),
      indexOfDot = address.indexOf('.', indexOfDash),
      indexOfColons = address.indexOf(':', indexOfDot);
    
    return {
      host: `external-host-${address.substring(indexOfDash + 1, indexOfDot)}.io`,
      port: Number(address.substring(indexOfColons + 1))
    };
  }
});

This is a common problem when using ElastiCache. See Accessing ElastiCache from outside AWS for more information on that.

Events

The Node Redis Cluster class extends Node.js’s EventEmitter and emits the following events:

Name When Listener arguments
connect The cluster has successfully connected and is ready to us No arguments
disconnect The cluster has disconnected No arguments
error The cluster has errored (error: Error)
node-ready A cluster node is ready to use, on its first connection and on every reconnection (node: { host: string, port: number })
node-connect A cluster node's socket has connected, before its handshake — on the first connection and on every reconnection (node: { host: string, port: number })
node-reconnecting A cluster node is attempting to reconnect after an error (node: { host: string, port: number })
node-disconnect A cluster node has disconnected (via close() or destroy()) (node: { host: string, port: number })
node-error A cluster node has has errored (usually during TCP connection) (error: Error, node: { host: string, port: number })

⚠️ You MUST listen to error events. If a cluster doesn't have at least one error listener registered and an error occurs, that error will be thrown and the Node.js process will exit. See the > EventEmitter docs for more details.

Command Routing

Commands that operate on Redis Keys

Commands such as GET, SET, etc. are routed by the first key specified. For example MGET 1 2 3 will be routed by the key 1.

Admin commands such as MEMORY STATS, FLUSHALL, etc. are not attached to the cluster, and must be executed on a specific node via .getSlotMaster().

"Forwarded Commands"

Certain commands (e.g. PUBLISH) are forwarded to other cluster nodes by the Redis server. The client sends these commands to a random node in order to spread the load across the cluster.

Transactions with WATCH

WATCH relies on connection-level state on a specific node, so it isn't exposed directly on the cluster client. Use .getNodeClientForKey() to get the node client responsible for a key's slot and run the optimistic-locking transaction on it:

const key = 'key';
const nodeClient = await cluster.getNodeClientForKey(key);

await nodeClient.watch(key);
const value = await nodeClient.get(key);
const reply = await nodeClient
  .multi()
  .set(key, calculateNewValue(value)) // application logic
  .exec(); // `null` if `key` changed since `WATCH`, retry in that case

All keys touched in the transaction must hash to the same slot. Pass true as the second argument (getNodeClientForKey(key, true)) to allow a replica for read-only use.