Le mode de connexion directe permet à un client de se connecter aux serveurs de données d'une instance en cluster Tair (compatible avec Redis OSS) via le protocole natif Redis Cluster. Cette approche contourne la couche proxy, réduit la latence des réponses et facilite la migration depuis des déploiements Redis Cluster auto-gérés.
Mode de connexion directe vs mode proxy
| Aspect | Mode de connexion directe | Mode proxy |
|---|---|---|
| Latence | Plus faible (aucun saut via le proxy) | Légèrement plus élevée |
| Prérequis client | Doit prendre en charge le protocole Redis Cluster | Tout client Redis |
| Mise à l'échelle | Le client gère la conscience de la topologie | Transparent pour le client |
| Haute disponibilité | Le client gère les redirections | Le proxy gère le basculement |
Pour plus d'informations sur le mode proxy, consultez Features of proxy nodes.
Fonctionnement
Lorsque le mode de connexion directe est activé, Tair (compatible avec Redis OSS) attribue une adresse IP virtuelle (VIP) au nœud maître de chaque shard de données. Un serveur DNS résout le point de terminaison privé vers la VIP d'un shard de données aléatoire. Le client utilise ensuite cette VIP pour gérer les données de l'instance selon le protocole Redis Cluster.
Prérequis
Avant de commencer, assurez-vous d'avoir :
Une instance en cluster Tair (compatible avec Redis OSS) avec le mode de connexion directe activé. Pour plus d'informations, consultez Activer le mode de connexion directe
Ajouté l'adresse IP du client à une liste d'autorisation. Pour plus d'informations, consultez Configurer les listes d'autorisation
Une instance Elastic Compute Service (ECS) située dans le même Virtual Private Cloud (VPC) que l'instance en cluster
Bibliothèques clientes prises en charge
Les bibliothèques clientes suivantes prennent en charge le protocole Redis Cluster et fonctionnent avec le mode de connexion directe.
| Client | Langage | Version testée | GitHub | Notes |
|---|---|---|---|---|
| Jedis | Java | 4.3.0 | jedis | Recommandé pour Spring Data Redis |
| Lettuce | Java | 6.3.0.RELEASE ou ultérieure | lettuce-core | Nécessite un réglage de TCP keepalive. Consultez Référence des paramètres Lettuce |
| redis-py | Python | 4.4.1 | redis-py | Utilisez la classe RedisCluster |
| PhpRedis | PHP | 5.3.7 | phpredis | - |
| StackExchange.Redis | .NET | 2.6.90 | - | Ne prend pas en charge SELECT (DB0 uniquement) |
| node-redis | Node.js | 4.5.1 | - | Fournissez les identifiants dans rootNodes et defaults |
| go-redis | Go | 9.5.1 (9,0 ou ultérieure requise) | - | Les versions antérieures à 9,0 provoquent des erreurs d'incompatibilité |
Si un client ne prend pas en charge le protocole Redis Cluster, il ne peut pas rediriger les requêtes vers le shard approprié. Cela peut entraîner des échecs de récupération des données. Pour obtenir la liste complète des clients compatibles, consultez la page Clients sur le site web de Redis.
Notes d'utilisation
Différentes architectures d'instance prennent en charge différents ensembles de commandes Redis natives. L'architecture en cluster impose des limites aux scripts Lua. Pour plus d'informations, consultez Limites sur les commandes prises en charge par les instances en cluster et les instances avec lecture/écriture séparées.
Si vous modifiez les configurations d'une instance en cluster en mode de connexion directe, une migration de slots est effectuée. Pendant la migration, le client peut recevoir des erreurs
MOVEDetTRYAGAINlors de l'accès aux slots en cours de migration. Configurez un mécanisme de nouvelle tentative pour le client. Pour plus d'informations, consultez Mécanismes de nouvelle tentative pour les clients. Pour plus d'informations, consultez Modifier les configurations d'une instance.La commande SELECT fonctionne en mode de connexion directe. Cependant, certains clients Redis Cluster, tels que StackExchange.Redis, ne prennent pas en charge SELECT. Avec ces clients, seule la base DB0 est disponible.
Les points de terminaison privés permettent un accès uniquement via le réseau interne Alibaba Cloud. L'accès sans mot de passe et l'authentification par compte et mot de passe sont tous deux pris en charge.
Se connecter avec redis-cli
Incluez le paramètre -c pour activer le mode cluster lors de la connexion via un point de terminaison privé. Sans ce paramètre, la connexion échoue.
./redis-cli -h r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com -p 6379 -c
Authentifiez-vous avec votre compte de base de données :
AUTH testaccount:Rp829dlwa
Pour plus d'informations, consultez Utiliser redis-cli pour se connecter à une instance.
Se connecter avec Jedis
Les exemples suivants utilisent Jedis 4.3.0. Pour plus d'informations, visitez GitHub.
Pool de connexions personnalisé (recommandé)
Un pool de connexions personnalisé offre un contrôle sur les limites de connexion. En mode de connexion directe, chaque client se connecte directement aux shards individuels. Définissez les tailles de pool de sorte que le nombre total de connexions entre tous les clients ne dépasse pas le nombre maximal de connexions par shard : Nombre de clients x MaxTotal < Nombre maximal de connexions vers un seul shard.
| Paramètre | Valeur | Description |
|---|---|---|
MaxTotal |
30 | Nombre maximal de connexions dans le pool. Doit satisfaire la formule par shard ci-dessus. |
MaxIdle |
20 | Nombre maximal de connexions inactives. Définissez en fonction de la charge de travail. |
MinIdle |
15 | Nombre minimal de connexions inactives à maintenir. |
import redis.clients.jedis.*;
import java.util.HashSet;
import java.util.Set;
public class DirectTest {
private static final int DEFAULT_TIMEOUT = 2000;
private static final int DEFAULT_REDIRECTIONS = 5;
private static final ConnectionPoolConfig config = new ConnectionPoolConfig();
public static void main(String args[]) {
// Number of clients x MaxTotal < Maximum connections to a single shard.
config.setMaxTotal(30);
config.setMaxIdle(20);
config.setMinIdle(15);
// Private endpoint of the cluster instance.
String host = "r-bp1xxxxxxxxxxxx.redis.rds.aliyuncs.com";
int port = 6379;
// Password for the cluster instance.
String password = "xxxxx";
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort(host, port));
JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS,
password, "clientName", config);
jc.set("key", "value");
jc.get("key");
jc.close(); // Close the connection and release resources when the application exits.
}
}
Pool de connexions par défaut
import redis.clients.jedis.ConnectionPoolConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;
public class DirectTest{
private static final int DEFAULT_TIMEOUT = 2000;
private static final int DEFAULT_REDIRECTIONS = 5;
private static final ConnectionPoolConfig DEFAULT_CONFIG = new ConnectionPoolConfig();
public static void main(String args[]){
// Private endpoint of the cluster instance.
String host = "r-bp1xxxxxxxxxxxx.redis.rds.aliyuncs.com";
int port = 6379;
String password = "xxxx";
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort(host, port));
JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
DEFAULT_REDIRECTIONS,password, "clientName", DEFAULT_CONFIG);
jc.set("key","value");
jc.get("key");
jc.close(); // Close the connection and release resources when the application exits.
}
}
Se connecter avec PhpRedis
L'exemple suivant utilise PhpRedis 5.3.7. Pour plus d'informations, visitez GitHub.
<?php
// Private endpoint and port of the cluster instance.
$array = ['r-bp1xxxxxxxxxxxx.redis.rds.aliyuncs.com:6379'];
// Password for the cluster instance.
$pwd = "xxxx";
// Connect to the cluster instance with the password.
$obj_cluster = new RedisCluster(NULL, $array, 1.5, 1.5, true, $pwd);
// Display the connection result.
var_dump($obj_cluster);
if ($obj_cluster->set("foo", "bar") == false) {
die($obj_cluster->getLastError());
}
$value = $obj_cluster->get("foo");
echo $value;
?>
Se connecter avec redis-py
L'exemple suivant utilise Python 3.9 et redis-py 4.4.1. Pour plus d'informations, visitez GitHub.
# !/usr/bin/env python
# -*- coding: utf-8 -*-
from redis.cluster import RedisCluster
# Replace with the endpoint and port of the instance.
host = 'r-bp10noxlhcoim2****.redis.rds.aliyuncs.com'
port = 6379
# Replace with the username and password of the instance.
user = 'testaccount'
pwd = 'Rp829dlwa'
rc = RedisCluster(host=host, port=port, username=user, password=pwd)
# Perform operations after connecting. Example: set and get.
rc.set('foo', 'bar')
print(rc.get('foo'))
Se connecter avec Spring Data Redis
Le projet Maven suivant utilise Spring Data Redis 2.4.2. Vous pouvez également télécharger manuellement le client Lettuce ou Jedis.
Étape 1 : Ajouter les dépendances Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.tair</groupId>
<artifactId>spring-boot-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>4.1.100.Final</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Étape 2 : Configurer la connexion
Choisissez Jedis (recommandé) ou Lettuce comme client sous-jacent.
Spring Data Redis avec Jedis (recommandé)
@Bean
JedisConnectionFactory redisConnectionFactory() {
List<String> clusterNodes = Arrays.asList("r-bp10noxlhcoim2****.redis.rds.aliyuncs.com:6379");
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodes);
redisClusterConfiguration.setUsername("user");
redisClusterConfiguration.setPassword("password");
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// Number of clients x MaxTotal < Maximum connections to a single shard.
jedisPoolConfig.setMaxTotal(30);
jedisPoolConfig.setMaxIdle(20);
// Disable testOn[Borrow|Return] to avoid extra ping commands.
jedisPoolConfig.setTestOnBorrow(false);
jedisPoolConfig.setTestOnReturn(false);
return new JedisConnectionFactory(redisClusterConfiguration, jedisPoolConfig);
}
Spring Data Redis avec Lettuce
Les configurations Lettuce par défaut peuvent entraîner une augmentation de la latence et une indisponibilité lors des modifications de l'instance. Lisez attentivement la Référence des paramètres Lettuce avant de configurer.
Utilisez Lettuce 6.3.0.RELEASE ou une version ultérieure. Pour plus d'informations, consultez [[Notice] Suggestions for upgrading Lettuce](t2556518.xdita#).
/**
* Enable TCP keepalive and configure the following three parameters:
* TCP_KEEPIDLE = 30
* TCP_KEEPINTVL = 10
* TCP_KEEPCNT = 3
*/
private static final int TCP_KEEPALIVE_IDLE = 30;
/**
* TCP_USER_TIMEOUT avoids scenarios where Lettuce remains stuck in a
* continuous timeout loop during a failure or crash event.
* refer: https://github.com/lettuce-io/lettuce-core/issues/2082
*/
private static final int TCP_USER_TIMEOUT = 30;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
List<String> clusterNodes = Arrays.asList("r-bp10noxlhcoim2****.redis.rds.aliyuncs.com:6379");
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodes);
redisClusterConfiguration.setUsername("user");
redisClusterConfiguration.setPassword("password");
// Config TCP KeepAlive
SocketOptions socketOptions = SocketOptions.builder()
.keepAlive(KeepAliveOptions.builder()
.enable()
.idle(Duration.ofSeconds(TCP_KEEPALIVE_IDLE))
.interval(Duration.ofSeconds(TCP_KEEPALIVE_IDLE / 3))
.count(3)
.build())
.tcpUserTimeout(TcpUserTimeoutOptions.builder()
.enable()
.tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
.build())
.build();
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enablePeriodicRefresh(Duration.ofSeconds(60))
.dynamicRefreshSources(false)
.enableAllAdaptiveRefreshTriggers()
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(15)).build();
LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder().
clientOptions(ClusterClientOptions.builder()
.socketOptions(socketOptions)
.validateClusterNodeMembership(false)
.topologyRefreshOptions(topologyRefreshOptions).build()).build();
return new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
}
Se connecter avec .NET (StackExchange.Redis)
L'exemple suivant utilise .NET 6.0 et StackExchange.Redis 2.6.90.
using StackExchange.Redis;
class RedisConnSingleton {
// Endpoint, port, username, and password for the cluster instance.
private static ConfigurationOptions configurationOptions = ConfigurationOptions.Parse("r-bp10noxlhcoim2****.redis.rds.aliyuncs.com:6379,user=testaccount,password=Rp829dlwa,connectTimeout=2000");
//the lock for singleton
private static readonly object Locker = new object();
//singleton
private static ConnectionMultiplexer redisConn;
//singleton
public static ConnectionMultiplexer getRedisConn()
{
if (redisConn == null)
{
lock (Locker)
{
if (redisConn == null || !redisConn.IsConnected)
{
redisConn = ConnectionMultiplexer.Connect(configurationOptions);
}
}
}
return redisConn;
}
}
class Program
{
static void Main(string[] args)
{
ConnectionMultiplexer cm = RedisConnSingleton.getRedisConn();
var db = cm.GetDatabase();
db.StringSet("key", "value");
String ret = db.StringGet("key");
Console.WriteLine("get key: " + ret);
}
}
Se connecter avec node-redis
L'exemple suivant utilise Node.js 19.4.0 et node-redis 4.5.1.
Fournissez les identifiants à la fois dans l'URL rootNodes et dans l'objet defaults. Les identifiants defaults authentifient les connexions vers tous les autres nœuds du cluster. Sans eux, une erreur NOAUTH se produit.
import { createCluster } from 'redis';
// Endpoint, port, username, and password for the instance.
// After supplying the username and password in the url parameter,
// also supply them in the defaults parameter.
// The defaults credentials authenticate the remaining nodes.
// Without defaults, a NOAUTH error occurs.
const cluster = createCluster({
rootNodes: [{
url: 'redis://testaccount:Rp829dlwa@r-bp10noxlhcoim2****.redis.rds.aliyuncs.com:6379'
}],
defaults: {
username: 'testaccount',
password: 'Rp829dlwa'
}
});
cluster.on('error', (err) => console.log('Redis Cluster Error', err));
await cluster.connect();
await cluster.set('key', 'value');
const value = await cluster.get('key');
console.log('get key: %s', value);
await cluster.disconnect();
Se connecter avec go-redis
L'exemple suivant utilise Go 1.19.7 et go-redis 9.5.1.
Utilisez go-redis 9,0 ou une version ultérieure. Les versions antérieures peuvent provoquer des erreurs d'incompatibilité lors de la connexion via un point de terminaison privé. Pour plus d'informations, consultez Erreurs courantes et dépannage.
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v9"
)
var ctx = context.Background()
func main() {
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"r-bp10noxlhcoim2****.redis.rds.aliyuncs.com:6379"},
Username: "testaccount",
Password: "Rp829dlwa",
})
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
panic(err)
}
fmt.Println("key", val)
}
Se connecter avec Lettuce (autonome)
Les configurations Lettuce par défaut peuvent entraîner une augmentation de la latence et une indisponibilité lors des modifications de l'instance. Lisez attentivement la Référence des paramètres Lettuce avant de configurer.
Utilisez Lettuce 6.3.0.RELEASE ou une version ultérieure. Pour plus d'informations, consultez [[Notice] Suggestions for upgrading Lettuce](t2556518.xdita#).
Étape 1 : Ajouter les dépendances Maven
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>4.1.65.Final</version>
<classifier>linux-x86_64</classifier>
</dependency>
Étape 2 : Ajouter le code de connexion
Remplacez les valeurs de host, port et password par les informations réelles de l'instance.
import io.lettuce.core.RedisURI;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import java.time.Duration;
public class ClusterDemo {
/**
* Enable TCP keepalive and configure the following three parameters:
* TCP_KEEPIDLE = 30
* TCP_KEEPINTVL = 10
* TCP_KEEPCNT = 3
*/
private static final int TCP_KEEPALIVE_IDLE = 30;
/**
* TCP_USER_TIMEOUT avoids situations where Lettuce remains stuck in a
* continuous timeout loop during a failure or crash event.
* refer: https://github.com/lettuce-io/lettuce-core/issues/2082
*/
private static final int TCP_USER_TIMEOUT = 30;
public static void main(String[] args) throws Exception {
// Replace with the actual instance information.
String host = "r-bp1ln3c4kopj3l****.redis.rds.aliyuncs.com";
int port = 6379;
String password = "Da****3";
RedisURI redisURI = RedisURI.Builder.redis(host)
.withPort(port)
.withPassword(password)
.build();
ClusterTopologyRefreshOptions refreshOptions = ClusterTopologyRefreshOptions.builder()
.enablePeriodicRefresh(Duration.ofSeconds(60))
.dynamicRefreshSources(false)
.enableAllAdaptiveRefreshTriggers()
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(15)).build();
// Config TCP KeepAlive
SocketOptions socketOptions = SocketOptions.builder()
.keepAlive(SocketOptions.KeepAliveOptions.builder()
.enable()
.idle(Duration.ofSeconds(TCP_KEEPALIVE_IDLE))
.interval(Duration.ofSeconds(TCP_KEEPALIVE_IDLE/3))
.count(3)
.build())
.tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
.enable()
.tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
.build())
.build();
RedisClusterClient redisClient = RedisClusterClient.create(redisURI);
redisClient.setOptions(ClusterClientOptions.builder()
.socketOptions(socketOptions)
.validateClusterNodeMembership(false)
.topologyRefreshOptions(refreshOptions).build());
StatefulRedisClusterConnection<String, String> connection = redisClient.connect();
connection.sync().set("key", "value");
System.out.println(connection.sync().get("key"));
}
}
Sortie attendue en cas de succès :
value
Référence des paramètres Lettuce
Les paramètres suivants contrôlent l'actualisation de la topologie du cluster Lettuce et le comportement TCP. Des valeurs par défaut incorrectes peuvent entraîner une augmentation de la latence et une indisponibilité lors des modifications de l'instance.
| Paramètre | Par défaut | Recommandé | Description |
|---|---|---|---|
enablePeriodicRefresh(Duration) |
Désactivé | 60 secondes | Active l'actualisation périodique de la topologie du cluster. Maintient la vue locale de la topologie à jour, même pour les connexions persistantes inactives. |
dynamicRefreshSources(boolean) |
true |
false |
Lorsque true, tous les nœuds renvoyés par CLUSTER NODES actualisent la topologie, ce qui augmente la charge du serveur. Lorsque false, seul le point de terminaison spécifié est utilisé. Lors des modifications de configuration, le point de terminaison est généralement plus rapide et plus fiable. |
enableAllAdaptiveRefreshTriggers() |
Désactivé | Activé (requis) | Déclenche une actualisation automatique de la topologie lorsqu'un message MOVED est reçu. Sans cela, Lettuce ne peut pas mettre à jour la topologie locale après un changement de topologie. |
adaptiveRefreshTriggersTimeout(Duration) |
30 s | 15 s | Limite la fréquence d'actualisation de la topologie à une actualisation par période de délai d'attente. Les changements de topologie entre les nœuds ne sont pas atomiques, donc la première actualisation peut échouer. Un délai d'attente plus court permet une actualisation de suivi plus rapide. Réduisez encore cette valeur lorsque le nombre d'applications clientes est faible. |
validateClusterNodeMembership(boolean) |
true |
false (requis) |
Lorsque true, les redirections MOVED vont uniquement vers les nœuds listés dans la sortie CLUSTER NODES. Définissez sur false pour permettre l'accès aux nœuds nouvellement ajoutés avant que la topologie locale ne soit actualisée. |
FAQ
Pourquoi l'erreur MOVED 4578 172.18.xx.xxx:6379 se produit-elle ?
L'erreur MOVED <slot> <IP:Port> signifie que la clé demandée se trouve sur un nœud différent. Cela se produit généralement lorsque le client ne prend pas en charge le protocole Redis Cluster. Les scénarios courants incluent :
Connexion avec redis-cli sans l'option
-c.Utilisation du client standard
redis-pyen Python, qui ne gère pas la redirection automatique. Utilisez plutôt la classeredis.cluster.RedisCluster.
Pour plus d'informations sur les autres erreurs, consultez Erreurs courantes et dépannage.