前言
最近用了公司某框架,部署到现场后,现场运维开始维护现场数据,在不断操作的过程中,系统崩溃,查看后台日志,druid连接池已经获取不到连接。于是开始了排查之旅。在此记录。
排查开始
首先后台的报错是这样的。
exception=org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 10, maxActive 10
第一反应maxActive设置的数量太少了。于是改为100。重新启动,并再次操作大量数据。发现过了一段时间100个也满了。
此时问题不简单了。看来是有代码用了程序连接后,没有释放。
接下来开始确认原因到底是不是有没有释放。
在项目中使用的druid连接池。druid连接池是自带图形化监控工具的。于是开始在项目中配置,启动druid连接池。
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean srb =
new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//设置控制台管理用户
srb.addInitParameter("loginUsername","root");
srb.addInitParameter("loginPassword","root");
//是否可以重置数据
srb.addInitParameter("resetEnable","false");
return srb;
}
/**
* 注册FilterRegistrationBean
* @return
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则.
bean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
bean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return bean;
}
}
重新启动后,访问:localhost:8081/druid。这个路径有人大概会怀疑如果我是通过网关管理的微服务框架,需要通过网关转发访问吗?其实大可不必,通过网关也可以,也是跳转到直接访问的地址。
进入访问地址,跳转到如下的登录界面。
我们此时要点击数据源,去关注建立的逻辑连接数和关闭的逻辑连接数,关键的指标在这里,如果连接池的开启和关闭是正常的,那么二者的值应该是相等的。
再看看此时的活跃连接数
为0,此时正常,因为还没有进行操作。
接下来开始对之前的操作进行复现,锁定具体的操作。重复之前现场运维所做操作。
通过不断的点击功能,缩小功能范围,最终发现,只要点击左侧树,就会造成逻辑打开连接和逻辑关闭次数不一致。
活跃连接数也到了二者之差。等了几分钟,仍然是这个情况。那么实锤了 这里的代码有问题,连接应该没有释放。那么代码那么多,该如何发现具体代码的位置呢。
接下来配置druid的abandon策略。通过abandon可以强制回收数据库的连接。而活跃的连接被回收则会打印堆栈信息,这是就知道是哪里的sql代码没有释放了。
配置如下:
spring:
datasource:
druid:
remove-abandoned: true
remove-abandoned-timeout: 30
log-abandoned: true
重启项目,这个时候durid的监控活跃连接数的功能就可以看到代码信息。
我们点击如下位置:
就会弹出上图的堆栈信息。打马赛克的地方就是代码的详细位置,会标记出来。开发人员去响应的类找到相应代码查看即可。
经过查看代码发现,代码的连接释放存在问题。是自己封装的sql查询类。改为mybatis的写法后,问题解决。