All Products
Search
Document Center

Tair:Use a private endpoint to connect to a Tair instance

Last Updated:Jan 26, 2024

If you have deployed a native Redis cluster on premises or on a third-party cloud platform, you can use a private endpoint in direct connection mode to seamlessly migrate your business to Tair. The private endpoints of Tair cluster instances support the native Redis Cluster protocol. In direct connection mode, the client directly connects to the Tair server, resulting in fast response from Tair.

Prerequisites

  • The direct connection mode is enabled for your Tair cluster instance.

  • The IP address of the client that you use to connect to the cluster instance is added to a whitelist of the instance. For more information, see Configure whitelists.

  • Clients that support Redis Cluster are used, such as Jedis and PhpRedis.

    Note
    • If you use a client that does not support Redis Cluster, you may be unable to obtain data because the client cannot redirect your request to the correct shard.

    • You can obtain a list of clients that support Redis Cluster from the Clients page on the Redis official website.

  • The Elastic Compute Service (ECS) instance on which the Tair client is deployed and the Tair cluster instance belong to the same virtual private cloud (VPC). The two instances have the same VPC ID.

Background Information

When you enable the direct connection mode for an instance, Tair allocates a virtual IP address (VIP) to the master node of each data shard in the instance. Before a client sends the first request to a private endpoint, the client uses a Domain Name System (DNS) server to resolve the private endpoint. The resolution result is the VIP of a random data shard in the cluster instance. The client can use this VIP to manage the data of the Tair cluster instance over the Redis Cluster protocol. The following figure shows the service architecture of a Tair cluster instance in direct connection mode.

Figure 1. Architecture of a cluster instance in direct connection modeRedis集群版直连模式服务架构

Usage notes

  • Instances of different architectures provide different levels of support for native Redis commands. For example, the cluster architecture does not support the SWAPDB command, and has limits on Lua scripts. For more information, see Limits on commands supported by cluster instances and read/write splitting instances.

  • If you change the configurations of an instance in direct connection mode, slot migration is performed. In this case, the client may prompt error messages such as MOVED and TRYAGAIN when the client accesses the slots that are being migrated. To ensure the successful execution of the request, configure a retry mechanism for the client. For more information, see Retry mechanisms for Tair clients.

  • In direct connection mode, you can use the SELECT command to switch to the desired database. However, specific Redis Cluster clients such as StackExchange.Redis do not support the SELECT command. If you work with one of these clients, you can only use database 0.

  • Private endpoints can be used to access cluster instances only over Alibaba Cloud internal networks. When you use the private endpoint of a cluster instance to access the instance, password-free access and account and password authentication are supported.

redis-cli

Use a private endpoint to connect to a cluster instance from redis-cli.

Important

When you use a private endpoint to connect to a cluster instance from redis-cli, you must add the -c parameter to the command. Otherwise, the connection fails.

./redis-cli -h r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com -p 6379 -c

Verify the password of your database account.

AUTH testaccount:Rp829dlwa

For more information, see Use redis-cli to connect to a Tair instance.

Jedis

In this example, Jedis 4.3.0 is used. For more information, visit GitHub.

  • Custom connection pool (recommended)

    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[]) {
            // Specify the maximum number of idle connections. In direct connection mode, the client directly connects to a shard of a cluster instance. Therefore, the following requirement must be met: Number of clients × Value of MaxTotal < Maximum number of connections to a single shard. 
            // Set the maximum number of connections to a shard of an ApsaraDB for Redis Community Edition instance to 10,000 and set the maximum number of connections to a shard of a Tair instance to 30,000. 
            config.setMaxTotal(30);
            // Specify the maximum number of idle connections based on your business needs. 
            config.setMaxIdle(20);
            config.setMinIdle(15);
    
            // Specify the private endpoint that is allocated to the cluster instance. 
            String host = "r-bp1xxxxxxxxxxxx.redis.rds.aliyuncs.com";
            int port = 6379;
            // Specify the password that is used to connect to 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();     // If the application exits and you want to destroy the resources, call this method. Then, the connection is closed, and the resources are released. 
        }
    }
  • Default connection pool

    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[]){
    
            // Specify the private endpoint that is allocated to 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();     // If the application exits and you want to destroy the resources, call this method. Then, the connection is closed, and the resources are released. 
        }
    }

PhpRedis

In this example, PhpRedis 5.3.7 is used. For more information, visit GitHub.

<?php
 // Specify the private endpoint and the port number that are used to connect to the cluster instance. 
 $array = ['r-bp1xxxxxxxxxxxx.redis.rds.aliyuncs.com:6379'];
 // Specify the password that is used to connect to the cluster instance. 
 $pwd = "xxxx";
 
 // Use the password to connect to the cluster instance. 
 $obj_cluster = new RedisCluster(NULL, $array, 1.5, 1.5, true, $pwd);
 
 // Display the result of the connection. 
 var_dump($obj_cluster);
 
 if ($obj_cluster->set("foo", "bar") == false) {
     die($obj_cluster->getLastError());
 }
 $value = $obj_cluster->get("foo");
 echo $value;
 ?>

redis-py

In this example, Python 3.9 and redis-py 4.4.1 are used. For more information, visit GitHub.

# !/usr/bin/env python
# -*- coding: utf-8 -*-
from redis.cluster import RedisCluster
# Replace the values of the host and port parameters with the endpoint and port number that are used to connect to the Tair instance. 
host = 'r-bp10noxlhcoim2****.redis.rds.aliyuncs.com'
port = 6379
# Replace the values of the user and pwd parameters with the username and password that are used to connect to the Tair instance. 
user = 'testaccount'
pwd = 'Rp829dlwa'
rc = RedisCluster(host=host, port=port, username=user, password=pwd)
# You can perform operations on the instance after the connection is established. For example, you can run the following code to call the SET and GET methods. 
rc.set('foo', 'bar')
print(rc.get('foo'))

Spring Data Redis

The following sample project is created by using Maven. You can also manually download the Lettuce or Jedis client.

  1. Add the following Maven dependencies:

    <?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>
  2. Enter the following code in the Spring Data Redis editor and modify the code based on the comments.

    In this example, Spring Data Redis 2.4.2 is used.

    • Spring Data Redis With Jedis

      @Bean
           JedisConnectionFactory redisConnectionFactory() {
               List<String> clusterNodes = Arrays.asList("host1:port1", "host2:port2", "host3:port3");
               RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodes);
               redisClusterConfiguration.setPassword("xxx");
       
               JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
               // Specify the maximum number of idle connections. In direct connection mode, the client directly connects to a shard of a cluster instance. Therefore, the following requirement must be met: Number of clients × Value of MaxTotal < Maximum number of connections to a single shard. 
               // Set the maximum number of connections to a shard of an ApsaraDB for Redis Community Edition instance to 10,000 and set the maximum number of connections to a shard of a Tair instance to 30,000. 
               jedisPoolConfig.setMaxTotal(30);
               // Specify the maximum number of idle connections based on your business needs. 
               jedisPoolConfig.setMaxIdle(20);
               // Disable testOn[Borrow|Return] to prevent generating additional ping commands.
               jedisPoolConfig.setTestOnBorrow(false);
               jedisPoolConfig.setTestOnReturn(false);
       
               return new JedisConnectionFactory(redisClusterConfiguration, jedisPoolConfig);
           }
    • Spring Data Redis With Lettuce

      /**
           * 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;
      
          /**
           * The TCP_USER_TIMEOUT parameter can avoid 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;
      
          @Bean
          public LettuceConnectionFactory redisConnectionFactory() {
              List<String> clusterNodes = Arrays.asList("host1:port1", "host2:port2", "host3:port3");
              RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodes);
              redisClusterConfiguration.setPassword("xxx");
      
              // 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(15))
                  .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);
          }

      The following table describes the parameters of ClusterTopologyRefreshOptions.builder.

      Parameter

      Description

      Example (recommended value)

      enablePeriodicRefresh(Duration refreshPeriod)

      The interval at which the topology of the cluster instance is refreshed. We recommend that you set the interval to 15 seconds. If you set the interval to a smaller value, a large number of calls are made to the nodes of the cluster instance. This compromises the performance of the cluster instance.

      15s

      dynamicRefreshSources()

      Specifies whether to use the nodes whose IP addresses are obtained from the topology as nodes to be called to refresh the topology of the cluster instance. To connect to Tair instances, you must set the parameter to false. Typically, Tair instances use VIPs. If you migrate an instance across zones, the VIPs of the instance are all changed. In this case, the route cannot be refreshed. If you set the parameter to false, the system uses a domain name that Alibaba Cloud provides to query the nodes of the cluster instance. The domain name service automatically performs load balancing and sends your request to the nodes of the Tair instance.

      false

      enableAllAdaptiveRefreshTriggers()

      Specifies whether to enable all triggers to adaptively refresh the topology. After adaptive refresh is enabled, the topology of the cluster instance is automatically refreshed each time a runtime event such as a MOVED redirection occurs on the cluster instance.

      No configuration is required.

      adaptiveRefreshTriggersTimeout(Duration timeout)

      The time period during which the topology of the cluster instance can be refreshed only once. This prevents the topology from being frequently refreshed.

      15s

      validateClusterNodeMembership()

      Specifies whether to validate the membership of cluster nodes. The validation is not required for Alibaba Cloud Tair instances.

      false

.NET

In this example, .NET 6.0 and StackExchange.Redis 2.6.90 are used.

using StackExchange.Redis;

class RedisConnSingleton {
    // Specify the endpoint, port number, username, and password that are used to connect to 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);
    }
}

node-redis

In this example, Node.js 19.4.0 and node-redis 4.5.1 are used.

import { createCluster } from 'redis';

// Specify the endpoint, port number, username, and password that are used to connect to the cluster instance. 
// After you supply the username and password for the url parameter, you must also supply the global username and password for the defaults parameter. 
// The global username and password are used to authenticate the remaining nodes. Otherwise, the 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();

go-redis

In this example, Go 1.18.5 and go-redis 8.11.5 are used.

Important

Use go-redis 8.0 or earlier for Tair instances that run Redis 6.0 or earlier.

package main

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8"
)

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)
}

Lettuce

We recommend that you use Lettuce 6.3.0 or later. In this example, Lettuce 6.3.0 is used.

  1. Add the following Maven dependencies:

            <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>
  2. Add the following code and modify the code based on the comments:

    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;
    
        /**
         * The TCP_USER_TIMEOUT parameter can avoid 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 the values of host, port, and password 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(15))
                    .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"));
        }
    }
  3. Run the preceding code. The following output is expected on successful completion:

    value

    For information about the parameters of ClusterTopologyRefreshOptions.builder, see the description in the preceding "Spring Data Redis" section.

References

The direct connection mode is suitable for simple application scenarios. The Tair proxy mode offers increased scalability and high availability. For more information, see Features of proxy nodes .

FAQ

Refer to Common errors and solutions.