问题背景

在项目启动时出现大量

c.a.d.pool.DruidAbstractDataSource: discard long time none received connection.
明显是Druid管理的数据库连接因为太长时间没有收到数据库发来的数据,把连接给回收掉了,这导致服务在启动时因为要重复创建连接让服务启动时间延长。

定位原因

根据错误信息,找到Druid源码
com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal(com.alibaba.druid.pool.DruidConnectionHolder, java.sql.Connection)

if (validConnectionChecker != null) {// 验证连接的有效性 mysql下实际调用代码在下面那块boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);long currentTimeMillis = System.currentTimeMillis();if (holder != null) {holder.lastValidTimeMillis = currentTimeMillis;holder.lastExecTimeMillis = currentTimeMillis;}if (valid && isMySql) { // unexcepted branchlong lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);if (lastPacketReceivedTimeMs > 0) {long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;if (lastPacketReceivedTimeMs > 0 //&& mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {discardConnection(holder);// 警告信息位置String errorMsg = "discard long time none received connection. "+ ", jdbcUrl : " + jdbcUrl+ ", version : " + VERSION.getVersionNumber()+ ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;LOG.warn(errorMsg);return false;}}}// ... 省略
}// com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker#isValidConnectionpublic boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {if (conn.isClosed()) {return false;}if (usePingMethod) {// 以ping的方式检测连接的有效性if (conn instanceof DruidPooledConnection) {conn = ((DruidPooledConnection) conn).getConnection();}if (conn instanceof ConnectionProxy) {conn = ((ConnectionProxy) conn).getRawObject();}if (clazz.isAssignableFrom(conn.getClass())) {if (validationQueryTimeout <= 0) {validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;}try {ping.invoke(conn, true, validationQueryTimeout * 1000);} catch (InvocationTargetException e) {Throwable cause = e.getCause();if (cause instanceof SQLException) {throw (SQLException) cause;}throw e;}return true;}}String query = validateQuery;if (validateQuery == null || validateQuery.isEmpty()) {// 以 sql SELECT 1 的方式验证连接有效性query = DEFAULT_VALIDATION_QUERY;}Statement stmt = null;ResultSet rs = null;try {stmt = conn.createStatement();if (validationQueryTimeout > 0) {stmt.setQueryTimeout(validationQueryTimeout);}rs = stmt.executeQuery(query);return true;} finally {JdbcUtils.close(rs);JdbcUtils.close(stmt);}}
}

这是调用 testConnectionInternal方法的上层.

可以看到,因为我们开启了testOnBorrow 开关,所以数据库连接会在申请成功后,立即进行一次测试,然后根据数据库连接的最后一次心跳时间,判断是否闲置过长要丢弃掉该数据库连接。
该开关主要在从连接池获取时立即检查连接的有效性。
而不开启testOnBorrow则会在保持连接过程中不断检查连接的闲置情况,对闲置过长的连接回收。

com.alibaba.druid.util.MySqlUtils#getLastPacketReceivedTimeMs 这个方法会返回连接最后一次收到消息的时间.

// 以mysql6的 com.mysql.cj.jdbc.ConnectionImpl 为栗子
// getLastPacketReceivedTimeMs 方法中获取链接时间的实际方法
public long getIdleFor() {return this.lastQueryFinishedTime == 0 ? 0 : System.currentTimeMillis() - this.lastQueryFinishedTime;}// com.mysql.cj.NativeSession#execSQLpublic <T extends Resultset> T execSQL(Query callingQuery, String query, int maxRows, NativePacketPayload packet, boolean streamResults,ProtocolEntityFactory<T, NativePacketPayload> resultSetFactory, ColumnDefinition cachedMetadata, boolean isBatch) {long queryStartTime = this.gatherPerfMetrics.getValue() ? System.currentTimeMillis() : 0;int endOfQueryPacketPosition = packet != null ? packet.getPosition() : 0;this.lastQueryFinishedTime = 0; // we're busy!if (this.autoReconnect.getValue() && (getServerSession().isAutoCommit() || this.autoReconnectForPools.getValue()) && this.needsPing && !isBatch) {try {ping(false, 0);this.needsPing = false;} catch (Exception Ex) {invokeReconnectListeners();}}try {return packet == null? ((NativeProtocol) this.protocol).sendQueryString(callingQuery, query, this.characterEncoding.getValue(), maxRows, streamResults,cachedMetadata, resultSetFactory): ((NativeProtocol) this.protocol).sendQueryPacket(callingQuery, packet, maxRows, streamResults, cachedMetadata, resultSetFactory);} catch (CJException sqlE) {if (getPropertySet().getBooleanProperty(PropertyKey.dumpQueriesOnException).getValue()) {String extractedSql = NativePacketPayload.extractSqlFromPacket(query, packet, endOfQueryPacketPosition,getPropertySet().getIntegerProperty(PropertyKey.maxQuerySizeToLog).getValue());StringBuilder messageBuf = new StringBuilder(extractedSql.length() + 32);messageBuf.append("\n\nQuery being executed when exception was thrown:\n");messageBuf.append(extractedSql);messageBuf.append("\n\n");sqlE.appendMessage(messageBuf.toString());}if ((this.autoReconnect.getValue())) {if (sqlE instanceof CJCommunicationsException) {// IO may be dirty or damaged beyond repair, force close it.this.protocol.getSocketConnection().forceClose();}this.needsPing = true;} else if (sqlE instanceof CJCommunicationsException) {invokeCleanupListeners(sqlE);}throw sqlE;} catch (Throwable ex) {if (this.autoReconnect.getValue()) {if (ex instanceof IOException) {// IO may be dirty or damaged beyond repair, force close it.this.protocol.getSocketConnection().forceClose();} else if (ex instanceof IOException) {invokeCleanupListeners(ex);}this.needsPing = true;}throw ExceptionFactory.createException(ex.getMessage(), ex, this.exceptionInterceptor);} finally {// 需要开启数据库连接的jdbc参数 maintainTimeStats=trueif (this.maintainTimeStats.getValue()) {// 连接的最后查询时间被更新this.lastQueryFinishedTime = System.currentTimeMillis();}if (this.gatherPerfMetrics.getValue()) {((NativeProtocol) this.protocol).getMetricsHolder().registerQueryExecutionTime(System.currentTimeMillis() - queryStartTime);}}}

解决

通过源码分析,就大致清楚问题的原因。
druid会从数据库获取一批连接持有在本地,以便快速使用。
为了检查连接的可用(如连接超时被数据库回收了,网络异常等),所以当开启testOnBorrow开关后,会在客户端从druid获取连接时进行闲置连接检查。
而闲置检查时比较连接当前时间与最后一次执行sql的时间的差值。
我们的服务在启动时没有进行数据查询,并且连接保活维持是通过ping的方式,所以当启动时间超过之前设置的15s后,再使用最开始池化的数据库借入连接时检测不过而抛出文章开头的异常信息。

我们可以通过调大闲置连接剔除时间和保活时间,让连接闲置能够撑过服务启动的无数据查询时间。
此外,如果服务的活跃情况很低,也就是执行sql的频率很低,可以设置环境变量druid.mysql.usePingMethodfalse,让druid以执行SELECT 1sql的方式来保活连接,如此就会顺带刷新getLastPacketReceivedTimeMs属性。

// com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker#configFromPropertiespublic void configFromProperties(Properties properties) {if (properties == null) {return;}String property = properties.getProperty("druid.mysql.usePingMethod");if ("true".equals(property)) {setUsePingMethod(true);} else if ("false".equals(property)) {setUsePingMethod(false);}}

当然通过源码还有其他方式,可以自行发现。

spring:datasource:druid:# 让底层的jdbc维护连接的状态的时间url: jdck:mysql://xxx?maintainTimeStats=true# 连接闲置剔除时间time-between-eviction-runs-millis: 300000# 必须大于 time-between-eviction-runs-millis 时间keep-alive-between-time-millis: 450000
 // 启动代码添加系统属性// 或者通过 -Ddruid.mysql.usePingMethod=false 的命令参数// 或者通过环境变量public static void main(String[] args) {Properties properties = System.getProperties();// 用 select 1 替换 ping 来检测连接保活properties.setProperty("druid.mysql.usePingMethod", "false");SpringApplication.run(App.class, args);}

druid报错 discard long time none received connection相关推荐

  1. springboot本地启动报错discard long time none received connection

    进到源码搜索这句「discard long time none received connection.」报错,在「com.alibaba.druid.pool.DruidAbstractDataSo ...

  2. springboot报错discard long time none received connection. 的解决方法

    原错误如下: 2022-10-07 20:13:20.839 ERROR 25784 --- [nio-2312-exec-6] c.a.druid.pool.DruidAbstractDataSou ...

  3. 服务器报错c.a.druid.pool.DruidAbstractDataSource : discard long time none received connection.

    分析 当程序获取数据库连接,对已存在的数据库连接进行检查,检查到空闲时间过久的连接会进行注销,并报出错误提示. 相关源码 com.alibaba.druid.pool.DruidAbstractDat ...

  4. Druid报错c.a.druid.pool.DruidAbstractDataSource : discard long time none received connection.

    在网上看了很多说的解决办法,修改版本,修改配置的好像都不行,可以试试这个. 我这里测试结果: 1.类文件配置,增加配置(在我这里无效) @PostConstructpublic void init() ...

  5. Spring Boot集成Druid异常discard long time none received connection.

    Spring Boot集成Druid异常 在Spring Boot集成Druid项目中,发现错误日志中频繁的出现如下错误信息: discard long time none received conn ...

  6. discard long time none received connection错误解决

    discard long time none received connection错误解决 1. 报错信息 用druid 数据库链接,日志中一直在报 Error,内容是 discard long t ...

  7. discard long time none received connection. , jdbcUrl :

    1报错 用druid 数据库链接,日志中一直在报 Error,内容是 discard long time none received connection. , jdbcUrl : 2原因 阿里他们给 ...

  8. discard long time none received connection

    使用DruidDataSource 做数据源时,如果创建的连接在长时间得不到调用后会报如题所示的错误 discard long time none received connection. ,jdbc ...

  9. charles抓取iphone https包报错:SSLHandshake: Remote host closed connection during handshake

    按照此方法:https://blog.csdn.net/lyhDream/article/details/53178118  在iphone上安装了charles的证书,在charles上也设置了ht ...

最新文章

  1. 无人机寻迹要两个单片机吗_你知道要从哪两个方面选择硬质合金锯片吗?
  2. Jenkins+Ant+TestNG+Testlink自动化构建集成
  3. mysql5.7学习nosql_如何学习NoSQL?
  4. [Android] 触屏setOnTouchListener实现图片缩放、移动、绘制和添加水印
  5. html中怎样调用xml,HTML中调用XML数据实例
  6. php 数组插入键和值,php数组中键和值的关系
  7. [shell]C语言调用shell脚本接口
  8. 组合数学 —— 概述
  9. scanf函数的返回值问题
  10. AWS想到办法让Alexa能在毫秒内做出回复
  11. 百度人脸识别技术应用002---百度云上创建人脸库_分组_以及通过网页上传人脸照片到人脸分组
  12. Linux下解压和压缩jar文件
  13. C语言项目实战—坦克大战
  14. unity urp raytrace体积光god ray效果
  15. sqlserver数据库快照和mysql_解析SQLServer视图、数据库快照_MySQL
  16. 【SSH框架/国际物流商综平台】-03 部门、用户、角色、模块 CURD BRAC认证 细粒度权限控制 BaseAction Page struts.xml *.hbm.xml
  17. 应对嵌入式校招面试手撕之——链表
  18. Java获取一年有多少周、某周的第一天和最后一天,当前时间所在当年的实际周数
  19. 【探路者】第五周立会报告4(总第30次)
  20. Boxcryptor与密叔叔使用体验对比——谁才是当下加密云存储的版本答案?

热门文章

  1. 通俗易懂 spring aop demo
  2. 微信小游戏开发,需要用到哪些接口?
  3. javafx扇形图、柱状图、折线图的使用
  4. [转]history.back(-1)和history.go(-1)的区别
  5. php 表格后台排序,element表格的后台排序
  6. 2021年中国充电站行业市场发展现状及市场竞争格局分析,广东省充电站保有量远超其他一线城市「图」
  7. 关于PPT2003的音频路径问题
  8. python的几种常用排序算法
  9. AI金典书单|入门人工智能书目推荐
  10. IT企业专利工程师之五——多维专利布局