资源中心——连接池调优

1、背景

前一段时间观察了一下资源中心CPU的利用率,入下图

CPU峰值利用率在10%左右,有点资源浪费,所以进行了缩容。在节省了30%的硬件资源之后,服务大部分指标正常,但是超时量有点增长,有原来的每天50以内,变到了如今的250以内。所以来看一波小小的优化。

首先对比下缩容前后的变化

  • 硬件资源减少
  • RPC工作线程减少
  • DB链接数减少

先看硬件资源缩容之后的监控如下:

最高值CPU40%以上,均值最大在20%左右,利用率并不是特别高。然后观察网卡、内存硬盘等参数均没有达到影响性能的程度。

2、方案

我们从剩下的两点入手:

1、增加RPC的工作队列数,说实话起到的效果并不大。

2、增加连接池的连接数。经过这两条观察超时量在40以内,效果更胜从前。

3、DBCP2原理

连接池本质上的原理和线程池的大体原理类似。我们用的是DPCP2,具体原理如下图

核心是空闲队列。连接池所有的核心工作都是围绕这个队列进行出队和入队的。整体上分为获取连接、归还连接、以及连接异步定时检测三大模块。

1)获取连接,从空闲连接池中拿连接。获取连接时会有对连接的有效性检查以及连接泄漏检查。这个两个环节都是可配置的对应的参数为:testOnBorrow,removeAbandonedOnBorrow。testOnBorrow(连接有效性检查)不建议开启的原因:有效性验证需要需要执行你的验证SQL,会损耗性能,并且testWhileIdle开启定期异步检查就可以了。其次当从连接池中获取不到连接时并且当前连接数小于最大连接数时(maxTotal),会自动创建一个连接;如果超过最大连接数会进行等待,如果超过了设置的等待的超时时间(maxWaitMillis)则抛出异常。这里注意的点是:只有大于最大空闲连接数时才会等待,空闲队列为空时不会等待。

2)连接归还。如果配置了testOnReturn,则在归还连接时进行有效性检查,同样不建议开启,浪费性能。其次如果空闲连接超过最大空闲连接数(maxIdle),则会销毁改连接,同步进行真正的关闭连接。所以最大连接数的控制是由获取连接和归还连接两个操作来控制

3) 连接异步定时检测  。timeBetweenEvictionRunsMillis只有配置了这个参数才会进行定时检测。

  • 空闲时间检测,检查连接是否超过最小空闲时间超过则需要清理。需要配置:minEvictableIdleTimeMillis(连接最小空闲时间)。除此之外还建议配置:numTestsPerEvictionRun,每次检查连接个数,不配置的话就是所有空闲连接,DBCP2检测空闲连接的做法是将要检测的连接设置为检测状态,避免与获取连接的线程冲突,所以如果是所有连接的话,那么在有一段时间内连接池将没有连接可以用,将会不起效果,所以numTestsPerEvictionRun不宜配置过大。
  • 最小连接数(minIdle)的维护。和空闲连接数检查是在一个线程中维护,需要配置softMinEvictableIdleTimeMillis,才会在连接超过最小连接数时,回收最小链接之外的连接。这个可以比minEvictableIdleTimeMillis小一些,这样超过最小连接数的连接回收的会快一些。
  • 有效性检查,需要配置testWhileIdle。如果配置了validationQuery,在连接有效性检查的时候会执行该SQL,否则会用PING来检查连接的有效性。
  • 连接泄漏检测。当我们从连接池获得了连接对象,但因为疏忽或其他原因没有close,这个时候这个连接对象就是一个泄露资源。通过配置以下参数可以回收这部分对象。removeAbandonedOnBorrow=true在每次从连接池中获取连接时尽心检查。该参数为true并不是每次都会检查需要:this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3,也就是说空闲连接数特别少,活跃连接数特别多并且接近最大连接数这就说明这时候有部分连接被持有并没有释放。removeAbandonedTimeout该参数是连接被使用久算超时,默认是300s。removeAbandonedOnMaintenance配置是否在定时任务中进行检测。以上说的几个检测都是在同一个线程中进行。

4、核心参数设置方式

1)队列策略选择:用户默认的就好,先进后出队列(栈)。正常连接从对头获取和归还。异步检测任务从队尾处理,减少两者冲突。

2)maxTotal在DB允许的情况下尽可能的大。可以保证突增流量,有充足的连接可以处理请求。maxIdle,minIdle,initialSize这三个值建议设成一个值。保证核心连接数的稳定,减少在使用连接时创建连接的频率。具体怎么设置呢?由于DBCP2没有对应的监控,我写了一下如下的简单的监控。

@Component
public class PoolMonitor {

private static final Logger logger = LoggerFactory.getLogger(PoolMonitor.class);

@Autowired
Map<String, BasicDataSource> pools;

@PostConstruct
public void init() {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
monitoring();
}, 0, 1L, TimeUnit.SECONDS);
}

public void monitoring() {
Set<Map.Entry<String, BasicDataSource>> poolEntries = pools.entrySet();
for (Map.Entry<String, BasicDataSource> poolEntry : poolEntries) {
BasicDataSource dataSource = poolEntry.getValue();
int maxTotal = dataSource.getMaxTotal();
int numActive = dataSource.getNumActive();
int maxIdle = dataSource.getMaxIdle();
int numIdle = dataSource.getNumIdle();
logger.info("basic datasource pools monitoring:name {} maxTotal {} numActive {} maxIdle {} numIdle {}",
poolEntry.getKey(), maxTotal, numActive, maxIdle, numIdle);
}
}
}

maxIdle,minIdle,initialSize这个值的设置要保证:活跃连接数+当前空闲连接数=最小连接数(minIdle)就行。这样就不会产生正常请求获取连接时连接不够从而创建连接的现象了。

3)numTestsPerEvictionRun这个值该怎么设置呢?numTestsPerEvictionRun,不要太大,最小值是采用如下的设置策略就行。mysql默认维护空闲连接的最大时间是8小时,超过这个时间就会断开。所以安全的配置策略是:minEvictableIdleTimeMillis+timeBetweenEvictionRunsMillis +maxTotal(最大空闲连接数)/numTestsPerEvictionRun*timeBetweenEvictionRunsMillis<8*3600*1000。这样设置能保证最坏的场景下都可检测到每一个连接在8小时内都被检测过。最坏的场景是:空闲连接数一下子达到了最大值,并且再也没有被方位过,最小连接数维护的配置没有开启。

注意:  initialSize  连接池初始化的时候并不会创建连接,在第一次与DB交互的时候才会初始化连接。如介意服务启动时请求较慢的服务的话,需要做好预热,很简单给每个库调用一次数据库查询就行。

5、我们的设置

#连接最大总数
jdbc.maxTotal=200
#最大空闲连接数
jdbc.maxIdle=30
#最小空闲连接数 类似于线程池中的coreSize
jdbc.minIdle=30
#初始化连接数,在第一次getConnection中进行创建连接
jdbc.initialSize=30 #从连接池中获取不到连接并且当前连接数超过最大空闲连接数时,等待对应的秒数后,再次从连接池中获取连接,再次获取不到则抛异常。默认无限等待。 jdbc.maxWaitMillis=500 #异步定时检测连接的有效性
jdbc.testWhileIdle=true#连接池检测time的定期执行时间
jdbc.timeBetweenEvictionRunsMillis=600000
#每次检测几个空闲连接,不能太大,否则会影响从连接池中取连接
jdbc.numTestsPerEvictionRun=10

#空闲连接超过最小连接数时,超过最小连接数这部分连接的检测连接的超时时间
jdbc.softMinEvictableIdleTimeMillis=300000

#连接最小空闲时间,最大连接空闲时间才会清理无用连接,不会根据minIdle进行清理,如果请求量大的话可能空闲连接会超过20个jdbc.minEvictableIdleTimeMillis=1800000

#开启连接泄漏检测,每次从从连接池中取连接时检测连接是否为泄漏连接,进行检测的条件是:(getNumIdle() < 2) and (getNumActive() > (getMaxActive() - 3))空闲连接较小或者活跃连接数时才会检测
jdbc.removeAbandonedOnBorrow=true
#在空闲连接回收器中进行检测
jdbc.removeAbandonedOnMaintenance=true
#连接泄漏中,判定连接为泄漏连接的时长
jdbc.removeAbandonedTimeout=20
#每次从连接池中去连接时是否进行有效性检测
jdbc.testOnBorrow=false

 

 参考链接 

https://www.cnblogs.com/ZhangZiSheng001/p/12003922.html

https://zhuanlan.zhihu.com/p/383694802

posted @ 2021-09-25 18:14  于林富  阅读(571)  评论(0编辑  收藏  举报