池化技术详解-对象池,连接池,线程池

在应用系统开发过程中,经常会使用到池化技术,如对象池,连接池,线程池等,通过复用技术来减少一些消耗,以提升性能。
1.对象池通过复用对象减少创建对象,垃圾回收的开销;
注,池不能太大,太大会影响GC时的扫描时间
2.连接池,如数据库连接池/Redis连接池/HTTP连接池,通过复用TCP连接来减少创建和释放连接的时间来提升性能
3.线程池,通过复用线程来提升性能

池化技术可以使用Apache-commons-pool2来实现,例如DBCP,Jedis连接池都是使用commons-pool2技术实现,不建议使用commons-pool1。

*****************************数据库连接池*****************************

数据库连接池有很多实现,如C3P0,DBCP,Druid等等,KT用的最多的是Durid和DBCP。
示例:commons-dbcp2 2.1.1
1.DBCP连接池配置
    <!-- dbcp2配置文件 --> 
    <bean id="propertyConfigurer" 
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
        <property name="location" value="classpath:jdbc.properties" /> 
    </bean> 
 
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" 
        destroy-method="close">
       
        <property name="driverClassName" value="${driver}" /> 
       
        <!-- 数据库连接相关配置(URL 用户名 密码 测试Query 默认超时时间) -->
        <property name="url" value="${url}" /> 
        <property name="username" value="${username}" /> 
        <property name="password" value="${password}" /> 
       
        <!-- Statement默认超时时间,单位:秒 -->
        <property name="defaultQueryTimeout" value="3"/>
        <!-- 默认是否自动提交事务 -->
        <property name="defaultAutoCommit" value="false"/>
       
        <!-- 数据库连接属性:不同数据库配置不一样 -->
        <property name="connectionProperties" value="connectionTimeout=2000;socketTimeout=2000"/>
       
        <!-- 连接池队列类型默认LIFO,false表示FIFO -->
        <property name="lifo" value="false"/>
       
        <!-- 建议以下值尽量一样,没必要频繁地过期空闲连接(除非出现连接池资源短缺等情况,才考虑) -->
        <property name="initialSize" value="${initialSize}"/>  <!-- 初始化连接大小 -->
        <property name="minIdle" value="${minIdle}"/>  <!-- 连接池最小空闲 -->
        <property name="maxIdle" value="${maxIdle}"/>  <!-- 连接池最大空闲 -->
        <property name="maxTotal" value="${maxTotal}"/> <!-- 连接池最大数量 --> 
         
        <!-- 等待获取连接最大等待时间,不需要太大,比如设置在500毫秒 --> 
        <property name="maxWaitMillis" value="${maxWaitMillis}"/> 
       
        <!-- 验证数据库连接是否有效/可用 -->
        <property name="testOnBorrow" value="true"/><!--  从池中获取连接时进行 validateConnection,默认为true -->
        <property name="testOnCreate" value="false"/><!-- 新建连接时进行 validateConnection,默认为false -->
        <property name="testOnReturn" value="false"/><!-- 将连接释放回池时进行validateConnection,默认为false -->
        <property name="validationQuery" value=""/><!-- 若不设置则调用Connection#isValid(int timeout)验证数据库是否有效 -->
        <property name="maxConnLifetimeMillis" value="0"/><!-- 连接存活的最长时间,<=0表示禁用 -->
       
        <property name="timeBetweenEvictionRunsMillis" value="0"/><!-- 驱除定时器执行周期,<=0表示禁用 -->
        <property name="softMinEvictableIdleTimeMillis"  value="0"/><!-- 连接空闲多久从池中驱除,<=0表示不做判断,当mindle<当前空闲连接数量时,开始这个判断 -->
        <property name="minEvictableIdleTimeMillis"  value="0"/>  <!-- 连接空闲多久从池中驱除,与 softMinEvictableIdleTimeMillis是或的关系-->
        <property name="numTestsPerEvictionRun" value="0"/><!-- 每次测试多少空闲对象,<=0相当于禁用 -->
        <property name="testWhileIdle" value="false"/>  <!-- 当连接空闲时是否测试,即保持连接一直存货,配合驱动定时器使用 -->
        <property name="evictionPolicyClassName" value="org.apache.commons.pool2.impl.DefaultEvictionPolicy"/> <!-- 判断连接是否需要驱除的策略,默认为DefaultEvictionPolicy -->
       
        <!-- 移除无引用连接(那些没有close的连接,此处设置为false,需要保证连接一定要释放) -->
        <property name="removeAbandonedOnBorrow" value="false"/>
        <property name="removeAbandonedOnMaintenance" value="false"/>
       
        <!-- 超时后将关闭无引用连接,单位:秒 -->
        <property name="removeAbandonedTimeout" value="10"/>
    </bean> 

说明:

【数据库连接配置】
备注一点,也可以在JDBC URL ?proName=prop Value配置这些属性

【池配置】

【验证数据库连接有效性】
三种方法:主动测试,定时测试,关闭孤儿连接

【关闭孤儿连接】



2.DBCP配置建议
2.1在并发量大的情况下:
(1)几个池大小设置为一样
(2)禁用关闭孤儿连接
(3)禁用定时器
如下:
    <!-- dbcp2配置文件 --> 
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" 
        destroy-method="close">     
        <!-- 省略url username password -->
        <!-- Statement默认超时时间,单位:秒 -->
        <property name="defaultQueryTimeout" value="3"/>
        <!-- 默认是否自动提交事务 -->
        <property name="defaultAutoCommit" value="false"/>
        <!-- 数据库连接属性:不同数据库配置不一样 -->
        <property name="connectionProperties" value="connectionTimeout=2000;socketTimeout=2000"/>
        <property name="testOnBorrow" value="true"/><!--  从池中获取连接时进行 validateConnection,默认为true -->       
        <!-- 连接池队列类型默认LIFO,false表示FIFO -->
        <property name="lifo" value="false"/>
        <!--省略池配置(池大小设置为一样)-->
        <!-- 等待获取连接最大等待时间,不需要太大,比如设置在500毫秒 --> 
        <property name="maxWaitMillis" value="500"/> 
        <property name="timeBetweenEvictionRunsMillis" value="0"/><!-- 驱除定时器执行周期,<=0表示禁用 -->
        <property name="numTestsPerEvictionRun" value="80"/><!-- 每次测试多少空闲对象,<=0相当于禁用 -->
        <property name="testWhileIdle" value="true"/>  <!-- 当连接空闲时是否测试,即保持连接一直存货,配合驱动定时器使用 -->
    </bean> 

2.2并发量不大时建议:
(1)按需设置池大小
(2)禁用关闭孤儿连接
(3)启用定时器(注意MYSQL空闲8小时自动断开)
如下:
    <!-- dbcp2配置文件 --> 
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" 
        destroy-method="close">     
        <!-- 省略url username password -->
        <!-- Statement默认超时时间,单位:秒 -->
        <property name="defaultQueryTimeout" value="3"/>
        <!-- 默认是否自动提交事务 -->
        <property name="defaultAutoCommit" value="false"/>
        <!-- 数据库连接属性:不同数据库配置不一样 -->
        <property name="connectionProperties" value="connectionTimeout=2000;socketTimeout=2000"/>
        <property name="testOnBorrow" value="false"/><!--  从池中获取连接时进行 validateConnection,默认为true -->       
        <!-- 连接池队列类型默认LIFO,false表示FIFO -->
        <property name="lifo" value="false"/>
        <!--省略池配置(池大小设置为一样)-->
        <!-- 等待获取连接最大等待时间,不需要太大,比如设置在500毫秒 --> 
        <property name="maxWaitMillis" value="500"/> 
        <property name="timeBetweenEvictionRunsMillis" value="3600000"/><!-- 驱除定时器执行周期,<=0表示禁用 -->
    </bean>

总结:
(1)无论何种方式,都必须设置超时时间
(2)在JVM关闭/重启时一定要销毁连接池(bean设置为destory-method="close")
   

3.数据库驱动超时 的 实现

4.连接池使用的一些建议
(1)注意网络阻塞/不稳定时出现的级联效应,还有当前等待连接池的人数过多,比如1000,那么接下来的等到就没有意义了,还会造成滚需求效应。
建议:
使用熔断和快速失败机制,即在连接池内部应该根据当前网络状态(例如超时次数过多),将一定时间内(比如100ms)的请求全部timeout,根本不进行await(maxAwait)

(2)DBCP比较容易出现的问题是:设置超时时间过长,造成大量TIMED_WAIT和线程阻塞,而且像滚雪球,一旦出现问题很难理解恢复,可以通过上下文方案解决。
等待超时时间应尽可能小些,即使返回错误页,也比等待并阻塞强

1. 数据库连接池应用中数据库服务器断开超时连接的问题
要点:应用中数据库连接池中会保存指定数量的数据库连接实例,而这些连接实例并没有定时地检测其到数据库服务器连接是否正常;
与此同时,数据库服务器存在数据库连接实例的超时时间,超过时间后它会自动断开连接。
也就是,被断开的那个连接此时仍然保存在应用的数据库连接池内,下次被使用的时候就会发生数据库连接断开而导致一次访问失败。

推荐解决方案:
1)如果能够提供这样一种检测机制,在应用的连接池管理中定时地检测连接池中连接的有效性,就完全可以避免上面描述的问题。
2)在应用代码中通过异常处理机制,来实现该次业务的重新处理,也可以很好地避免。
3)C3P0,Proxool,BoneCP,Druid等连接池的断开自动重联功能
参考:https://www.oschina.net/question/59889_44927
参考:http://blog.csdn.net/shirdrn/article/details/8248814

<property name="driverClass" value="${jdbc.driver}" /> com.mysql.jdbc.Driver
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
http://blog.sina.com.cn/s/blog_85d71fb70101ab99.html

Spring配C3P0
http://blog.csdn.net/vebasan/article/details/5059000

Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource卡住
解决方案:肯定是网络,用户名密码的错误导致



*****************************HttpClient连接池**********************************
见 《HttpClient使用详解》



*****************************Java线程池****************************************
总的:线程池,通过复用线程来提升性能;

背景:
线程是一个操作系统概念。操作系统负责这个线程的创建、挂起、运行、阻塞和终结操作。而操作系统创建线程、切换线程状态、终结线程都要进行CPU调度,这是一个耗费时间和系统资源的事情。

在如下场景下:
处理某一次请求的时间是非常短暂的,但是请求数量是巨大的。如果为每个请求都单独创建一个线程,那么物理机的所有资源基本上都被操作系统创建线程、切换线程状态、销毁线程这些操作所占用,用于业务请求处理的资源反而减少了。
另外,一些操作系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操作系统会变得不稳定。这也是我们要限制线程数量的原因。每个线程都需要一个内存栈,用于存储局部变量,操作栈等信息,通过-Xss参数来调整每个线程栈大小(64位系统默认1024kb,可根据实际需要调小,比如256KB)
通过调整该参数可以创建更多的线程,不过JVM不能无限制地创建线程

最理想的处理方式:将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理本身。
使用线程池:线程池会限制创建的线程数,从而保护系统;线程池配合队列工作,限制并发处理的任务数量,当任务超限时,通过一定的策略来处理,可避免系统因为大流量
而导致崩溃-只是部分拒绝服务,还是有一部分是正常服务的。
 
线程池分为:核心线程池和最大数量线程池,线程池中线程空闲一段时间会被回收,核心线程是不会被回收的。

合适的线程数:
(1)建议根据实际业务情况来压测决定
(2)利特尔法则:在一个稳定系统内,长时间观察到的平均用户数量L = 长时间观察到的有效达到率 * 平均每个用户在系统花费的时间。
针对(2)实际情况更复杂,如:在处理超时,网络抖动会导致线程花费时间不一样。

鉴于在处理超时,网络抖动会导致线程花费时间不一样,可能造成的线程数不合理,需要考虑:超时机制,线程隔离机制,快速失败机制等来保护系统

Java语言为我们提供了两种基础线程池的选择:ScheduledThreadPoolExecutor和ThreadPoolExecutor。它们都实现了ExecutorService接口
(注意,ExecutorService接口本身和“线程池”并没有直接关系,它的定义更接近“执行器”,而“使用线程管理的方式进行实现”只是其中的一种实现方式)。
这篇文章中,我们主要围绕ThreadPoolExecutor类进行讲解。


Java提供了ExecutorService三种实现
(1)ThreadPoolExecutor:标准线程池
(2)ScheduledThreadPoolExecutor:支持延迟任务的线程池
(3)ForkJoinPool:
类似于ThreadPoolExecutor,但是使用work-stealing模式,其会为线程池中的每个线程创建一个队列,从而用work-stealing(任务窃取)算法使得线程可以从其他
线程队列里窃取任务来执行。即如果自己的任务处理完成了,可以去忙碌的工作线程哪里窃取任务执行。


*****************************Tomcat线程池**************************************
见<<Tomcat实践>> 【2.1.5 连接池配置】

猜你喜欢

转载自zjjndnr.iteye.com/blog/2389319