您的位置:首页 > 编程语言 > Java开发

Java 连接池的工作原理

2016-01-20 00:00 387 查看
摘要: 连接池

什么是连接?

连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。

有人接受这种说法,却不知道它的真正含义。因此,下面我将解释它究竟是什么。

创建连接的代码片段:

1

2

3
String connUrl ="jdbc:mysql://your.database.domain/yourDBname";

Class.forName("com.mysql.jdbc.Driver");

Connection con = DriverManager.getConnection (connUrl);

当我们创建了一个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个连接对象,并把它存放在“可用”状态的任何集合或数组中。

例如,代码片段:

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");

}

}

...

2.当我们调用connectionProvider.getConnection(),然后它会从集合中获取一个连接,当然状态也会更改为“不可用”。

例如,代码片段:

?

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;

}

...

3.当我们关闭得到的连接,ConnectionProvider是不会真正关闭连接。相反,只是将状态更改为“AVAILABLE”。

例如,代码片段:

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");

}

}

}

}

...

基本上连接池的实际工作原理就是这样,但也有可能使用不同的方式。

现在,你可能有一个问题,我们是否可以创造我们自己的连接池机制?

我的建议是使用已经存在的连接池机制,像C3P0DBCP等。
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不会起作用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  连接池