问题描述:

生产报错,“数据库操作异常”,日志错误信息如下:

com.MySQL.jdbc.CommunicationsException: The last packet successfully received from the server was58129 seconds ago.The last packet sent successfully to the server was 58129 seconds ago, which is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.

现场情况及观测现象:mysql端发现周五晚上因连接到了8小时,断开了一部分连接;

周六周日运行正常,无报错;

周一早上业务高峰期时发现大量上述报错;

应用配置了testWhileIdle具体配置是minIdle=125,testWhileIdle=true,validationQuery=SELECT 1,numTestsPerEvictionRun=10,minEvictableEvictionTimeMillis=180000,timeBetweenEvictionMillis=30000

初步分析:从报错信息上看,是因为数据库使用到了超过8小时没有使用过的连接导致,可明明配置了testWhileIdle,理论上不应该有问题,只能分析代源码了。

源码分析:

DBCP在初始化参数的时候就会启动一个检查线程public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {

_timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;

startEvictor(_timeBetweenEvictionRunsMillis);

}

protected synchronized void startEvictor(long delay) {

if(null != _evictor) {

EvictionTimer.cancel(_evictor);

_evictor = null;

}

if(delay > 0) {

_evictor = new Evictor();

EvictionTimer.schedule(_evictor, delay, delay);

}

}

public void run() {

try {

// 逐出逻辑(包含了validate逻辑)

evict();

} catch(Exception e) {

// ignored

} catch(OutOfMemoryError oome) {

// Log problem but give evictor thread a chance to continue in

// case error is recoverable

oome.printStackTrace(System.err);

}

try {

// 逐出之后,确保池子中连接数满足最小连接数

ensureMinIdle();

} catch(Exception e) {

// ignored

}

}

复制代码

每隔30秒执行一次,检查的逻辑是:public void evict() throws Exception {

assertOpen();

synchronized (this) {

if(_pool.isEmpty()) {

return;

}

if (null == _evictionCursor) {

// 为pool建立一个游标,从尾部到头遍历

_evictionCursor = _pool.cursor(_lifo ? _pool.size() : 0);

}

}

//每次检查numTestsPerEvictionRun个连接

for (int i=0,m=getNumTests();i

final ObjectTimestampPair pair;

synchronized (this) {

if ((_lifo && !_evictionCursor.hasPrevious()) ||

!_lifo && !_evictionCursor.hasNext()) {

// 当游标走到头部,重新从尾部循环遍历

_evictionCursor.close();

_evictionCursor = _pool.cursor(_lifo ? _pool.size() : 0);

}

pair = _lifo ?

_evictionCursor.previous() :

_evictionCursor.next();

//将连接取出

_evictionCursor.remove();

_numInternalProcessing++;

}

boolean removeObject = false;

final long idleTimeMilis = System.currentTimeMillis() - pair.tstamp;

//如果存活时间已经超过MinEvictableIdleTimeMillis则准备移除该链接

if ((getMinEvictableIdleTimeMillis() > 0) &&

(idleTimeMilis > getMinEvictableIdleTimeMillis())) {

removeObject = true;

} else if ((getSoftMinEvictableIdleTimeMillis() > 0) &&

(idleTimeMilis > getSoftMinEvictableIdleTimeMillis()) &&

((getNumIdle() + 1)> getMinIdle())) { // +1 accounts for object we are processing

removeObject = true;

}

// 若果开启testwhileidle并且没有到达minEvictableIdleTimeMillis,执行test逻辑

if(getTestWhileIdle() && !removeObject) {

boolean active = false;

try {

_factory.activateObject(pair.value);

active = true;

} catch(Exception e) {

removeObject=true;

}

if(active) {

// 执行validationQuery

if(!_factory.validateObject(pair.value)) {

removeObject=true;

} else {

try {

_factory.passivateObject(pair.value);

} catch(Exception e) {

removeObject=true;

}

}

}

}

if (removeObject) {

try {

_factory.destroyObject(pair.value);

} catch(Exception e) {

// ignored

}

}

synchronized (this) {

if(!removeObject) {

//对没有过期的连接放回队列

_evictionCursor.add(pair);

if (_lifo) {

// Skip over the element we just added back

_evictionCursor.previous();

}

}

_numInternalProcessing--;

}

}

allocate();

}

复制代码

OK,梳理一下思路dbcp会按照timeBetweenEvictionMillis启动一个调度线程,每隔timeBetweenEvictionMillis执行一次,每次检查numTestsPerEvictionRun个连接;

DBCP的数据库连接池底层为一个双向链表

当一个数据库链接执行过语句,数据库端会重置连接时间,即正常情况下一个连接如果被执行SELECT 1后,数据库的8小时计时会重置

检查的逻辑基于一个游标从后向前遍历,且会循环遍历

对超过minEvictableEvictionTimeMillis的连接执行remove,其他的执行validateQuery

dbcp获取连接时是从前往后获取的

感觉....似乎...没毛病啊,只能自己开脑洞想了

开脑洞

脑洞1:

pool双向链表断了?因为获取连接是从前往后遍历的,但是检查时从后往前的,假设某个节点的前指针指的出问题了,完全可能用到遍历不到的节点。

合情合理,但没有证据首先从代码入口,我们认为双向链表本身的逻辑是不可能有问题的,多半是因为高并发情况下没有锁好,导致的问题,但是经过排查发现代码中对链表进行操作的地方,都加了锁;

测试环境无意中复现出了这个现象,即从mysql端看到有一些链接长期没有刷新Timeout时间,于是我们dump了当时的堆,找到pool对象,并对其进行人工遍历,发现竟然是对的上的,从前到后从后到前遍历的是一样的。

脑洞1不合情不合理。

脑洞2

有些链接跑到了链表外,导致这些链接没有被遍历到,可是周一早上确实又使用到了这个连接,所以这个脑洞不成立。

脑洞3

会不会因为高并发,导致每次执行_evictionCursor.hasPrevious()都有新节点(因为连接归还和新建都是加到头部的),嗯,有可能,合情合理。但是这也太巧了,而且我们是有交易高峰期和低谷的,大半夜的没那么大并发才对,总归可以走到头的。

不过脑洞3也给我们提供了一个思路,我们一直以为游标是可以走到头的,但是会不会是因为什么情况,游标走不到头,所以一些链接至遍历了一次呢。

找到问题

借着上面的思路,我们发现了真正的问题所在,文字不太好表达,画个图各位自己看吧。

问题解决

找到问题原因,解决就简单了,将minEvictableEvictionTimeMillis调大一些就好了。

java testwhileidle_DBCP踩坑(二):连接池检查testWhileIdle失效相关推荐

  1. 【Java笔记+踩坑】SpringBoot基础3——开发。热部署+配置高级+整合NoSQL/缓存/任务/邮件/监控

      导航: [黑马Java笔记+踩坑汇总]JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城 目录 ...

  2. 【Java笔记+踩坑】SpringBoot基础2——运维实用

      导航: [黑马Java笔记+踩坑汇总]JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城 目录 ...

  3. 【Java笔记+踩坑】SpringBoot——基础

      导航: [黑马Java笔记+踩坑汇总]JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城 目录 ...

  4. java beanlisthandler_Java篇-DBUtils与连接池

    一 : DBUtils DBUtils就是为了简化JDBC的快发而产生的开发工具包.对JDBC的一种封装. 核心功能 1. QueryRunner 中提供对sql语句操作的API update(Con ...

  5. C++踩坑之连接mysql数据库

    C++踩坑之连接mysql数据库 实现的效果 https://www.cr173.com/soft/105990.html 下载connect C++ 安装软件 安装到D盘,安装32位即可 64位不要 ...

  6. 西安交通大学915考研--编程题Java代码踩坑(2020年真题)

    西安交通大学915考研–编程题Java代码踩坑(2020年真题) 目录 西安交通大学915考研--编程题Java代码踩坑(2020年真题) 2020.1--寻找方程组的解 2020.2--几组数中筛选 ...

  7. 【黑马Java笔记+踩坑】JavaWeb基础——JDBC

    目录 JDBC JDBC概述 JDBC概念 JDBC本质 JDBC好处 JDBC步骤 标准代码 编写代码步骤 获取数据库连接的各种方式 JDBC所有API 驱动管理类 DriverManager 数据 ...

  8. JAVA WEB DAY 11_ JDBC 连接池

    文章目录 JDBC & 连接池 目标 01_ JDBC 概述-[★★] 02_ JDBC 核心 API 概述-[★★] 03_ JDBC 之注册驱动-[★★★] 04_ JDBC 之获取连接对 ...

  9. java spring druid_Spring配置Druid连接池

    最近项目用c3p0数据连接池有问题,因此换成了druid连接池,它的优点是可以很好的监控DB池连接和SQL的执行情况.在此做个记录便于下次使用. 1.首先导入Spring(网上很多这里我就不列举了)和 ...

最新文章

  1. 27.3. source code
  2. GNU make 和 makefile
  3. why-use-getters-and-setters
  4. vim设置显示行号,vim跳转到文件头,文件尾
  5. 电子信息工程班徽设计_蜻蜓AI说专业:与5G时代息息相关的电子信息工程专业怎么样?...
  6. Please install [clang](http://clang.llvm.org/) or check configuration `clang.executable`
  7. 【Spring】Spring高级话题-@Enable***注解的工作原理
  8. 计算机检索技术与技巧的检索式为,第四章计算机检索技术和数据库检索方式.ppt...
  9. android学习笔记---使用AsyncTask实现异步处理,内部使用线程加Handler
  10. mysql数据库中如何创建角色_MySQL数据库如何创建用户呢?
  11. 基于c语言中调试工具的用法汇总(不包含gdb)【转】
  12. 剑指Offer 09 用两个栈实现队列
  13. 勒索老黄未果!黑客公布英伟达核心源代码,超40万个文件、75GB机密数据
  14. c#无标题窗口的拖动
  15. 机房建设整体设计方案
  16. 嵌入式系统开发-麦子学院(10)——arm汇编基础
  17. 微信支付(java版本)
  18. matlab中二维散点图,MATLAB实例:二维散点图
  19. 互联网春节红包的寓言:奇迹如斯,赢家寥寥
  20. 如何建立一个网站?规划、设计、目的、原则、宣传(一)

热门文章

  1. openssl升级_CVE-2020-1967:OpenSSL拒绝服务漏洞警报
  2. 如何在浏览器里开发并运行 SAP UI5 应用
  3. 在 SAP BTP 平台 Neo 环境里使用 SAP Cloud SDK 创建应用
  4. 网友提问:关于CX_VSI_SYSTEM_ERROR异常,Fiori病毒扫描参数文件
  5. SAP Spartacus category navigation按钮的差异
  6. Angular应用的index.html
  7. SAP WebClient UI的会话重启原理
  8. SAP UI5 ABAP repository的handler class
  9. why unit test of user status failed
  10. role cache - set data user parameter - /UI2/CACHE_DISABLE