工单处理之--springboot自定义数据源使用注意事项之数据库连接池一定记得配置连接数

一、问题背景

近期现场小伙伴反馈卷宗桥接服务经常出现调用失败的情况,重启后可临时解决,但业务高峰期需频繁重启。问题复现期间观察后台日志,往往伴随着获取连接超时的系统错误,此时使用其他工具连接数据库是正常的。
org.springframework.dao.DataAccessResourceFailureException: Unable to acquire JDBC Connection; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.

二、排查过程

2.1、初步思路

刚开始看到这个错误的时候,直接告诉现场:报错很明显呀,去看看你们数据库是否能连接上,调整一下数据库参数试试(弱弱的认个错,上次有个工单就被我以排查数据库参数给申请关闭了,悔不当初呀!!!)。

2.2、二次试验

现在既然程序宕掉了数据库还是可正常使用的,这问题出在数据库的概率就不大了,怎么办呢?
赶巧了,前两天刚发现卷宗服务在业务高峰期也频繁出现获取连接超时的问题,而且也是问题期间确认数据库可正常使用,最终定位到卷宗服务数据库连接池未配置最大连接数,默认连接数是8(druid连接池),配置最大连接数为30后现象明显好转。
然后按照之前的方式增加了数据库最大连接数,很放心的告诉现场,再试试吧,这次应该没问题了(然而,这两个应用的连接池不是同一个,所以他俩的参数名也是不同的…错误信息里面已经明确提示了是HikariPool,我竟然没注意)。然而,运行一段时间后现场又复现了这个问题,不但现场要崩溃了,用户也开始有怨言了,怎么回事儿,老推送失败呢?

2.3、再次定位

想通过actuator组件确认配置是否生效(集成actuator组件后一定要记得配置management.endpoints.web.exposure.include,不然默认只能访问info和health,很多有用的接口不能用),然而该应用没有集成…想通过jstack看看数据库连接有多少,都在干嘛,然而应用进程PID是1,运行命令直接报错 Unable to get pid of LinuxThreads manager thread 不让使用。
后来通过数据库端查询当前连接信息(postgresql可以通过pg_stat_activity来查看当前连接信息)确认该应用的当前连接数是10,结论就是最大连接数依然没有生效。
难道是参数配置错了?看了下源码是多数据源,于是按照实际规则重新修改了连接数大小,发现依然不生效,唉???然后去谷歌了一通,简单看了下源码,找了好几个可能的配置(当前应用是Hikari连接池,与公司技术规范还不一致…),穷举了一下各种可能的配置,然而并没有什么卵用,服务器端连接数始终是10。

2.4、跟踪源码

既然偷懒不可取,那就老老实实扒代码吧

上图是连接池实例化的位置


使用自己的BeanUtils实例化了一个数据源实例


实例化的过程中也没有传参



默认构造函数也没有设置最大连接数属性(springboot 2.x自动装配的连接池用的不是默认构造函数,而是使用org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.Hikari构建的)


获取连接时会初始化连接池


会针对阈值参数进行特殊处理


逐层调用可以看到如果没设置最大连接数,会给个默认值(最大和最小连接数默认是-1,所以未设置时会走默认值)


默认连接数是10,跟数据库服务器查到的信息匹配上了
找了一遍源码没发现什么可疑点,反过来再继续看一下实例化的位置吧


自定义配置覆盖了默认的数据源配置前缀


在连接池的默认配置发现了可疑点,这个配置属性列表里面只有实例化数据源的基本属性,没有最大连接数属性(可以看到默认的数据源配置前缀是spring.datasource,所以非多数据源,没有人工干预数据源的实例化过程时,默认的参数是可以生效的)。


反过来看HikariDataSource,也没有这个属性


但他的父类是有这个属性的,而这个对象被spring管理,所以用spring的注解也没问题

2.5、根因定位

再回来看看注解使用

从这里看到只有firstDataSourceProperties@ConfigurationProperties注解了,而primaryDataSource方法没有使用对应注解,所以spring不会帮我们默认装配对应配置(我们自己管理数据源会导致默认数据源构建逻辑失效)

三、解决方案


给数据源构建方法也加上@ConfigurationProperties注解


并配置db1.datasource.maximumPoolSize参数值


可以看到配置生效了,问题搞定

四、写在最后

1、线上应用一定要合理配置线程池连接数大小,绝对不能用默认的(两三个人用的那种小应用除外),包括数据库连接池和线程池及http请求池等各种池,线上一定要记得修改他们的默认最大最小池大小
2、自定义数据源时一定要注意配置参数是否可被正常加载,否则在你需要修改对应配置时会是一件很头疼的事儿(尤其是配置多数据源时,往往会覆盖默认数据源构建逻辑)
3、springboot项目推荐集成actuator组件,线上排查问题真的很有用
4、打包docker镜像时请使用tini等方式启动容器,不然无法正常使用jstack等命令
5、Hikari数据库连接池支持在运行时使用HikariConfigMXBean实现配置热更新
6、想起来以后再写吧,欢迎拍砖

PS:看完帖子的客观请回去自查自己的应用是否有控制默认数据库连接池大小(记得同时设置最大值和最小值哦,否则始终保持长连接也是挺浪费资源的,当然需要结合实际场景分析,有的应用就适合最大值和最小值保持一致,但不推荐用默认值哦)

猜你喜欢

转载自blog.csdn.net/leandzgc/article/details/104872243