GeminiDB Redis客户端重试机制
Redis客户端设置重试机制可以在网络不稳定或服务器临时故障的情况下,尽量保持应用程序的高可用性和稳定性。
GeminiDB Redis可能会遇到如下一些临时性故障场景:
|
原因 |
说明 |
|---|---|
|
触发高可用机制的场景 |
GeminiDB Redis支持自动监测各个节点的健康状态,在节点宕机后自动触发主备倒换或集群数据分片接管。通常,触发高可用机制可能有以下场景: |
|
网络波动 |
客户端与GeminiDB服务端之间的网络链路通常较为复杂,偶发性的网络抖动、数据丢包重传是难以避免的。一旦触发网络波动,可能会导致客户端发起的请求出现超时。 |
|
服务端过载 |
在数据库服务出现高负载、慢查询等场景,客户端发起的请求可能无法立即响应,从而出现超时。 |
客户端设置重试机制时,需注意以下原则:
|
原则 |
说明 |
|---|---|
|
设置合适的重试次数和间隔 |
应根据实际业务情况,设置合适的重试次数和间隔。重试次数过多可能会延长故障恢复时间,间隔过短可能会对服务器造成额外压力。对于高负载场景,建议使每次重试的间隔时间指数增长,避免在服务器负载高时大量请求同时重试,导致雪崩效应。 |
|
避免对非幂等命令执行重试 |
当客户端检测到命令执行超时后,存在服务端已经执行完命令但是在回包阶段出现异常的场景,此时执行重试可能会导致命令被重复执行。因此,通常推荐只针对幂等操作进行重试(例如SET操作,多次执行得到的结果不变),而对于非幂等操作,则需业务确认是否能容忍重复数据(例如INCR操作,多次操作可能导致计数器额外增加)。 |
|
客户端记录日志 |
建议重试时打印客户端日志,例如连接的IP和端口,报错的命令和涉及的key等,方便问题定位和排查。 |
以下提供部分语言SDK的代码示例,仅供参考。
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);
// MAX_ATTEMPTS 设置合适的重试次数
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();
}
}
}
以下是一个简单的示例,展示如何通过设置RETRY_ATTEMPTS、RETRY_INTERVAL参数来设置合适的重试次数和间隔。
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");
}
}
以下是一个简单的示例,展示如何通过设置MaxRetries、MinRetryBackoff、MaxRetryBackoff参数来设置合适的重试次数和间隔。
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)
}
以下是一个简单的示例,展示如何通过设置Retry函数的参数来设置合适的重试次数。
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 是一个低级别的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;
}