您当前的位置: 首页 >  ui

qq_34412985

暂无认证

  • 0浏览

    0关注

    1061博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程

qq_34412985 发布时间:2020-08-01 16:19:41 ,浏览量:0

一、前言

        记录一次服务假死的整个排查过程,服务基础为spring boot + druid + 多数据源切换,在请求过多(尤其是长事务请求)时,服务出现请求无响应的状况,之前未完结的查询也没有任何返回结果。

二、定位问题原因

        问题出现时,表现如下图,后台无任何报错,sql语句戛然而止,后续的查询被中断。这时如果再次发起某个请求,后台服务处于大部分时间不能收到新的请求的状态,或者偶尔可收到请求但不会执行crud。经过一段时间后,日志输出了session校验的内容,此时我推测服务并不是真的宕机,而是处于假死状态。

   

    druid配置如下

        

        看了下配置文件,最大连接池数量设置为100,但重现问题的过程不需要特别多的请求,稳妥起见,将最大数量改为200,问题没有解决,初步推测不是因为请求达到了连接池上限。

        CPU和内存均无异常。

         期间还优化过业务逻辑,甚至将同步请求改为异步请求,都无济于事。一筹莫展之际,想起了druid自带的监控功能。打开监控页面,找到了一处令我怀疑的地方。数据源中的逻辑打开和逻辑关闭次数起初是一致的,随着查询次数增多,逻辑关闭次数小于逻辑打开次数,于是我怀疑数据库连接池出现了泄露的情况,根据URI监控中显示的情况,jdbc出错数刚好等于逻辑打开与逻辑关闭次数的差,也就是说,很有可能由于jdbc出错导致数据库连接池未正确关闭。

(图片为部分截图,下面还有个1没有截到) 

        按着这个思路,对测试部分代码进行了排查,无果。后来根据druid官方的文档,找到了下面这段话。

         

        从这段话中可以看出,判断是否是泄露应该在URI监控中,点击URI进入详情页面,查看打开和关闭的数量是否相等。于是我在逻辑连接打开和逻辑连接关闭次数有巨大差异的情况下,对每一个URI都进行了核查,所有URI详情中,连接池获取连接次数都是等于连接池关闭连接次数的,理论上证明数据库连接池并无泄露。

        前面图中druid的文档中还提到了removeAbandoned等三个属性用以检测数据库连接池泄露,于是我将这三个属性写在了yml里。

        一通华丽丽的操作下来,服务依旧被玩坏,然后打开了druid的监控,找到了数据源页面中的ActiveConnectionStackTrace。点开,没数据???控制台也没输出日志??? 网上找了一些文章,然后大胆的怀疑是不是druid的配置没生效?再看一下druid运行时的数据源,果不其然。初始化连接大小、最小空闲连接数、最大连接数、超时时间等等等等,除了数据库指向是生效的,其他配置使用的都是缺省值。所以我即使把最大数量从100改成了200,依然是没几个请求就爆炸了。

 三、解决方案

        下面开始着手解决配置没生效的问题。由于项目是多数据源切换,于是找到了这个配置文件,尝试着改造了一下,将yml中的配置set到了DruidDataSource对象中。

      

(下图仅展示了与本文有关的内容) 

 
  1. @Configuration

  2. public class DruidConfig {

  3.  
  4. @Value("${spring.datasource.druid.initial-size}")

  5. private int initialSize;

  6.  
  7. @Value("${spring.datasource.druid.min-idle}")

  8. private int minIdle;

  9.  
  10. @Value("${spring.datasource.druid.max-active}")

  11. private int maxActive;

  12.  
  13. @Value("${spring.datasource.druid.max-wait}")

  14. private int maxWait;

  15.  
  16. @Value("${spring.datasource.druid.pool-prepared-statements}")

  17. private boolean poolPreparedStatements;

  18.  
  19. @Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")

  20. private int maxPoolPreparedStatementPerConnectionSize;

  21.  
  22. @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")

  23. private int timeBetweenEvictionRunsMillis;

  24.  
  25. @Value("${spring.datasource.druid.min-evictable-idle-time-millis}")

  26. private int minEvictableIdleTimeMillis;

  27.  
  28. @Value("${spring.datasource.druid.test-while-idle}")

  29. private boolean testWhileIdle;

  30.  
  31. @Value("${spring.datasource.druid.test-on-borrow}")

  32. private boolean testOnBorrow;

  33.  
  34. @Value("${spring.datasource.druid.test-on-return}")

  35. private boolean testOnReturn;

  36.  
  37. @Value("${spring.datasource.druid.remove-abandoned}")

  38. private boolean removeAbandoned;

  39.  
  40. @Value("${spring.datasource.druid.remove-abandoned-timeout}")

  41. private int removeAbandonedTimeout;

  42.  
  43. @Value("${spring.datasource.druid.log-abandoned}")

  44. private boolean logAbandoned;

  45.  
  46. /**

  47. * 设置数据库连接池

  48. *

  49. * @author sunbin

  50. * @since 2020年4月21日

  51. * @version 2020年4月21日

  52. * @param dataSource

  53. */

  54. private void setDruidDataSource(DruidDataSource dataSource) {

  55. dataSource.setInitialSize(initialSize);

  56. dataSource.setMinIdle(minIdle);

  57. dataSource.setMaxActive(maxActive);

  58. dataSource.setMaxWait(maxWait);

  59. dataSource.setPoolPreparedStatements(poolPreparedStatements);

  60. dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);

  61. dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

  62. dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);

  63. dataSource.setTestWhileIdle(testWhileIdle);

  64. dataSource.setTestOnBorrow(testOnBorrow);

  65. dataSource.setTestOnReturn(testOnReturn);

  66. dataSource.setRemoveAbandoned(removeAbandoned);

  67. dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout);

  68. dataSource.setLogAbandoned(logAbandoned);

  69. }

  70.  
  71. /**

  72. * 默认数据源

  73. *

  74. * @author 89390

  75. * @since 2019年4月15日

  76. * @version 2019年4月15日

  77. * @return

  78. */

  79. @Bean

  80. @ConfigurationProperties("spring.datasource.druid.master")

  81. public DataSource masterDataSource() {

  82. DruidDataSource dataSource = DruidDataSourceBuilder.create().build();

  83. setDruidDataSource(dataSource);

  84. return dataSource;

  85. }

  86. }

        重启服务,查看druid monitor,与yml中配置的一致了,即证明此时配置已经生效,再次测试,服务假死问题解决。

四、分析 

        项目中,spring boot版本为2.0.3.RELEASE

        druid版本为1.1.10

        

        考虑到版本等问题,虽然我们在yml中配置了druid连接池的其它属性,但是不会生效。因为默认是使用的java.sql.Datasource的类来获取属性的,有些属性datasource没有。如果我们想让配置生效,需要手动创建Druid的配置文件。由于项目中多数据源的功能,已经有了Druid的配置文件,所以无需新建,适当修改即可。

五、参考 

https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B

https://www.cnblogs.com/SimpleWu/p/10049825.html

关注
打赏
1653291990
查看更多评论
立即登录/注册

微信扫码登录

0.0436s