在 Tomcat 中启用 Context reload="true" 时,JDBC 连接池连接不足
Posted
技术标签:
【中文标题】在 Tomcat 中启用 Context reload="true" 时,JDBC 连接池连接不足【英文标题】:JDBC connection pool runs out of connections when Context reload="true" is enabled in Tomcat 【发布时间】:2012-11-28 00:34:47 【问题描述】:我正在 Eclipse Juno 中开发 Java EE Web 应用程序。我已将 Tomcat 配置为使用 JDBC 连接池 (org.apache.tomcat.jdbc.pool) 和 PostgreSQL 数据库。 以下是我项目的 META-INF/context.xml 中的配置:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- Configuration for the Tomcat JDBC Connection Pool -->
<Resource name="jdbc/someDB"
type="javax.sql.DataSource"
auth="Container"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://localhost:5432/somedb"
username="postgres"
password="12345"
maxActive="100"
minIdle="10"
initialSize="10"
validationQuery="SELECT 1"
validationInterval="30000"
removeAbandoned="true"
removeAbandonedTimeout="60"
abandonWhenPercentageFull="50" />
</Context>
我的应用程序使用 Eclipse 部署到 Tomcat,并且在 Tomcat 的 context.xml 中,属性 reloadable 设置为“true”,以便在检测到更改时自动重新加载 Web 应用程序:
<Context reloadable="true">
我注意到,每次发生上述自动重新加载时,都会保留 10 多个与 PostgreSQL 数据库的连接(因为在 webapp 的 context.xml 中 initialSize="10")。所以在 10 次更改后会抛出 PSQLException:
org.postgresql.util.PSQLException: FATAL: sorry, too many clients already
...
如果我手动重启 Tomcat - 一切都很好,只保留了 10 个连接。
是否有人知道解决此问题的方法,因此可以将 reloadable 设置为“true”进行开发,并且每次重新加载上下文时不会导致池化更多连接?
不胜感激。
附: Apache Tomcat 版本 7.0.32
【问题讨论】:
很可能是***.com/questions/8435359/…的副本 @Isaac “它已从 Tomcat 7.0.11 更正”,但我有 7.0.32 并且结果仍然相同。所以基本上这是一个错误? 可能是回归。如果您绝对确定您正在释放所有连接,但问题仍然存在,那么我会要求重新打开错误报告。 @Isaac 是的,关闭所有 ResultSets、Statements/PreparedStatements、finally 块中的连接。无论如何感谢您的帮助 context.xml 中有错字:validatonQuery="SELECT 1" 验证错过了一个 I。在复制/粘贴过程中注意到但没有机会编辑问题。 【参考方案1】:解决方案(tl;dr)
为了解决这个问题,在上下文中的 Resource 元素中添加一个属性closeMethod
(记录在here),其值为“close”。 xml 文件。
这是我的 /META-INF/context.xml 文件的正确内容:
<Context>
<!-- Configuration for the Tomcat JDBC Connection Pool -->
<Resource name="jdbc/someDB"
type="javax.sql.DataSource"
auth="Container"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://localhost:5432/somedb"
username="postgres"
password="12345"
maxActive="100"
minIdle="10"
initialSize="10"
validationQuery="SELECT 1"
validationInterval="30000"
removeAbandoned="true"
removeAbandonedTimeout="60"
abandonWhenPercentageFull="50"
closeMethod="close" />
</Context>
注意属性closeMethod。我对其进行了测试,现在连接数严格按照 context.xml 文件中的定义进行保存!
注意 有一个时刻(与 JNDI 相关)需要处理。有关完整说明,请参阅更新 3。
长答案
好的,感谢 Apache Tomcat 提交者Konstantin Kolinko,我找到了上述解决方案。我在 ASF Bugzilla 上将 this issue 报告为 Apache Tomcat 错误,结果证明这不是错误(参见更新 1)。
=== 更新 1 (2012-12-03) 又名“新希望” ===
好吧,它仍然是一个错误。 Mark Thomas,Apache Tomcat 7 发布经理,confirmed(引用):
"这是 jdbc-pool 中的内存泄漏错误。PoolCleaner 实例是 保留对 ConnectionPool 的引用,防止它被 GC'd。 ... 这已在主干和 7.0.x 中修复,并将包含在 7.0.34 起。”
所以如果你有一个旧的Tomcat版本(小于7.0.34),请使用上面的解决方案,否则,从Apache Tomcat 7.0.34版本开始,应该不会出现我描述的问题。(见更新 2)
=== 更新 2 (2014-01-13) 又名“问题反击” ===
似乎my bug report 中最初描述的问题仍然存在,即使对于当前最新的 Apache Tomcat 版本 7.0.50,我也使用 Tomcat 7.0.47 复制了它(感谢Miklos Krivan 指出)。虽然现在 Tomcat 有时会在重新加载后设法关闭额外的连接,有时重新加载后连接数会增加然后保持稳定,但最终这种行为仍然不可靠。
我仍然可以重现最初描述的问题(虽然也不是那么容易:它可能与连续重新加载的频率有关)。似乎这只是时间问题,即如果 Tomcat 在重新加载后有足够的时间,它会或多或少地管理连接池。正如 Mark Thomas 在他的comment(引用)中提到的那样:“根据 closeMethod 的文档,该方法的存在只是为了加速释放资源,否则这些资源会被 GC 释放。” (引用结束),看起来速度是决定性因素。
当使用 Konstantin Kolinko 提出的解决方案(使用 closeMethod="close")时,一切正常,并且保留的连接数严格保持在 context.xml 文件中的定义。因此,使用 closeMethod="close" 似乎是(目前)避免在上下文重新加载后耗尽连接的唯一正确方法。
=== 更新 3 (2014-01-13) 又名“Tomcat 发布管理器的回归” ===
UPDATE 2 中描述的行为背后的谜团已解开。在我收到来自 Mark Thomas(Tomcat 发布经理)的reply 之后,现在已经清除了更多细节。我希望这是最后一次更新。因此,正如更新 1 中提到的那样,该错误确实已修复。我将 Mark 回复中的重要部分作为引用发布在这里(强调我的):
调查此错误时发现的实际内存泄漏是 根据 cmets #4 到 #6 在 7.0.34 及以后修复。
重新加载时连接未关闭的问题是由于 JNDI 资源的 J2EE 规范和这部分错误 因此报告无效。我正在将此错误的状态恢复到 已修复以反映确实存在的内存泄漏已得到修复。
展开后为什么无法立即关闭连接 重新加载无效,J2EE 规范没有为 容器告诉资源它不再需要。因此所有 容器可以做的是清除对资源的引用并等待 垃圾收集(这将触发池的关闭和 关联的连接)。垃圾收集发生在确定的时间 由 JVM 所以 这就是为什么它需要不确定的时间 上下文重新加载后要关闭的连接作为垃圾 收集可能有一段时间不会发生。
Tomcat 添加了 Tomcat 特定的 JNDI 属性 closeMethod 可用于触发 JNDI 资源的显式关闭,当 上下文停止。 如果等待GC清理资源不是 可接受然后只需使用此参数。 Tomcat 不使用这个 默认情况下,可能会产生意想不到和不想要的副作用 一些 JNDI 资源。
如果您希望看到提供的用于告诉 JNDI 的标准机制 不再需要它们的资源,那么您需要游说 J2EE 专家组。
结论
只需使用本文开头介绍的解决方案(但以防万一,请记住使用它理论上可能会出现的 JNDI 相关问题)。
替代解决方案
Michael Osipov 建议使用他的CloseableResourceListener,这样可以防止在取消部署Web 应用程序期间由于未打开的资源导致的内存泄漏。所以你也可以试试看。
免责声明 UPDATES 的别名受到Star Wars 电影系列的启发。所有权利归其各自所有者所有。
【讨论】:
不幸的是,Tomcat 7.0.35 也有同样的问题,但 closeMethod="close" 值解决了我的问题。 Tomcat 7.0.47 也有同样的问题,但 closeMethod="close" 效果很好。 什么是“一些 JNDI 资源的意外和不需要的副作用”,它是否适用于 postgres 连接? 简短的回答是“视情况而定”。 closeMethod 的默认值是“关闭”。当不再需要资源时调用 JndiResource.close()(如果存在这样的方法)可能会产生预期的效果,但是因为“close()”不是任何标准 API 的一部分,如果它确实存在,我们无法知道行为会是什么。因此,调用此方法的结果可能是“意外和不需要的”。关于 postgres,如果你使用 Tomcat 的内置连接池,那么“close()”将在连接池上被调用,这几乎肯定是你想要的。 @gbenroscience 您的数据源是如何配置的?在哪里?如何以编程方式使其由 Tomcat 管理?还是完全由任何 IoC 容器管理?以上是关于在 Tomcat 中启用 Context reload="true" 时,JDBC 连接池连接不足的主要内容,如果未能解决你的问题,请参考以下文章
在tomcat下context.xml中配置各种数据库连接池
在tomcat下context.xml中配置各种数据库连接池(JNDI)
Tomcat 的context.xml说明Context标签讲解