数据库链接池的原理

一.早期咱们怎么进行数据库操做html

       1.原理:通常来讲,java应用程序访问数据库的过程是:java

   ①装载数据库驱动程序;web

   ②经过jdbc创建数据库链接;spring

   ③访问数据库,执行sql语句;sql

   ④断开数据库链接。数据库

       2.代码 apache

       // 查询全部用户设计模式

 

[java]  view plain  copy
 
 print?
  1. Public void FindAllUsers(){  
  2.        //一、装载sqlserver驱动对象  
  3.        DriverManager.registerDriver(new SQLServerDriver());               
  4.        //二、经过JDBC创建数据库链接  
  5.        Connection con =DriverManager.getConnection("jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");              
  6.        //三、建立状态  
  7.        Statement state =con.createStatement();             
  8.        //四、查询数据库并返回结果  
  9.        ResultSet result =state.executeQuery("select * from users");             
  10.        //五、输出查询结果  
  11.        while(result.next()){  
  12.               System.out.println(result.getString("email"));  
  13.        }              
  14.        //六、断开数据库链接  
  15.        result.close();  
  16.        state.close();  
  17.        con.close();  
  18.  }  

 

3.分析api

       程序开发过程当中,存在不少问题:首先,每一次web请求都要创建一次数据库链接。创建链接是一个费时的活动,每次都得花费0.05s~1s的时间,并且系统还要分配内存资源。这个时间对于一次或几回数据库操做,或许感受不出系统有多大的开销。但是对于如今的web应用,尤为是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种状况下,频繁的进行数据库链接操做势必占用不少的系统资源,网站的响应速度一定降低,严重的甚至会形成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库链接,使用完后都得断开。不然,若是程序出现异常而未能关闭,将会致使数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被建立的链接对象数,系统资源会被毫无顾及的分配出去,如链接过多,也可能致使内存泄漏,服务器崩溃。缓存

       上述的用户查询案例,若是同时有1000人访问,就会不断的有数据库链接、断开操做:

 

 

       经过上面的分析,咱们能够看出来,“数据库链接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实咱们查询完数据库后,若是不关闭链接,而是暂时存放起来,当别人使用时,把这个链接给他们使用。就避免了一次创建数据库链接和断开的操做时间消耗。原理以下:

二. 技术演进出来的数据库链接池

       由上面的分析能够看出,问题的根源就在于对数据库链接资源的低效管理。咱们知道,对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所形成的问题。为解决上述问题,能够采用数据库链接池技术。数据库链接池的基本思想就是为数据库链接创建一个“缓冲池”。预先在缓冲池中放入必定数量的链接,当须要创建数据库链接时,只需从“缓冲池”中取出一个,使用完毕以后再放回去。咱们能够经过设定链接池最大链接数来防止系统无尽的与数据库链接。更为重要的是咱们能够经过链接池的管理机制监视数据库的链接的数量﹑使用状况,为系统开发﹑测试及性能调整提供依据。

       咱们本身尝试开发一个链接池,来为上面的查询业务提供数据库链接服务:

       ①   编写class 实现DataSource 接口

       ②   在class构造器一次性建立10个链接,将链接保存LinkedList中

       ③   实现getConnection  从 LinkedList中返回一个链接

       ④   提供将链接放回链接池中方法

 

       一、链接池代码       

[java]  view plain  copy
 
 print?
  1. public class MyDataSource implements DataSource {  
  2.           //链表 --- 实现栈结构  
  3.           privateLinkedList<Connection> dataSources = new LinkedList<Connection>();  
  4.   
  5.           //初始化链接数量  
  6.           publicMyDataSource() {  
  7.                  //一次性建立10个链接  
  8.                  for(int i = 0; i < 10; i++) {  
  9.                         try {  
  10.                            //一、装载sqlserver驱动对象  
  11.                            DriverManager.registerDriver(new SQLServerDriver());  
  12.                            //二、经过JDBC创建数据库链接  
  13.                            Connection con =DriverManager.getConnection(  
  14.                               "jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");  
  15.                            //三、将链接加入链接池中  
  16.                            dataSources.add(con);  
  17.                         } catch (Exception e) {  
  18.                            e.printStackTrace();  
  19.                         }  
  20.                  }  
  21.           }  
  22.   
  23.           @Override  
  24.           publicConnection getConnection() throws SQLException {  
  25.                  //取出链接池中一个链接  
  26.                  finalConnection conn = dataSources.removeFirst(); // 删除第一个链接返回  
  27.                  returnconn;  
  28.           }  
  29.   
  30.           //将链接放回链接池  
  31.           publicvoid releaseConnection(Connection conn) {  
  32.                  dataSources.add(conn);  
  33.                  }  
  34.    }  

 

       二、使用链接池重构咱们的用户查询函数       

[java]  view plain  copy
 
 print?
  1. //查询全部用户  
  2. Public void FindAllUsers(){  
  3.        //一、使用链接池创建数据库链接  
  4.        MyDataSource dataSource = new MyDataSource();  
  5.        Connection conn =dataSource.getConnection();          
  6.        //二、建立状态  
  7.        Statement state =con.createStatement();             
  8.        //三、查询数据库并返回结果  
  9.        ResultSet result =state.executeQuery("select * from users");             
  10.        //四、输出查询结果  
  11.        while(result.next()){  
  12.               System.out.println(result.getString("email"));  
  13.        }              
  14.        //五、断开数据库链接  
  15.        result.close();  
  16.        state.close();  
  17.        //六、归还数据库链接给链接池  
  18.        dataSource.releaseConnection(conn);  
  19.  }  

 

 

       这就是数据库链接池的原理,它大大提供了数据库链接的利用率,减少了内存吞吐的开销。咱们在开发过程当中,就不须要再关心数据库链接的问题,天然有数据库链接池帮助咱们处理,这回放心了吧。但链接池须要考虑的问题不只仅如此,下面咱们就看看还有哪些问题须要考虑。

三.链接池还要考虑更多的问题

       一、并发问题

       为了使链接管理服务具备最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,由于java语言自身提供了对并发管理的支持,使用synchronized关键字便可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:

      publicsynchronized connection getconnection()

 

       二、多数据库服务器和多用户

       对于大型的企业级应用,经常须要同时链接不一样的数据库(如链接oracle和sybase)。如何链接不一样的数据库呢?咱们采用的策略是:设计一个符合单例模式的链接池管理类,在链接池管理类的惟一实例被建立时读取一个资源文件,其中资源文件中存放着多个数据库的url地址等信息。根据资源文件提供的信息,建立多个链接池类的实例,每个实例都是一个特定数据库的链接池。链接池管理类实例为每一个链接池实例取一个名字,经过不一样的名字来管理不一样的链接池。

       对于同一个数据库有多个用户使用不一样的名称和密码访问的状况,也能够经过资源文件处理,即在资源文件中设置多个具备相同url地址,但具备不一样用户名和密码的数据库链接信息。

 

       三、事务处理

       咱们知道,事务具备原子性,此时要求对数据库的操做符合“all-all-nothing”原则即对于一组sql语句要么全作,要么全不作。

       在java语言中,connection类自己提供了对事务的支持,能够经过设置connection的autocommit属性为false 而后显式的调用commit或rollback方法来实现。但要高效的进行connection复用,就必须提供相应的事务支持机制。可采用每个事务独占一个链接来实现,这种方法能够大大下降事务管理的复杂性。

 

       四、链接池的分配与释放

       链接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,能够提升链接的复用度,从而下降创建新链接的开销,同时还能够加快用户的访问速度。

       对于链接的管理可以使用空闲池。即把已经建立但还没有分配出去的链接按建立时间存放到一个空闲池中。每当用户请求一个链接时,系统首先检查空闲池内有没有空闲链接。若是有就把创建时间最长(经过容器的顺序存放实现)的那个链接分配给他(实际是先作链接是否有效的判断,若是可用就分配给用户,如不可用就把这个链接从空闲池删掉,从新检测空闲池是否还有链接);若是没有则检查当前所开链接池是否达到链接池所容许的最大链接数(maxconn)若是没有达到,就新建一个链接,若是已经达到,就等待必定的时间(timeout)。若是在等待的时间内有链接被释放出来就能够把这个链接分配给等待的用户,若是等待时间超过预约时间timeout 则返回空值(null)。系统对已经分配出去正在使用的链接只作计数,当使用完后再返还给空闲池。对于空闲链接的状态,可开辟专门的线程定时检测,这样会花费必定的系统开销,但能够保证较快的响应速度。也可采起不开辟专门线程,只是在分配前检测的方法。

 

       五、链接池的配置与维护

       链接池中到底应该放置多少链接,才能使系统的性能最佳?系统可采起设置最小链接数(minconn)和最大链接数(maxconn)来控制链接池中的链接。最小链接数是系统启动时链接池所建立的链接数。若是建立过多,则系统启动就慢,但建立后系统的响应速度会很快;若是建立过少,则系统启动的很快,响应起来却慢。这样,能够在开发时,设置较小的最小链接数,开发起来会快,而在系统实际使用时设置较大的,由于这样对访问客户来讲速度会快些。最大链接数是链接池中容许链接的最大数目,具体设置多少,要看系统的访问量,可经过反复测试,找到最佳点。

       如何确保链接池中的最小链接数呢?有动态和静态两种策略。动态即每隔必定时间就对链接池进行检测,若是发现链接数量小于最小链接数,则补充相应数量的新链接以保证链接池的正常运转。静态是发现空闲链接不够时再去检查。

四.实际开发中有成熟的开源链接池供咱们使用

       理解了链接池的原理就能够了,没有必要什么都从头写一遍,那样会花费不少时间,而且性能及稳定性也不必定知足要求。事实上,已经存在不少流行的性能优良的第三方数据库链接池jar包供咱们使用。如:

       1.Apache commons-dbcp 链接池

        下载:http://commons.apache.org/proper/commons-dbcp/

 

       2.c3p0 数据库链接池

        下载:http://sourceforge.net/projects/c3p0/

 

  • c3p0是什么

  c3p0的出现,是为了大大提升应用程序和数据库之间访问效率的。

  它的特性:

  1. 编码的简单易用
  2. 链接的复用
  3. 链接的管理

  说到c3p0,不得不说一下jdbc自己,c3p0愿意就是对数据库链接的管理,那么原有的概念仍是得清晰:DriverManager、Connection、StateMent、ResultMent。

  jdbc:java database connective这套API,不用多说,是一套用于链接各式dbms或链接桥接器的api,两个层级:上层供应用方调用api,下层,定义了各个dbms的spi的api(具体文档见:这里)。

  主要要提的是:datasource、DriverManager,想到哪儿写到哪儿,datasource是更高级一点的api,缘由在于相对对应用来讲更透明。

  Connection:同dbms的逻辑连接,相似于session管理概念, SQL statements are executed and results are returned within the context of a connection.

  jdbc的概念就到这里,平时用得比较多。

  • c3P0的概念

 

  c3p0的bean配置以下:

 1 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
 2         <property name="driverClass" value="${jdbc.driverClassName}" />
 3         <property name="jdbcUrl" value="${jdbc.url}" />
 4         <property name="user" value="${jdbc.username}" />
 5         <property name="password" value="${jdbc.password}" />
 6         <property name="checkoutTimeout" value="30000" />
 7         <property name="maxPoolSize" value="15" />
 8         <property name="idleConnectionTestPeriod" value="180" />
 9         <property name="maxIdleTime" value="180" />
10     </bean>

还有一些配置选项,后续详细说明。可见c3p0的bean引用使用的是:ComboPooledDataSource,该类结构以下:

 

 

以上类图都不是很彻底,不过大致能表达出类之间的原理:

一、bean:ComboPooledDataSource的父类:AbstractPoolBackedDataSource有一个poolmanager字段,存储着对pool管理器

二、获取ds.getConnection()连接对象时,内部使用getPoolManger()获取C3p0ConnectionPooledManager(mgr)对象,该manager管理着pool对象:C3P0PooledConnectionPool对象,mgr.getPool().checkoutPooledConnection()

三、自此该connection已经被获取到了

四、让咱们看看该connection的真实面目吧:

 ProxyConnection。

五、所以其实原理是:

  从pool里获取到的connection,是proxy包装的connection,而对connection的释放或者重用,是pool的管理责任:初始化池大小,维护池的大小(expand或shrink),管理unused、expired、checkout、checkin链接。

真正底层的链接是jdbc本身的链接,而c3p0的管理部分,基本上使用的是synchronized关键字,使用timerTask定时器工做。

 

配置介绍:

<c3p0-config> 
<default-config> 
<!--当链接池中的链接耗尽的时候c3p0一次同时获取的链接数。Default: 3 --> 
<property name="acquireIncrement">3</property> 

<!--定义在从数据库获取新链接失败后重复尝试的次数。Default: 30 --> 
<property name="acquireRetryAttempts">30</property> 

<!--两次链接中间隔时间,单位毫秒。Default: 1000 --> 
<property name="acquireRetryDelay">1000</property> 

<!--链接关闭时默认将全部未提交的操做回滚。Default: false --> 
<property name="autoCommitOnClose">false</property> 

<!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。若是定义了这个参数那么 
属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操做,它将只供c3p0测试 
使用。Default: null--> 
<property name="automaticTestTable">Test</property> 

<!--获取链接失败将会引发全部等待链接池来获取链接的线程抛出异常。可是数据源仍有效 
保留,并在下次调用getConnection()的时候继续尝试获取链接。若是设为true,那么在尝试 
获取链接失败后该数据源将申明已断开并永久关闭。Default: false--> 
<property name="breakAfterAcquireFailure">false</property> 

<!--当链接池用完时客户端调用getConnection()后等待获取新链接的时间,超时后将抛出 
SQLException,如设为0则无限期等待。单位毫秒。Default: 0 --> 
<property name="checkoutTimeout">100</property> 

<!--经过实现ConnectionTester或QueryConnectionTester的类来测试链接。类名需制定全路径。 
Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester--> 
<property name="connectionTesterClassName"></property> 

<!--指定c3p0 libraries的路径,若是(一般都是这样)在本地便可得到那么无需设置,默认null便可 
Default: null--> 
<property name="factoryClassLocation">null</property> 

<!--Strongly disrecommended. Setting this to true may lead to subtle and bizarre bugs. 
(文档原文)做者强烈建议不使用的一个属性--> 
<property name="forceIgnoreUnresolvedTransactions">false</property> 

<!--每60秒检查全部链接池中的空闲链接。Default: 0 --> 
<property name="idleConnectionTestPeriod">60</property> 

<!--初始化时获取三个链接,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> 
<property name="initialPoolSize">3</property> 

<!--最大空闲时间,60秒内未使用则链接被丢弃。若为0则永不丢弃。Default: 0 --> 
<property name="maxIdleTime">60</property> 

<!--链接池中保留的最大链接数。Default: 15 --> 
<property name="maxPoolSize">15</property> 

<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但因为预缓存的statements 
属于单个connection而不是整个链接池。因此设置这个参数须要考虑到多方面的因素。 
若是maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0--> 
<property name="maxStatements">100</property> 

<!--maxStatementsPerConnection定义了链接池内单个链接所拥有的最大缓存statements数。Default: 0 --> 
<property name="maxStatementsPerConnection"></property> 

<!--c3p0是异步操做的,缓慢的JDBC操做经过帮助进程完成。扩展这些操做能够有效的提高性能 
经过多线程实现多个操做同时被执行。Default: 3--> 
<property name="numHelperThreads">3</property> 

<!--当用户调用getConnection()时使root用户成为去获取链接的用户。主要用于链接池链接非c3p0 
的数据源时。Default: null--> 
<property name="overrideDefaultUser">root</property> 

<!--与overrideDefaultUser参数对应使用的一个参数。Default: null--> 
<property name="overrideDefaultPassword">password</property> 

<!--密码。Default: null--> 
<property name="password"></property> 

<!--定义全部链接测试都执行的测试语句。在使用链接测试的状况下这个一显著提升测试速度。注意: 
测试的表必须在初始数据源的时候就存在。Default: null--> 
<property name="preferredTestQuery">select id from test where id=1</property> 

<!--用户修改系统配置参数执行前最多等待300秒。Default: 300 --> 
<property name="propertyCycle">300</property> 

<!--因性能消耗大请只在须要的时候使用它。若是设为true那么在每一个connection提交的 
时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable 
等方法来提高链接测试的性能。Default: false --> 
<property name="testConnectionOnCheckout">false</property> 

<!--若是设为true那么在取得链接的同时将校验链接的有效性。Default: false --> 
<property name="testConnectionOnCheckin">true</property> 

<!--用户名。Default: null--> 
<property name="user">root</property> 

在Hibernate(spring管理)中的配置:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
  <property name="driverClass"><value>oracle.jdbc.driver.OracleDriver</value></property>
  <property name="jdbcUrl"><value>jdbc:oracle:thin:@localhost:1521:Test</value></property>
  <property name="user"><value>Kay</value></property>
  <property name="password"><value>root</value></property>
  <!--链接池中保留的最小链接数。-->            
    <property name="minPoolSize" value="10" />        
    <!--链接池中保留的最大链接数。Default: 15 -->         
    <property name="maxPoolSize" value="100" />        
    <!--最大空闲时间,1800秒内未使用则链接被丢弃。若为0则永不丢弃。Default: 0 -->               
    <property name="maxIdleTime" value="1800" />        
    <!--当链接池中的链接耗尽的时候c3p0一次同时获取的链接数。Default: 3 -->               
    <property name="acquireIncrement" value="3" />         
    <property name="maxStatements" value="1000" />          
    <property name="initialPoolSize" value="10" />          
    <!--每60秒检查全部链接池中的空闲链接。Default: 0 -->       
    <property name="idleConnectionTestPeriod" value="60" />          
    <!--定义在从数据库获取新链接失败后重复尝试的次数。Default: 30 -->       
    <property name="acquireRetryAttempts" value="30" />          
    <property name="breakAfterAcquireFailure" value="true" />              
    <property name="testConnectionOnCheckout" value="false" /> 
 </bean>

 

出处:http://blog.csdn.net/shuaihj/article/details/14223015

相关文章
相关标签/搜索