文档章节

Java 连接池的工作原理

旋转木马-千里马
 旋转木马-千里马
发布于 2016/01/20 11:12
字数 2729
阅读 74
收藏 1
什么是连接?
连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。

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

创建连接的代码片段:

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不会起作用。

© 著作权归作者所有

旋转木马-千里马
粉丝 15
博文 83
码字总数 85714
作品 0
海淀
程序员
私信 提问

暂无文章

Angular 英雄编辑器

应用程序现在有了基本的标题。 接下来你要创建一个新的组件来显示英雄信息并且把这个组件放到应用程序的外壳里去。 创建英雄组件 使用 Angular CLI 创建一个名为 heroes 的新组件。 ng gener...

honeymoose
今天
4
0
Kernel DMA

为什么会有DMA(直接内存访问)?我们知道通常情况下,内存数据跟外设之间的通信是通过cpu来传递的。cpu运行io指令将数据从内存拷贝到外设的io端口,或者从外设的io端口拷贝到内存。由于外设...

yepanl
今天
6
0
hive

一、hive的定义: Hive是一个SQL解析引擎,将SQL语句转译成MR Job,然后再在Hadoop平台上运行,达到快速开发的目的 Hive中的表是纯逻辑表,就只是表的定义,即表的元数据。本质就是Hadoop的目...

霉男纸
今天
3
0
二、Spring Cloud—Eureka(Greenwich.SR1)

注:本系列文章所用工具及版本如下:开发工具(IDEA 2018.3.5),Spring Boot(2.1.3.RELEASE),Spring Cloud(Greenwich.SR1),Maven(3.6.0),JDK(1.8) Eureka: Eureka是Netflix开发...

倪伟伟
昨天
13
0
eclipse常用插件

amaterasUML https://takezoe.github.io/amateras-update-site/ https://github.com/takezoe/amateras-modeler modelGoon https://www.cnblogs.com/aademeng/articles/6890266.html......

大头鬼_yc
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部