GeminiDB Redis客户端重试指南
Redis客户端设置重试机制可以在网络不稳定或服务器临时故障的情况下,尽量保持应用程序的高可用性和稳定性。
GeminiDB Redis可能会遇到如下一些临时性故障场景
原因 |
说明 |
---|---|
触发高可用机制的场景 |
GeminiDB Redis支持自动监测各个节点的健康状态,在节点宕机后自动触发主备倒换或集群数据分片接管。通常,触发高可用机制可能有以下场景: |
网络波动 |
客户端与GeminiDB服务端之间的网络链路通常较为复杂,偶发性的网络抖动、数据丢包重传是难以避免的。一旦触发网络波动,可能会导致客户端发起的请求出现超时。 |
服务端过载 |
在数据库服务出现高负载、慢查询等场景,客户端发起的请求可能无法立即响应,从而出现超时。 |
客户端设置重试机制时,需注意以下最佳实践指南
最佳实践 |
说明 |
---|---|
设置合适的重试次数和间隔 |
应根据实际业务情况,设置合适的重试次数和间隔。重试次数过多可能会延长故障恢复时间,间隔过短可能会对服务器造成额外压力。对于高负载场景,建议使每次重试的间隔时间指数增长,避免在服务器负载高时大量请求同时重试,导致雪崩效应。 |
避免对非幂等命令执行重试 |
当客户端检测到命令执行超时后,存在服务端已经执行完命令但是在回包阶段出现异常的场景,此时执行重试可能会导致命令被重复执行。因此,通常推荐只针对幂等操作进行重试(例如SET操作,多次执行得到的结果不变),而对于非幂等操作,则需业务确认是否能容忍重复数据(例如INCR操作,多次操作可能导致计数器额外增加)。 |
客户端记录日志 |
建议重试时打印客户端日志,例如连接的IP和端口,报错的命令和涉及的key等,方便问题定位和排查。 |
以下提供部分语言SDK的代码示例,仅供参考。
Jedis
对于JedisPool模式,Jedis版本在4.0.0以上支持重试,以4.0.0为例:
package nosql.cloud.huawei.jedis; import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.providers.PooledConnectionProvider; import java.time.Duration; // UnifiedJedis API supported in Jedis >= 4.0.0 public class UnifiedJedisDemo { private static final int MAX_ATTEMPTS = 5; private static final Duration MAX_TOTAL_RETRIES_DURATION = Duration.ofSeconds(15); public static void main(String[] args) { // Basic connection config JedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder().password("xxx").build(); // Implement retry PooledConnectionProvider provider = new PooledConnectionProvider(HostAndPort.from("{ip}:{port}"), jedisClientConfig); UnifiedJedis jedis = new UnifiedJedis(provider, MAX_ATTEMPTS, MAX_TOTAL_RETRIES_DURATION); try { System.out.println("set key: " + jedis.set("key", "value")); } catch (Exception e) { // Signifies reaching either the maximum number of failures, MAX_ATTEMPTS, or the maximum query time, MAX_TOTAL_RETRIES_DURATION e.printStackTrace(); } } }
Redisson
package nosql.cloud.huawei.jedis; import org.redisson.Redisson; import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedissonDemo { private static final int TIME_OUT = 3000; private static final int RETRY_ATTEMPTS = 5; private static final int RETRY_INTERVAL = 1500; public static void main(String[] args) { Config config = new Config(); config.useSingleServer() .setPassword("xxx") .setTimeout(TIME_OUT) .setRetryAttempts(RETRY_ATTEMPTS) .setRetryInterval(RETRY_INTERVAL) .setAddress("redis://{ip}:{port}"); RedissonClient redissonClient = Redisson.create(config); RBucket<String> bucket = redissonClient.getBucket("key"); bucket.set("value"); } }
Go-redis
package main import ( "context" "fmt" "time" "github.com/redis/go-redis/v9" ) var ctx = context.Background() func main() { client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB MaxRetries: 3, // set max retry times MinRetryBackoff: time.Duration(1) * time.Second, // set retry interval MaxRetryBackoff: time.Duration(2) * time.Second, // set retry interval }) // Execute command err := client.Set(ctx, "key", "value", 0).Err() if err != nil { panic(err) } // Test pong, err := client.Ping(ctx).Result() if err != nil { fmt.Println("Failed:", err) return } fmt.Println("Success:", pong) }
Redis-py
import redis from redis.retry import Retry from redis.exceptions import ConnectionError from redis.backoff import ExponentialBackoff from redis.client import Redis from redis.exceptions import ( BusyLoadingError, ConnectionError, TimeoutError ) # Run 3 retries with exponential backoff strategy retry_strategy = Retry(ExponentialBackoff(), 3) # Redis client with retries client = redis.Redis( host = 'localhost', port = 6379, retry = retry_strategy, # Retry on custom errors retry_on_error = [BusyLoadingError, ConnectionError, TimeoutError], # Retry on timeout retry_on_timeout = True ) try: client.ping() print("Connected to Redis!") except ConnectionError: print("Failed to connect to Redis after retries.") try: client.set('key', 'value') print("Set key and value success!") except ConnectionError: print("Failed to set key after retries.")
Hiredis
Hiredis 是一个低级别的C语言库,不提供内置的自动重试机制,需要手动编写逻辑。
以下是一个简单的示例,展示如何通过一个循环和延迟来实现建立连接阶段的自动重试,对于执行命令的重试,也是类似的方案。
#include <hiredis/hiredis.h> #include <stdio.h> #include <unistd.h> redisContext* connect_with_retry(const char *hostname, int port, int max_retries, int retry_interval) { redisContext *c = NULL; int attempt = 0; while (attempt < max_retries) { c = redisConnect(hostname, port); if (c != NULL && c->err == 0) { printf("Connection success!\n"); return c; } if (c != NULL) { printf("Connection error: %s\n", c->errstr); redisFree(c); } else { printf("Connection failed\n"); } printf("Retrying in %d seconds...\n", retry_interval); sleep(retry_interval); attempt++; } return NULL; } int main() { const char* hostname = "127.0.0.1"; int port = 6379; int max_retries = 5; int retry_interval = 2; redisContext *c = connect_with_retry(hostname, port, max_retries, retry_interval); if (c == NULL) { printf("Failed to connect to Redis after %d attempts\n", max_retries); return 1; } redisFree(c); return 0; }