客户端配置注意事项
云数据库 Redis 集群版和单机版兼容常用语言(Java/Golang/Python/Javascript 等)的主流客户端,且支持单机、redis cluster、sentinel 等连接模式。各个 SDK 均提供了丰富的配置项,本文将对部分比较重要的配置项做一些描述,指出部分注意事项。
客户端超时时间
客户端配置的超时时间不宜过短,否则后端 server 可能尚未处理完成就发生了超时。另外也不建议不配置超时,或者超时时间太长,比如请求长时间未完成,或连接长时间未建立,此时客户端有理由允许请求快速失败。 某些命令执行成本比其他命令要高,比如,一次性读写多个 key 的 MGET、MSET 命令,Lua 脚本,事务、响应比较大的命令,对于这类命令,建议使用更大的客户端超时,以避免命令尚未处理完成客户端就超时了,且重试后再次出现相同的情况。这类命令包含:
- 包含较多 key 的 MSET、MGET、DEL 等:这类命令会在 Proxy 层做拆分路由到多个分片,最终响应时间由最后一个分段收到响应的时间来确定,因此这类命令往往具有相对较高的延迟。
- 包含较多命令的 MULTI/EXEC 事务或 Lua 脚本:这类命令需要 server 处理较长的时间,一条 eval 命令脚本耗时可能是 GET/SET 的数千倍。但通常只要控制好复杂度和并发,则影响比较可控。
- 读取较大的值:如 hgetall、keys 这类命令,返回的内容较多,这需要更长的网络传输时间,通常具有较高的延迟。
- 执行诸如 BLPOP 之类的阻塞操作:这类阻塞命令,在无数据就绪时,本就需要等待较长时间。
目前 Proxy 和后端 server 间,针对非阻塞的请求超时默认为 2 秒,因此客户端配置的超时时间应该小于 2 秒。使用内存型 Redis 通常响应能在 10 毫秒内完成,如果遇到流量激增负载较重时,可能达到几十毫秒。因此,建议根据业务中对延迟的容忍程度,将超时时间设置为 50ms~2s 之间的数字。
连接池
一些客户端使用独占连接来处理请求,即在一个请求未执行完成之前,不能处理其他请求,当应用程序需要同时发送多个 Redis 命令时,就需要使用连接池。使用连接池可以避免频繁地创建和释放连接,可有效提升性能。在配置使用连接池时,需要考虑连接池的容量,链接探活策略,连接获取顺序等。
容量:
连接池容量不宜设置过大,否则会导致 server 端存在大量连接,同时也不可设置过小,否则会导致 Proxy 的流量不均衡,建议和发送命令的线程数保持在一个数量级。
连接探活策略:
Proxy 会主动关闭空闲时间超过 3600s 的连接,因此建议开启连接探活,探活周期可以适当调大,小于 3600s 即可。
连接获取顺序:
连接获取策略建议使用 FIFO (First In First Out),使用 LIFO(Last In First Out)可能导致只有少量连接被使用到,引起Proxy流量不均。 客户端 SDK 一般默认 LIFO(Last In First Out)的策略获取连接池中的连接,即每次使用最近放入连接池的连接。考虑一个极端的例子,有 50 个客户端, 每个客户端中只有一个连接活跃,那么连接到云数据库 Redis 集群的活跃连接数则为 50。如果集群有 20 个 Proxy,50 个连接到 20 个 Proxy,则极有可能出现 Proxy 上负载不均衡的问题。这可能导致部分 Proxy CPU 被打满,而其他的则很空闲。因此,我们建议使用 FIFO (First In First Out)的策略,即轮流地使用所有的连接。
Jedis 连接池配置
下面给出使用 Jedis 时,配置使用连接池的例子:
String host = "redis.*******.baidubce.com";
int port = 6379;
String password = "*****";
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(30);
config.setTestOnBorrow(false);
config.setTestOnReturn(false);
config.setLifo(false);
try (JedisPool pool = new JedisPool(config, host, port, 3000, password)) {
Jedis jedis = pool.getResource();
jedis.set("client", "jedis");
assert jedis.get("client").equals("jedis");
}
catch (Exception e) {
e.printStackTrace();
}
```Go
你可以在 JedisPoolConfig 对象上配置连接池的细节:
config.setMaxTotal(10); // 设置最大连接数 config.setTestOnBorrow(false); // 从连接池中拿连接是测试其可用性 config.setTestOnReturn(false); // 放回连接池中是测试其可用性 config.setTestWhileIdle(false); // 连接空闲时测试其可用性 config.setLifo(false); // 设置连接池是否为后进先出
参数 | 说明 | 默认值 | 建议 |
---|---|---|---|
maxTotal | 连接池中最大连接数 | 8 | 建议和同时发送命令的线程数保持在一个数量级。 |
maxIdle | 连接池允许的最大空闲连接数 | 8 | 这表示连接池中最大允许的空闲连接数,当此值设置的比较大时,突发的大量请求会导致创建较多的连接,而后这些连接一直处于空闲状态。当如果设置的过小,连接可能会频繁地被创建和释放。建议根据流量波动来设置,如果波动很小,可以设置为一个较小值,比如保持默认值。如果波动比较剧烈,为了避免频繁地创建和释放连接,可以和 maxTotal 保持一致。 |
minIdle | 连接池中最少空闲连接数 | 0 | 如果设置 minIdle 为非零的值,连接池中始终会有一些空闲连接。 |
blockWhenExhausted | 当连接池中连接用尽后,获取连接时是否要等待。 当值为 true 时,下面的 maxWait 才会生效。 | true | 建议使用默认值。 |
maxWait | 从连接池获取连接时的最大等待时间。 | -1(一直等待) | 此值不宜设置得过大。在大多数使用 Redis 的场景下,网络延迟都在百毫秒以下,将此值设置为 2-5 秒比较合适。如果对延迟有更高的要求,可以设置得更小一些。 |
testOnBorrow | 从连接池取连接时是否做连接有效性检测(发送 PING 命令)。检测到的无效连接将会被移除。 | false | 用于连接保活,连接池可以快速发现并剔除错误连接,如果高频地从连接池获取连接,开启后集群会增加不少探测流量。通常连接不会无故断开,另外用户在执行命令时也会做异常的检测,所以这里建议设置为 false。 |
testOnReturn | 向资源池归还连接时是否做连接有效性检测(ping)。检测到无效连接将会被移除。 | false | 同 testOnBorrow,建议设置为 false。 |
lifo | 从连接池中取连接的策略,是否采用 Last in first out 的策略 | true | 用于配置连接池获取连接的策略,和前文描述的 golang 客户端中 PoolFIFO含义类似,这里建议设置为 false,以保证 Proxy 上流量更均衡。 |
失败重试
用户使用云数据库 Redis 集群时,网络抖动、机器偶发故障等因素均可能导致请求失败,百度智能云数据库 Redis 集群有自动故障恢复的策略,设计合理的客户端自动重试机制能够显著提升应用程序的健壮性和用户体验。本节内容将描述配置客户端重试时需要注意的事项。
Jedis 客户端重试示例
Jedis 4.0.0 及以上版本支持重试机制。以下示例展示了如何使用 Jedis 5.0.0 进行重试操作。
PooledConnectionProvider provider = new PooledConnectionProvider(HostAndPort.from("127.0.0.1:6379"));
int maxAttempts = 5; // 最大重试次数
Duration maxTotalRetriesDuration = Duration.ofSeconds(10); // 最大的重试时间
UnifiedJedis jedis = new UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration);
try {
jedis.set("key", "value");
} catch (Exception e) {
// 多次尝试仍没成功
e.printStackTrace();
}
GoLang 客户端重试示例
GoLang 中配置重试可关注以下三个配置项:
配置项 | 默认值 | 说明 |
---|---|---|
MaxRetries | 3 | 默认失败后重试三次 |
MinRetryBackoff | 8 | 如果多次失败,重试间隔时间会逐渐变长,此为最短间隔 |
MaxRetryBackoff | 512 | 此为最长间隔 |
下面是在 Golang 中配置重试的示例:
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "password",
MaxRetries: 3,
MinRetryBackoff: 8 * time.Millisecond,
MaxRetryBackoff: 512 * time.Millisecond,
})