Java 连接池的工作原理
2016-01-20 00:00
387 查看
摘要: 连接池
什么是连接?
连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。
有人接受这种说法,却不知道它的真正含义。因此,下面我将解释它究竟是什么。
创建连接的代码片段:
当我们创建了一个Connection对象,它在内部都执行了什么:
1.“DriverManager”检查并注册驱动程序,
2.“com.mysql.jdbc.Driver”就是我们注册了的驱动程序,它会在驱动程序类中调用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根据我们请求的“connUrl”,创建一个“Socket连接”,连接到IP为“your.database.domain”,默认端口3306的数据库。
4.创建的Socket连接将被用来查询我们指定的数据库,并最终让程序返回得到一个结果。
为什么昂贵?
现在让我们谈谈为什么说它“昂贵“。
如果创建Socket连接花费的时间比实际的执行查询的操作所花费的时间还要更长。
这就是我们所说的“数据库连接很昂贵”,因为连接资源数是1,它需要每次创建一个Socket连接来访问DB。
因此,我们将使用连接池。
连接池初始化时创建一定数量的连接,然后从连接池中重用连接,而不是每次创建一个新的。
怎样工作?
接下来我们来看看它是如何工作,以及如何管理或重用现有的连接。
我们使用的连接池供应者,它的内部有一个连接池管理器,当它被初始化:
1.它创建连接池的默认大小,比如指定创建5个连接对象,并把它存放在“可用”状态的任何集合或数组中。
例如,代码片段:
2.当我们调用connectionProvider.getConnection(),然后它会从集合中获取一个连接,当然状态也会更改为“不可用”。
例如,代码片段:
?
3.当我们关闭得到的连接,ConnectionProvider是不会真正关闭连接。相反,只是将状态更改为“AVAILABLE”。
例如,代码片段:
基本上连接池的实际工作原理就是这样,但也有可能使用不同的方式。
现在,你可能有一个问题,我们是否可以创造我们自己的连接池机制?
我的建议是使用已经存在的连接池机制,像C3P0,DBCP等。
DBCP连接池的自我检测
默认配置的DBCP连接池,是不对池中的连接做测试的,有时连接已断开了,但DBCP连接池不知道,还以为连接是好的呢。
应用从池中取出这样的连接访问数据库一定会报错。这也是好多人不喜欢DBCP的原因。
问题例一:
MySQL8小时问题,Mysql服务器默认连接的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该 connection。
但是DBCP连接池并不知道连接已经断开了,如果程序正巧使用到这个已经断开的连接,程序就会报错误。
问题例二:
以前还使用Sybase数据库,由于某种原因,数据库死了后重启、或断网后恢复。
等了约10分钟后,DBCP连接池中的连接还都是不能使用的(断开的),访问数据应用一直报错,最后只能重启Tomcat问题才解决 。
解决方案:
方案1、定时对连接做测试,测试失败就关闭连接。
方案2、控制连接的空闲时间达到N分钟,就关闭连接,(然后可再新建连接)。
以上两个方案使用任意一个就可以解决以述两类问题。如果只使用方案2,建议 N <= 5分钟。连接断开后最多5分钟后可恢复。
也可混合使用两个方案,建议 N = 30分钟。
下面就是DBCP连接池,同时使用了以上两个方案的配置配置
解释:
配置timeBetweenEvictionRunsMillis = "30"后,每30秒运行一次空闲连接回收器(独立线程)。并每次检查3个连接,如果连接空闲时间超过30分钟就销毁。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接,维护数量不少于minIdle,过行了新老更替。
testWhileIdle = "true" 表示每30秒,取出3条连接,使用validationQuery = "SELECT 1" 中的SQL进行测试 ,测试不成功就销毁连接。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接。
testOnBorrow = "false" 一定要配置,因为它的默认值是true。false表示每次从连接池中取出连接时,不需要执行validationQuery = "SELECT 1" 中的SQL进行测试。若配置为true,对性能有非常大的影响,性能会下降7-10倍。所在一定要配置为false.
每30秒,取出numTestsPerEvictionRun条连接(本例是3,也是默认值),发出"SELECT 1" SQL语句进行测试 ,测试过的连接不算是“被使用”了,还算是空闲的。连接空闲30分钟后会被销毁。
DBCP连接池配置参数注意事项
--
maxIdle值与maxActive值应配置的接近。
因为,当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是我想要的空闲M秒后再销毁起一个缓冲作用。这一点DBCP做的可能与你想像的不一样。
若maxIdle应与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是我想要的。
高负载的系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。
timeBetweenEvictionRunsMillis建议设置值
initialSize="5",会在tomcat一启动时,创建5条连接,效果很理想。
但同时我们还配置了minIdle="10",也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?
1、等业务压力上来了, DBCP就会创建新的连接。
2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。
连接销毁的逻辑
DBCP的连接数会在 0 - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:
默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。
随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。
直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。
但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。
默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。
随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销 毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。
当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若 连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新 连接已使数量稳定在minIdle,并进行着新老更替。
配置initialSize=“10”时,tomcat一启动就创建10条连接。其它同上。
minIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。
什么是连接?
连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。
有人接受这种说法,却不知道它的真正含义。因此,下面我将解释它究竟是什么。
创建连接的代码片段:
1 2 3 | String connUrl ="jdbc:mysql://your.database.domain/yourDBname"; Class.forName("com.mysql.jdbc.Driver"); Connection con = DriverManager.getConnection (connUrl); |
1.“DriverManager”检查并注册驱动程序,
2.“com.mysql.jdbc.Driver”就是我们注册了的驱动程序,它会在驱动程序类中调用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根据我们请求的“connUrl”,创建一个“Socket连接”,连接到IP为“your.database.domain”,默认端口3306的数据库。
4.创建的Socket连接将被用来查询我们指定的数据库,并最终让程序返回得到一个结果。
为什么昂贵?
现在让我们谈谈为什么说它“昂贵“。
如果创建Socket连接花费的时间比实际的执行查询的操作所花费的时间还要更长。
这就是我们所说的“数据库连接很昂贵”,因为连接资源数是1,它需要每次创建一个Socket连接来访问DB。
因此,我们将使用连接池。
连接池初始化时创建一定数量的连接,然后从连接池中重用连接,而不是每次创建一个新的。
怎样工作?
接下来我们来看看它是如何工作,以及如何管理或重用现有的连接。
我们使用的连接池供应者,它的内部有一个连接池管理器,当它被初始化:
1.它创建连接池的默认大小,比如指定创建5个连接对象,并把它存放在“可用”状态的任何集合或数组中。
例如,代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ... String connUrl ="jdbc:mysql://your.database.domain/yourDBname"; String driver ="com.mysql.jdbc.Driver"; privateMap<java.sql.Connection, String> connectionPool =null; privatevoidinitPool() { try{ connectionPool =newHashMap<java.sql.Connection, String>(); Class.forName(driver); java.sql.Connection con = DriverManager.getConnection(dbUrl); for(intpoolInd = poolSize; poolInd <0; poolInd++) { connectionPool.put(con,"AVAILABLE"); } } ... |
例如,代码片段:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ... publicjava.sql.Connection getConnection()throwsClassNotFoundException, SQLException { booleanisConnectionAvailable =true; for(Entry<java.sql.Connection, String> entry : connectionPool.entrySet()) { synchronized(entry) { if(entry.getValue()=="AVAILABLE") { entry.setValue("NOTAVAILABLE"); return(java.sql.Connection) entry.getKey(); } isConnectionAvailable =false; } } if(!isConnectionAvailable) { Class.forName(driver); java.sql.Connection con = DriverManager.getConnection(connUrl); connectionPool.put(con,"NOTAVAILABLE"); returncon; } returnnull; } ... |
例如,代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 | ... publicvoidcloseConnection(java.sql.Connection connection)throwsClassNotFoundException, SQLException { for(Entry<java.sql.Connection, String> entry : connectionPool.entrySet()) { synchronized(entry) { if(entry.getKey().equals(connection)) { //Getting Back the conncetion to Pool entry.setValue("AVAILABLE"); } } } } ... |
现在,你可能有一个问题,我们是否可以创造我们自己的连接池机制?
我的建议是使用已经存在的连接池机制,像C3P0,DBCP等。
DBCP连接池的自我检测
默认配置的DBCP连接池,是不对池中的连接做测试的,有时连接已断开了,但DBCP连接池不知道,还以为连接是好的呢。
应用从池中取出这样的连接访问数据库一定会报错。这也是好多人不喜欢DBCP的原因。
问题例一:
MySQL8小时问题,Mysql服务器默认连接的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该 connection。
但是DBCP连接池并不知道连接已经断开了,如果程序正巧使用到这个已经断开的连接,程序就会报错误。
问题例二:
以前还使用Sybase数据库,由于某种原因,数据库死了后重启、或断网后恢复。
等了约10分钟后,DBCP连接池中的连接还都是不能使用的(断开的),访问数据应用一直报错,最后只能重启Tomcat问题才解决 。
解决方案:
方案1、定时对连接做测试,测试失败就关闭连接。
方案2、控制连接的空闲时间达到N分钟,就关闭连接,(然后可再新建连接)。
以上两个方案使用任意一个就可以解决以述两类问题。如果只使用方案2,建议 N <= 5分钟。连接断开后最多5分钟后可恢复。
也可混合使用两个方案,建议 N = 30分钟。
下面就是DBCP连接池,同时使用了以上两个方案的配置配置
validationQuery = "SELECT 1" 验证连接是否可用,使用的SQL语句 testWhileIdle = "true" 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除. testOnBorrow = "false" 借出连接时不要测试,否则很影响性能 timeBetweenEvictionRunsMillis = "30" 每30秒运行一次空闲连接回收器 minEvictableIdleTimeMillis = "1800" 池中的连接空闲30分钟后被回收,默认值就是30分钟。 numTestsPerEvictionRun="3" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量,默认值就是3.
解释:
配置timeBetweenEvictionRunsMillis = "30"后,每30秒运行一次空闲连接回收器(独立线程)。并每次检查3个连接,如果连接空闲时间超过30分钟就销毁。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接,维护数量不少于minIdle,过行了新老更替。
testWhileIdle = "true" 表示每30秒,取出3条连接,使用validationQuery = "SELECT 1" 中的SQL进行测试 ,测试不成功就销毁连接。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接。
testOnBorrow = "false" 一定要配置,因为它的默认值是true。false表示每次从连接池中取出连接时,不需要执行validationQuery = "SELECT 1" 中的SQL进行测试。若配置为true,对性能有非常大的影响,性能会下降7-10倍。所在一定要配置为false.
每30秒,取出numTestsPerEvictionRun条连接(本例是3,也是默认值),发出"SELECT 1" SQL语句进行测试 ,测试过的连接不算是“被使用”了,还算是空闲的。连接空闲30分钟后会被销毁。
DBCP连接池配置参数注意事项
--
maxIdle值与maxActive值应配置的接近。
因为,当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是我想要的空闲M秒后再销毁起一个缓冲作用。这一点DBCP做的可能与你想像的不一样。
若maxIdle应与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是我想要的。
高负载的系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。
timeBetweenEvictionRunsMillis建议设置值
initialSize="5",会在tomcat一启动时,创建5条连接,效果很理想。
但同时我们还配置了minIdle="10",也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?
1、等业务压力上来了, DBCP就会创建新的连接。
2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。
连接销毁的逻辑
DBCP的连接数会在 0 - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:
默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。
随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。
直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。
但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。
默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。
随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销 毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。
当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若 连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新 连接已使数量稳定在minIdle,并进行着新老更替。
配置initialSize=“10”时,tomcat一启动就创建10条连接。其它同上。
minIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。
相关文章推荐
- Asp实现的数据库连接池功能函数分享
- .net 中的SqlConnection连接池机制详解
- 在Tomcat服务器下使用连接池连接Oracle数据库
- Tomeat6.0 连接池数据库配置
- Tomcat 5.5 数据库连接池配置
- asp.net 数据库连接池浅析
- .NET 数据库连接池
- 用于App服务端的MySQL连接池(支持高并发)
- .NET Framework SQL Server 数据提供程序连接池
- java配置数据库连接池的方法步骤
- Java实现数据库连接池的方法
- 简单实现一个连接池
- 数据源连接池
- 持久层之DAO
- 持久层之DAO工厂类
- php-cp 连接池 php-connect-pool 介绍
- java Tomcat数据库连接池
- tomcat6数据库连接池配置详解
- 各种连接池参数比较
- mongodb连接池