Java 链接池的工做原理

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

有人接受这种说法,殊不知道它的真正含义。所以,下面我将解释它到底是什么。 java

建立链接的代码片断: mysql

1
2
3
String connUrl ="jdbc:mysql://your.database.domain/yourDBname";
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection (connUrl);

当咱们建立了一个Connection对象,它在内部都执行了什么: sql

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。 tomcat

所以,咱们将使用链接池。 服务器

链接池初始化时建立必定数量的链接,而后从链接池中重用链接,而不是每次建立一个新的。 并发

怎样工做? dom

接下来咱们来看看它是如何工做,以及如何管理或重用现有的链接。

咱们使用的链接池供应者,它的内部有一个链接池管理器,当它被初始化:

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问题才解决 。

解决方案:

    方案一、定时对链接作测试,测试失败就关闭链接。

    方案二、控制链接的空闲时间达到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条链接呢?

一、等业务压力上来了, DBCP就会建立新的链接。

二、配置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不会起做用。

相关文章
相关标签/搜索