diff --git a/content/develop/clients/dotnet/connect.md b/content/develop/clients/dotnet/connect.md index 2a2e842cf1..5f9320532b 100644 --- a/content/develop/clients/dotnet/connect.md +++ b/content/develop/clients/dotnet/connect.md @@ -67,6 +67,64 @@ db.StringSet("foo", "bar"); Console.WriteLine(db.StringGet("foo")); // prints bar ``` +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +list the Sentinels in `EndPoints` and set `ServiceName` to the name of the primary that +the Sentinels monitor. The client asks the Sentinels for the current primary. + +```csharp +using StackExchange.Redis; + +ConfigurationOptions conf = new ConfigurationOptions { + EndPoints = { "localhost:26379", "localhost:26380", "localhost:26381" }, + ServiceName = "mymaster" +}; + +ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(conf); +IDatabase db = redis.GetDatabase(); + +db.StringSet("foo", "bar"); +Console.WriteLine(db.StringGet("foo")); // prints bar +``` + +You can also use a configuration string, such as +`"localhost:26379,localhost:26380,localhost:26381,serviceName=mymaster"`. + +If the Sentinels and the data nodes have different credentials, connect to the Sentinels +first with `SentinelConnect()`, using the Sentinel credentials. Then call +`GetSentinelMasterConnection()` with the credentials for the primary and replicas: + +```csharp +using StackExchange.Redis; + +// Connect to the Sentinels with the Sentinel credentials. +ConfigurationOptions sentinelConf = new ConfigurationOptions { + EndPoints = { "localhost:26379", "localhost:26380", "localhost:26381" }, + User = "yourSentinelUsername", + Password = "yourSentinelPassword" +}; +ConnectionMultiplexer sentinel = ConnectionMultiplexer.SentinelConnect(sentinelConf); + +// Connect to the primary with the Redis credentials. +ConfigurationOptions conf = new ConfigurationOptions { + ServiceName = "mymaster", + User = "yourUsername", + Password = "yourPassword" +}; +ConnectionMultiplexer redis = sentinel.GetSentinelMasterConnection(conf); +IDatabase db = redis.GetDatabase(); +``` + +The connection also includes the replicas that the Sentinels report. To read from a +replica, pass `CommandFlags.PreferReplica` (or `CommandFlags.DemandReplica`) to a read-only +command. Replication is asynchronous, so a replica can return stale data. + +```csharp +// Read from a replica if one is available, otherwise from the primary. +Console.WriteLine(db.StringGet("foo", CommandFlags.PreferReplica)); // prints bar +``` + ## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security](/content/operate/oss_and_stack/management/security/_index.md) guidelines. diff --git a/content/develop/clients/go/connect.md b/content/develop/clients/go/connect.md index 49d465cc73..060d835e2e 100644 --- a/content/develop/clients/go/connect.md +++ b/content/develop/clients/go/connect.md @@ -80,6 +80,45 @@ client := redis.NewClusterClient(&redis.ClusterOptions{ }) ``` +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +use `NewFailoverClient()`. Set `MasterName` to the name of the primary that the Sentinels +monitor, and list one or more Sentinels in `SentinelAddrs`. The client asks the Sentinels +for the current primary and switches to the new one after a failover. + +```go +client := redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: "mymaster", + SentinelAddrs: []string{"localhost:26379", "localhost:26380", "localhost:26381"}, +}) +``` + +The Sentinels and the data nodes have separate credentials. Set `SentinelPassword` for the +Sentinels, and `Username` and `Password` for the primary and replicas: + +```go +client := redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: "mymaster", + SentinelAddrs: []string{"localhost:26379", "localhost:26380", "localhost:26381"}, + SentinelPassword: "sentinel-secret", // use your Sentinel password + Username: "default", // use your Redis user + Password: "secret", // use your Redis password +}) +``` + +To send commands to a replica, set `ReplicaOnly`. The client sends every command to a +random replica, including writes, which fail, so use this option only for a client that +reads: + +```go +replica := redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: "mymaster", + SentinelAddrs: []string{"localhost:26379", "localhost:26380", "localhost:26381"}, + ReplicaOnly: true, +}) +``` + ## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the diff --git a/content/develop/clients/hiredis/connect.md b/content/develop/clients/hiredis/connect.md index 475f0d9069..f88836976e 100644 --- a/content/develop/clients/hiredis/connect.md +++ b/content/develop/clients/hiredis/connect.md @@ -145,3 +145,9 @@ pending commands execute and activate their callbacks. Use `redisAsyncFree()` to disconnect immediately. If you do this then any pending callbacks from commands that have already executed will be called with a `NULL` reply pointer. + +## Redis Sentinel + +`hiredis` doesn't support [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md). +It has no API to find the current primary from a set of Sentinels or to reconnect after a +failover. diff --git a/content/develop/clients/ioredis/connect.md b/content/develop/clients/ioredis/connect.md index 70935592d8..d38fedbdd3 100644 --- a/content/develop/clients/ioredis/connect.md +++ b/content/develop/clients/ioredis/connect.md @@ -66,6 +66,68 @@ const redis = new Redis.Cluster([ ]); ``` +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +pass a list of Sentinels in the `sentinels` option and the name of the primary that the +Sentinels monitor in `name`. The client asks the Sentinels for the current primary. + +```js +const redis = new Redis({ + sentinels: [ + { host: "localhost", port: 26379 }, + { host: "localhost", port: 26380 }, + { host: "localhost", port: 26381 }, + ], + name: "mymaster", +}); + +await redis.set('foo', 'bar'); +const value = await redis.get('foo'); +console.log(value); // >>> bar +``` + +The Sentinels and the data nodes have separate credentials. Set `sentinelPassword` (and +`sentinelUsername`, if needed) for the Sentinels, and `username` and `password` for the +primary and replicas: + +```js +const redis = new Redis({ + sentinels: [ + { host: "localhost", port: 26379 }, + { host: "localhost", port: 26380 }, + { host: "localhost", port: 26381 }, + ], + name: "mymaster", + // Credentials for the Sentinel instances. + sentinelPassword: "my-sentinel-password", + // Credentials for the primary and replica data nodes. + username: "default", + password: "my-password", +}); +``` + +If the Sentinel password is wrong or missing, the error says that all Sentinels are +unreachable. + +By default, ioredis doesn't listen for failover announcements from the Sentinels. After a +failover, the client keeps using the old primary until Sentinel reconfigures it as a +replica and closes the connection. Any writes that the old primary accepts during that time +are lost. Set `failoverDetector: true` to switch to the new primary as soon as the Sentinels +announce it. + +To connect to a replica instead of the primary, set `role: "slave"`. The client connects to +one replica, chosen at random. Replication is asynchronous, so a replica can return stale +data. + +```js +const replica = new Redis({ + sentinels: [{ host: "localhost", port: 26379 }], + name: "mymaster", + role: "slave", +}); +``` + ## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security](/content/operate/oss_and_stack/management/security/_index.md) guidelines. diff --git a/content/develop/clients/jedis/connect.md b/content/develop/clients/jedis/connect.md index e5284a0d97..b10d1d313a 100644 --- a/content/develop/clients/jedis/connect.md +++ b/content/develop/clients/jedis/connect.md @@ -76,6 +76,68 @@ jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7380)); RedisClusterClient jedis = RedisClusterClient.create(jedisClusterNodes); ``` +### Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +use `RedisSentinelClient`. Pass the name of the primary that the Sentinels monitor to +`masterName()`, and the Sentinel addresses to `sentinels()`. The client asks the Sentinels +for the current primary and switches to the new one after a failover. + +```java +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.RedisSentinelClient; + +import java.util.HashSet; +import java.util.Set; + +//... + +Set sentinels = new HashSet<>(); +sentinels.add(new HostAndPort("localhost", 26379)); +sentinels.add(new HostAndPort("localhost", 26380)); +sentinels.add(new HostAndPort("localhost", 26381)); + +RedisSentinelClient jedis = RedisSentinelClient.builder() + .masterName("mymaster") + .sentinels(sentinels) + .build(); + +jedis.set("foo", "bar"); +System.out.println(jedis.get("foo")); // bar + +jedis.close(); +``` + +The Sentinels and the data nodes have separate credentials. Pass a client configuration for +the primary to `clientConfig()`, and one for the Sentinels to `sentinelClientConfig()`: + +```java +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.JedisClientConfig; + +//... + +// Credentials for the primary and replica nodes. +JedisClientConfig masterConfig = DefaultJedisClientConfig.builder() + .password("secret!") // use your Redis password + .build(); + +// Credentials for the Sentinel nodes. +JedisClientConfig sentinelConfig = DefaultJedisClientConfig.builder() + .password("sentinelSecret!") // use your Sentinel password + .build(); + +RedisSentinelClient jedis = RedisSentinelClient.builder() + .masterName("mymaster") + .sentinels(sentinels) + .clientConfig(masterConfig) + .sentinelClientConfig(sentinelConfig) + .build(); +``` + +`RedisSentinelClient` always sends commands to the primary. It doesn't support reading +from replicas. + ### Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security](/content/operate/oss_and_stack/management/security/_index.md) guidelines. diff --git a/content/develop/clients/lettuce/connect.md b/content/develop/clients/lettuce/connect.md index ae8c50f807..e6889c5d70 100644 --- a/content/develop/clients/lettuce/connect.md +++ b/content/develop/clients/lettuce/connect.md @@ -66,6 +66,82 @@ try (RedisClusterClient clusterClient = RedisClusterClient.create(redisURI)) { Learn more about Cluster connections and how to configure them in [the reference guide](https://redis.github.io/lettuce/ha-sharding/#redis-cluster). +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +build a `RedisURI` with `RedisURI.Builder.sentinel()`, passing the address of one Sentinel +and the name of the primary that the Sentinels monitor. Add the other Sentinels with +`withSentinel()`. The client asks the Sentinels for the current primary. + +```java +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; + +//... + +RedisURI redisUri = RedisURI.Builder + .sentinel("localhost", 26379, "mymaster") + .withSentinel("localhost", 26380) + .withSentinel("localhost", 26381) + .build(); + +try (RedisClient client = RedisClient.create(redisUri)) { + StatefulRedisConnection connection = client.connect(); + RedisCommands commands = connection.sync(); + + commands.set("foo", "bar"); + System.out.println(commands.get("foo")); // >>> bar + + connection.close(); +} +``` + +The Sentinels and the data nodes have separate credentials. Pass the Sentinel password with +each Sentinel address, and set the credentials for the primary and replicas with +`withAuthentication()`: + +```java +RedisURI redisUri = RedisURI.Builder + // The password argument authenticates to each Sentinel. + .sentinel("localhost", 26379, "mymaster", "yourSentinelPassword") + .withSentinel("localhost", 26380, "yourSentinelPassword") + .withSentinel("localhost", 26381, "yourSentinelPassword") + // withAuthentication() authenticates to the primary and replicas. + .withAuthentication("default", "yourPassword") + .build(); +``` + +A `RedisClient` connection doesn't listen for failover announcements from the Sentinels. +After a failover, it keeps using the old primary until Sentinel reconfigures it as a +replica and closes the connection. Any writes that the old primary accepts during that time +are lost. + +To read from replicas, open a connection with `MasterReplica.connect()` and set a +`ReadFrom` policy. This connection also listens for failover announcements from the +Sentinels, so it switches to a new primary sooner. Replication is asynchronous, so a +replica can return stale data. + +```java +import io.lettuce.core.ReadFrom; +import io.lettuce.core.codec.StringCodec; +import io.lettuce.core.masterreplica.MasterReplica; +import io.lettuce.core.masterreplica.StatefulRedisMasterReplicaConnection; + +//... + +try (RedisClient client = RedisClient.create()) { + StatefulRedisMasterReplicaConnection connection = + MasterReplica.connect(client, StringCodec.UTF8, redisUri); + connection.setReadFrom(ReadFrom.REPLICA); + + System.out.println(connection.sync().get("foo")); // >>> bar + + connection.close(); +} +``` + ## Asynchronous connection ```java diff --git a/content/develop/clients/nodejs/connect.md b/content/develop/clients/nodejs/connect.md index eef6f44f27..a2171e9fcb 100644 --- a/content/develop/clients/nodejs/connect.md +++ b/content/develop/clients/nodejs/connect.md @@ -97,6 +97,77 @@ console.log(value); // returns 'bar' await cluster.close(); ``` +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +use `createSentinel()`. Set `name` to the name of the primary that the Sentinels monitor, +and list one or more Sentinels in `sentinelRootNodes`. The client asks the Sentinels for +the current primary and switches to the new one after a failover. + +```js +import { createSentinel } from 'redis'; + +const sentinel = createSentinel({ + name: 'mymaster', + sentinelRootNodes: [ + { host: 'localhost', port: 26379 }, + { host: 'localhost', port: 26380 }, + { host: 'localhost', port: 26381 } + ] +}); + +sentinel.on('error', (err) => console.log('Redis Sentinel Error', err)); + +await sentinel.connect(); + +await sentinel.set('foo', 'bar'); +const value = await sentinel.get('foo'); +console.log(value); // returns 'bar' + +await sentinel.close(); +``` + +The Sentinels and the data nodes have separate credentials. Set the Sentinel credentials in +`sentinelClientOptions`, and the credentials for the primary and replicas in +`nodeClientOptions`: + +```js +const sentinel = createSentinel({ + name: 'mymaster', + sentinelRootNodes: [ + { host: 'localhost', port: 26379 }, + { host: 'localhost', port: 26380 }, + { host: 'localhost', port: 26381 } + ], + // Credentials for the Sentinel instances. + sentinelClientOptions: { + password: 'sentinel-secret' + }, + // Credentials for the primary and replica data nodes. + nodeClientOptions: { + username: 'default', + password: 'secret' + } +}); +``` + +Neither option accepts a `url`, so you can't use a connection string with +`createSentinel()`. + +By default, the client sends all commands to the primary. To send read-only commands such +as `GET` to the replicas, set `replicaPoolSize` to the number of connections to open to each +replica. Replication is asynchronous, so a read that follows a write can return stale data. + +```js +const sentinel = createSentinel({ + name: 'mymaster', + sentinelRootNodes: [ + { host: 'localhost', port: 26379 } + ], + replicaPoolSize: 1 +}); +``` + ## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security](/content/operate/oss_and_stack/management/security/_index.md) guidelines. diff --git a/content/develop/clients/php/connect.md b/content/develop/clients/php/connect.md index 9e6d7d3b91..3ee729370b 100644 --- a/content/develop/clients/php/connect.md +++ b/content/develop/clients/php/connect.md @@ -99,6 +99,81 @@ echo $rc->get('foo'), PHP_EOL; // >>> bar ``` +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +pass a list of Sentinels as the first parameter. In `options`, set +`'replication' => 'sentinel'` and set `service` to the name of the primary that the +Sentinels monitor: + +```php +$sentinels = [ + 'tcp://localhost:26379', + 'tcp://localhost:26380', + 'tcp://localhost:26381', +]; +$options = [ + 'replication' => 'sentinel', + 'service' => 'mymaster', +]; + +$r = new PredisClient($sentinels, $options); + +echo $r->set('foo', 'bar'), PHP_EOL; +// >>> OK + +echo $r->get('foo'), PHP_EOL; +// >>> bar +``` + +The default timeout for connecting to a Sentinel is 100 milliseconds. If your Sentinels +are on remote hosts, add a longer timeout to each Sentinel address, such as +`tcp://:26379?timeout=0.5`. + +The Sentinels and the data nodes have separate credentials. Add the Sentinel credentials +to each Sentinel address, and set the credentials for the primary and replicas in the +`parameters` option. Include the Sentinel `username` even if it's `default`, because +otherwise Predis uses the `username` from `parameters` for the Sentinels too: + +```php +// Credentials for the Sentinels. +$sentinels = [ + 'tcp://localhost:26379?username=yourSentinelUsername&password=yourSentinelPassword', + 'tcp://localhost:26380?username=yourSentinelUsername&password=yourSentinelPassword', + 'tcp://localhost:26381?username=yourSentinelUsername&password=yourSentinelPassword', +]; +$options = [ + 'replication' => 'sentinel', + 'service' => 'mymaster', + // Credentials for the primary and replicas. + 'parameters' => [ + 'username' => 'yourUsername', + 'password' => 'yourPassword', + ], +]; + +$r = new PredisClient($sentinels, $options); +``` + +Predis sends read-only commands to a replica until the first write command. After that, it +sends all commands to the primary. Call `switchToSlave()` to send reads to a replica again. +Replication is asynchronous, so a replica can return stale data. + +```php +// A write command switches the client to the primary. +echo $r->set('foo', 'baz'), PHP_EOL; +// >>> OK + +// Switch back to a replica for later reads. +$r->getConnection()->switchToSlave(); +echo $r->get('foo'), PHP_EOL; +// >>> baz +``` + +Predis doesn't listen for failover announcements from the Sentinels. After a failover, the +client keeps using the old primary until Sentinel reconfigures it as a replica and closes +the connection. Any writes that the old primary accepts during that time are lost. + ## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the diff --git a/content/develop/clients/redis-py/connect.md b/content/develop/clients/redis-py/connect.md index 8b2837aeeb..3b68f759ca 100644 --- a/content/develop/clients/redis-py/connect.md +++ b/content/develop/clients/redis-py/connect.md @@ -67,6 +67,61 @@ rc.get('foo') ``` For more information, see [redis-py Clustering](https://redis.readthedocs.io/en/stable/clustering.html). +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +create a `Sentinel` object with a list of Sentinel addresses. Then call `master_for()` with +the name of the primary that the Sentinels monitor. The client asks the Sentinels for the +current address of the primary. + +```python +from redis.sentinel import Sentinel + +sentinel = Sentinel([('localhost', 26379), ('localhost', 26380), ('localhost', 26381)]) +r = sentinel.master_for('mymaster', decode_responses=True) + +r.set('foo', 'bar') +# True + +r.get('foo') +# bar +``` + +The Sentinels and the data nodes have separate credentials. Pass the Sentinel credentials +in `sentinel_kwargs`, and the credentials for the primary and replicas to `master_for()`: + +```python +from redis.sentinel import Sentinel + +sentinel = Sentinel( + [('localhost', 26379), ('localhost', 26380), ('localhost', 26381)], + sentinel_kwargs={'password': 'sentinel-secret'}, # use your Sentinel password +) +r = sentinel.master_for( + 'mymaster', + username='default', # use your Redis user + password='secret', # use your Redis password + decode_responses=True, +) +``` + +If you set `sentinel_kwargs`, the Sentinel connections don't inherit `socket_timeout` or the +other `socket_*` options, so add any that you need to `sentinel_kwargs`. + +To send read-only commands to a replica, use `slave_for()`. Replication is asynchronous, so +a replica can return stale data. + +```python +replica = sentinel.slave_for('mymaster', decode_responses=True) + +replica.get('foo') +# bar +``` + +redis-py doesn't listen for failover announcements from the Sentinels. After a failover, +the client keeps using the old primary until Sentinel reconfigures it as a replica and it +starts rejecting writes. Any writes that the old primary accepts during that time are lost. + ## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security](/content/operate/oss_and_stack/management/security/_index.md) guidelines.