druida 报错 descartar mucho tiempo ninguno recibió conexión

fondo del problema

Cuando comenzó el proyecto, había una gran cantidad
inserte la descripción de la imagen aquí
c.a.d.pool.DruidAbstractDataSource: discard long time none received connection.
de conexiones de bases de datos que obviamente fueron administradas por Druid porque no habían recibido datos de la base de datos durante demasiado tiempo y las conexiones se reciclaron, lo que provocó que el servicio prolongara el tiempo de inicio del servicio debido a la creación repetida de conexiones al inicio.

razón de posicionamiento

Según el mensaje de error, busque el código fuente de 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 branch
        long 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#isValidConnection
    public 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);
        }

    }
}

Esta es testConnectionInternalla capa superior del método de llamada.
inserte la descripción de la imagen aquí
Se puede ver que debido a que hemos activado testOnBorrowel interruptor, la conexión de la base de datos se probará inmediatamente después de que la aplicación sea exitosa y luego, de acuerdo con el último latido de la conexión de la base de datos, Se juzga si ha estado inactivo durante demasiado tiempo y debe descartarse Conectividad de base de datos.
Este conmutador comprueba principalmente la validez de la conexión tan pronto como se obtiene del grupo de conexiones.
Si no está habilitado testOnBorrow, verificará constantemente la inactividad de la conexión durante el proceso de mantenimiento de la conexión y reciclará la conexión que ha estado inactiva durante demasiado tiempo.

com.alibaba.druid.util.MySqlUtils#getLastPacketReceivedTimeMsEste método devuelve la hora en que la conexión recibió un mensaje por última vez.

// 以mysql6的 com.mysql.cj.jdbc.ConnectionImpl 为栗子
// getLastPacketReceivedTimeMs 方法中获取链接时间的实际方法
public long getIdleFor() {
    
    
     return this.lastQueryFinishedTime == 0 ? 0 : System.currentTimeMillis() - this.lastQueryFinishedTime;
 }

// com.mysql.cj.NativeSession#execSQL
    public <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=true
            if (this.maintainTimeStats.getValue()) {
    
    
            	// 连接的最后查询时间被更新
                this.lastQueryFinishedTime = System.currentTimeMillis();
            }

            if (this.gatherPerfMetrics.getValue()) {
    
    
                ((NativeProtocol) this.protocol).getMetricsHolder().registerQueryExecutionTime(System.currentTimeMillis() - queryStartTime);
            }
        }
    }

resolver

A través del análisis del código fuente, la causa del problema queda más o menos clara.
Druid obtendrá un lote de conexiones de la base de datos y las guardará localmente para un uso rápido.
Para verificar la disponibilidad de la conexión (como el tiempo de espera de la conexión que la base de datos recicla, la excepción de la red, etc.), cuando el testOnBorrowinterruptor está encendido, la verificación de la conexión inactiva se realizará cuando el cliente obtenga la conexión del druid.
Cuando realice una comprobación inactiva, compare la diferencia entre la hora actual de la conexión y la hora de la última ejecución de SQL.
Nuestro servicio no realizó una consulta de datos cuando se inició y la conexión se mantiene activa mediante ping, por lo que cuando el tiempo de inicio excede los 15 segundos previamente establecidos, cuando se utiliza la conexión de base de datos agrupada inicial, no se puede detectar y el comienzo de la El artículo 借入arroja información de excepción.

Podemos aumentar el tiempo de eliminación de la conexión inactiva y el tiempo de mantenimiento para que las conexiones inactivas puedan sobrevivir al tiempo de consulta sin datos del inicio del servicio.
Además, si la actividad del servicio es muy baja, es decir, la frecuencia de ejecución de sql es muy baja, puede configurar la variable de entorno druid.mysql.usePingMethodpara falsepermitir que druid ejecute SELECT 1sql para mantener viva la conexión, de modo que getLastPacketReceivedTimeMslos atributos se actualicen de manera incidental. .

// com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker#configFromProperties
    public 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);
        }
    }

Por supuesto, hay otras formas a través del código fuente, que usted mismo puede descubrir.

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);
    }

Supongo que te gusta

Origin blog.csdn.net/weixin_46080554/article/details/129878573
Recomendado
Clasificación