O modo de conexão direta conecta um cliente aos servidores de dados de uma instância de cluster Tair (Redis OSS-Compatible) usando o protocolo nativo Redis Cluster. Essa abordagem elimina a camada de proxy, reduz a latência de resposta e facilita a migração de implantações Redis Cluster autogerenciadas.
Conexão direta vs. modo proxy
|
Aspecto |
Modo de conexão direta |
Modo proxy |
|
Latência |
Menor (sem salto de proxy) |
Ligeiramente maior |
|
Requisito do cliente |
Deve suportar o protocolo Redis Cluster |
Qualquer cliente Redis |
|
Escalabilidade |
O cliente gerencia o reconhecimento de topologia |
Transparente para o cliente |
|
Alta disponibilidade |
O cliente trata os redirecionamentos |
O proxy gerencia o failover |
Para obter mais informações sobre o modo proxy, consulte Features of proxy nodes.
Como funciona
Quando o modo de conexão direta é ativado, o Tair (Redis OSS-Compatible) aloca um endereço ip virtual (VIP) para o nó master de cada shard de dados. Um servidor dns resolve o endpoint privado para o VIP de um shard de dados aleatório. O cliente usa esse VIP para gerencie os dados da instância com base no protocolo Redis Cluster.
Pré-requisitos
Antes de começar, certifique-se de ter:
Uma instância de cluster Tair (Redis OSS-Compatible) com o modo de conexão direta ativado. Para obter mais informações, consulte Ativar o modo de conexão direta
O endereço ip do cliente adicionado a uma lista de permissões. Para obter mais informações, consulte Configurar listas de permissões
Uma instância Elastic Compute Service (ECS) na mesma virtual private cloud (VPC) que a instância de cluster
Bibliotecas de cliente compatíveis
As bibliotecas de cliente a seguir são compatíveis com o protocolo Redis Cluster e funcionam com o modo de conexão direta.
|
Cliente |
Linguagem |
Versão testada |
GitHub |
Observações |
|
Jedis |
Java |
4.3.0 |
Recomendado para Spring Data Redis |
|
|
Lettuce |
Java |
6.3.0.RELEASE ou posterior |
Requer ajuste de tcp keepalive. Consulte Referência de parâmetros do Lettuce |
|
|
redis-py |
Python |
4.4.1 |
Use a classe |
|
|
PhpRedis |
PHP |
5.3.7 |
- |
|
|
StackExchange.Redis |
.NET |
2.6.90 |
- |
Não suporta selecione (apenas DB0) |
|
node-redis |
Node.js |
4.5.1 |
- |
Forneça as credenciais tanto em |
|
go-redis |
Go |
9.5.1 (9,0 ou posterior obrigatório) |
- |
Versões anteriores à 9,0 causam erros de incompatibilidade |
Se um cliente não é compatível com o protocolo Redis Cluster, ele não consegue redirecionar requisições para o shard correto, o que pode resultar em falhas na recuperação de dados. Para a lista completa de clientes compatíveis, consulte a página Clients no site do Redis.
Observações de uso
Diferentes arquiteturas de instância são compatíveis com conjuntos distintos de comandos nativos do Redis. A arquitetura de cluster impõe restrições a scripts Lua. Para obter mais informações, consulte Limites dos comandos suportados por instâncias de cluster e instâncias com separação de leitura/gravação.
Ao alterar as configurações de uma instância de cluster no modo de conexão direta, ocorre migração de slot. Durante a migração, o cliente pode receber erros
MOVEDeTRYAGAINao acessar os slots em migração. configure um mecanismo de repetição no cliente. Para obter mais informações, consulte Mecanismos de repetição para clientes e Alterar as configurações de uma instância.O comando selecione funciona no modo de conexão direta. Entretanto, alguns clientes Redis Cluster, como o StackExchange.Redis, não são compatíveis com selecione. Com esses clientes, apenas DB0 está disponível.
Os endpoints privados fornecem acesso somente pela rede interna da Alibaba Cloud. Tanto o acesso sem senha quanto a autenticação por conta e senha são compatíveis.
Conectar com redis-cli
Inclua o parâmetro -c para ative o modo cluster ao conectar por um endpoint privado. Sem ele, a conexão falha.
./redis-cli -h r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com -p 6379 -c
Autentique-se com sua conta de banco de dados:
AUTH testaccount:Rp829dlwa
Para obter mais informações, consulte Usar o redis-cli para conectar a uma instância.
Conectar com Jedis
Os exemplos a seguir utilizam Jedis 4.3.0. Para obter mais informações, acesse GitHub.
Pool de conexões personalizado (recomendado)
Um pool de conexões personalizado oferece controle sobre os limites de conexão. No modo de conexão direta, cada cliente se conecta diretamente aos shards individuais. defina os tamanhos do pool de forma que o total de conexões entre todos os clientes não ultrapasse o número máximo de conexões por shard: Número de clientes x MaxTotal < Conexões máximas a um único shard.
|
Parâmetro |
Valor |
Descrição |
|
|
30 |
Número máximo de conexões no pool. Deve satisfazer a fórmula por shard acima. |
|
|
20 |
Máximo de conexões ociosas. defina com base na carga de trabalho. |
|
|
15 |
Mínimo de conexões ociosas a manter. |
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 conexões padrão
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.
}
}
Conectar com PhpRedis
O exemplo a seguir utiliza PhpRedis 5.3.7. Para obter mais informações, acesse 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;
?>
Conectar com redis-py
O exemplo a seguir utiliza Python 3.9 e redis-py 4.4.1. Para obter mais informações, acesse 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'))
Conectar com Spring Data Redis
O projeto Maven a seguir utiliza Spring Data Redis 2.4.2. Como alternativa, baixe o cliente Lettuce ou Jedis manualmente.
Etapa 1: Adicionar dependências 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>
Etapa 2: configure a conexão
Escolha Jedis (recomendado) ou Lettuce como cliente subjacente.
Spring Data Redis com Jedis (recomendado)
@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 com Lettuce
As configurações padrão do Lettuce podem causar aumento de latência e inacessibilidade durante alterações na instância. Leia a Referência de parâmetros do Lettuce com atenção antes de configure.
Utilize Lettuce 6.3.0.RELEASE ou posterior. Para obter mais informações, consulte [[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);
}
Conectar com .NET (StackExchange.Redis)
O exemplo a seguir utiliza .NET 6.0 e 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);
}
}
Conectar com node-redis
O exemplo a seguir utiliza Node.js 19.4.0 e node-redis 4.5.1.
Forneça as credenciais tanto na URL de rootNodes quanto no objeto defaults. As credenciais em defaults autenticam as conexões com todos os demais nós do cluster. Sem elas, ocorre o erro NOAUTH.
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();
Conectar com go-redis
O exemplo a seguir utiliza Go 1.19.7 e go-redis 9.5.1.
Utilize go-redis 9,0 ou posterior. Versões anteriores podem causar erros de incompatibilidade ao conectar por um endpoint privado. Para obter mais informações, consulte Erros comuns e solução de problemas.
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)
}
Conectar com Lettuce (standalone)
As configurações padrão do Lettuce podem causar aumento de latência e inacessibilidade durante alterações na instância. Leia a Referência de parâmetros do Lettuce com atenção antes de configure.
Utilize Lettuce 6.3.0.RELEASE ou posterior. Para obter mais informações, consulte [[Notice] Suggestions for upgrading Lettuce](t2556518.xdita#).
Etapa 1: Adicionar dependências 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>
Etapa 2: Adicionar o código de conexão
Substitua os valores de host, port e password pelas informações reais da instância.
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"));
}
}
Saída esperada em caso de sucesso:
value
Referência de parâmetros do Lettuce
Os parâmetros a seguir controlam a atualização de topologia de cluster e o comportamento tcp do Lettuce. Valores padrão incorretos podem causar aumento de latência e inacessibilidade durante alterações na instância.
|
Parâmetro |
Padrão |
Recomendado |
Descrição |
|
|
Desativado |
60 segundos |
ative a atualização periódica da topologia de cluster. Mantém a visão local da topologia atualizada, mesmo para conexões persistentes ociosas. |
|
|
|
|
Quando |
|
|
Desativado |
Ativado (obrigatório) |
Aciona a atualização automática da topologia ao receber uma mensagem MOVED. Sem este parâmetro, o Lettuce não atualiza a topologia local após uma mudança de topologia. |
|
|
30s |
15s |
Limita a frequência de atualização de topologia a uma atualização por período de timeout. Como as alterações de topologia entre nós não são atômicas, a primeira atualização pode falhar. Um timeout menor permite uma atualização de acompanhamento mais rápida. Reduza esse valor quando o número de aplicações cliente for pequeno. |
|
|
|
|
Quando |
Perguntas frequentes
Por que ocorre o erro MOVED 4578 172.18.xx.xxx:6379?
O erro MOVED <slot> <IP:Port> indica que a chave solicitada está em um nó diferente. Isso ocorre quando o cliente não é compatível com o protocolo Redis Cluster. Os cenários mais comuns incluem:
Conectar com redis-cli sem a opção
-c.Usar o cliente padrão
redis-pyem Python, que não trata redirecionamentos automáticos. Use a classeredis.cluster.RedisClustercomo alternativa.
Para informações sobre outros erros, consulte Erros comuns e solução de problemas.