从源码角度看JedisPoolConfig参数配置
做一个积极的人
编码、改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
的配置。主要是maxTotal
、maxIdle
、minIdle
。 此类不是线程安全的;它仅用于提供创建池时使用的属性。在创建单例的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的相关资源!
备注: 由于本人能力有限,文中若有错误之处,欢迎指正。
谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!
扫描关注,后台回复【秘籍】,获取珍藏干货! 99.9%的伙伴都很喜欢
从源码角度看JedisPoolConfig参数配置的更多相关文章
- 从JDK源码角度看Short
概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...
- 从JDK源码角度看Byte
Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...
- 从JDK源码角度看Object
Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...
- 从JDK源码角度看Boolean
Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...
- 从template到DOM(Vue.js源码角度看内部运行机制)
写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...
- Android布局性能优化—从源码角度看ViewStub延迟加载技术
在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码 ...
- 从源码角度看finish()方法的执行流程
1. finish()方法概览 首先我们来看一下finish方法的无参版本的定义: /** * Call this when your activity is done and should be c ...
- 从源码角度看 PHP 字符串类型转换
PHP 的类型转换是比较方便的,但是越是容易使用的东西,底层的实现越是复杂,而且在使用中像我这样的新手也往往不清楚转换后的结果到底是什么.有时候,对于 Java 这种强类型的语言,使用的时候需要强制进 ...
- 从源码角度看MySQL memcached plugin——1. 系统结构和引擎初始化
本章尝试回答两个问题: 一.memcached plugin与MySQL的关系: 二.MySQL系统如何启动memcached plugin. 1. memcached plugin与MySQL的关系 ...
随机推荐
- tensorflow 使用 cpu 而不使用 gpu 问题
查看 tensorflow 版本 conda list 例如发现 tensorflow 1.10.0 tensorflow-gpu 1.10.0 当两个版本相同时,默认会使用 cpu 版本 如果同时存 ...
- 「小技巧」使用Git从其他分支merge个别文件
小明发现在实际项目开发过程中,总会遇到各种各样的情况,比如一个大型的项目或版本迭代可能不是一次上线,可能会分好几次上线,这时候就会涉及创建多个分支,分别开发. 项目背景 产品经理:我们本次开发三个功能 ...
- Laravel 5.4 快速开发简书:
Laravel 5.4 快速开发简书第1章 课程介绍 介绍课程的大体脉络和课程安排 第2章 Laravel 5.4介绍 本节课会带领大家介绍laravel的各个版本历史以及讨论php框架的未来发展趋势 ...
- Windows下更换MAC地址
使用TMAC软件是最佳方案.官网地址:www.technitium.com
- PTA A1009&A1010
第五天 A1009 Product of Polynomials (25 分) 题目内容 This time, you are supposed to find A×B where A and B a ...
- [工具][vim] vim设置显示行号
转载自:electrocrazy的博客 在linux环境下,vim是常用的代码查看和编辑工具.在程序编译出错时,一般会提示出错的行号,但是用vim打开的代码确不显示行号,错误语句的定位非常不便.那么怎 ...
- Java线程池Executor&ThreadPool
java自1.5版本之后,提供线程池,供开发人员快捷方便的创建自己的多线程任务.下面简单的线程池的方法及说明. 1.Executor 线程池的顶级接口.定义了方法execute(Runnable),该 ...
- 夯实Java基础系列5:Java文件和Java包结构
目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...
- SpringBootSecurity学习(01)网页版登录入门介绍
Web应用安全管理 Web应用的安全管理,主要包括两个方面的内容,一个是用户身份的认证,即用户登录的设计,二是用户授权,即一个用户在一个应用系统中能够执行哪些操作的权限管理.权限管理的设计一般使用角色 ...
- element-ui入门
element-ui入门 element-ui是一个ui库,它不依赖于vue.但是却是当前和vue配合做项目开发的一个比较好的ui框架. Layout布局(el-row.el-col) element ...