Druid 数据库连接池高并发时大量线程等待 TIMED_WAITING 问题处理

druid数据库连接池 application.yml 最大连接池数量max-active 最大等待时间max-wait 高并发时大量线程等待TIMED_WAITING。

一、问题

在日常开发中,我们经常面临压力测试。这时会发现程序在一定并发数量下突然卡住。此时需要进行排查。

1.获取现场故障信息

jstack 6688

参数为PID,这时我们看堆栈信息,发现没有自己程序相关的类,但发现下面这样的句子:

"Druid-ConnectionPool-Create-418179060" #25 daemon prio=5 os_prio=0 tid=0x00007fa74d8ad000 nid=0x15a84 waiting on condition [0x00007fa6f8a30000]
java.lang.Thread.State: WAITING (parking)

at sun.misc.Unsafe.park(Native Method)

- parking to wait for <0x00000000c47d0990> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)

at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)

at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)

at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2480)

哦,原来是Druid在等待导致线程卡住,那怎么办呢?

2.根据打印信息检索解决方案

开始使用必应检索了一下,发现这篇讨论:线程卡死在druiddatasource上

3.根据检索到的信息做出尝试

既然讨论中提到可能和版本有关就检查了自己使用的版本,发现版本很新,出问题的可能很小。那么就调大最大活跃数进行试验,默认max-active为20,我们调为 100, 发现在调大最大活跃数之后,使用top命令查询,果然,cpu不再占用率很低,cpu开始开心地跑起来了。通过了这个并发数量的压力测试。

二、配置

spring:
  datasource:

    druid:
      initial-size: 5 #初始化时建立物理连接的个数
      min-idle: 5 #最小连接池数量
      max-active: 100 #最大连接池数量,like12 find bug,此值设得太小(如20) 高并发时会导致大量请求因等待连接池连接超时而处理失败(max-wait调小时) 推荐设置: 普通20 高并发100 最大200
      max-wait: 5000 #获取连接时最大等待时间,单位毫秒,like12 find bug,此值不能设得太大(如60000),高并发时会导致系统卡死(大量线程一直挂起等待) 推荐设置: 普通60000 高并发5000
      time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      min-evictable-idle-time-millis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
      #Oracle模式
      #validation-query: SELECT 1 FROM DUAL #用来检测连接是否有效的sql
      #MySQL模式
      validation-queryM: SELECT 1 #用来检测连接是否有效的sql
      test-while-idle: true #申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
      test-on-borrow: false #申请连接时执行validationQuery检测连接是否有效,如果为true会降低性能
      test-on-return: false #归还连接时执行validationQuery检测连接是否有效,如果为true会降低性能
      pool-prepared-statements: true #打开PSCache,并且指定每个连接上PSCache的大小
      max-pool-prepared-statement-per-connection-size: 20  #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
      #like12 modified,jpa拦截dbms_random wall会拦截dbms_random,使得无法随机排序
      #filters: stat,wall,slf4j #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,slf4j #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      #通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      #配置DruidStatFilter,用于采集web-jdbc关联监控的数据
      web-stat-filter:
        enabled: true
        url-pattern: "/*"
        #排除一些不必要的url过滤
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      #配置DruidStatViewServlet
      stat-view-servlet:
        url-pattern: "/druid/*"
        #IP白名单(没有配置或者为空,则允许所有访问)
        #allow: 127.0.0.1,192.168.1.106
        #IP黑名单(存在共同时,deny优先于allow)
        #deny: 192.168.1.73
        #禁用HTML页面上的“Reset All”功能
        reset-enable: fasle
        #可视化界面,登录名
        login-username: ***
        #可视化界面,登录密码
        login-password: ***

另外的参考:
以下为经过压测验证后的我们服务的配置,供参考:

// 初始连接数
dataSource.setInitialSize(10);
// 最小连接池数量
dataSource.setMinIdle(10);
// 最大连接池数量
dataSource.setMaxActive(100);
// 配置获取连接等待超时时间  毫秒
dataSource.setMaxWait(100);
//缓存通过以下两个方法发起的SQL:
dataSource.setPoolPreparedStatements(true);
//每个连接最多缓存多少个SQL
dataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
//检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查
dataSource.setTimeBetweenEvictionRunsMillis(-1);
//池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将回收该连接,要小于防火墙超时设置
dataSource.setMinEvictableIdleTimeMillis(300000);
//当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效)
dataSource.setTestWhileIdle(true);
// 程序 申请 连接时,进行连接有效性检查(低效,影响性能)
dataSource.setTestOnBorrow(false);
//程序 返还 连接时,进行连接有效性检查(低效,影响性能)
dataSource.setTestOnReturn(false);
// 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。
dataSource.setRemoveAbandoned(true);
// 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此值后,druid将强制回收该连接,单位秒。
// 结合业务来看,存在jpa极大事务;不好设置 暂时为设置两分钟
dataSource.setRemoveAbandonedTimeout(120);
//当druid强制回收连接后,是否将stack trace 记录到日志中
dataSource.setLogAbandoned(true);

相关文章:
druid数据库连接池 application.yml 最大连接池数量max-active 最大等待时间max-wait 高并发时大量线程等待TIMED_WAITING
踩坑DruidDataSource导致的服务卡死

为者常成,行者常至