Skip to content

Commit 98bdec2

Browse files
committed
feat(cluster): support scaling reads to slaves
The new option scaleReads is used to specify where to send the reads. Add two new events: 1. "+node": a new node is discovered. 2. "-node": a node is disconnected. BREAKING CHANGE: 1. Cluster#masterNodes and Cluster#nodes is removed. Use Cluster#nodes('masters') and Cluster#nodes('all') instead. 2. Cluster#to() is removed. Use Promise.all(Cluster#nodes().map(function (node) {})) instead. Closes #170.
1 parent 0a4186e commit 98bdec2

File tree

7 files changed

+333
-395
lines changed

7 files changed

+333
-395
lines changed

API.md

-86
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<dl>
33
<dt><a href="#Redis">Redis</a> ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code></dt>
44
<dd></dd>
5-
<dt><a href="#Cluster">Cluster</a> ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code></dt>
6-
<dd></dd>
75
<dt><a href="#Commander">Commander</a></dt>
86
<dd></dd>
97
</dl>
@@ -12,9 +10,6 @@
1210
<dt><a href="#defaultOptions">defaultOptions</a></dt>
1311
<dd><p>Default options</p>
1412
</dd>
15-
<dt><a href="#defaultOptions">defaultOptions</a></dt>
16-
<dd><p>Default options</p>
17-
</dd>
1813
</dl>
1914
<a name="Redis"></a>
2015
## Redis ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>
@@ -190,81 +185,6 @@ Define a custom command using lua script
190185
Create a Redis instance
191186

192187
**Kind**: static method of <code>[Redis](#Redis)</code>
193-
<a name="Cluster"></a>
194-
## Cluster ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>
195-
**Kind**: global class
196-
**Extends:** <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>, <code>[Commander](#Commander)</code>
197-
198-
* [Cluster](#Cluster) ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>
199-
* [new Cluster(startupNodes, options)](#new_Cluster_new)
200-
* [.disconnect()](#Cluster+disconnect)
201-
* [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ <code>Array.&lt;string&gt;</code>
202-
* [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ <code>object</code>
203-
* [.defineCommand(name, definition)](#Commander+defineCommand)
204-
* *[.sendCommand()](#Commander+sendCommand)*
205-
206-
<a name="new_Cluster_new"></a>
207-
### new Cluster(startupNodes, options)
208-
Creates a Redis Cluster instance
209-
210-
211-
| Param | Type | Default | Description |
212-
| --- | --- | --- | --- |
213-
| startupNodes | <code>Array.&lt;Object&gt;</code> | | An array of nodes in the cluster, [{ port: number, host: string }] |
214-
| options | <code>Object</code> | | |
215-
| [options.enableOfflineQueue] | <code>boolean</code> | <code>true</code> | See Redis class |
216-
| [options.lazyConnect] | <code>boolean</code> | <code>false</code> | See Redis class |
217-
| [options.readOnly] | <code>boolean</code> | <code>false</code> | Connect in READONLY mode |
218-
| [options.maxRedirections] | <code>number</code> | <code>16</code> | When a MOVED or ASK error is received, client will redirect the command to another node. This option limits the max redirections allowed to send a command. |
219-
| [options.clusterRetryStrategy] | <code>function</code> | | See "Quick Start" section |
220-
| [options.retryDelayOnFailover] | <code>number</code> | <code>2000</code> | When an error is received when sending a command(e.g. "Connection is closed." when the target Redis node is down), |
221-
| [options.retryDelayOnClusterDown] | <code>number</code> | <code>1000</code> | When a CLUSTERDOWN error is received, client will retry if `retryDelayOnClusterDown` is valid delay time. |
222-
223-
<a name="Cluster+disconnect"></a>
224-
### cluster.disconnect()
225-
Disconnect from every node in the cluster.
226-
227-
**Kind**: instance method of <code>[Cluster](#Cluster)</code>
228-
**Access:** public
229-
<a name="Commander+getBuiltinCommands"></a>
230-
### cluster.getBuiltinCommands() ⇒ <code>Array.&lt;string&gt;</code>
231-
Return supported builtin commands
232-
233-
**Kind**: instance method of <code>[Cluster](#Cluster)</code>
234-
**Returns**: <code>Array.&lt;string&gt;</code> - command list
235-
**Access:** public
236-
<a name="Commander+createBuiltinCommand"></a>
237-
### cluster.createBuiltinCommand(commandName) ⇒ <code>object</code>
238-
Create a builtin command
239-
240-
**Kind**: instance method of <code>[Cluster](#Cluster)</code>
241-
**Returns**: <code>object</code> - functions
242-
**Access:** public
243-
244-
| Param | Type | Description |
245-
| --- | --- | --- |
246-
| commandName | <code>string</code> | command name |
247-
248-
<a name="Commander+defineCommand"></a>
249-
### cluster.defineCommand(name, definition)
250-
Define a custom command using lua script
251-
252-
**Kind**: instance method of <code>[Cluster](#Cluster)</code>
253-
254-
| Param | Type | Default | Description |
255-
| --- | --- | --- | --- |
256-
| name | <code>string</code> | | the command name |
257-
| definition | <code>object</code> | | |
258-
| definition.lua | <code>string</code> | | the lua code |
259-
| [definition.numberOfKeys] | <code>number</code> | <code></code> | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command |
260-
261-
<a name="Commander+sendCommand"></a>
262-
### *cluster.sendCommand()*
263-
Send a command
264-
265-
**Kind**: instance abstract method of <code>[Cluster](#Cluster)</code>
266-
**Overrides:** <code>[sendCommand](#Commander+sendCommand)</code>
267-
**Access:** public
268188
<a name="Commander"></a>
269189
## Commander
270190
**Kind**: global class
@@ -331,9 +251,3 @@ Default options
331251

332252
**Kind**: global variable
333253
**Access:** protected
334-
<a name="defaultOptions"></a>
335-
## defaultOptions
336-
Default options
337-
338-
**Kind**: global variable
339-
**Access:** protected

lib/cluster/connection_pool.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict';
2+
3+
var util = require('util');
4+
var EventEmitter = require('events').EventEmitter;
5+
var _ = require('lodash');
6+
var Redis = require('../redis');
7+
8+
function ConnectionPool(redisOptions) {
9+
EventEmitter.call(this);
10+
this.redisOptions = redisOptions;
11+
12+
// this.masters + this.slaves = this.nodes
13+
this.nodes = {};
14+
this.masters = {};
15+
this.slaves = {};
16+
17+
this.specifiedOptions = {};
18+
}
19+
20+
util.inherits(ConnectionPool, EventEmitter);
21+
22+
ConnectionPool.prototype.findOrCreate = function (node, readOnly) {
23+
node.port = node.port || 6379;
24+
node.host = node.host || '127.0.0.1';
25+
node.key = node.key || node.host + ':' + node.port;
26+
27+
if (this.specifiedOptions[node.key]) {
28+
_.assign(node, this.specifiedOptions[node.key]);
29+
} else {
30+
this.specifiedOptions[node.key] = node;
31+
}
32+
33+
if (this.nodes[node.key] && this.nodes[node.key].options.readOnly !== readOnly) {
34+
this.remove(node.key);
35+
}
36+
37+
if (!this.nodes[node.key]) {
38+
var redis = this.nodes[node.key] = new Redis(_.defaults({
39+
retryStrategy: null,
40+
enableOfflineQueue: true,
41+
readOnly: readOnly
42+
}, node, this.redisOptions, { lazyConnect: true }));
43+
this[readOnly ? 'slaves' : 'masters'][node.key] = redis;
44+
45+
var _this = this;
46+
redis.once('end', function () {
47+
delete _this.nodes[node.key];
48+
delete _this.masters[node.key];
49+
delete _this.slaves[node.key];
50+
_this.emit('-node', redis);
51+
if (!Object.keys(_this.nodes).length) {
52+
_this.emit('drain');
53+
}
54+
});
55+
56+
this.emit('+node', redis);
57+
}
58+
59+
return this.nodes[node.key];
60+
};
61+
62+
ConnectionPool.prototype.remove = function (key) {
63+
if (this.nodes[key]) {
64+
this.nodes[key].disconnect();
65+
delete this.nodes[key];
66+
delete this.masters[key];
67+
delete this.slaves[key];
68+
}
69+
};
70+
71+
ConnectionPool.prototype.reset = function (nodes) {
72+
var newNodes = {};
73+
for (var i = 0; i < nodes.length; i++) {
74+
var node = nodes[i];
75+
node.key = node.host + ':' + node.port;
76+
newNodes[node.key] = node;
77+
}
78+
var _this = this;
79+
Object.keys(this.nodes).forEach(function (key) {
80+
if (!newNodes[key]) {
81+
_this.remove(key);
82+
}
83+
});
84+
Object.keys(newNodes).forEach(function (key) {
85+
_this.findOrCreate(newNodes[key], newNodes[key].readOnly);
86+
});
87+
};
88+
89+
module.exports = ConnectionPool;

0 commit comments

Comments
 (0)