最近遇到一个现象,每天21:00-23:00才会执行一个应用,连接数据库执行操作,出现连接超时错误periodically,Connectiontimedout(Readfailed)因为应用和数据库是跨网段的,参考下面,防火墙超时配置是30分钟,应用使用的MyBatis连接池,相关配置如下,相关参数解释如下,POOLED-这个数据源的实现利用了“池”的概念来组织JDBC连接对象,避免了创建新连接实例时必要的初始化和认证时间。这种方法很流行,可以使并发Web应用程序快速响应请求。除了上面提到的UNPOOLED下的属性,还有更多的属性用来配置POOLED的数据源:poolMaximumActiveConnections–随时可以存在的活跃(使用中)连接数,默认值:10poolMaximumIdleConnections–可能存在的idle任何时候的连接数。poolMaximumCheckoutTime-在被强制返回之前,池中的连接被检出(checkedout)的时间,默认值:20000毫秒(即20秒)poolTimeToWait-这是一个低级别的设置,如果需要很长时间才能获取连接时,连接池会打印状态日志并重试获取连接(避免配置错误时失败不打印日志),默认值:20000毫秒(即20秒)。poolMaximumLocalBadConnectionTolerance–这是一个关于错误连接容忍度的低级设置,它适用于尝试从缓冲池获取连接的每个线程。如果线程获取到坏连接,数据源允许线程尝试获取新连接,但是重试次数不能超过poolMaximumIdleConnections和poolMaximumLocalBadConnectionTolerance的总和。默认值:3(在3.4.5中添加)poolPingQuery-发送到数据库的探测查询,以验证连接是否正常工作并准备好接受请求。默认值为“NOPINGQUERYSET”,这会导致大多数数据库驱动程序在发生错误时返回适当的错误消息。poolPingEnabled–是否启用检测查询。如果启用,则需要将poolPingQuery属性设置为可执行的SQL语句(最好是速度非常快的SQL语句),默认值:false。poolPingConnectionsNotUsedFor–配置poolPingQuery的频率。可以设置为与数据库连接超时相同,以避免不必要的ping,默认值:0(即每次都ping所有连接-当然仅适用于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。在构造函数中定义了PooledConnection,在获取连接(popConnection)和回收连接(pushConnection)时会调用PooledConnection,而获取连接和回收连接会调用getConnection()和invoke(),所以(2)是当前连接的空闲时间是否大于这个参数poolPingConnectionsNotUsedFor定义的时间。3.如果满足条件(1)和(2),就会执行poolPingQuery的SQL,这里是“select1fromdual”。如果执行失败,连接将被关闭。从应用程序日志中可以看到这个信息,Testingconnection0000000000...Executionofpingquery'select1fromdual'failed:Connectiontimedout(Readfailed)这个问题的关键在于调用这个pingConnection的时候,它决定了poolPingConnectionsNotUsedFor什么时候起作用。可以看到在这个isValid方法中调用了他,每次获取连接回收都会调用这个isValid。换句话说,是被动调用,不是我们认为空闲时的主动调用,所以这个应用只在晚上运行,空闲连接超过30分钟是正常的,应用开启了debug,间隔时间这两段是获取超时连接的时间。经过单线程测试,大约15分钟。因此,这种testOnBorrow的连接检测机制各有优缺点。缺点和优点是会在一定程度上保证应用正常的业务请求得到可用的连接。毕竟不可用连接已经通过poolPingQuery定义的SQL测试过了。一般正常的业务请求是不会报错的,除非连接池中没有可用的连接。缺点是如果配置的poolPingConnectionsNotUsedFor很小,有些请求会在执行前进行验证,但是换个角度看,如果是高并发,只要参数不为0,一般可能不满足需要的条件得到验证。如果设置为0,可能会有很多次pingQuery定义的SQL执行。而且,如果像上面说的单线程操作,会一个一个去尝试连接,等待一个连接超时的时间间隔是15分钟,效率很低。连接池的选择和配置必须根据实际场景的需要来决定。通过这个问题,至少让我明白了“自以为是”的机制是对还是错,要看它的执行情况。这是最可靠的验证。此外,通过他的逻辑,我们可以从一些设计路径中学习,更多地考虑他所做的事情背后的意义和影响将有助于我们将其放在正确的上下文中。
