# Redis-MySQL偶然连接失败

Redis-MySQL偶然连接失败

# 1. Redis偶然连接失败

Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out after 5 second(s)
	at io.lettuce.core.ExceptionFactory.createTimeoutException(ExceptionFactory.java:51)
	at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)
	at io.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:123)
	at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)
	at com.sun.proxy.$Proxy338.get(Unknown Source)
	at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:66)
	... 86 common frames omitted

Redis 连接池默认空闲时间 30 分钟,服务器配置的 timeout 为 5 分钟,导致服务器一直将客户端连接强制断开,客户端再次连接就会连不上,正常应该客户端空闲时间要小于服务端,这样能保证客户端的连接都是有效的,开启 time-between-eviction-runs-millis 定时扫描清理无效连接,开启 test-while-idle,会和服务端进行验证是否有效,不开启只会淘汰超过空闲时间的连接,更能准确淘汰无效连接,调整大服务器配置 timeout 为 45 分钟,上线后没有再出现偶然连接失败问题

# 2. MySQL偶然连接失败

The last packet successfully received from the server was 4,998 milliseconds ago. The last packet sent successfully to the server was 5,003 milliseconds ago.
	at sun.reflect.GeneratedConstructorAccessor182.newInstance(Unknown Source)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
	at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
	at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
	at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:167)
	at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:539)
	at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:703)
	at com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol.java:642)
	at com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:941)
	at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1075)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:930)
	... 215 common frames omitted
Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
	at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:67)
	at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:63)
	at com.mysql.cj.protocol.a.SimplePacketReader.readHeader(SimplePacketReader.java:45)
	at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:52)
	at com.mysql.cj.protocol.a.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:41)
	at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:54)
	at com.mysql.cj.protocol.a.MultiPacketReader.readHeader(MultiPacketReader.java:44)
	at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:533)
	... 220 common frames omitted

应该是还添加了 keep-alive = true 配置导致的,druid 1.2 以下的版本 keep-alive 保活机制有问题,要升级到1.2以上,这个功能是超过空闲时间不会直接清理掉连接,而是先去判定这个连接是否有效,有效不会发起新连接,然后导致无效连接一直存在,打开 keep-alive 会增强一定的性能,所以就顺带加上了,没想到这个版本有问题

服务端 Mysql 配置的 30 分钟,将连接池配置 min-evictable-idle-time-millis 及 max-evictable-idle-time-millis 最小及最大空闲时间,启动出现问题,原因是 Druid 版本低于 1.2,会有个 Bug,读取不到配置导致使用 min-evictable-idle-time-millis 默认值 30 分钟,而 max-evictable-idle-time-millis 配置的是小于 30 分钟,min 不能小于 max,启动不了,升级版本就解决了

改了但是还是不行,因为还使用了 Dble 中间件,客户端是连接 Dble,Dble 连接 Mysql,发现 Dble 还有对应的连接池配置,空闲超时默认是 10 分钟,所以客户端最大空闲时间调整为小于 10 分钟就行了,最后调整为 min 5分钟,max 8分钟,空闲检测 1分钟,上线后没有再出现偶然连接失败问题

spring.datasource.druid.time-between-eviction-runs-millis = 60000
spring.datasource.druid.min-evictable-idle-time-millis = 300000
spring.datasource.druid.max-evictable-idle-time-millis = 480000
spring.datasource.druid.test-while-idle = true

连接池是怎么判断一条连接是Idle状态的? 就是通过这两个参数进行判断的

  • minEvictableIdleTimeMillis:最小空闲时间,默认30分钟,如果连接池中非运行中的连接数大于minIdle,并且那部分连接的非运行时间大于minEvictableIdleTimeMillis,则连接池会将那部分连接设置成Idle状态并关闭;也就是说如果一条连接30分钟都没有使用到,并且这种连接的数量超过了minIdle,则这些连接就会被关闭了。

  • maxEvictableIdleTimeMillis:最大空闲时间,默认7小时,如果minIdle设置得比较大,连接池中的空闲连接数一直没有超过minIdle,这时那些空闲连接是不是一直不用关闭?当然不是,如果连接太久没用,数据库也会把它关闭,这时如果连接池不把这条连接关闭,系统就会拿到一条已经被数据库关闭的连接。为了避免这种情况,Druid会判断池中的连接如果非运行时间大于maxEvictableIdleTimeMillis,也会强行把它关闭,而不用判断空闲连接数是否小于minIdle; 这两参数是怎么起作用的?

这两参数是在DestroyTask的shrink方法中用来判断连接是不是应该被关闭的

//关闭条件,空闲时间大于minEvictableIdleTimeMillis,并且空闲连接大于minIdle,
// 其中checkCount为poolingCount - minIdle,即可能被关闭的连接数量
//或者空闲时间大于maxEvictableIdleTimeMillis
if (idleMillis >= minEvictableIdleTimeMillis) {
    if (checkTime && i < checkCount) {
        evictConnections[evictCount++] = connection;
        continue;
    } else if (idleMillis > maxEvictableIdleTimeMillis) {
        evictConnections[evictCount++] = connection;
        continue;
    }
}
  • minEvictableIdleTimeMillis连接空闲时间大于该值并且池中空闲连接大于minIdle则关闭该连接
  • maxEvictableIdleTimeMillis连接空闲时间大于该值,不管minIdle都关闭该连接

参考

上次更新时间: 2023-12-15 03:14:55