Cluster
Redis Cluster provides a way to run a Redis installation where data is automatically sharded across multiple Redis nodes. You can connect to a Redis Cluster like this:
const Redis = require("ioredis");
const cluster = new Redis.Cluster([
{
port: 6380,
host: "127.0.0.1",
},
{
port: 6381,
host: "127.0.0.1",
},
]);
cluster.set("foo", "bar");
cluster.get("foo", (err, res) => {
// res === 'bar'
});
Cluster
constructor accepts two arguments, where:
- The first argument is a list of nodes of the cluster you want to connect to. Just like Sentinel, the list does not need to enumerate all your cluster nodes, but a few so that if one is unreachable the client will try the next one, and the client will discover other nodes automatically when at least one node is connected.
-
The second argument is the options, where:
clusterRetryStrategy
: When none of the startup nodes are reachable,clusterRetryStrategy
will be invoked. When a number is returned, ioredis will try to reconnect to the startup nodes from scratch after the specified delay (in ms). Otherwise, an error of "None of startup nodes is available" will be returned. The default value of this option is:
It's possible to modify the
startupNodes
property in order to switch to another set of nodes here:JavaScriptfunction (times) { this.startupNodes = [{ port: 6790, host: '127.0.0.1' }]; return Math.min(100 + times * 2, 2000); }
dnsLookup
: Alternative DNS lookup function (dns.lookup()
is used by default). It may be useful to override this in special cases, such as when AWS ElastiCache used with TLS enabled.enableOfflineQueue
: Similar to theenableOfflineQueue
option ofRedis
class.enableReadyCheck
: When enabled, "ready" event will only be emitted whenCLUSTER INFO
command reporting the cluster is ready for handling commands. Otherwise, it will be emitted immediately after "connect" is emitted.scaleReads
: Config where to send the read queries. See below for more details.maxRedirections
: When a cluster related error (e.g.MOVED
,ASK
andCLUSTERDOWN
etc.) is received, the client will redirect the command to another node. This option limits the max redirections allowed when sending a command. The default value is16
.retryDelayOnFailover
: If the target node is disconnected when sending a command, ioredis will retry after the specified delay. The default value is100
. You should make sureretryDelayOnFailover * maxRedirections > cluster-node-timeout
to insure that no command will fail during a failover.retryDelayOnClusterDown
: When a cluster is down, all commands will be rejected with the error ofCLUSTERDOWN
. If this option is a number (by default, it is100
), the client will resend the commands after the specified time (in ms).retryDelayOnTryAgain
: If this option is a number (by default, it is100
), the client will resend the commands rejected withTRYAGAIN
error after the specified time (in ms).retryDelayOnMoved
: By default, this value is0
(in ms), which means when aMOVED
error is received, the client will resend the command instantly to the node returned together with theMOVED
error. However, sometimes it takes time for a cluster to become state stabilized after a failover, so adding a delay before resending can prevent a ping pong effect.redisOptions
: Default options passed to the constructor ofRedis
when connecting to a node.slotsRefreshTimeout
: Milliseconds before a timeout occurs while refreshing slots from the cluster (default1000
).slotsRefreshInterval
: Milliseconds between every automatic slots refresh (default5000
).
Read-write splitting
A typical redis cluster contains three or more masters and several slaves for each master. It's possible to scale out redis cluster by sending read queries to slaves and write queries to masters by setting the scaleReads
option.
scaleReads
is "master" by default, which means ioredis will never send any queries to slaves. There are other three available options:
- "all": Send write queries to masters and read queries to masters or slaves randomly.
- "slave": Send write queries to masters and read queries to slaves.
- a custom
function(nodes, command): node
: Will choose the custom function to select to which node to send read queries (write queries keep being sent to master). The first node innodes
is always the master serving the relevant slots. If the function returns an array of nodes, a random node of that list will be selected.
For example:
const cluster = new Redis.Cluster(
[
/* nodes */
],
{
scaleReads: "slave",
}
);
cluster.set("foo", "bar"); // This query will be sent to one of the masters.
cluster.get("foo", (err, res) => {
// This query will be sent to one of the slaves.
});
NB In the code snippet above, the res
may not be equal to "bar" because of the lag of replication between the master and slaves.
Running commands to multiple nodes
Every command will be sent to exactly one node. For commands containing keys, (e.g. GET
, SET
and HGETALL
), ioredis sends them to the node that serving the keys, and for other commands not containing keys, (e.g. INFO
, KEYS
and FLUSHDB
), ioredis sends them to a random node.
Sometimes you may want to send a command to multiple nodes (masters or slaves) of the cluster, you can get the nodes via Cluster#nodes()
method.
Cluster#nodes()
accepts a parameter role, which can be "master", "slave" and "all" (default), and returns an array of Redis
instance. For example:
// Send `FLUSHDB` command to all slaves:
const slaves = cluster.nodes("slave");
Promise.all(slaves.map((node) => node.flushdb()));
// Get keys of all the masters:
const masters = cluster.nodes("master");
Promise.all(
masters
.map((node) => node.keys())
.then((keys) => {
// keys: [['key1', 'key2'], ['key3', 'key4']]
})
);
NAT Mapping
Sometimes the cluster is hosted within a internal network that can only be accessed via a NAT (Network Address Translation) instance. See Accessing ElastiCache from outside AWS as an example.
You can specify nat mapping rules via natMap
option:
const cluster = new Redis.Cluster(
[
{
host: "203.0.113.73",
port: 30001,
},
],
{
natMap: {
"10.0.1.230:30001": { host: "203.0.113.73", port: 30001 },
"10.0.1.231:30001": { host: "203.0.113.73", port: 30002 },
"10.0.1.232:30001": { host: "203.0.113.73", port: 30003 },
},
}
);
This option is also useful when the cluster is running inside a Docker container.
Transaction and pipeline in Cluster mode
Almost all features that are supported by Redis
are also supported by Redis.Cluster
, e.g. custom commands, transaction and pipeline.
However there are some differences when using transaction and pipeline in Cluster mode:
- All keys in a pipeline should belong to slots served by the same node, since ioredis sends all commands in a pipeline to the same node.
- You can't use
multi
without pipeline (akacluster.multi({ pipeline: false })
). This is because when you callcluster.multi({ pipeline: false })
, ioredis doesn't know which node themulti
command should be sent to.
When any commands in a pipeline receives a MOVED
or ASK
error, ioredis will resend the whole pipeline to the specified node automatically if all of the following conditions are satisfied:
- All errors received in the pipeline are the same. For example, we won't resend the pipeline if we got two
MOVED
errors pointing to different nodes. - All commands executed successfully are readonly commands. This makes sure that resending the pipeline won't have side effects.
Pub/Sub
Pub/Sub in cluster mode works exactly as the same as in standalone mode. Internally, when a node of the cluster receives a message, it will broadcast the message to the other nodes. ioredis makes sure that each message will only be received once by strictly subscribing one node at the same time.
const nodes = [
/* nodes */
];
const pub = new Redis.Cluster(nodes);
const sub = new Redis.Cluster(nodes);
sub.on("message", (channel, message) => {
console.log(channel, message);
});
sub.subscribe("news", () => {
pub.publish("news", "highlights");
});
Events
Event | Description |
---|---|
connect | emits when a connection is established to the Redis server. |
ready | emits when CLUSTER INFO reporting the cluster is able to receive commands (if enableReadyCheck is true ) or immediately after connect event (if enableReadyCheck is false). |
error | emits when an error occurs while connecting with a property of lastNodeError representing the last node error received. This event is emitted silently (only emitting if there's at least one listener). |
close | emits when an established Redis server connection has closed. |
reconnecting | emits after close when a reconnection will be made. The argument of the event is the time (in ms) before reconnecting. |
end | emits after close when no more reconnections will be made. |
+node | emits when a new node is connected. |
-node | emits when a node is disconnected. |
node error | emits when an error occurs when connecting to a node. The second argument indicates the address of the node. |
Password
Setting the password
option to access password-protected clusters:
const Redis = require("ioredis");
const cluster = new Redis.Cluster(nodes, {
redisOptions: {
password: "your-cluster-password",
},
});
If some of nodes in the cluster using a different password, you should specify them in the first parameter:
const Redis = require("ioredis");
const cluster = new Redis.Cluster(
[
// Use password "password-for-30001" for 30001
{ port: 30001, password: "password-for-30001" },
// Don't use password when accessing 30002
{ port: 30002, password: null },
// Other nodes will use "fallback-password"
],
{
redisOptions: {
password: "fallback-password",
},
}
);
Special note: AWS ElastiCache Clusters with TLS
AWS ElastiCache for Redis (Clustered Mode) supports TLS encryption. If you use
this, you may encounter errors with invalid certificates. To resolve this
issue, construct the Cluster
with the dnsLookup
option as follows:
const cluster = new Redis.Cluster(
[
{
host: "clustercfg.myCluster.abcdefg.xyz.cache.amazonaws.com",
port: 6379,
},
],
{
dnsLookup: (address, callback) => callback(null, address),
redisOptions: {
tls: {},
},
}
);