一、关于池化
提高系统性能方式之一是合理的增加并发数,单位时间内处理更多的任务。但是频繁的创建线程或者新建数据库链接非常的消耗性能,这时候可以通过池化技术,更合理的管理数据库链接、线程等资源。
1、数据库连接池
数据库连接池有两个最重要的配置:最小连接数和最大连接数,它们控制着从连接池中获取连接的流程:
- 如果当前连接数小于最小连接数,则创建新的连接处理数据库请求;
- 如果连接池中有空闲连接则复用空闲连接;
- 如果空闲池中没有连接并且当前连接数小于最大连接数,则创建新的连接处理请求;
- 如果当前连接数已经大于等于最大连接数,则按照配置中设定的时间(C3P0 的连接池配置是 checkoutTimeout)等待旧的连接可用;
- 如果等待超过了这个设定时间则向用户抛出错误。
2、链接池中链接可用性
- 启动一个线程来定期检测连接池中的连接是否可用,比如使用连接发送“select 1”的命令给数据库看是否会抛出异常,如果抛出异常则将这个连接从连接池中移除,并且尝试关闭。目前 C3P0 连接池可以采用这种方式来检测连接是否可用,也是我比较推荐的方式。
- 在获取到连接之后,先校验连接是否可用,如果可用才会执行 SQL 语句。比如 DBCP 连接池的 testOnBorrow 配置项,就是控制是否开启这个验证。这种方式在获取连接时会引入多余的开销,在线上系统中还是尽量不要开启,在测试服务上可以使用。
3、异步线程池
有很多方式异步执行任务,可以手动创建线程,或者自行管理线程池,spring中更简单,只需要添加注解@Async即可使得方法异步执行
JDK 1.5 中引入的 ThreadPoolExecutor 就是一种线程池的实现,它有两个重要的参数:coreThreadCount 和 maxThreadCount,这两个参数控制着线程池的执行过程。
- 如果线程池中的线程数少于 coreThreadCount 时,处理新的任务时会创建新的线程;
- 如果线程数大于 coreThreadCount 则把任务丢到一个队列里面,由当前空闲的线程执行;
- 当队列中的任务堆积满了的时候,则继续创建线程,直到达到 maxThreadCount;
- 当线程数达到 maxTheadCount 时还有新的任务提交,那么我们就不得不将它们丢弃了。
这里有个坑,当线程池中线程数达到coreThreadCount时,任务会被丢到队列中等待。队列默认容量是int最大值,如果不修改则意味着有可能堆积大量的任务,但不会创建新的线程。如果设置了合适的等待队列容量,当等待队列填满时,会创建新的线程,但是默认的maxThreadCount是int最大值,在流量高峰期会大量创建线程,最终导致OOM。
jdk原生线程池的机制,适合CPU密集型的任务,只要设置coreThreadCount和CUP核心数相同就行,超过数量的任务等待CPU空闲下来在执行。超过CPU核心数的线程反而会因为上下文切换造成资源浪费,反而效率会下降。普通WEB项目大多是IO密集型系统,线程池中线程数达到coreThreadCount时,最好是优先创建线程,才能更好的提高效率。
4、池化总结
总的来说池化是一种用空间换时间的技术。对于池化技术的使用需要注意一下几点:
- 池子的最大值和最小值设置很重要,要根据项目情况设置合适的值,才能发挥最大的作用
- 池子需要预热,在系统启动时就进行初始化,虽然会导致系统启动时间变长,但是瑕不掩瑜。否则会导致系统启动后有些慢请求
- 因为是通过空间换取的时间,所以要更加关注空间的使用,避免出现OOM,或者频繁Full GC