zoukankan      html  css  js  c++  java
  • dbcp基本配置和重连配置

    转载自:http://agapple.iteye.com/blog/772507


    最近在看一些dbcp的相关内容,顺便做一下记录,免得自己给忘记了。

    1. 引入dbcp (选择1.4)

    Java代码  收藏代码
    1. <dependency>  
    2.     <groupId>com.alibaba.external</groupId>  
    3.     <artifactId>jakarta.commons.dbcp</artifactId>  
    4.     <version>1.4</version>  
    5. </dependency>  

    2. dbcp的基本配置

    相关配置说明:

     

    1. initialSize :连接池启动时创建的初始化连接数量(默认值为0)
    2. maxActive :连接池中可同时连接的最大的连接数(默认值为8,调整为20,高峰单机器在20并发左右,自己根据应用场景定)
    3. maxIdle:连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制(默认为8个,maxIdle不能设置太小,因为假如在高负载的情况下,连接的打开时间比关闭的时间快,会引起连接池中idle的个数 上升超过maxIdle,而造成频繁的连接销毁和创建,类似于jvm参数中的Xmx设置)
    4. minIdle:连接池中最小的空闲的连接数,低于这个数量会被创建新的连接(默认为0,调整为5,该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大,因为在机器很空闲的时候,也会创建低于minidle个数的连接,类似于jvm参数中的Xmn设置)
    5. maxWait  :最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限,调整为60000ms,避免因线程池不够用,而导致请求被无限制挂起)
    6. poolPreparedStatements:开启池的prepared(默认是false,未调整,经过测试,开启后的性能没有关闭的好。)
    7. maxOpenPreparedStatements:开启池的prepared 后的同时最大连接数(默认无限制,同上,未配置)
    8. minEvictableIdleTimeMillis  :连接池中连接,在时间段内一直空闲, 被逐出连接池的时间
    9. (默认为30分钟,可以适当做调整,需要和后端服务端的策略配置相关)
    10. removeAbandonedTimeout  :超过时间限制,回收没有用(废弃)的连接(默认为 300秒,调整为180)
    11. removeAbandoned  :超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收(默认为false,调整为true)

    removeAbandoned参数解释:
    1. 如果开启了removeAbandoned,当getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)时被触发.
    2. 举例当maxActive=20, 活动连接为18,空闲连接为1时可以触发"removeAbandoned".但是活动连接只有在没有被使用的时间超 过"removeAbandonedTimeout"时才被回收
    3. logAbandoned: 标记当连接被回收时是否打印程序的stack traces日志(默认为false,未调整)

    一般会是几种情况出现需要removeAbandoned: 
    1. 代码未在finally释放connection , 不过我们都用sqlmapClientTemplate,底层都有链接释放的过程
    2. 遇到数据库死锁。以前遇到过后端存储过程做了锁表操作,导致前台集群中连接池全都被block住,后续的业务处理因为拿不到链接所有都处理失败了。

    一份优化过的配置:
    基本配置代码 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">   
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="xxxx" />  
        <property name="username"><value>xxxx</value></property>  
            <property name="password"><value>xxxxx</value></property>  
            <property name="maxActive"><value>20</value></property>  
            <property name="initialSize"><value>1</value></property>  
            <property name="maxWait"><value>60000</value></property>  
            <property name="maxIdle"><value>20</value></property>  
            <property name="minIdle"><value>3</value></property>  
            <property name="removeAbandoned"><value>true</value></property>  
            <property name="removeAbandonedTimeout"><value>180</value></property>  
            <property name="connectionProperties"><value>clientEncoding=GBK</value></property>  
    </bean>  


     
     

    2. dbcp的链接validate配置
    1. dbcp是采用了commons-pool做为其连接池管理,testOnBorrow,testOnReturn, testWhileIdle是pool是提供的几种校验机制,通过外部钩子的方式回调dbcp的相关数据库链接(validationQuery)校验
    2. dbcp相关外部钩子类:PoolableConnectionFactory,继承于common-pool PoolableObjectFactory
    3. dbcp通过GenericObjectPool这一入口,进行连接池的borrow,return处理
    4. testOnBorrow : 顾明思义,就是在进行borrowObject进行处理时,对拿到的connection进行validateObject校验
    5. testOnReturn : 顾明思义,就是在进行returnObject对返回的connection进行validateObject校验,个人觉得对数据库连接池的管理意义不大
    6. testWhileIdle : 关注的重点,GenericObjectPool中针对pool管理,起了一个Evict的TimerTask定时线程进行控制(可通过设置参数timeBetweenEvictionRunsMillis>0),定时对线程池中的链接进行validateObject校验,对无效的链接进行关闭后,会调用ensureMinIdle,适当建立链接保证最小的minIdle连接数。
    7. timeBetweenEvictionRunsMillis,设置的Evict线程的时间,单位ms,大于0才会开启evict检查线程
    8. validateQuery, 代表检查的sql
    9. validateQueryTimeout, 代表在执行检查时,通过statement设置,statement.setQueryTimeout(validationQueryTimeout)
    10. numTestsPerEvictionRun,代表每次检查链接的数量,建议设置和maxActive一样大,这样每次可以有效检查所有的链接.

    Validate配置代码  收藏代码
    1. <property name="testWhileIdle"><value>true</value></property> <!-- 打开检查,用异步线程evict进行检查 -->  
    2.     <property name="testOnBorrow"><value>false</value></property>  
    3.     <property name="testOnReturn"><value>false</value></property>  
    4.     <property name="validationQuery"><value>select sysdate from dual</value></property>  
    5.     <property name="validationQueryTimeout"><value>1</value></property>  
    6.     <property name="timeBetweenEvictionRunsMillis"><value>30000</value></property>  
    7.     <property name="numTestsPerEvictionRun"><value>20</value></property>  

     相关配置需求:

    1. 目前网站的应用大部分的瓶颈还是在I/O这一块,大部分的I/O还是在数据库的这一层面上,每一个请求可能会调用10来次SQL查询,如果不走事务,一个请求会重复获取链接,如果每次获取链接都进行validateObject,性能开销不是很能接受,可以假定一次SQL操作消毫0.5~1ms(一般走了网络请求基本就这数)
    2. 网站异常数据库重启,网络异常断开的频率是非常低的,一般也就在数据库升级,演习维护时才会进行,而且一般也是选在晚上,访问量相对比较低的请求,而且一般会有人员值班关注,所以异步的validateObject是可以接受,但一个前提需要确保能保证在一个合理的时间段内,数据库能完成自动重联。

    从代码层面简单介绍下dbcp的validate实现:

    1.  common-pools提供的PoolableObjectFactory,针对pool池的管理操作接口
    Java代码  收藏代码
    1. public interface PoolableObjectFactory {  
    2.   
    3.   Object makeObject() throws Exception;  
    4.   
    5.   void destroyObject(Object obj) throws Exception;  
    6.   
    7.   boolean validateObject(Object obj);  
    8.   
    9.   void activateObject(Object obj) throws Exception;  
    10.   
    11.   void passivateObject(Object obj) throws Exception;  
    12. }  

    2. dbcp实现的pool从池管理操作

    这里贴了一个相关validate代码,具体类可见:PoolableConnectionFactory.validateConnection()

    Java代码  收藏代码
    1. public class PoolableConnectionFactory implements PoolableObjectFactory {  
    2.   
    3. ......  
    4. public boolean validateObject(Object obj) { //验证validateObject  
    5.         if(obj instanceof Connection) {  
    6.             try {  
    7.                 validateConnection((Connection) obj);  
    8.                 return true;  
    9.             } catch(Exception e) {  
    10.                 return false;  
    11.             }  
    12.         } else {  
    13.             return false;  
    14.         }  
    15.     }  
    16. public void validateConnection(Connection conn) throws SQLException {  
    17.         String query = _validationQuery;  
    18.         if(conn.isClosed()) {  
    19.             throw new SQLException("validateConnection: connection closed");  
    20.         }  
    21.         if(null != query) {  
    22.             Statement stmt = null;  
    23.             ResultSet rset = null;  
    24.             try {  
    25.                 stmt = conn.createStatement();  
    26.                 if (_validationQueryTimeout > 0) {  
    27.                     stmt.setQueryTimeout(_validationQueryTimeout);  
    28.                 }  
    29.                 rset = stmt.executeQuery(query);  
    30.                 if(!rset.next()) {  
    31.                     throw new SQLException("validationQuery didn't return a row");  
    32.                 }  
    33.             } finally {  
    34.                 if (rset != null) {  
    35.                     try {  
    36.                         rset.close();  
    37.                     } catch(Exception t) {  
    38.                         // ignored  
    39.                     }  
    40.                 }  
    41.                 if (stmt != null) {  
    42.                     try {  
    43.                         stmt.close();  
    44.                     } catch(Exception t) {  
    45.                         // ignored  
    46.                     }  
    47.                 }  
    48.             }  
    49.         }  
    50.     }  
    51.   
    52. ....  
    53.   
    54. }  

    3. pool池的evict调用代码:GenericObjectPool (apache commons pool version 1.5.4)

    Java代码  收藏代码
    1. protected synchronized void startEvictor(long delay) { //启动Evictor为TimerTask  
    2.         if(null != _evictor) {  
    3.             EvictionTimer.cancel(_evictor);  
    4.             _evictor = null;  
    5.         }  
    6.         if(delay > 0) {  
    7.             _evictor = new Evictor();  
    8.             EvictionTimer.schedule(_evictor, delay, delay);  
    9.         }  
    10.     }  
    11.   
    12. for (int i=0,m=getNumTests();i<m;i++) {  
    13.             final ObjectTimestampPair pair;  
    14.            .......  
    15.             boolean removeObject = false;  
    16.             // 空闲链接处理  
    17.             final long idleTimeMilis = System.currentTimeMillis() - pair.tstamp;  
    18.             if ((getMinEvictableIdleTimeMillis() > 0) &&  
    19.                     (idleTimeMilis > getMinEvictableIdleTimeMillis())) {  
    20.                 removeObject = true;  
    21.             } else if ((getSoftMinEvictableIdleTimeMillis() > 0) &&  
    22.                     (idleTimeMilis > getSoftMinEvictableIdleTimeMillis()) &&  
    23.                     ((getNumIdle() + 1)> getMinIdle())) {   
    24.                 removeObject = true;  
    25.             }  
    26.             //  testWhileIdle sql 检查处理  
    27.             if(getTestWhileIdle() && !removeObject) {  
    28.                 boolean active = false;  
    29.                 try {  
    30.                     _factory.activateObject(pair.value);  
    31.                     active = true;  
    32.                 } catch(Exception e) {  
    33.                     removeObject=true;  
    34.                 }  
    35.                 if(active) {  
    36.                     if(!_factory.validateObject(pair.value)) {   
    37.                         removeObject=true;  
    38.                     } else {  
    39.                         try {  
    40.                             _factory.passivateObject(pair.value);  
    41.                         } catch(Exception e) {  
    42.                             removeObject=true;  
    43.                         }  
    44.                     }  
    45.                 }  
    46.             }  
    47.             // 真正关闭  
    48.             if (removeObject) {  
    49.                 try {  
    50.                     _factory.destroyObject(pair.value);  
    51.                 } catch(Exception e) {  
    52.                     // ignored  
    53.                 }  
    54.             }  
    55.           ........  
     

    注意: 目前dbcp的pool的实现是使用了公用的apache common pools进行扩展处理,所以和原生的连接池处理,代码看上去有点别扭,感觉自动重连这块异常处理不怎么好,我也就只重点关注了这部分代码而已   .


    3. dbcp的链接自动重链相关测试

    相关场景:

    1. 数据库意外重启后,原先的数据库连接池能自动废弃老的无用的链接,建立新的数据库链接
    2. 网络异常中断后,原先的建立的tcp链接,应该能进行自动切换

    测试需求1步骤

    1. 建立一testCase代码
    2. 配置mysql数据库
    3. 循环执行在SQL查询过程
    4. 异常重启mysql数据库

    测试需求2步骤

    1. 建立一testCase代码
    2. 配置mysql数据库
    3. 循环执行在SQL查询过程
    4. 通过iptables禁用网络链接 

    /sbin/iptables -A INPUT -s 10.16.2.69 -j REJECT
    /sbin/iptables -A FORWARD -p tcp -s 10.16.2.69 --dport 3306 -m state --state NEW,ESTABLISHED -j DROP

         5. iptables -F 清空规则,恢复链接通道。

    测试需求问题记录

    分别测试了两种配置,有validateObject的配置和没有validateObject的相关配置。

    1. 没有validate配置
    问题一: 异常重启mysql数据库后,居然也可以自动恢复链接,sql查询正常
    跟踪了一下代码,发现这么一个问题:

    1. 在数据库关闭的时候,client中pool通过borrowObject获取一个异常链接返回给client
    2. client在使用具体的异常链接进行sql调用出错了,抛了异常
    3. 在finally,调用connection.close(),本意是应该调用pool通过returnObject返回到的池中,但在跟踪代码时,未见调用GenericObjectPool的returnObject
    4. 继续查,发现在dbcp在中PoolingDataSource(实现DataSource接口)调用PoolableConnection(dbcp pool相关的delegate操作)进行相应关闭时,会检查_conn.isClosed(),针对DataSource如果isClosed返回为true的则不调用returnObject,直接丢弃了链接  

    解释:

    • 正因为在获取异常链接后,因为做了_conn.isClosed()判断,所以异常链接并没有返回到连接池中,所以到数据库重启恢复后,每次都是调用pool重新构造一个新的connection,所以后面就正常了
    • _conn.isClosed()是否保险,从jdk的api描述中: A connection is closed if the method close has been called on it or if certain fatal errors have occurred. 里面提供两种情况,一种就是被调用了closed方法,另一种就是出现一些异常也说的比较含糊。

    问题二:validateObject调用时,dbcp设置的validationQueryTimeout居然没效果

    看了mysql statement代码实现,找到了答案。 

    mysql com.mysql.jdbc.statemen 部分代码

    timeout时间处理:

    Java代码  收藏代码
    1. timeoutTask = new CancelTask();  
    2. //通过TimerTask启动一定时任务  
    3. Connection.getCancelTimer().schedule(timeoutTask,  this.timeoutInMillis);  

    对应的CancelTask的代码: 

    Java代码  收藏代码
    1. class CancelTask extends TimerTask {  
    2.   
    3.         long connectionId = 0;  
    4.   
    5.         CancelTask() throws SQLException {  
    6.             connectionId = connection.getIO().getThreadId();  
    7.         }  
    8.   
    9.         public void run() {  
    10.   
    11.             Thread cancelThread = new Thread() {  
    12.   
    13.                 public void run() {  
    14.                     Connection cancelConn = null;  
    15.                     java.sql.Statement cancelStmt = null;  
    16.   
    17.                     try {  
    18.                         cancelConn = connection.duplicate();  
    19.                         cancelStmt = cancelConn.createStatement();  
    20.                                                 // 简单暴力,再发起一条KILL SQL,关闭先前的sql thread id  
    21.                         cancelStmt.execute("KILL QUERY " + connectionId);  
    22.                         wasCancelled = true;  
    23.                     } catch (SQLException sqlEx) {  
    24.                         throw new RuntimeException(sqlEx.toString());  
    25.                     } finally {  
    26.                         if (cancelStmt != null) {  
    27.                             try {  
    28.                                 cancelStmt.close();  
    29.                             } catch (SQLException sqlEx) {  
    30.                                 throw new RuntimeException(sqlEx.toString());  
    31.                             }  
    32.                         }  
    33.   
    34.                         if (cancelConn != null) {  
    35.                             try {  
    36.                                 cancelConn.close();  
    37.                             } catch (SQLException sqlEx) {  
    38.                                 throw new RuntimeException(sqlEx.toString());  
    39.                             }  
    40.                         }  
    41.                     }  
    42.                 }  
    43.             };  
    44.   
    45.             cancelThread.start();  
    46.         }  
    47.     }  

    原因总结一句话: queryTimeout的实现是通过底层数据库提供的机制,比如KILL QUERY pid.  如果此时的网络不通,出现阻塞现象,对应的kill命令也发不出去,所以timeout设置的超时没效果。

    4.最后

    最后还是决定配置testWhileIdle扫描,主要考虑:

    1. pool池中的链接如果未被使用,可以通过testWhileIdle进行链接检查,避免在使用时后总要失败那么一次,可以及时预防
    2. 配合连接池的minEvictableIdleTimeMillis(空闲链接),removeAbandoned(未释放的链接),可以更好的去避免因为一些异常情况引起的问题,防范于未然。比如使用一些分布式数据库的中间件,会有空闲链接关闭的动作,动态伸缩连接池,这时候需要能及时的发现,避免请求失败。
    3. testOnBorrow个人不太建议使用,存在性能问题,试想一下连接一般会在什么情况出问题,网络或者服务端异常终端空闲链接,网络中断你testOnBorrow检查发现不对再取一个链接还是不对,针对空闲链接处理异常关闭,可以从好业务端的重试策略进行考虑,同时配置客户端的空闲链接超时时间,maxIdle,minIdle等。

    --------------------------------------------

    新加的内容:

    5.dbcp密码加密处理

    以前使用jboss的jndi数据源的方式,是通过配置oracle-ds.xml,可以设置<security-domain>EncryptDBPassword</security-domain>,引用jboss login-config.xml配置的加密配置。

    Java代码  收藏代码
    1. <application-policy name="EncryptDBPassword">  
    2.         <authentication>  
    3.             <login-module code="org.jboss.resource.security.SecureIdentityLoginModule" flag="required">  
    4.                 <module-option name="username">${username}</module-option>  
    5.                 <module-option name="password">${password_encrypt}</module-option>  
    6.                 <module-option name="managedConnectionFactoryName">jboss.jca:service=LocalTxCM,name=${jndiName}</module-option>  
    7.             </login-module>  
    8.         </authentication>  
    9.     </application-policy>  
     

    为了能达到同样的效果,切换为spring dbcp配置时,也有类似密码加密的功能,运行期进行密码decode,最后进行数据链接。

    实现方式很简单,分析jboss的对应SecureIdentityLoginModule的实现,无非就是走了Blowfish加密算法,自己拷贝实现一份。

    Java代码  收藏代码
    1. private static String encode(String secret) throws NoSuchPaddingException, NoSuchAlgorithmException,  
    2.                                                InvalidKeyException, BadPaddingException, IllegalBlockSizeException {  
    3.         byte[] kbytes = "jaas is the way".getBytes();  
    4.         SecretKeySpec key = new SecretKeySpec(kbytes, "Blowfish");  
    5.   
    6.         Cipher cipher = Cipher.getInstance("Blowfish");  
    7.         cipher.init(Cipher.ENCRYPT_MODE, key);  
    8.         byte[] encoding = cipher.doFinal(secret.getBytes());  
    9.         BigInteger n = new BigInteger(encoding);  
    10.         return n.toString(16);  
    11.     }  
    12.   
    13.     private static char[] decode(String secret) throws NoSuchPaddingException, NoSuchAlgorithmException,  
    14.                                                InvalidKeyException, BadPaddingException, IllegalBlockSizeException {  
    15.         byte[] kbytes = "jaas is the way".getBytes();  
    16.         SecretKeySpec key = new SecretKeySpec(kbytes, "Blowfish");  
    17.   
    18.         BigInteger n = new BigInteger(secret, 16);  
    19.         byte[] encoding = n.toByteArray();  
    20.   
    21.         Cipher cipher = Cipher.getInstance("Blowfish");  
    22.         cipher.init(Cipher.DECRYPT_MODE, key);  
    23.         byte[] decode = cipher.doFinal(encoding);  
    24.         return new String(decode).toCharArray();  
    25.     }  
     

    最后的配置替换为:

    Xml代码  收藏代码
    1. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">   
    2. ......  
    3.         <property name="password"><!-- 注意多了一层转化,将密码串调用decode解密为最初的数据库密码 -->  
    4.             <bean class="com.xxxxx.EncryptDBPasswordFactory">  
    5.                 <property name="password" value="${xxxx.password.encrypted}" />  
    6.             </bean>  
    7.         </property>  
    8. ........  
    9. </bean>  

    --------------------------------------------

    新加的内容:

    6.数据库重连机制

    常见的问题:

    1. 数据库意外重启后,原先的数据库连接池能自动废弃老的无用的链接,建立新的数据库链接

    2. 网络异常中断后,原先的建立的tcp链接,应该能进行自动切换。比如网站演习中的交换机重启会导致网络瞬断

    3. 分布式数据库中间件,比如amoeba会定时的将空闲链接异常关闭,客户端会出现半开的空闲链接。

    大致的解决思路:  

    1. sql心跳检查

      主动式 ,即我前面提到的sql validate相关配置

    2. 请求探雷

        牺牲小我,完成大我的精神。 拿链接尝试一下,发现处理失败丢弃链接,探雷的请求总会失败几个,就是前面遇到的问题一,dbcp已经支持该功能,不需要额外置。

    3. 设置合理的超时时间,

          解决半开链接. 一般数据库mysql,oracle都有一定的链接空闲断开的机制,而且当你使用一些分布式中间件(软件一类的),空闲链接控制会更加严格,这时候设置合理的超时时间可以有效避免半开链接。

         一般超时时间,dbcp主要是minEvictableIdleTimeMillis(空闲链接) , removeAbandonedTimeout(链接泄漏)。可以见前面的参数解释。


  • 相关阅读:
    how to pass a Javabean to server In Model2 architecture.
    What is the Web Appliation Archive, abbreviation is "WAR"
    Understaning Javascript OO
    Genetic Fraud
    poj 3211 Washing Clothes
    poj 2385 Apple Catching
    Magic Star
    关于memset的用法几点
    c++ 函数
    zoj 2972 Hurdles of 110m
  • 原文地址:https://www.cnblogs.com/ycpanda/p/3637361.html
Copyright © 2011-2022 走看看