diff --git a/content/develop/clients/nodejs/connect.md b/content/develop/clients/nodejs/connect.md index cb1d11e739..9857f47878 100644 --- a/content/develop/clients/nodejs/connect.md +++ b/content/develop/clients/nodejs/connect.md @@ -67,7 +67,7 @@ createClient({ ``` To check if the client is connected and ready to send commands, use `client.isReady`, which returns a Boolean. `client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it isn't (for example, when the client is still connecting or reconnecting after a network error). -### Connect to a Redis cluster +## Connect to a Redis cluster To connect to a Redis cluster, use `createCluster`. @@ -97,7 +97,7 @@ console.log(value); // returns 'bar' await cluster.quit(); ``` -### Connect to your production Redis with TLS +## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security]({{< relref "/operate/oss_and_stack/management/security/" >}}) guidelines. @@ -127,3 +127,77 @@ await client.disconnect(); ``` You can also use discrete parameters and UNIX sockets. Details can be found in the [client configuration guide](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md). + +## Reconnect after disconnection + +By default, `node-redis` doesn't attempt to reconnect automatically when +the connection to the server is lost. However, you can set the +`socket.reconnectionStrategy` field in the configuration to decide +whether to try to reconnect and how to approach it. Choose one of the following values for +`socket.reconnectionStrategy`: + +- `false`: (Default) Don't attempt to reconnect. +- `number`: Wait for this number of milliseconds and then attempt to reconnect. +- ``: Use a custom + function to decide how to handle reconnection. + +The custom function has the following signature: + +```js +(retries: number, cause: Error) => false | number | Error +``` + +It is called before each attempt to reconnect, with the `retries` +indicating how many attempts have been made so far. The `cause` parameter is an +[`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) +object with information about how the connection was lost. The return value +from the function can be any of the following: + +- `false`: Don't attempt to reconnect. +- `number`: Wait this number of milliseconds and then try again. +- `Error`: Same as `false`, but lets you supply extra information about why + no attempt was made to reconnect. + +The example below shows a `reconnectionStrategy` function that implements a +custom [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) +strategy: + +```js +createClient({ + socket: { + reconnectStrategy: retries => { + // Generate a random jitter between 0 – 200 ms: + const jitter = Math.floor(Math.random() * 200); + + // Delay is an exponential back off, (times^2) * 50 ms, with a + // maximum value of 2000 ms: + const delay = Math.min(Math.pow(2, retries) * 50, 2000); + + return delay + jitter; + } + } +}); +``` + +## Connection events + +The client object emits the following +[events](https://developer.mozilla.org/en-US/docs/Web/API/Event) that are +related to connection: + +- `connect`: (No parameters) The client is about to start connecting to the server. +- `ready`: (No parameters) The client has connected and is ready to use. +- `end`: (No parameters) The client has been intentionally closed using `client.quit()`. +- `error`: An error has occurred, which is described by the + [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) + parameter. This is usually a network issue such as "Socket closed unexpectedly". +- `reconnecting`: (No parameters) The client is about to try reconnecting after the + connection was lost due to an error. + +Use code like the following to respond to these events: + +```js +client.on('error', error => { + console.error(`Redis client error:`, error); +}); +``` diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md new file mode 100644 index 0000000000..b89fbb19ad --- /dev/null +++ b/content/develop/clients/nodejs/migration.md @@ -0,0 +1,375 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Discover the differences between `ioredis` and `node-redis`. +linkTitle: Migrate from ioredis +title: Migrate from ioredis +weight: 6 +--- + +Redis previously recommended the [`ioredis`](https://github.com/redis/ioredis) +client library for development with [Node.js](https://nodejs.org/en), +but this library is now deprecated in favor of +[`node-redis`]({{< relref "/develop/clients/nodejs" >}}). This guide +outlines the main similarities and differences between the two libraries. +You may find this information useful if you are an `ioredis` user and you want to +start a new Node.js project or migrate an existing `ioredis` project to `node-redis`. + +## Comparison of `ioredis` and `node-redis` + +The tables below summarize how `ioredis` and `node-redis` implement some +key features of Redis. See the following sections for more information about +each feature. + +### Connection + +| Feature | `ioredis` | `node-redis` | +| :-- | :-- | :-- | +| [Initial connection](#initial-connection) | Happens when you create a client instance | Requires you to call a method on the client instance | +| [Reconnection after a connection is lost](#reconnection) | Automatic by default | Manual by default | +| [Connection events](#connection-events) | Emits `connect`, `ready`, `error`, and `close` events | Emits `connect`, `ready`, `error`, `end`, and `reconnecting` events | + +### Command handling + +| Feature | `ioredis` | `node-redis` | +| :-- | :-- | :-- | +| [Command case](#command-case) | Lowercase only (eg, `hset`) | Uppercase or camel case (eg, `HSET` or `hSet`) | +| [Command argument handling](#command-argument-handling) | Argument objects flattened and items passed directly | Argument objects parsed to generate correct argument list | +| [Asynchronous command result handling](#async-result) | Callbacks and Promises | Promises only | +| [Arbitrary command execution](#arbitrary-command-execution) | Uses the `call()` method | Uses the `sendCommand()` method | + +### Techniques + +| Feature | `ioredis` | `node-redis` | +| :-- | :-- | :-- | +| [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command | +| [Scan iteration](#scan-iteration) | Uses `scanStream()`, etc | Uses `scanIterator()`, etc | +| [Subscribing to channels](#subscribing-to-channels) | Uses `client.on('message', ...)` event | Uses `subscribe(...)` command | + +### Specific commands + +| Command | `ioredis` | `node-redis` | +| :-- | :-- | :-- | +| [`SETNX`](#setnx-command) | Supported explicitly | Supported as an option for `SET` | +| [`HMSET`](#hmset-command) | Supported explicitly | Supported with standard `HSET` functionality | +| [`CONFIG`](#config-command) | Supported explicitly | Supported with separate `configGet()`, `configSet()`, etc |co + +## Details + +The sections below explain the points of comparison between `ioredis` and +`node-redis` in more detail. + +### Initial connection + +`ioredis` makes the connection to the Redis server when you create an instance +of the client object: + +```js +const client = require('ioredis'); + +// Connects to localhost:6379 on instantiation. +const client = new Redis(); +``` + +`node-redis` requires you to call the `connect()` method on the client object +to make the connection: + +```js +import { createClient } from 'redis'; + +const client = await createClient(); +await client.connect(); // Requires explicit connection. +``` + +### Reconnection after a connection is lost {#reconnection} + +`ioredis` automatically attempts to reconnect if the connection +was lost due to an error. By default, `node-redis` doesn't attempt +to reconnect, but you can enable a custom reconnection strategy +when you create the client object. See +[Reconnect after disconnection]({{< relref "/develop/clients/nodejs/connect#reconnect-after-disconnection" >}}) +for more information. + +### Connection events + +The `connect`, `ready`, `error`, and `close` events that `ioredis` emits +are equivalent to the `connect`, `ready`, `error`, and `end` events +in `node-redis`, but `node-redis` also emits a `reconnecting` event. +See [Connection events]({{< relref "/develop/clients/nodejs/connect#connection-events" >}}) +for more information. + +### Command case + +Command methods in `ioredis` are always lowercase. With `node-redis`, you can +use uppercase or camel case versions of the method names. + +```js +// ioredis +client.hset('key', 'field', 'value'); + +// node-redis +client.HSET('key', 'field', 'value'); + +// ...or +client.hSet('key', 'field', 'value'); +``` + +### Command argument handling + +`ioredis` parses command arguments to strings and then passes them to +the server, in a similar way to [`redis-cli`]({{< relref "/develop/tools/cli" >}}). + +```js +// Equivalent to the command line `SET key 100 EX 10`. +client.set('key', 100, 'EX', 10); +``` + +Arrays passed as arguments are flattened into individual elements and +objects are flattened into sequential key-value pairs: + +```js +// These commands are all equivalent. +client.hset('user' { + name: 'Bob', + age: 20, + description: 'I am a programmer', +}); + +client.hset('user', ['name', 'Bob', 'age', 20, 'description', 'I am a programmer']); + +client.hset('user', 'name', 'Bob', 'age', 20, 'description', 'I am a programmer'); +``` + +`node-redis` uses predefined formats for command arguments. These include specific +classes for commmand options that generally don't correspond to the syntax +of the CLI command. Internally, `node-redis` constructs the correct command using +the method arguments you pass: + +```js +// Equivalent to the command line `SET bike:5 bike EX 10`. +client.set('bike:5', 'bike', {EX: 10}); +``` + +### Asynchronous command result handling {#async-result} + +All commands for both `ioredis` and `node-redis` are executed +asynchronously. `ioredis` supports both callbacks and +[`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +return values to respond to command results: + +```js +// Callback +client.get('mykey', (err, result) => { + if (err) { + console.error(err); + } else { + console.log(result); + } +}); + +// Promise +client.get('mykey').then( + (result) => { + console.log(result); + }, + (err) => { + console.error(err); + } +); +``` + +`node-redis` supports only `Promise` objects for results, so +you must always use a `then()` handler or the +[`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) +operator to receive them. + +### Arbitrary command execution + +`ioredis` lets you issue arbitrary commands in a similar format to +[`redis-cli`]({{< relref "/develop/tools/cli" >}}) using the `call()` +command: + +```js +await client.call('JSON.SET', 'doc', "$", '{"f1": {"a":1}, "f2":{"a":2}}'); +``` + +In `node-redis`, you can get the same effect outside a transaction using `sendCommand()`: + +```js +await client.sendCommand(['hset', 'hash2', 'number', '3']); +``` + +Within a transaction, use `addCommand()` to include arbitrary commands. Note that +you can freely mix `addCommand()` calls with standard commands in the same +transaction: + +```js +const responses = await client.multi() + .addCommand(['hset', 'hash3', 'number', '4']) + .hGet('hash3', 'number') + .exec(); +``` + +### Pipelining + +Both `ioredis` and `node-redis` will pipeline commands automatically if +they are executed in the same "tick" of the +[event loop](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#what-is-the-event-loop) +(see +[Execute a pipeline]({{< relref "/develop/clients/nodejs/transpipe#execute-a-pipeline" >}}) +for more information). + +You can also create a pipeline with explicit commands in both clients. +With `ioredis`, you use the `pipeline()` command with a chain of +commands, ending with `exec()` to run the pipeline: + +```js +// ioredis example +client.pipeline() + .set('foo', '1') + .get('foo') + .set('foo', '2') + .incr('foo') + .get('foo') + .exec(function (err, results) { + // Handle results or errors. + }); +``` + +For `node-redis`, the approach is similar, except that you call the `multi()` +command to start the pipeline and `execAsPipeline()` to run it: + +```js +client.multi() + .set('seat:3', '#3') + .set('seat:4', '#4') + .set('seat:5', '#5') + .execAsPipeline() + .then((results) => { + // Handle array of results. + }, + (err) => { + // Handle errors. + }); +``` + +### Scan iteration + +`ioredis` supports the `scanStream()` method to create a readable stream +from the set of keys returned by the [`SCAN`]({{< relref "/commands/scan" >}}) +command: + +```js +const client = new Redis(); +// Create a readable stream (object mode) +const stream = client.scanStream(); +stream.on('data', (resultKeys) => { + // `resultKeys` is an array of strings representing key names. + // Note that resultKeys may contain 0 keys, and that it will sometimes + // contain duplicates due to SCAN's implementation in Redis. + for (let i = 0; i < resultKeys.length; i++) { + console.log(resultKeys[i]); + } +}); +stream.on('end', () => { + console.log('all keys have been visited'); +}); +``` + +You can also use the similar `hscanStream()`, `sscanStream()`, and +`zscanStream()` to iterate over the items of a hash, set, or sorted set, +respectively. + +`node-redis` handles scan iteration using the `scanIterator()` method +(and the corresponding `hscanIterator()`, `sscanIterator()`, and +`zscanIterator()` methods). These return a collection object for +each page scanned by the cursor (this can be helpful to improve +efficiency using [`MGET`]({{< relref "/commands/mget" >}}) and +other multi-key commands): + +```js +for await (const keys of client.scanIterator()) { + const values = await client.mGet(keys); + // Process values... +} +``` + +### Subscribing to channels + +`ioredis` reports incoming pub/sub messages with a `message` +event on the client object (see +[Publish/subscribe]({{< relref "/develop/interact/pubsub" >}}) for more +information about messages): + +```js +client.on('message', (channel, message) => { + console.log(Received message from ${channel}: ${message}); +}); +``` + +With `node-redis`, you use the `subscribe()` command to register the +message callback. Also, when you use a connection to subscribe, that +connection can't issue any other commands, so you must create a +dedicated connection for the subscription. Use the `client.duplicate()` +method to create a new connection with the same settings as the original: + +```js +const subscriber = client.duplicate(); +await subscriber.connect(); + +await subscriber.subscribe('channel', (message) => { + console.log(Received message: ${message}); +}); +``` + +### `SETNX` command + +`ioredis` implements the [`SETNX`]({{< relref "/commands/setnx" >}}) +command with an explicit method: + +```js +client.setnx('bike:1', 'bike'); +``` + +`node-redis` doesn't provide a `SETNX` method but implements the same +functionality with the `NX` option to the [`SET`]({{< relref "/commands/set" >}}) +command: + +```js +await client.set('bike:1', 'bike', {'NX': true}); +``` + +### `HMSET` command + +The [`HMSET`]({{< relref "/commands/hmset" >}}) command has been deprecated +since Redis v4.0.0, but it is still supported by `ioredis`. With `node-redis` +you should use the [`HSET`]({{< relref "/commands/hset" >}}) command with +multiple key-value pairs. See the [`HSET`]({{< relref "/commands/hset" >}}) +command page for more information. + +### `CONFIG` command + +`ioredis` supports a `config()` method to set or get server configuration +options: + +```js +client.config('SET', 'notify-keyspace-events', 'KEA'); +``` + +`node-redis` doesn't have a `config()` method, but instead supports the +standard commands [`configSet()`]({{< relref "/commands/config-set" >}}), +[`configGet()`]({{< relref "/commands/config-get" >}}), +[`configResetStat()`]({{< relref "/commands/config-resetstat" >}}), and +[`configRewrite`]({{< relref "/commands/config-rewrite" >}}): + +```js +await client.configSet('maxclients', '2000'); +``` diff --git a/content/develop/clients/nodejs/transpipe.md b/content/develop/clients/nodejs/transpipe.md index 8934379e5b..d47deee9ba 100644 --- a/content/develop/clients/nodejs/transpipe.md +++ b/content/develop/clients/nodejs/transpipe.md @@ -30,8 +30,10 @@ There are two types of batch that you can use: ## Execute a pipeline -There are two ways to execute commands in a pipeline. The first is -to include the commands in a +There are two ways to execute commands in a pipeline. Firstly, `node-redis` will +automatically pipeline commands that execute within the same "tick" of the +[event loop](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#what-is-the-event-loop). +You can ensure that commands happen in the same tick very easily by including them in a [`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) call, as shown in the following example. The chained `then(...)` callback is optional and you can often omit it for commands that write data and only return a