最近碰到个现象,某个应用,每天在21:00-23:00才会执行,连接数据库执行操作,间隔性出现连接超时的错误,
Connection timed out (Read failed)
因为应用和数据库是跨网段,咨询了下,防火墙超时时间配置的是30分钟,应用用的MyBatis连接池,相关配置如下,
相关参数解释,如下所示,
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到UNPOOLED下的属性外,还有更多属性用来配置POOLED的数据源:
poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。默认值:3(新增于 3.4.5)
poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
P.S.
https://mybatis.org/mybatis-3/zh/configuration.html#environments
按照这字面意思,一开始我们理解poolPingConnectionsNotUsedFor参数控制的是连接多久没用,即处于空闲状态,在参数poolPingEnabled开启时,就会执行poolPingQuery定义的SQL主动探测。
如果按照这理解,poolPingConnectionsNotUsedFor设置了3000,即3秒,远小于30分钟防火墙超时的设置,不应该出现连接超时的现象。
我们怀疑过防火墙的配置,但从应用端看,并不是所有的请求都超时,而且防火墙端,没看到什么异常。数据库层,应该未设置过相关的配置。
原因是什么?
作为一款成熟的产品,不太可能因为bug,更多还是对他的理解存在偏差。
下载3.3.0的源码,链接如下,
https://github.com/mybatis/mybatis-3/releases/tag/mybatis-3.3.0
搜索这几个参数所在的文件,找到了PooledDataSource类,可以看到这三个参数都设置了初始值,
看下这个pingConnection方法,
如果连接未关闭,判断逻辑如下,
1. poolPingConnectionsNotUsedFor的值>=0;
2. getTimeElapsedSinceLastUse()>poolPingConnectionsNotUsedFor;
getTimeElapsedSinceLastUse()定义如下,
lastUsedTimestamp是在构造函数PooledConnection中定义的,
PooledConnection会在获取连接(popConnection)和回收连接(pushConnection)的时候调用,获取连接和回收连接则会被getConnection()和invoke()调用,因此,(2)的意思是当前这个连接空闲的时间是否大于这个参数poolPingConnectionsNotUsedFor定义的时间。
3. 如果满足条件(1)和(2),则会执行poolPingQuery的SQL,此处就是"select 1 from dual",如果执行失败,会关闭这个连接,
从应用日志,能看到这些信息,
Testing connection 0000000000 ...
Execution of ping query 'select 1 from dual' failed: Connection timed out (Read failed)
这个问题的关键,就是这个pingConnection,在什么时候调用,就决定了poolPingConnectionsNotUsedFor什么时候起作用,可以看到,他是在这个isValid的方法中调用的,
而这个isValid是在每次获取连接和回收连接时调用的,换句话说,他是被动调用,并不是我们认为的空闲时主动调用,所以这个应用,只是晚上会跑,空闲连接超过30分钟是很正常的,
应用开了debug,这两段之间的间隔时间,就是得到超时连接的时间,
经过单线程测试,大约在15分钟,
因此,对这种testOnBorrow的连接探测机制,各有优缺点,优点就是会在一定程度保证应用正常的业务请求得到可用的连接,毕竟不可用的连接都已经被poolPingQuery定义的SQL测试了,一般情况下,不会让正常的业务请求出现报错,除非连接池没任何可用的连接。缺点就是如果配置的poolPingConnectionsNotUsedFor很小,某些请求都会在执行之前先进行验证,但是换个角度,如果是高并发,只要参数不是0,一般可能都不会满足需要验证的条件,如果设置为0,就可能会有很多pingQuery定义的SQL执行。而且,如果像上述单线程的操作,他会一个连接一个连接的尝试,等待一个连接出现超时错误的时间间隔是15分钟,这就很低效了。
对连接池的选择和配置,确实得结合实际场景需求来决策。
通过这个问题,至少让我明白,“自以为对的”机制正确还是错误,还是看他的实现,这才是最可靠的验证,而且,通过他的逻辑,可以让我们借鉴一些设计路径,多考虑他这么做背后的意义和影响,更有助我们将其用到正确的场景。
标签:poolPingConnectionsNotUsedFor,默认值,探测,poolPingQuery,SQL,MyBatis,超时,连接,空闲 From: https://blog.51cto.com/u_13950417/6512313