commons-pool 解析
首先抛出个常见的长连接问题:
1 都知道连接MySQL的应用中大多会使用框架例如 c3p0 ,dbcp proxool 等来管理数据库连接池。 数据库连接池毫无疑问都是采用长连接方式。 那么MySQL经典八小时问题为何产生?
我一开始的疑惑是既然是长连接必然有不停的心跳检测机制一直不停的骚扰者服务端, 那么服务端怎么还能检测到一个八个小时毫无动静的连接呢? 无非是因为八个小时内client端没有发送心跳包给服务器,不禁会问长连接会没有心跳包么?
2 又或者我们常用的SecureCRT工具,当我们想要保持终端一直畅通就需要设置以下参数,意味着60秒发一次心跳
3 再者用client list 观察我们的redis 服务器,会发现大量的ping请求, 那么这些心跳ping请求由谁来控制触发? 什么情况下会触发?
-------------------------------------------------------那么接下来是揭开谜底的序幕-------------耐心分析---------------------------------------------------------------------------------------------
我们常用的jedis-XXX.jar以及c3p0XX.jar底层都是使用 commons-pool来作为对象池的实现。下面以租车公司为例子说明这张图,介绍commons pool的基本工作方式:
图1
GenericObjectPool(租车公司)
作为租车公司,需要提供租车和收回归还的车辆的两个服务,同时它还要管理着所有的那些车辆,随着业务发展壮大,需要买新车;对于已经不能安全驾驶的车辆,需要将其销毁;同时还要定期对车辆进行安全检测等。
PooledObject(租车公司的所有车辆)
租车公司的车辆分为三类:空闲可租用的车辆(Idle Objects),已借出的车辆(Active Objects),认为已丢弃的车辆(Anandoned Objects)
Borrow Object(租车)
- A1: 世界那么大,一位年轻人想租辆车出去逛逛
- A2: 老板先看看有没有空闲的车
- A3.1: 如果有,则将最近归还的车借出去,并标记为已借出(Active),如果没有空闲的车了,就买辆,同时也标记为已借出(这是一家不差钱的公司)
- A3.2: 老板把标记好的车租给年轻人
Return Object(还车)
- B1: 世界那么大,年轻人终于逛完了,回来还车
- B2: 老板把车放回停车场,并把标记改为空闲状态(Idle),可以再被其他人租用。
TestOnBorrow/TestOnReturn(租出/归还时进行检查)
这家公司不仅不差钱,它对车辆的安全还很负责,对于租出去的车,不管是从空闲车辆里取出的,还是新买回的,都会先检查一遍这车的好坏,总不能坑了年轻人,如果发现有问题,立马再换一辆。归还的时候,也会检查一遍,如果有问题,就扔掉(真土豪),除此之外,公司还专门请了一位车辆安检员,定期对闲置了一段时间的车辆进行安全检测(Evict Thread),一有问题也扔掉。
有借有还,看上去一切都很美好。
然而现实里总有意外发生:
年轻人借走车后,发现世界越逛越大,久久不愿回家。 安检员定期检查时发现这车子都借出去大半年了,还没还回来,是不是丢了?于是掏出手机,”啪“的按了一下,远程将车子熄了火,标记为报废车辆(Abandoned),当作报废处理了。
Evict Thread(定期检查的安检人员)
- C1: 对于已归还标记为空闲的车辆,安检员定期对它们抽查,如果超过一段时间没有使用,看看是否坏掉,坏了就及时作废掉(C2).
- D1: 对于标记为已借出的对象,安检员定期检查时发现借出很久都未还,直接作废(D2)。
好了,故事讲完了,希望大家对Commons Pool都理解了。
------------------------------------------------------------------接下来剖析Commons-pool关键源码-----------------耐心分析----------------------------------------------------------------------------------------------------------------------------------------------
为了简单起见,该图只表现继承和实现关系
图2
上层的jar应用大部分情况下我们只使用ObjectPool和PoolableObjectFactory的相关实现类, 例如jedisFactory 继承的是BasePoolableObjectFactory(典型工厂设计模式), 其pool直接实例化GenricObjectPool类 。
下面以jedis为例简单说明JedisFactory干了啥事:
1 对象如何创建
jedis的JedisFactory继承了org.apache.commons.pool.BasePoolableObjectFactory实现了makeObject()方法 :
public Object makeObject() throws Exception {
final Jedis jedis = new Jedis(this.host, this.port, this.timeout);
// 开始进行socket连接
jedis.connect();
if (null != this.password) {
jedis.auth(this.password);
}
if( database != 0 ) {
jedis.select(database);
} return jedis;
}
这段代码很好懂,无非就是底层开启个socket 开始连接对应的服务器, 例如最底层的代码:
public void connect() {
if (!isConnected()) {
try {
socket = new Socket();
//->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); //Will monitor the TCP connection is valid
socket.setTcpNoDelay(true); //Socket buffer Whetherclosed, to ensure timely delivery of data
socket.setSoLinger(true,0); //Control calls close () method, the underlying socket is closed immediately
//<-@wjw_add socket.connect(new InetSocketAddress(host, port), timeout);
socket.setSoTimeout(timeout);
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
throw new JedisConnectionException(ex);
}
}
}
2 activateObject 激活对象
public void activateObject(Object obj) throws Exception {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis)obj;
if (jedis.getDB() != database) {
jedis.select(database);
}
}
}
3 destroyObject 销毁对象
public void destroyObject(final Object obj) throws Exception {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
if (jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) { }
}
}
}
4 validateObject 检测对象(对象借入借出时如果需要检测此对象的健康状况,或者定时对池对象进行evict)
public boolean validateObject(final Object obj) {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
try {
return jedis.isConnected() && jedis.ping().equals("PONG");
} catch (final Exception e) {
return false;
}
} else {
return false;
}
}
只做了上述四件事情,告诉对象池(GenricObjectPool) 如何创建对象,如何激活对象, 如何销毁对象,以及用什么方式做心跳检测。结合图1的池对象流转图
接下来分析GenricObjectPoo里面的几个功能: 对象的借出:borrowObject(), 对象的归还:returnObject() ,以及对象池的后台管理:Evictor定时
这里参考一篇文章:http://deyimsf.iteye.com/blog/2119488
此文章基本上很好的论述了源码关键部位。 接下来分析下 idel 时间是怎么算出来的, 以及作用是什么
A : 一个对象的出生时打上个系统时间--------borrowObject()方法或者evict()方法的--->ensureMinIdle()方法(维持最小的idel个对象)
borrowObject方法示例:
boolean newlyCreated = false;
if(null == latch.getPair()) {
try {
T obj = _factory.makeObject();
// 此刻创建一个新生命, new ObjectTimestampPair是拿当前的系统时间当此生命的生日
latch.setPair(new ObjectTimestampPair<T>(obj));
newlyCreated = true;
} finally {
if (!newlyCreated) {
// object cannot be created
synchronized (this) {
_numInternalProcessing--;
// No need to reset latch - about to throw exception
}
allocate();
}
}
}
ensureMinIdle()方法示例
private void ensureMinIdle() throws Exception {
// this method isn't synchronized so the
// calculateDeficit is done at the beginning
// as a loop limit and a second time inside the loop
// to stop when another thread already returned the
// needed objects
// 上面英文注释很明确
int objectDeficit = calculateDeficit(false);
for ( int j = 0 ; j < objectDeficit && calculateDeficit(true) > 0 ; j++ ) {
try {
// 跟进去会发现此处就是不断创建新生命,如果满足条件就放入池中
addObject();
} finally {
synchronized (this) {
_numInternalProcessing--;
}
allocate();
}
}
}
B : 一个对象使用完归来时再次打上个系统时间--------returnObject()方法的addObjectToPool方法代码片段
synchronized (this) {
if (isClosed()) {
shouldDestroy = true;
} else {
if((_maxIdle >= 0) && (_pool.size() >= _maxIdle)) {
shouldDestroy = true;
} else if(success) {
// borrowObject always takes the first element from the queue,
// so for LIFO, push on top, FIFO add to end
if (_lifo) {
// 此处对归来的obj 对象打上系统时间并放入对首。 last in first out 算法是jedis 默认算法
_pool.addFirst(new ObjectTimestampPair<T>(obj));
} else {
_pool.addLast(new ObjectTimestampPair<T>(obj));
}
if (decrementNumActive) {
_numActive--;
}
doAllocate = true;
}
}
}
c : 当给minEvictableIdleTimeMillis这个参数配置上一个大于0 的数,意味着对象空闲了多久就被会干掉, 例如evict()代码片段
// 此处去算此对象空闲了多久,接下来判断需不需要把此对象干掉
final long idleTimeMilis = System.currentTimeMillis() - pair.tstamp;
if ((getMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getMinEvictableIdleTimeMillis())) {
removeObject = true;
} else if ((getSoftMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getSoftMinEvictableIdleTimeMillis()) &&
((getNumIdle() + 1)> getMinIdle())) { // +1 accounts for object we are processing
removeObject = true;
}
if(getTestWhileIdle() && !removeObject) {
boolean active = false;
try {
_factory.activateObject(pair.value);
active = true;
} catch(Exception e) {
removeObject=true;
}
if(active) {
if(!_factory.validateObject(pair.value)) {
removeObject=true;
} else {
try {
_factory.passivateObject(pair.value);
} catch(Exception e) {
removeObject=true;
}
}
}
}
OK 以上三段代码讲述完毕。 阐述了对象的idel 时间的由来以及用处。 个人建议把minEvictableIdleTimeMillis设置成-1
---------------------------------------------------来个使用总结吧-----------------------------------------------------------------------------------------------------------------------------------
redis 使用注意事项:
1 redis server 的time_out(单位是S清除idel多久的客户端连接)最好不要设置成一个大于0的时间。
设置成0即不加限制的隐患 :当客户端此刻与服务端断网, 而http请求依然不断,那么当一个请求从池中取得一个连接(连接池此刻remove一个连接), 接着调用validateObject()方法发现ping出错, 紧接着就会调用destroyObject 进行对象的销毁(由于断网客户端的quit命令压根发送不到服务器端), 服务器端就会出现一堆僵尸进程。
2 服务器端如果设置了idel>0的空闲时间, 那么客户端最好设置上对应的心跳频率即多久心跳一次,
timeBetweenEvictionRunsMillis=60000 // 多久跑一次任务
pool.minEvictableIdleTimeMillis=-1 // idel 多久的对象就会被清理掉 , -1意味着无论空着多久都没事。 实际上上面的那个定时任务没跑一次,被定位到的线程都要去ping一次服务器,所以对象的空闲时间也就是60S。
这里面还涉及到每一次清理定时任务去清理多少个对象的问题。 一次清理的对象不要太少(或者走默认配置隔一定时间, 对象池里面的空闲对象全部心跳一次)即以下配置(不配置会走默认配置即全部检查):
numTestsPerEvictionRun // 驱逐器每次运行时检查池中闲置对象的最大个数 ,(比如该值设置为3,此时池中有5个闲置对象,那么每次只会检查前三个闲置对象。
所以也就回答了mysql 八小时问题, 就是因为服务器端设置了一个超时时间,而客户端却没有配置上定时心跳检测的功能,导致八个小时之后被服务器端干掉。
commons-pool 解析的更多相关文章
- Tomcat 开发web项目报Illegal access: this web application instance has been stopped already. Could not load [org.apache.commons.pool.impl.CursorableLinkedList$Cursor]. 错误
开发Java web项目,在tomcat运行后报如下错误: Illegal access: this web application instance has been stopped already ...
- NoClassDefFoundError: org/apache/commons/pool/impl/GenericObjectPool
错误:Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/pool/impl ...
- Spring + Tomcat 启动报错java.lang.ClassNotFoundException: org.apache.commons.pool.impl.GenericObjectPool
错误如下: -- ::,-[TS] INFO http-- org.springframework.beans.factory.support.DefaultListableBeanFactory - ...
- Apache Commons Pool 故事一则
Apache Commons Pool 故事一则 最近工作中遇到一个由于对commons-pool的使用不当而引发的问题,习得正确的使用姿势后,写下这个简单的故事,帮助理解Apache Commons ...
- 池化 - Apache Commons Pool
对于那些创建耗时较长,或者资源占用较多的对象,比如网络连接,线程之类的资源,通常使用池化来管理这些对象,从而达到提高性能的目的.比如数据库连接池(c3p0, dbcp), java的线程池 Execu ...
- org/apache/commons/pool/impl/GenericObjectPool异常的解决办法
org/apache/commons/pool/impl/GenericObjectPool异常的解决办法 webwork+spring+hibernate框架的集成, 一启动Tomcat服务器就出了 ...
- 对象池化技术 org.apache.commons.pool
恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率.Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以 ...
- JedisCluster中应用的Apache Commons Pool对象池技术
对象池技术在服务器开发上应用广泛.在各种对象池的实现中,尤其以数据库的连接池最为明显,可以说是每个服务器必须实现的部分. apache common pool 官方文档可以参考:https://c ...
- Cache Lucene IndexReader with Apache Commons Pool
IndexReaderFactory.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2 ...
- Java_异常_03_ java.lang.NoClassDefFoundError: org/apache/commons/pool/KeyedObjectPoolFactory
异常信息: java.lang.NoClassDefFoundError: org/apache/commons/pool/KeyedObjectPoolFactory 原因: 我用的是commons ...
随机推荐
- 配置Codis-Service主机
Codis-Service配置在三台机器上,每台机器分别配置3个redis,如下: ip:(redis-.redis-.redis-) ip:(redis-.redis-.redis-) ip:(re ...
- Mysql索引会失效的几种情况分析
转:https://www.jb51.net/article/50649.htm 学习啦
- 查看Linux 版本
如何得知自己正在使用的linux是什么版本呢,下面的几种方法将给你带来答案! 1. 查看内核版本命令: 1) [root@q1test01 ~]# cat /proc/version Linux v ...
- cool--music
1.三年的赌注 2.非酋 3.Swan Song 4.Better Than A Hallelujah
- sqlplus sys as sysdba
Enter user-name:sys Enter password:password as sysdba --以sys用户登陆的话 必须要加上 as sysdba 子句
- FFmpeg简易播放器的实现-音频播放
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10068490.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- [转]一分钟告诉你究竟DevOps是什么鬼?
本文转自:https://www.cnblogs.com/jetzhang/p/6068773.html 一分钟告诉你究竟DevOps是什么鬼? 历史回顾 为了能够更好的理解什么是DevOps,我 ...
- Linux下安装SQL Server 2016(连接篇SQL Server on linux)
连接数据库 (1)设置防火墙 要连接数据库,首先要打开防火墙上1433端口,也就是,增加tcp端口1433到公共区域,并且永久生效. [root@localhost Desktop]# firewal ...
- 【Spring】3、BeanFactory 和 ApplicationContext的区别
转自:http://blog.csdn.net/intlgj/article/details/5660587 在spring中,两个最基本最重要的包是 org.springframework.bean ...
- Java虚拟机 - Class类文件结构
[深入Java虚拟机]之二:Class类文件结构 平台无关性 Java是与平台无关的语言,这得益于Java源代码编译后生成的存储字节码的文件,即Class文件,以及Java虚拟机的实现.不仅使用Jav ...