Redis 性能优化基本设置

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() × minIdleclose(),归还到池中(参考 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 默认每次 @Injectnew 一个新实例。若 RedisClusterconfigure() 里被 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,防止指令重排导致半初始化对象被读到。

  • 同步块内二次判空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值