点击上方 IT牧场 ,选择 置顶或者星标
技术干货每日送达
前言
最近利用 MHA 做好 Mysql 读写分离后,时不时有用户反馈后台发布文章时,报程序“通用异常",经问题排查,里面涉及应用JDBC连接池参数及Mysql参数调整问题。
问题回顾
异常日志描述:

从异常信息反映来看,问题关键有两点
数据库连接池超时设置大于wait_timeout
日志提示,可以通过验证数据库连接或者设置:autoReconnect=true 来避免此异常
从以上两点可以推测
问题定位
wait_timeout参数说明:
wait_timeout具体含义是服务器关闭非交互连接之前等待活动秒数。MySQL缺省配置情况下,wait_timeout的初始值是28800秒,也就是8小时。如果wait_timeout超时时间设置过大,在MySQL管理系统里会产生大量的SLEEP进程无法及时释放,会导致服务器系统性能下降;同时该参数设置过小,会导致Mysql处理某些事务未处理,连接不可用状态。
也就是说如果在wait_timeout设置期间内,数据库连接Connection一直处于空闲等待状态,mysql内部会自动关闭此连接,而应用程序无法感知到,依然认为连接池合法持有该连接。当应用端再次用该连接来进行数据库操作时,就产生上述异常错误。
应用端Druid数据库连接池参数设置代码如下:

发现连接池有个MaxWait参数设置过大:60000毫秒
druidDataSource.setMaxWait(60000)
然后在CSDN上,发现有个同行碰到同样的问题:
发现数据库等待超时时间(wait_timeout)是28800,也就是8小时,而应用程连接池参数max-wait: 30000,所以导致项目判定该链接可用,而mysql判定该连接不可用导致连接失败。
解决办法
根据上面的分析思路,我们排查了Mysql生产库,发现默认Mysql超时时间(wait_timeout)也是28800,但是应用层连接池MaxWait参数设置成60000,于是我把MaxWait参数设置成10000,小于Mysql超时时间(wait_timeout):28800 ,在测试环境等待8小时后,报错消失了。
其他扩展思路(来源网络):
思路一:在jdbc-url后添加 &autoReconnect=true,使用后无效,查的该方案只适用于Mysql4之前的版本有效
思路二:将mysql回收空闲连接的时间变长,mysql默认回收时间是8小时,可以在mysql
目录下的my.ini中增加下面配置,将时间改为1天。单位是秒,最大好像是24天。此配置会拖累数据库性能,弃用该方案。
思路三:配置druid链接池,使用 validation-query test-on-borrow: true test-while-idle: true 三种属性,每次获取数据库连接时判断该连接是否可用。同时设置druidDataSource.setPhyTimeoutMillis 参数:连接最大存活时间,默认是-1(不限制物理连接时间),从创建连接开始计算,如果超过该时间,则会被清理。
druidDataSource.setPhyTimeoutMillis(15000)
参考例子
目前项目中趋于稳定的数据库连接池参数优化实战,Druid 代码如下:
private void configDruidParams(DruidDataSource druidDataSource) { druidDataSource.setMaxActive(20); druidDataSource.setInitialSize(1); druidDataSource.setMaxWait(10000); druidDataSource.setMinIdle(1); druidDataSource.setTimeBetweenEvictionRunsMillis(60000); druidDataSource.setMinEvictableIdleTimeMillis(30000); druidDataSource.setMaxEvictableIdleTimeMillis(180000); druidDataSource.setPhyTimeoutMillis(15000); druidDataSource.setValidationQuery("select 1"); druidDataSource.setTestWhileIdle(true); druidDataSource.setTestOnBorrow(false); druidDataSource.setTestOnReturn(false); druidDataSource.setPoolPreparedStatements(true); druidDataSource.setMaxOpenPreparedStatements(20); druidDataSource.setUseGlobalDataSourceStat(true); druidDataSource.setKeepAlive(true); druidDataSource.setRemoveAbandoned(true); druidDataSource.setRemoveAbandonedTimeout(180); try { druidDataSource.setFilters("stat,slf4j"); List filterList = new ArrayList<>(); filterList.add(wallFilter()); druidDataSource.setProxyFilters(filterList); } catch (SQLException e) { e.printStackTrace(); } }
干货分享
最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!
•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术群讨论
近期热文
•LinkedBlockingQueue vs ConcurrentLinkedQueue•解读Java 8 中为并发而生的 ConcurrentHashMap•Redis性能监控指标汇总•最全的DevOps工具集合,再也不怕选型了!•微服务架构下,解决数据库跨库查询的一些思路•聊聊大厂面试官必问的 MySQL 锁机制
关注我

喜欢就点个"在看"呗^_^