关于池化

330 阅读4分钟

一、关于池化

提高系统性能方式之一是合理的增加并发数,单位时间内处理更多的任务。但是频繁的创建线程或者新建数据库链接非常的消耗性能,这时候可以通过池化技术,更合理的管理数据库链接、线程等资源。

1、数据库连接池

数据库连接池有两个最重要的配置:最小连接数和最大连接数,它们控制着从连接池中获取连接的流程

  • 如果当前连接数小于最小连接数,则创建新的连接处理数据库请求;
  • 如果连接池中有空闲连接则复用空闲连接;
  • 如果空闲池中没有连接并且当前连接数小于最大连接数,则创建新的连接处理请求;
  • 如果当前连接数已经大于等于最大连接数,则按照配置中设定的时间(C3P0 的连接池配置是 checkoutTimeout)等待旧的连接可用;
  • 如果等待超过了这个设定时间则向用户抛出错误。

2、链接池中链接可用性

  1. 启动一个线程来定期检测连接池中的连接是否可用,比如使用连接发送“select 1”的命令给数据库看是否会抛出异常,如果抛出异常则将这个连接从连接池中移除,并且尝试关闭。目前 C3P0 连接池可以采用这种方式来检测连接是否可用,也是我比较推荐的方式。
  2. 在获取到连接之后,先校验连接是否可用,如果可用才会执行 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