Redis性能优化基本设置
下面的推荐设置针对的是高 QPS 小 Key 快速返回场景,对于其它服务大 key 较多或QPS 偏低等情况需要按实际情况综合考虑
1. 参数优化
连接池参数分三层理解:
| 层级 | 参数 | 作用 |
|---|---|---|
| 容量 | minIdle / maxIdle / maxTotal | 控制连接数量与预热规模 |
| 等待与超时 | maxWait / connTimeout / soTimeout | 控制「拿连接」和「等 Redis 响应」的上限 |
| 健康检测 | testOn* + 驱逐相关 | 控制连接有效性检查的时机与成本 |
推荐参数设置(高QPS 低延时)
适用于过滤、频控、检索等读多、超时敏感服务,CDB Redis 数据库的连接上限是 10000,部署 100 个实例情况下单机可以 100 连接:
# 容量
MAX_TOTAL=128
REDIS_MAX_IDLE=64
REDIS_MIN_IDLE=64
# 等待与超时(毫秒)
REDIS_MAX_WAIT=50 # 等连接的时间
REDIS_CONN_TIME_OUT=100 # 建立连接超时
REDIS_TIME_OUT=5 # 命令执行超时 soTimeout
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128);
poolConfig.setMaxIdle(64);
poolConfig.setMinIdle(64);
poolConfig.setMaxWaitMillis(50L);
// 显式传入超时,避免走 Jedis 默认 2000ms
JedisCluster cluster = new JedisCluster(
nodes,
100, // connectionTimeout
5, // soTimeout
2, // maxAttempts
poolConfig
);
1.1 minIdle
含义:连接池中保持的最小空闲连接数。低于该值时,池会尝试新建连接补足。
为什么要调:
-
minIdle=0(Jedis 默认)时,低峰或刚启动后池内无连接,请求需现场建连,首包延迟高。 -
设为非 0 后,可在启动时预热到
minIdle,把建连成本前移。
推荐写法:
-
高 QPS 服务:
minIdle = 64,与maxIdle对齐。 -
启动后对各节点
JedisPool循环getResource()×minIdle再close(),归还到池中(参考 ptr 的warmUpJedisPools做法)。
for (int i = 0; i < minIdle; i++) {
borrowed.add(pool.getResource());
}
// finally 中 close,连接回到 idle 池
1.2 maxIdle
含义:池中允许存在的最大空闲连接数。超过时,归还的多余连接会被销毁。
为什么要调:
-
过大:占用 Redis
maxclients与服务端文件描述符。 -
过小:频繁建连/断连,增加 CPU 与 GC 压力。
推荐写法:
-
与
minIdle设为相同值(如均为 64),池规模稳定,行为可预期。 -
不建议
maxIdle远大于minIdle,否则低峰后仍会保留大量空闲连接。
1.3 maxWait
含义:连接池耗尽时,调用方等待获取连接的最长时间(BlockWhenExhausted=true 时生效)。超时抛 PoolExhaustedException。
为什么要调:
-
默认
-1(无限等待)会导致线程在池满时长时间阻塞,级联拖垮业务线程池。 -
广告链路要求快速失败,把等待压到毫秒级。
推荐写法:
-
maxWait = 50(ms)。 -
配合合理的
maxTotal,若频繁触发 maxWait 超时,应优先排查连接泄漏或maxTotal过小,而不是一味加大 maxWait。
1.4 soTimeout
含义:Socket 读写超时(命令执行超时)。一次 get/hgetAll 等调用,等待 Redis 返回数据的最长时间。
为什么要调:
-
Jedis 若不显式传入,可能使用默认 2000ms,Redis 慢或节点故障时线程被长时间占用。
-
过滤/频控链路通常有整体 deadline(如 6–10ms),Redis 单步应更短。
推荐写法:
-
soTimeout = 5(ms),与业务超时预算对齐。 -
必须在
JedisCluster构造函数中显式传入,不要只配在 properties 里却用无参构造:
// 推荐
new JedisCluster(nodes, connTimeout, soTimeout, maxAttempts, poolConfig);
// 避免:超时参数未生效
new JedisCluster(nodes, poolConfig);
1.5 connTimeout
含义:建立 TCP 连接的超时(connect timeout),与命令读写超时分开。
为什么要调:
-
节点宕机、网络分区时,建连可能 hang 很久。
-
与
soTimeout分工:connTimeout管握手,soTimeout管命令。
推荐写法:
-
connTimeout = 100(ms),比soTimeout宽松,允许建连有一定余量。 -
集群模式下
maxAttempts = 2,减少故障节点重试带来的额外等待。
1.6 testOnCreate
含义:连接创建时是否执行 PING 验证。
推荐写法:
-
testOnCreate = true。 -
创建频率低,一次 PING 可尽早发现连到坏节点,成本可接受。
1.7 testOnBorrow
含义:每次从池中取出连接时是否 PING 验证。
推荐写法:
-
热路径服务:
**testOnBorrow = false**。 -
依赖
testOnCreate+ 短超时快速失败 + 集群拓扑刷新,而不是每次 borrow 检测。 -
若网络极不稳定、主从切换频繁,可评估
testWhileIdle,而非打开testOnBorrow。
1.8 testWhileIdle
含义:空闲连接巡检时是否 PING 验证。
推荐写法:
-
testWhileIdle = true,配合驱逐线程使用。 -
在 borrow 不做检测的前提下,由后台线程周期性清理死连接。
1.9 timeBetweenEvictionRunsMillis
含义:空闲连接驱逐线程的运行间隔。仅当该值 > 0 时,驱逐线程才会启动。
推荐写法:
| 策略 | 配置 | 适用 |
|---|---|---|
| 启用巡检 | 30000(30s) | 需要清理死连接、网络偶发断连 |
| 禁用巡检 | 不设置或保持默认 | 与下面两项 -1 组合,彻底关闭驱逐 |
poolConfig.setTestWhileIdle(true);
poolConfig.setTimeBetweenEvictionRunsMillis(30_000);
1.10 minEvictableIdleTimeMillis
含义:连接空闲超过该时长后,才可能被驱逐线程检测并回收。
推荐写法:
| 策略 | 值 | 说明 |
|---|---|---|
| 积极回收 | 60_000(60s) | ptr 做法,空闲过久则回收 |
| 禁用驱逐 | -1 | 频控/向量检索做法,配合 numTestsPerEvictionRun=-1,避免后台 PING |
高 QPS 短连接复用场景,倾向 禁用驱逐,减少后台线程对 Redis 的额外请求。
1.11 numTestsPerEvictionRun
含义:每次驱逐线程运行时,抽检的空闲连接数量。
推荐写法:
-
启用驱逐时:
8(ptr)或按minIdle的一定比例。 -
禁用驱逐时:
-1(与minEvictableIdleTimeMillis=-1配套)。
参数配置速查
// 方案 A:启用空闲巡检
config.setTimeBetweenEvictionRunsMillis(30_000);
config.setMinEvictableIdleTimeMillis(60_000);
config.setNumTestsPerEvictionRun(8);
// 方案 B:禁用空闲驱逐
// config.setTimeBetweenEvictionRunsMillis(30_000);
// config.setMinEvictableIdleTimeMillis(-1);
// config.setNumTestsPerEvictionRun(-1);
2. 加载
原则:加载失败必须阻断启动
Redis 是核心依赖。初始化失败仍继续启动,会导致:
-
所有请求在运行时 NPE 或连接异常;
-
监控误报为业务逻辑错误,难以定位根因;
-
滚动发布时「坏实例」混入集群,放大故障面。
反例(不要这样写)
try {
jedis = new JedisCluster(nodes, connTimeout, soTimeout, 2, poolConfig);
} catch (Exception e) {
LOGGER.error("初始化 Redis 出错", e);
// 继续启动 —— 危险
}
推荐写法
**方式一:抛出运行时异常
try {
jedis = new JedisCluster(hostAndPortsSet, conTimeout, soTimeout, 2, poolConfig);
LOGGER.info("JedisCluster init success, maxTotal={}, minIdle={}, soTimeout={}ms",
maxActive, minIdle, soTimeout);
} catch (Exception e) {
LOGGER.error("JedisCluster init failed, config={}", redisConfig, e);
throw new RuntimeException("JedisCluster init failed", e);
}
**方式二:或者直接退出
if (!properties.containsKey("REDIS_HOST")) {
LOGGER.error("redis init fail");
System.exit(1);
}
3. 连接池单例加载
3.1 Guice 默认非单例
Guice 默认每次 @Inject 都 new 一个新实例。若 RedisCluster 在 configure() 里被 bind 为普通实现类,且被多处注入,会重复创建 JedisCluster,带来:
-
连接数翻倍(每套池子各建一套);
-
内存与文件描述符浪费;
-
行为不一致(不同模块连的是不同池实例)。
**添加 ****@Singleton**:
@Slf4j
@Singleton // 关键
public class RedisCluster implements Configurable {
@Inject
public RedisCluster(@Named("redis") SysConfig sysConfig) {
// init once
}
}
显式绑定:
bind(RedisCluster.class).in(Singleton.class);
静态工具类方式(zpfilter / 频控):用 static Map 持有全局唯一池,在 ScfInit / ServiceInit 中只调用一次 init()。
public class RedisClient {
public static final Map<String, JedisCluster> JEDIS_CLUSTER_MAP = new HashMap<>();
public static void initRedisCluster(String fileName, Properties prop) {
// 仅在启动时调用一次
JEDIS_CLUSTER_MAP.put(fileName, jedisCluster);
}
}
3.2 注意单例的写法:double check 或者 static 直接加载
写法 A:静态初始化(最简单,推荐用于工具类)
public class RedisHolder {
private static final JedisCluster INSTANCE = createCluster();
private static JedisCluster createCluster() {
// 读配置、建连;失败直接 throw,类加载阶段即失败
return new JedisCluster(...);
}
public static JedisCluster get() {
return INSTANCE;
}
}
-
优点:线程安全、无竞态。
-
缺点:类加载即初始化,配置路径需静态可确定。
写法 B:双重检查锁定(Pipeline 单例)
JedisClusterPipeline 等延迟初始化的单例需保证线程安全。vectorRetrieval 曾修复 DCL 缺陷,标准写法如下:
public class JedisClusterPipeline {
private static volatile JedisClusterPipeline instance;
public static JedisClusterPipeline getInstance(JedisCluster cluster) {
if (instance null) {
synchronized (JedisClusterPipeline.class) {
if (instance null) { // 如果 instance 在 map 中,需要重新 get 一次
instance = new JedisClusterPipeline(cluster);
}
}
}
return instance;
}
}
要点:
-
instance必须volatile,防止指令重排导致半初始化对象被读到。 -
同步块内二次判空。

1575

被折叠的 条评论
为什么被折叠?



