更新时间:2024-10-24 GMT+08:00
分享

优化Jedis连接池

方案概述

JedisPool是Jedis客户端的连接池,合理设置JedisPool资源池参数能够有效地提升Redis性能与资源利用率。本文档将对JedisPool的使用和资源池的参数配置提供详细的说明和配置建议。

JedisPool使用方法

以Jedis 5.1.3为例,其Maven依赖如下:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>5.1.3</version>
</dependency>

Jedis使用Apache Commons-pool2对资源池进行管理,在定义JedisPool时需设置关键参数GenericObjectPoolConfig(资源池)。该参数的使用示例如下,其中的参数的说明请参见JedisPool参数说明

GenericObjectPoolConfig jedisPoolConfig = new GenericObjectPoolConfig();
jedisPoolConfig.setMaxTotal(...);
jedisPoolConfig.setMaxIdle(...);
jedisPoolConfig.setMinIdle(...);
jedisPoolConfig.setMaxWaitMillis(...);

JedisPool的初始化方法如下:

// redisHost为Redis实例的连接IP, redisPort为Redis实例连接端口,redisPassword为Redis实例的连接密码,timeout是连接超时/读写超时。
JedisPool jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, timeout, redisPassword);

// 执行命令如下
Jedis jedis = null;
try {
    jedis = jedisPool.getResource();
    // 具体的命令
    jedis.set("key", "value");
} catch (Exception e) {
    logger.error(e.getMessage(), e);
} finally {
    // 在JedisPool模式下,Jedis会被归还给资源池
    if (jedis != null) 
        jedis.close();
}

JedisPool参数说明

Jedis连接是连接池中JedisPool管理的资源,JedisPool保证资源在一个可控范围内,并保障线程安全。使用合理的GenericObjectPoolConfig配置能够提升Redis的服务性能,降低资源开销。表1表2提供了一些重要参数的说明及配置建议。

表1 资源设置与使用相关参数

参数

说明

默认值

建议

maxTotal

资源池中的最大连接数。

8

请参见关键参数配置建议

maxIdle

资源池允许的最大空闲连接数。

8

请参见关键参数配置建议

minIdle

资源池允许的最小空闲连接数。

0

请参见关键参数配置建议

blockWhenExhausted

当资源池用尽后,调用者是否要等待。

  • true:等待。
  • false:不等待。

只有当值为true时,设置的maxWaitMillis才会生效。

true

建议使用默认值。

maxWaitMillis

当资源池连接用尽后,调用者的最大等待时间(单位:毫秒)。

值为-1表示一直等待。

-1

建议设置具体的最大等待时间。

testOnBorrow

向资源池借用连接时是否做连接有效性检测(ping)。检测到的无效连接将会被移除。

  • true:校验。
  • false:不校验。

false

业务量很大时候建议设置为false,减少一次ping的开销。

testOnReturn

向资源池归还连接时是否做连接有效性检测(ping)。检测到无效连接将会被移除。

  • true:校验。
  • false:不校验。

false

业务量很大时候建议设置为false,减少一次ping的开销。

jmxEnabled

是否开启JMX监控。

  • true:开启。
  • false:不开启。

true

建议开启,请注意应用本身也需要开启。

空闲Jedis对象检测由表2中的参数组合完成。

表2 空闲资源检测相关参数

名称

说明

默认值

建议

testWhileIdle

是否在空闲资源监测时通过ping命令监测连接有效性,无效连接将被销毁。

false

true

timeBetweenEvictionRunsMillis

空闲资源的检测周期(单位:毫秒)。

值为-1表示不检测。

-1

建议设置,周期自行选择,也可以默认也可以使用下方JedisPoolConfig 中的配置。

minEvictableIdleTimeMillis

资源池中资源的最小空闲时间(单位:毫秒),达到此值后空闲资源将被移除。

1,800,000(即30分钟)

可根据自身业务决定,一般默认值即可,也可以考虑使用下方JeidsPoolConfig中的配置。

numTestsPerEvictionRun

做空闲资源检测时,每次检测资源的个数。

3

可根据自身应用连接数进行微调,设置为-1时,表示对所有连接做空闲监测。

为了方便使用,Jedis提供了JedisPoolConfig,它继承了GenericObjectPoolConfig在空闲检测上的一些设置。

public class JedisPoolConfig extends GenericObjectPoolConfig {
  public JedisPoolConfig() {
    setTestWhileIdle(true);
    setMinEvictableIdleTimeMillis(60000);
    setTimeBetweenEvictionRunsMillis(30000);
    setNumTestsPerEvictionRun(-1);
    }}

可以在org.apache.commons.pool2.impl.BaseObjectPoolConfig中查看全部默认值。

关键参数配置建议

  • maxTotal设置建议

    想合理设置maxTotal(最大连接数)需要考虑的因素较多,如:

    • 业务希望的Redis并发量。
    • 客户端执行命令时间。
    • Redis资源,例如Redis分片数。
    • maxTotal不能超过Redis的最大连接数(查看Redis的最大连接数请参考查看或修改实例最大连接数)。
    • 资源开销,例如虽然希望控制空闲连接,但又不希望因为连接池中频繁地释放和创建连接造成不必要的开销。

    假设一次命令时间,即borrow|return resource加上Jedis执行命令(含网络耗时)的平均耗时约为1ms,一个连接的QPS大约是1s/1ms = 1000,而业务期望的单个Redis的QPS是50000(业务总的QPS/Redis分片个数),那么理论上需要的资源池大小(即MaxTotal)是50000 / 1000 = 50。

    但事实上在理论值基础上,还要预留一些资源,所以maxTotal可以比理论值大一些。这个值不是越大越好,一方面连接太多会占用客户端和服务端资源,另一方面对于Redis这种高QPS的服务器,如果出现大命令的阻塞,即使设置再大的资源池也无济于事。

  • maxIdle和minIdle设置建议

    maxIdle是业务需要的最大连接数,maxTotal是为了给出余量,所以maxIdle不要设置得过小,否则会有new Jedis(新连接)开销,而minIdle是为了控制空闲资源检测。

    连接池的最佳性能是maxTotal=maxIdle,这样就避免了连接池伸缩带来的性能干扰。如果您的业务存在突峰访问,建议设置这两个参数的值相等;如果并发量不大或者maxIdle设置过高,则会导致不必要的连接资源浪费。

    您可以根据实际总QPS和调用Redis的客户端规模整体评估每个节点所使用的连接池大小。

  • 使用监控获取合理值

    在实际环境中,比较可靠的方法是通过监控来尝试获取参数的最佳值。可以考虑通过JMX等方式实现监控,从而找到合理值。

常见报错

  • 资源不足

    下面两种情况均属于无法从资源池获取到资源。此类异常的原因不一定是资源池不够大,请参见关键参数设置建议中的分析。建议从网络、资源池参数设置、资源池监控(如果对JMX监控)、代码(例如没执行jedis.close())、慢查询、DNS等方面进行排查。

    1. 超时:
      redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
      …Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
      at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject
    2. blockWhenExhausted为false时,资源池用尽后不会等待资源释放:
      redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
      …Caused by: java.util.NoSuchElementException: Pool exhausted
      at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject
  • 预热JedisPool

    由于一些原因(如超时时间设置较小等),项目在启动成功后可能会出现超时。JedisPool定义最大资源数、最小空闲资源数时,不会在连接池中创建Jedis连接。初次使用时,池中没有资源使用则会先新建一个new Jedis,使用后再放入资源池,该过程会有一定的时间开销,所以建议在定义JedisPool后,以最小空闲数量为基准对JedisPool进行预热,示例如下:

    List<Jedis> minIdleJedisList = new ArrayList<Jedis>(jedisPoolConfig.getMinIdle());
    for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
        Jedis jedis = null;
        try {
            jedis = pool.getResource();
            minIdleJedisList.add(jedis);
            jedis.ping();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
        }
    }
    for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
        Jedis jedis = null;
        try {
            jedis = minIdleJedisList.get(i);
            jedis.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
        
        }
    }

相关文档