Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: v4 #642

Merged
merged 15 commits into from
Jul 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
language: node_js

node_js:
- '0.11'
- '0.12'
- '4'
- '5'
- '6'
- '8'
- '9'
Expand All @@ -19,18 +15,20 @@ script:
env:
- CXX=g++-4.8 INSTALL_HIREDIS="yes"
- INSTALL_HIREDIS="no"
- CC_TEST_REPORTER_ID="4cee2f60edbf31acac6ddff823f0b93e2e9882c3e5c55130049e0fd878549f84"

before_script:
- if [ "$INSTALL_HIREDIS" = "yes" ]; then npm install hiredis; fi
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build

addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
code_climate:
repo_token: 4cee2f60edbf31acac6ddff823f0b93e2e9882c3e5c55130049e0fd878549f84

after_success:
- if [ "$INSTALL_HIREDIS" = "yes" ]; then cat ./coverage/lcov.info | ./node_modules/.bin/codeclimate-test-reporter; fi
- if [ "$INSTALL_HIREDIS" = "yes" ]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; fi
12 changes: 8 additions & 4 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* [Redis](#Redis) ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>
* [new Redis([port], [host], [options])](#new_Redis_new)
* _instance_
* [.connect([callback])](#Redis+connect) ⇒ <code>Promise</code>
* [.connect([callback])](#Redis+connect) ⇒ <code>Promise.&lt;void&gt;</code>
* [.disconnect()](#Redis+disconnect)
* ~~[.end()](#Redis+end)~~
* [.duplicate()](#Redis+duplicate)
Expand Down Expand Up @@ -68,7 +68,6 @@ Creates a Redis instance
var Redis = require('ioredis');

var redis = new Redis();
// or: var redis = Redis();

var redisOnPort6380 = new Redis(6380);
var anotherRedis = new Redis(6380, '192.168.100.1');
Expand All @@ -80,9 +79,13 @@ var authedRedis = new Redis(6380, '192.168.100.1', { password: 'password' });
```
<a name="Redis+connect"></a>

### redis.connect([callback]) ⇒ <code>Promise</code>
### redis.connect([callback]) ⇒ <code>Promise.&lt;void&gt;</code>
Create a connection to Redis.
This method will be invoked automatically when creating a new Redis instance.
This method will be invoked automatically when creating a new Redis instance
unless `lazyConnect: true` is passed.

When calling this method manually, a Promise is returned, which will
be resolved when the connection status is ready.

**Kind**: instance method of [<code>Redis</code>](#Redis)
**Access**: public
Expand Down Expand Up @@ -234,6 +237,7 @@ Creates a Redis Cluster instance
| [options.retryDelayOnClusterDown] | <code>number</code> | <code>100</code> | When a CLUSTERDOWN error is received, client will retry if `retryDelayOnClusterDown` is valid delay time. |
| [options.retryDelayOnTryAgain] | <code>number</code> | <code>100</code> | When a TRYAGAIN error is received, client will retry if `retryDelayOnTryAgain` is valid delay time. |
| [options.slotsRefreshTimeout] | <code>number</code> | <code>1000</code> | The milliseconds before a timeout occurs while refreshing slots from the cluster. |
| [options.slotsRefreshInterval] | <code>number</code> | <code>5000</code> | The milliseconds between every automatic slots refresh. |
| [options.redisOptions] | <code>Object</code> | | Passed to the constructor of `Redis`. |

<a name="Cluster+connect"></a>
Expand Down
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ used in the world's biggest online commerce company [Alibaba](http://www.alibaba

0. Full-featured. It supports [Cluster](http://redis.io/topics/cluster-tutorial), [Sentinel](http://redis.io/topics/sentinel), [Pipelining](http://redis.io/topics/pipelining) and of course [Lua scripting](http://redis.io/commands/eval) & [Pub/Sub](http://redis.io/topics/pubsub) (with the support of binary messages).
0. High performance.
0. Delightful API. It works with Node callbacks and [Bluebird promises](https://github.com/petkaantonov/bluebird).
0. Delightful API. It works with Node callbacks and Native promises.
0. Transformation of command arguments and replies.
0. Transparent key prefixing.
0. Abstraction for Lua scripting, allowing you to define custom commands.
Expand Down Expand Up @@ -490,7 +490,8 @@ var redis = new Redis();
var stream = redis.scanStream();
var keys = [];
stream.on('data', function (resultKeys) {
// `resultKeys` is an array of strings representing key names
// `resultKeys` is an array of strings representing key names.
// Note that resultKeys may contain 0 keys.
for (var i = 0; i < resultKeys.length; i++) {
keys.push(resultKeys[i]);
}
Expand Down Expand Up @@ -525,6 +526,26 @@ var stream = redis.hscanStream('myhash', {

You can learn more from the [Redis documentation](http://redis.io/commands/scan).

**Useful Tips**
It's pretty common that doing an async task in the `data` handler. We'd like the scanning process to be paused until the async task to be finished. `Stream#pause()` and `Stream.resume()` do the trick. For example if we want to migrate data in Redis to MySQL:

```javascript
var stream = redis.scanStream();
stream.on('data', function (resultKeys) {
// Pause the stream from scanning more keys until we've migrated the current keys.
stream.pause();

Promise.all(resultKeys.map(migrateKeyToMySQL)).then(() => {
// Resume the stream here.
stream.resume();
});
});

stream.on('end', function () {
console.log('done migration');
});
```

## Auto-reconnect
By default, ioredis will try to reconnect when the connection to Redis is lost
except when the connection is closed manually by `redis.disconnect()` or `redis.quit()`.
Expand Down Expand Up @@ -554,6 +575,16 @@ This behavior can be disabled by setting the `autoResubscribe` option to `false`
And if the previous connection has some unfulfilled commands (most likely blocking commands such as `brpop` and `blpop`),
the client will resend them when reconnected. This behavior can be disabled by setting the `autoResendUnfulfilledCommands` option to `false`.

By default, all pending commands will be flushed with an error every 20 retry attempts. That makes sure commands won't wait forever when the connection is down. You can change this behavior by setting `maxRetriesPerRequest`:

```javascript
var redis = new Redis({
maxRetriesPerRequest: 1
});
```

Set maxRetriesPerRequest to `null` to disable this behavior, and every command will wait forever until the connection is alive again (which is the default behavior before ioredis v4).

### Reconnect on error

Besides auto-reconnect when the connection is closed, ioredis supports reconnecting on the specified errors by the `reconnectOnError` option. Here's an example that will reconnect when receiving `READONLY` error:
Expand Down Expand Up @@ -752,6 +783,7 @@ but a few so that if one is unreachable the client will try the next one, and th
will resend the commands rejected with `TRYAGAIN` error after the specified time (in ms).
* `redisOptions`: Default options passed to the constructor of `Redis` when connecting to a node.
* `slotsRefreshTimeout`: Milliseconds before a timeout occurs while refreshing slots from the cluster (default `1000`)
* `slotsRefreshInterval`: Milliseconds between every automatic slots refresh (default `5000`)

### Read-write splitting

Expand Down Expand Up @@ -951,6 +983,24 @@ var redis = new Redis();
redis.set('foo');
```

# Plugging in your own Promises Library
If you're an advanced user, you may want to plug in your own promise library like [bluebird](https://www.npmjs.com/package/bluebird). Just set Redis.Promise to your favorite ES6-style promise constructor and ioredis will use it.

```javascript
const Redis = require('ioredis')
Redis.Promise = require('bluebird')

const redis = new Redis()

// Use bluebird
assert.equal(redis.get().constructor, require('bluebird'))

// You can change the Promise implementation at any time:
Redis.Promise = global.Promise
assert.equal(redis.get().constructor, global.Promise)
```


# Running tests

Start a Redis server on 127.0.0.1:6379, and then:
Expand Down
10 changes: 10 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ exports.Promise = require('bluebird');
exports.Cluster = require('./lib/cluster');
exports.Command = require('./lib/command');

var PromiseContainer = require('./lib/promise_container');
Object.defineProperty(exports, 'Promise', {
get: function() {
return PromiseContainer.get();
},
set: function(lib) {
PromiseContainer.set(lib);
}
});

exports.print = function (err, reply) {
if (err) {
console.log('Error: ' + err);
Expand Down
11 changes: 9 additions & 2 deletions lib/cluster/connection_pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ ConnectionPool.prototype.findOrCreate = function (node, readOnly) {
readOnly = Boolean(readOnly);

if (this.specifiedOptions[node.key]) {
_.assign(node, this.specifiedOptions[node.key]);
Object.assign(node, this.specifiedOptions[node.key]);
} else {
this.specifiedOptions[node.key] = node;
}
Expand All @@ -46,7 +46,7 @@ ConnectionPool.prototype.findOrCreate = function (node, readOnly) {
redis = this.nodes.all[node.key];
if (redis.options.readOnly !== readOnly) {
redis.options.readOnly = readOnly;
debug('Change role of %s to %s', node.key, readOnly ? 'slave' : 'master')
debug('Change role of %s to %s', node.key, readOnly ? 'slave' : 'master');
redis[readOnly ? 'readonly' : 'readwrite']().catch(_.noop);
if (readOnly) {
delete this.nodes.master[node.key];
Expand All @@ -59,7 +59,14 @@ ConnectionPool.prototype.findOrCreate = function (node, readOnly) {
} else {
debug('Connecting to %s as %s', node.key, readOnly ? 'slave' : 'master');
redis = new Redis(_.defaults({
// Never try to reconnect when a node is lose,
// instead, waiting for a `MOVED` error and
// fetch the slots again.
retryStrategy: null,
// Offline queue should be enabled so that
// we don't need to wait for the `ready` event
// before sending commands to the node.
enableOfflineQueue: true,
readOnly: readOnly
}, node, this.redisOptions, { lazyConnect: true }));
this.nodes.all[node.key] = redis;
Expand Down
Loading