DBCP老矣,但能饭

数据库连接池在J2EE领域是一个不可缺失的组件,尽管DRUID越来越流行,但是DBCP作为一个老牌的数据库连接池一直在企业系统中默默的奉献着自己。比如我们的大部分系统还都是使用DBCP,那么我们很有必要对DBCP有一个熟悉的认识。鉴于这样的需求,我根据相关资料(文后都有列出)结合自己的认知重新做了一个梳理,一来自己可以在以后的工作中回过头来温习,二来也希望能够帮助其他同学对DBCP以及涉及到的相关概念和知识比如超时机制,连接池原理等有一个复习。我们都知道操作一个数据库的流程,创建数据源,获得链接,构造statement,执行请求,接下来我们逐步梳理总结。

一、相关概念复习

在谈DBCP之前我们先来复习一下几个相关的概念:

1.1、JNDI

Java Naming and Directory Interface (JNDI)JNDI API被用于执行名字和目录服务。它提供了一致的模型来存取和操作企业级的资源。JNDI的api位于javax.naming包中,它的作用是,它可以把对象放到一个容器中(JNDI容器),JAVA对象在这个容器中都有一个名称,程序则可以通过这个名称来获取对象,例如下面这样:

// Construct BasicDataSource
  BasicDataSource bds = new BasicDataSource();
  bds.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
  bds.setUrl("jdbc:apache:commons:testdriver");
  bds.setUsername("username");
  bds.setPassword("password");

  ic.rebind("jdbc/basic", bds);
   
  // Use
  InitialContext ic2 = new InitialContext();
  DataSource ds = (DataSource) ic2.lookup("jdbc/basic");
  assertNotNull(ds);
  Connection conn = ds.getConnection();
  assertNotNull(conn);
  conn.close();
1.2、JDBC
JDBC Type 4.png

JAVA数据库连接(Java Database Connectivity,简称JDBC),是java语言中用来规范话我们的客户端应用程序,比如我们的web应用程序等如何访问关系型数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
JDBC驱动程序一共有四种类型:

  • 类型1-JDBC-ODBC桥
  • 类型2-本地API驱动
  • 类型3-网络协议驱动
  • 类型4-本地协议驱动
    我们常用的是类型4-本地协议驱动,这种类型的驱动使用socket链接,直接在客户端和数据库之间进行通信。
    优点是1-访问速度快 2-最直接,最纯粹的JAVA实现
    缺点是1-需要每个数据库厂商提供自己的JDBC驱动。2-需要针对不同的数据库使用不同的驱动程序。
    JDBC的API在jdk的java.sql包中,扩展的内容在javax.sql包中。主要包括(斜体代表接口,需驱动程序提供者来具体实现):
  • DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
  • Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。
  • Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
  • Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
  • PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
  • CallableStatement:用以调用数据库中的存储过程。
  • SQLException:代表在数据库连接的创建和关闭和SQL语句的执行过程中发生了例外情况(即错误)。
1.3、DBCP数据库连接池和JDBC之间的关系

数据库连接池负责创建(通过JDBC API)、管理、销毁数据库的连接。应用程序可以从数据库连接池中重复使用一个现有的连接,而不是重新创建一个。连接池,common-pool中的GenericObjectPool它负责缓存和管理连接;连接,这是是指PoolableConnection;连接池和连接一对多的关系。池化技术是通过commons-pool来实现的,每个连接是一个对象,换言之,是对象池的使用与管理。DBCP连接池是基于commons-pool这种对象池来实现的。

二、commons-pool的理解

Apache commons-pool是一种对象池技术,我们使用的很多涉及池的场景一般都是基于该组件来实现的,DBCP数据库连接池也是基于commons-pool来实现的,因此我们先来了解下这种对象池技术。下面这个图是对象池的对象生命周期流程图。

对象池.png

三、DBCP核心类图及序列图

3.1、BasicDataSource.java
BasicDataSource.gif
3.2、ConnectionFactory.java
ConnectionFactory.gif
3.3、PoolingDataSource.java
PoolingDataSource.gif
3.4、PoolingConnection.java
PoolingConnection.gif
3.5、Delegating.java
Delegating.gif
3.6、AbandonedObjectPool.java
AbandonedObjectPool.gif
3.7、创建数据源createDataSource
createDataSource序列图.gif
3.8、创建连接getConnection
getConnection序列图.gif
3.9、创建statement prepareStatement
prepareStatement序列图.gif

四、DBCP配置及使用

DBCP是Apache下的一个开源数据库连接池,我们在使用的时候需要两个JAR文件,分别是commons-dbcp.jar(连接池的实现)和commons-pool.jar(连接池实现的依赖库),不过我们在使用的时候只需要引入下面mvaen坐标,commons-pool是在commons-dbcp里面隐含引用了。注意一点,就是从1.x升级到2.x的时候,由于dbcp的包路径已经变了,需要升级者修改局部代码。

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>2.2</version>
</dependency>

DBCP可以在应用程序中独立的使用,也可以与web应用服务器整合使用。

4.1、tomcat中使用

比如tomcat的连接池就是采用该连接池来实现的。如下:

<Context> 
    <Resource name="jdbc/datasource" auth="Container"  type="javax.sql.DataSource" 
            username="root" password="root"  driverClassName="com.mysql.jdbc.Driver" 
            url="jdbc:mysql://192.168.0.1:3306/test"  maxActive="8" maxIdle="4"  /> 
</Context>
4.2、应用程序中独立使用
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  destroy-method="close"> 
        <!--如果没有配置 destroy-method="close" ,但是重启次数如果太频繁的话,将造成重启tomcat后旧的数据库连接池的连接不释放,连接堆满了,后续启动无法建立连接-->
        
        <!--数据库连接相关配置,用户名、密码、连接地址、驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>  
        <property name="url" value="jdbc:mysql://192.168.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8"/>  
        <property name="username" value="root"/>  
        <property name="password" value="root"/>

        <!--数据库连接属性 connectTimeout:建立连接的超时时间,socketTimeout:客户端和服务进行数据交互的时间,是指两者之间如果两个数据包之间的时间大于该时间则认为超时-->
        <property name="connectionProperties"
                  value="connectTimeout=2000;socketTimeout=15000"/>
        
        <!--以下属性也有的建议,值配置一样-->
        <!--initialSize: 初始化连接-->
        <property name="initialSize" value="30"/>  
        <!--maxActive: 最大连接数量-->    
        <property name="maxActive" value="150"/>  
        <!--minIdle: 最小空闲连接-->    
        <property name="minIdle" value="5"/>  
        <!--maxIdle: 最大空闲连接-->    
        <property name="maxIdle" value="20"/>  
        
        
        <!--等待获取连接池连接的时间(在1.x版本是maxWait),不要太大-->
        <property name="maxWaitMillis" value="500"/>
        <!--连接池中的连接空闲多久,从池中删除,单位MS。小于0,则表示不启动-->   
        <property name="minEvictableIdleTimeMillis" value="" />
        <!--检查失效连接的定时器执行间隔,单位MS,小于0,则表示不启动。注意:mysql数据库如果8小时内没有连接请求,则连接会自动断开,因此这个参数如果并发量不大的情况下还是要配上-->    
        <property name="timeBetweenEvictionRunsMillis" value="" />
       
    </bean>

更多的配置请参照https://commons.apache.org/proper/commons-dbcp/configuration.html

五、连接超时机制

对网络资源访问的时候,超时设置是必须的。没有超时的保护,一旦依赖资源发生故障或者网络故障,就会引起线程堆积,甚至发生雪崩。

5.1、超时层级
超时层次.png

从这张图中我们也能够看出DBCP并不参与数据库超时的处理,它只负责管理连接。根据上图我们可以看到超时有一个依赖层级,上层超时依赖下层超时。依次为:事务超时->Statement超时->JDBC Driver socket超时->操作系统超时。

5.1.1、事务超时:

事务是应用层级的概念,我们知道事务是有一组SQL执行单元组成的。那么事务超时的时间阈值,实际是Statement超时N个需要执行的Statement数量。比如一个事务里面有3条Statement,每条Statement的执行时间是50MS,其它业务上的逻辑执行时间+框架执行时间未100ms,那么最终事务的超时时间为:350+100=250ms。

5.1.2、statement超时:

用来限制sql语句的执行时间,通过setQueryTimeout(int timeout)来设置,不过现在大都是ibatis了,可以通过 SqlMapConfig.xml 中的 setting 属性defaultStatementTimeout 来设置全局的 statement 超时缺省值<settings defaultStatementTimeout="15"/>,还可以在每个sql.xml文件中,根据业务实际需要来设置<select timeout="10"/> <insert timeout="10"/> <update timeout="10"/>,这样就会覆盖掉全局的值,而采用具体的阈值。

5.1.3、Socket超时:

这是底层的一种超时,因为我们使用的JDBC驱动类型是TYPE4,它是基于socket来通信的。mysql的jdbc驱动中的connectTimeout 和 socketTimeout 的默认值是 0 ,这意味着不会发生超时。所以我们必须在dbcp配置中设置这两个值。<property name="connectionProperties" value="connectTimeout=2000;socketTimeout=15000"/>
connectTimeout为建立连接的超时时间,socketTimeout为JDBC客户端和数据库服务器之间数据交互的时间。注意这里配置的socketTimeout的值必须要大于Statement的超时时间值。否则Statement超时就没有意义,也不能生效。

5.1.4、操作系统的socket超时

linux操作系统也会设置socket超时,比如我们这边的服务器一般配置的是20分钟,因为公司的linux服务器的KeepAlive检查周期为20分钟。这样即使上面的socketTimeout值为0用不超时,也还是要收到linux服务器的超时限制,也就是由于网络原因引起的数据库网络连接问题也不会超过20分钟。

5.2、Mysql处理超时的机制及原理
MySQL 的 Statement 超时执行过程.png

上图是一mysql在执行一个命令的过程中的步骤以及发生超时现象后的处理机制
1、通过Connection的createStatement()方法去创建一个Statement,以便后续进行读写操作
2、执行第1步创建的Statement的executeQuery()方法
3、将查询请求命令发送到mysql数据库服务器
4、创建一个超时线程(从5.1版本以后在,创建每个连接的时候,会随之创建一个处理超时的线程timeout-execution)
5、把当前的statement对象注册到超时线程timeout-execution中
6、发生了超时(阈值是你在Statement执行前候配置的setQueryTimeout(int timeout),如果是mybatis则是在配置文件里面配置的值defaultStatementTimeout="15",单位s)
7、超时线程会重新创建一个新的Connection,这个Connection的属性配置都跟先前的一样
8、用新创建的这个Connection去发送取消查询请求

六、各种数据库连接池性能对比

测试环境,OS: OS X 10.8.2 CPU:intel i7 2GHz 4 core JVM:java version "1.7.0_05"
测试执行申请归还连接1,000,000(一百万)次总耗时性能对比,Java7 的基准测试结果如下:

各种连接池性能对比.png

Druid是性能最好的数据库连接池,DBCP性能属于中上,我们在以后的业务场景中可以根据实际需要做选择。这个是DRUID的官方测试,不过可以利用测试代码自己进行验证一遍,测试代码如下:https://github.com/alibaba/druid/blob/master/src/test/java/com/alibaba/druid/benckmark/pool/Case1.java

七、总结

我们梳理了数据库连接相关的概念,JNDI、JDBC、连接池,DBCP的核心类和时序图,另外我们还梳理了commons的池化组件,最后我们描述了JDBC连接过程中的超时机制,包括超时依赖和超时的机制原理,超时的知识点对与我们非常重要,超时是一种防护措施,当依赖DB性能变慢或者网络故障的情况下,可以快速失败。以便保护我们的应用程序不会导致线程堆积而发生雪崩这类致命事故。DBCP经历了各种互联网应用的验证,稳定可靠的性能会一直服务于我们的系统中。当今比较流行的spring boot内部集成的Tomcat应用依然在使用dbcp连接池。

转载请注明出处,并附上链接http://www.jianshu.com/p/7e1b36c7fb82

参考资料
https://www.cubrid.org/blog/understanding-jdbc-internals-and-timeout-configuration
https://zh.wikipedia.org/wiki/Java%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5
http://shift-alt-ctrl.iteye.com/blog/1917782
https://commons.apache.org/proper/commons-pool/index.html
https://commons.apache.org/proper/commons-dbcp/index.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容