检测和解决 Java 应用程序的数据库连接泄漏

在处理客户的生产设置时,我们有机会分析他的代码和配置。客户面临的问题是,在高流量期间,应用程序变得无响应,并且不接受任何进一步的请求。唯一的解决方法是重新启动应用程序。该应用程序部署在 Red Hat Fuse 6.3.0 上。

DB 团队分析说,在 Database 上创建了太多连接,这也影响了 Database。在完成代码和配置后,我们发现应用程序代码未使用任何数据库连接池实现。

第一步是使用 dbcp 驱动程序配置数据库连接池。由于兼容性问题,我们使用了旧版 Apache DBCP 1.4 和 commons-pool 1 库。初始配置设置如下。这些配置非常标准,对于大多数生产环境来说已经绰绰有余了。所有这些配置细节都提到了 commons-dbcp 文档。

    <bean id="XXX" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
      <property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
      <property name="url" value="${oracle.url}"/>
      <property name="username" value="${oracle.user}"/>
      <property name="password" value="${oracle.password}"/>
      <property name="initialSize" value="10"/>
      <property name="minIdle" value="10"/>
      <property name="maxIdle" value="30"/>
      <property name="maxActive" value="50"/>
      <property name="testOnBorrow" value="true"/>
      <property name="testOnReturn" value="true"/>
      <property name="testWhileIdle" value="true"/>
      <property name="validationQuery" value="SELECT 1 FROM DUAL"/>
      <property name="connectionProperties" value="oracle.jdbc.ReadTimeout=10000"/>
          <property name="timeBetweenEvictionRunsMillis" value="-1"/>
      </bean>

配置连接池后,应用程序也卡住了,无法响应进一步的传入请求。但是,数据库团队观察到的与数据库的连接减少了。一个奇怪的行为是,如果我们分析从应用程序到数据库的连接数,计数将减少到 0。在连接池中,minIdle 和 InitialSize 设置为 10。因此,预计连接在任何时候都应保持 10,但在应用程序卡住时,我们观察到从应用程序到数据库的连接为 0。

netstat -anp|grep [application_pid]|grep [database_port]

因此,他的应用程序中的某处存在连接泄漏。连接泄漏意味着某些数据库请求/事务未正确关闭或未提交,最终这些连接被永久关闭。但是有许多应用程序(超过 50 个)与数据库交互,并且不清楚连接泄漏可能在哪里。此外,浏览所有应用程序并审查代码和配置将很困难,并且至少需要几周时间,因为它也需要彻底的测试。

为了缩小这个问题的范围并找到连接泄漏的可能原因,我们也可以设置以下 dbcp 配置。.

<property name="logAbandoned" value="true"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="30"/>
<property name="timeBetweenEvictionRunsMillis" value="30000" />

此处 removeAbandoned 设置为 true 时,尝试删除放弃的连接,并在几秒钟内在配置的 removeAbandonedTimeout 中再次将它们返回到池中。如果将其设置为 true,则可以从无法关闭连接的编写不佳的应用程序中恢复数据库连接。logAbandoned 属性也非常重要,因为它可以记录可能泄漏连接的完整堆栈跟踪,因此对于识别应用程序中的连接泄漏非常有用。Stack-Trace 记录在终端本身中。在 Red Hat Fuse 中,我们可以看到这些堆栈跟踪是由 logAbandoned 记录在 karaf 终端中,而不是在应用程序日志或fuse.log文件中记录的。所有这些属性都提到了 commons-dbcp doc。

timeBetweenEvictionRunsMillis 属性也很有帮助,它以毫秒为单位设置。设置后,将运行一个单独的线程,以在每配置的毫秒内删除空闲对象 eprinter 线程。它的默认值为 -1,这意味着此空闲对象 evic 线程不会处于活动状态并正在运行,并且仅当设置为正整数时,它才会生效。有时,两者之间的防火墙甚至数据库会永久关闭空闲连接,此时 TimeBetweenEvictionRunsMillis 将帮助这些连接返回到池中。

上述配置 removeAbandoned 和 timeBetweenEvictionRunsMillis 只是避免应用程序无响应的解决方法,这些可能会延迟应用程序无响应。

为了进一步缩小问题范围,在问题发生时,我们捕获了 Red Hat Fuse 的线程转储,其中该应用程序是使用 jstack 实用程序安装的。此 jstack 实用程序仅适用于 JDK 安装,不适用于基于 JRE 的安装。因此,如果您没有可用的 jstack 实用程序,请尝试使用相同版本的基于 JRE 的 java 安装下载 JDK。具有相同的版本很重要,否则由于JDK的jstack实用程序与在JRE中运行的Application JVM之间的兼容性冲突,可能会导致主要/次要版本错误。此 jstack 实用程序位于 JDK 安装的 bin 文件夹中。

jstack -l [application_pid] > threaddump.txt

请注意,我们在 20 秒的间隔内捕获了 3 到 4 个线程转储样本,在 10 或 20 秒的间隔内捕获不同的样本有助于识别长时间运行的线程。当我们在任何一个我们最喜欢的 IDE 中打开这个threaddump.txt时,我们发现以下堆栈跟踪。

"qtp308220548-1262" #1262 prio=5 os_prio=0 tid=0x00007fc7bc192800 nid=0x70aa in Object.wait() [0x00007fc6f24d9000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1118)
    - locked <0x00000007382b9f30> (a org.apache.commons.pool.impl.GenericObjectPool$Latch)
    at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
    at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
    at Proxy4fb1bf6f_84b1_4ec9_94ae_0661ba1b9af6.getConnection(Unknown Source)
    at org.apache.camel.component.jdbc.JdbcProducer.processingSqlBySettingAutoCommit(JdbcProducer.java:80)
    at org.apache.camel.component.jdbc.JdbcProducer.process(JdbcProducer.java:67)
    at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
    at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:145)
    at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:77)
    at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:196)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:83)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:196)
    at org.apache.camel.component.cxf.jaxrs.CxfRsInvoker.asyncInvoke(CxfRsInvoker.java:93)
    - locked <0x000000074cf27b40> (a org.apache.cxf.transport.http.Servlet3ContinuationProvider$Servlet3Continuation)   
    - at org.apache.camel.component.cxf.jaxrs.CxfRsInvoker.performInvocation(CxfRsInvoker.java:68)
    at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96)
    at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:189)
    at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:99)

从org.apache.commons.pool.impl.GenericObjectPool.borrowObject操作中,我们发现线程正在等待验证来自连接池的连接。

此外,如果我们通过堆栈跟踪,我们会发现这个线程源自 camel-jdbc producer,并进一步源自 CXF JAXRS 组件。这可能是我们在线程转储中观察到的有问题的应用程序。

最重要的是,这些配置帮助我们识别问题并解决问题。

最后,使用try-with-resources进行数据库连接也有助于自动关闭资源,但dbcp 1.4版本与Java6更兼容。自动资源管理(try-with-resources)可以与 dbcp 1.4 一起使用,但它需要更多的测试以确保没有其他中断,因此我们避免了 dbcp 1.4,这是一个遗留库。

在 finally 子句中启动连接时和关闭连接后,到处都有 log 语句肯定也会有所帮助,因为通过负载测试,我们可以比较启动日志和关闭日志的数量,它们之间的任何差异都意味着连接泄漏并且连接未关闭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值