做一个积极的人

编码、改bug、提升自己

我有一个乐园,面向编程,春暖花开!

你好,JedisPoolConfig

Java中使用Jedis作为连接Redis的工具。在使用Jedis的也可以配置JedisPool连接池,JedisPool配置参数大部分是由JedisPoolConfig的对应项来赋值的。本文简单总结几个常用的配置,然后通过源码(版本jedis-3.1.0)的角度让你理解配置这些参数的原理。

首先了解一下池化((对象池、数据库连接池、线程池等等))的一些思想和好处。方便后面对JedisPoolConfig的配置的理解。

池化的基本思想

1、可以在初始化的时候创建一些对象,当有需要使用的时候不直接从池中获取,提高响应速度;

2、使用过的对象不进行销毁,保存起来,等下一次需要对象的时候,拿出来重复使用,减少频繁创建对象所造成的开销;

3、创建的对象统一保存,方面管理和维护。

池化好处总结

1、提高响应的速度

2、降低资源的消耗

3、方便管理和维护

JedisPoolConfig配置说明

类图和源码解析

首先看一下类图:

  • BaseGenericObjectPool:封装公共的配置的参数。

private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;    // DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L    private long minEvictableIdleTimeMillis =            DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;​    // DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L    private long timeBetweenEvictionRunsMillis =            DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;​    // DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3    private int numTestsPerEvictionRun =            DEFAULT_NUM_TESTS_PER_EVICTION_RUN;​    // DEFAULT_TEST_ON_CREATE = false    private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;    // DEFAULT_TEST_ON_BORROW = false    private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;    // DEFAULT_TEST_ON_RETURN = false    private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;    // DEFAULT_TEST_WHILE_IDLE = false    private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;    //...}
  • GenericObjectPoolConfig:继承BaseGenericObjectPool,内部代码很简单,封装了GenericObjectPool的配置。主要是maxTotalmaxIdleminIdle。 此类不是线程安全的;它仅用于提供创建池时使用的属性。在创建单例的JedisPool 使用JedisPoolConfig需要注意线程安全问题,下面会有个demo介绍创建单例JedisPool

public class GenericObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {​    /**     * The default value for the {@code maxTotal} configuration attribute.     * @see GenericObjectPool#getMaxTotal()     */    public static final int DEFAULT_MAX_TOTAL = 8;    // ...​    // DEFAULT_MAX_TOTAL = 8    private int maxTotal = DEFAULT_MAX_TOTAL;    // DEFAULT_MAX_IDLE = 8    private int maxIdle = DEFAULT_MAX_IDLE;    // DEFAULT_MIN_IDLE = 0    private int minIdle = DEFAULT_MIN_IDLE;    // ...}
  • JedisPoolConfig继承了上面的优良基因,然后又对其他的几个设置属性重新设值。

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

public class JedisPoolConfig extends GenericObjectPoolConfig {  public JedisPoolConfig() {    // defaults to make your life with connection pool easier :)    setTestWhileIdle(true);    setMinEvictableIdleTimeMillis(60000);    setTimeBetweenEvictionRunsMillis(30000);    setNumTestsPerEvictionRun(-1);  }}

配置参数解析

JedisPoolConfig中可以能够配置的参数有很多,连接池实现依赖apache 的commons-pool2。上面源码也大致列举了一些配置参数,下面在详细说明一下。

把池理解为工厂,池中的实例理解为工人,如下图,这样池中的很多参数理解起来就比较容易了。

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

参数 说明 默认值 建议
maxTotal 资源池中的最大连接数 8 参见关键参数设置建议
maxIdle 资源池允许的最大空闲连接数 8 参见关键参数设置建议
minIdle 资源池确保的最少空闲连接数 0 参见关键参数设置建议
blockWhenExhausted 当资源池用尽后,调用者是否要等待。只有当值为true时,下面的maxWaitMillis才会生效。 true 建议使用默认值。
maxWaitMillis 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)。 -1(表示永不超时) 不建议使用默认值。
testOnBorrow 向资源池借用连接时是否做连接有效性检测(ping)。检测到的无效连接将会被移除。 false 业务量很大时候建议设置为false,减少一次ping的开销。
testOnReturn 向资源池归还连接时是否做连接有效性检测(ping)。检测到无效连接将会被移除。 false 业务量很大时候建议设置为false,减少一次ping的开销。
jmxEnabled 是否开启JMX监控 true 建议开启,请注意应用本身也需要开启。

空闲Jedis对象检测由下列四个参数组合完成,testWhileIdle是该功能的开关。

名称 说明 默认值 建议
testWhileIdle 是否开启空闲资源检测。 false true
timeBetweenEvictionRunsMillis 空闲资源的检测周期(单位为毫秒) -1(不检测) 建议设置,周期自行选择,也可以默认也可以使用下方JedisPoolConfig 中的配置。
minEvictableIdleTimeMillis 资源池中资源的最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除。 180000(即30分钟) 可根据自身业务决定,一般默认值即可,也可以考虑使用下方JeidsPoolConfig中的配置。
numTestsPerEvictionRun 做空闲资源检测时,每次检测资源的个数。 3 可根据自身应用连接数进行微调,如果设置为 -1,就是对所有连接做空闲监测。

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

关键参数设置建议

maxTotal(最大连接数)

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

  • 业务希望的Redis并发量;

  • 客户端执行命令时间;

  • Redis资源,例如nodes (如应用个数等) * maxTotal不能超过Redis的最大连接数;

  • 资源开销,例如虽然希望控制空闲连接,但又不希望因为连接池中频繁地释放和创建连接造成不必要的开销。

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

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

maxIdle与minIdle

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

连接池的最佳性能是maxTotal=maxIdle,这样就避免了连接池伸缩带来的性能干扰。但如果并发量不大或者maxTotal设置过高,则会导致不必要的连接资源浪费。

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

使用监控获取合理值

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

上面参数配置:JedisPool资源池优化

创建JedisPool代码

// volatile 修饰private static volatile JedisPool jedisPool = null;​private JedisPoolUtils(){}​public static JedisPool getJedisPoolInstance() {    // 使用双重检查创建单例    if(null == jedisPool) {        synchronized (JedisPoolUtils.class) {            if(null == jedisPool) {                JedisPoolConfig poolConfig = new JedisPoolConfig();                poolConfig.setMaxTotal(10);                poolConfig.setMaxIdle(10);                poolConfig.setMinIdle(2);                poolConfig.setMaxWaitMillis(30*1000);                poolConfig.setTestOnBorrow(true);                poolConfig.setTestOnReturn(true);                poolConfig.setTimeBetweenEvictionRunsMillis(10*1000);                poolConfig.setMinEvictableIdleTimeMillis(30*1000);                poolConfig.setNumTestsPerEvictionRun(-1);                jedisPool = new JedisPool(poolConfig,"localhost",6379);            }        }    }    return jedisPool;}

实例创建和释放大致流程解析

根据流程进行源码解析

创建过程

使用pool.getResource()进行Jedis实例的创建。

//org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)public T borrowObject(final long borrowMaxWaitMillis) throws Exception {​    final boolean blockWhenExhausted = getBlockWhenExhausted();​    PooledObject<T> p = null;    boolean create;    final long waitTime = System.currentTimeMillis();​    while (p == null) {        create = false;        // 从空闲队列中获取        p = idleObjects.pollFirst();        if (p == null) {            // 创建实例            p = create();            if (p != null) {                create = true;            }        }        // 吃资源是否耗尽        if (blockWhenExhausted) {            if (p == null) {                // 等待时间小于0                if (borrowMaxWaitMillis < 0) {                    p = idleObjects.takeFirst();                } else {                    p = idleObjects.pollFirst(borrowMaxWaitMillis,                                              TimeUnit.MILLISECONDS);                }            }            if (p == null) {                throw new NoSuchElementException(                    "Timeout waiting for idle object");            }        } else {            if (p == null) {                throw new NoSuchElementException("Pool exhausted");            }        }        if (!p.allocate()) {            p = null;        }​        if (p != null) {            try {                // 重新初始化要由池返回的实例。                factory.activateObject(p);            } catch (final Exception e) {​            }        }    }    updateStatsBorrow(p, System.currentTimeMillis() - waitTime);​    return p.getObject();}    ​

释放过程

从Jedis3.0版本后pool.returnResource()遭弃用,官方重写了Jedis的close方法用以代替;官方建议应用redis.clients.jedis#Jedis的close方法进行资源回收,官方代码如下:

 @Override  public void close() {    if (dataSource != null) {      JedisPoolAbstract pool = this.dataSource;      this.dataSource = null;      if (client.isBroken()) {        pool.returnBrokenResource(this);      } else {        pool.returnResource(this);      }    } else {      super.close();    }  }

这里主要看:pool.returnResource(this);

//org.apache.commons.pool2.impl.GenericObjectPool#returnObjectpublic void returnObject(final T obj) {    // 获取要释放的实例对象    final PooledObject<T> p = allObjects.get(new IdentityWrapper<>(obj));​    if (p == null) {        if (!isAbandonedConfig()) {            throw new IllegalStateException(                "Returned object not currently part of this pool");        }        return; // Object was abandoned and removed    }    // 将对象标记为返回池的状态。    markReturningState(p);​    final long activeTime = p.getActiveTimeMillis();

    // 这里就和上面配置的参数有关系,释放的时候是否做连接有效性检测(ping)    if (getTestOnReturn() && !factory.validateObject(p)) {        try {            destroy(p);        } catch (final Exception e) {            swallowException(e);        }        try {            ensureIdle(1, false);        } catch (final Exception e) {            swallowException(e);        }        updateStatsReturn(activeTime);        return;    }

    // 检查空闲对象,如果最大空闲对象数小于当前idleObjects大小,则销毁    final int maxIdleSave = getMaxIdle();    if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {        try {            destroy(p);        } catch (final Exception e) {            swallowException(e);        }    } else {        // 否则加入到空闲队列中,空闲队列是一个双端队列        // getLifo 也和配置的参数有关,默认True        if (getLifo()) {            // last in first out,加到队头            idleObjects.addFirst(p);        } else {            // first in first out ,加到队尾            idleObjects.addLast(p);        }    }    updateStatsReturn(activeTime);}

上面创建和释放删除了一些代码,具体完整代码都是在GenericObjectPool类中。

小结,后悔有期

看完本文,应该大致对JedisPoolConfig有了一定的了解,指定里面的一些配置参数,并且能够基本的参数调优,以及实例资源的创建和释放的过程。

如果感谢兴趣的伙伴可以下载Jedis的源码进行阅读和学习,掌握了JedisPoolConfig的配置,其他池化框架的配置也是大同小异,举一反三! 江湖不远,后会有期!

如果需要Reids相关的资源可以扫码下方二维码,里面有Redis的相关资源!

备注: 由于本人能力有限,文中若有错误之处,欢迎指正。


谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!


Java编程技术乐园:一个分享编程知识的公众号。跟着老司机一起学习干货技术知识,每天进步一点点,让小的积累,带来大的改变!

扫描关注,后台回复【秘籍】,获取珍藏干货! 99.9%的伙伴都很喜欢

©每天都在变得更好的阿飞云

并且能够基本的参数调优
[Bìngqiě nénggòu jīběn de cānshù diào yōu]
And tuning parameters can be substantially
 

从源码角度看JedisPoolConfig参数配置的更多相关文章

  1. 从JDK源码角度看Short

    概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...

  2. 从JDK源码角度看Byte

    Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...

  3. 从JDK源码角度看Object

    Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...

  4. 从JDK源码角度看Boolean

    Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...

  5. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  6. Android布局性能优化—从源码角度看ViewStub延迟加载技术

    在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码 ...

  7. 从源码角度看finish()方法的执行流程

    1. finish()方法概览 首先我们来看一下finish方法的无参版本的定义: /** * Call this when your activity is done and should be c ...

  8. 从源码角度看 PHP 字符串类型转换

    PHP 的类型转换是比较方便的,但是越是容易使用的东西,底层的实现越是复杂,而且在使用中像我这样的新手也往往不清楚转换后的结果到底是什么.有时候,对于 Java 这种强类型的语言,使用的时候需要强制进 ...

  9. 从源码角度看MySQL memcached plugin——1. 系统结构和引擎初始化

    本章尝试回答两个问题: 一.memcached plugin与MySQL的关系: 二.MySQL系统如何启动memcached plugin. 1. memcached plugin与MySQL的关系 ...

随机推荐

  1. 机器学习Label Encoder和One Hot Encoder

    标签编码(Label Encoder) 在本例中第一列是Country, 如果我们要运行任何模型, 数据中不能包含文本 所以要对文本进行处理 接下来,我们从sklearn库中导入LabelEncode ...

  2. 创建多线程之threading.Thread的使用

    1.threading模块 threading模块是众多多线程管理模块的其一,它能确保重要的子线程退出后进程才退出. multiprocess模块的完全模仿了threading模块的接口,二者在使用层 ...

  3. 【Rocketmq】通过 docker 快速搭建 rocketmq 环境

    1. 安装 Namesrv 拉取镜像 docker pull rocketmqinc/rocketmq:4.4.0` 启动容器 docker run -d -p 9876:9876 -v {RmHom ...

  4. Java抽象类构造方法

    java中抽象类的子类的构造方法会隐含父类的无参构造方法. package com.zempty.abstractclass; public class AbstractDemo01 { public ...

  5. java 当前时间月份

    public static void main(String[] arg) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd ...

  6. IO流的工具类

    1.需要先导入jar包: FilenameUtils import org.apache.commons.io.FilenameUtils; public class FilenameUtilesDe ...

  7. cython的安装

    cython 在linux(ubuntu)下安装 sudo apt-get install cython 安装后  输入 cython 即可验证是否安装成功

  8. ES6新增常见特性

    一:声明属性let const var let const 区别 1.var声明变量会发生变量提升,let.const不会发生变量提升 2.var允许重复声明变量,let不可以 3.const声明变量 ...

  9. 深入全面理解面向对象的 JavaScript

    深入全面理解面向对象的 JavaScript (原著: 曾 滢, 软件工程师, IBM,2013 年 4 月 17 日) JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来 ...

  10. SUSE Ceph 增加节点、减少节点、 删除OSD磁盘等操作 - Storage6

    一.测试环境描述 之前我们已快速部署好一套Ceph集群(3节点),现要测试在现有集群中在线方式增加节点 如下表中可以看到增加节点node004具体配置 主机名 Public网络 管理网络 集群网络 说 ...