上文说到 Druid德鲁伊参数调优实战,也正因此次优化,为后续问题埋下了伏笔
背景
2024/04/16日,业务反馈某个定时统计的数据未出来,大清早排查定位是其统计任务跑批失败,下面给一段伪代码
// 无事务执行
public void executor() {
List<String> allShop = new ArrayList<>();
List<Object> result = allShop.stream().map(shop -> {
// 订单数量统计
// 订单支付金额
// 订单退款
// 冲账支付
// 冲账退款
// todo 以上五个查询都是未指定事务的查询(默认连接为autocommit)
return new Object();
}).collect(Collectors.toList());
// 批量插入
mysql.batchInsert(result);
}
注:allShop大约几万个,这段代码表明了每次获取连接执行完SQL后都是将连接归还,然后下次再获取一个新的连接来执行
第一次失败异常
Cause: java.sql.SQLException: No operations allowed after statement closed. ;
No operations allowed after statement closed.; nested exception is java.sql.SQLException: No operations allowed after statement closed. at
看到这很清晰了,连接关闭后不允许再执行操作,突然回想前不久将德鲁伊中testOnBorrow 修改为false
到这真相大白,赶紧将testOnBorrow改回true,先将问题解决
打补丁发版再次重跑
修改后打补丁继续执行,又又又失败了…
失败异常
Cause: com.mysql.cj.jdbc.exceptions.MySQLTimeoutException:
Statement cancelled due to timeout or client request ;
Statement cancelled due to timeout or client request;
nested exception is com.mysql.cj.jdbc.exceptions.MySQLTimeoutException:
Statement cancelled due to timeout or client
心好累,心好累,执行超时,立马查看mysql连接 以及 德鲁伊连接池 的超时参数,果然…
因此业务是历史遗留的,本着解决问题的思路先将此参数修改大一下,赶紧搞定就下班回家洗洗睡了
最后贴一版最后德鲁伊连接池参数
# 初始连接数
initialSize: 5
# 最大连接池数量
maxActive: 20
# 最小连接池数量
minIdle: 10
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
use-unfair-lock: true
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
# 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
testWhileIdle: true
# 指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
testOnBorrow: true
# 指明是否在归还到池中前进行检验
testOnReturn: false
# 开启池的prepared statement 池功能
pool-prepared-statements: true
max-open-prepared-statements: 100
use-global-data-source-stat: true
# 对于长时间不使用的连接强制关闭
remove-abandoned: true
# 超过120分钟开始关闭空闲连接
remove-abandoned-timeout: 7200
# 将当前关闭动作记录到日志
log-abandoned: true
# 修改过后的查询超时时间120秒
query-timeout: 120
kill-when-socket-read-timeout: true
感想
其实在这段排查过程中,踩了好几个坑,也被好几个坑给阻碍排查的进度,下面列举下
1、一个跑批的定时任务居然没有重试机制,更何况没考虑任何事物问题(那部分事物提交后,后续没处理的数据如何继续处理?)
2、此任务作为一个统计任务一条日志都不留(所有日志都找不到此堆栈,后面走邮件清理掉跑成功的部分数据,然后重新单独跑此子任务才看清楚上面两个异常)
3、此任务是xxl-job一个子任务,当主任务成功时,子任务失败,在xxl-job看板上实际上是子任务异常信息展示
4、本项目数据库作为主从架构,跑批的查询选择走主库,这是一个统计上一日收益数据的任务完全没有必要查询主库(同样的SQL主从查询性能差距近40%-现场实测)