【问题标题】:SpringMVC, c3p0, hibernate, JPA Application leaking Connections leads to Too Many Connections errorSpringMVC, c3p0, hibernate, JPA Application leaking Connections 导致 Too Many Connections 错误
【发布时间】:2015-06-09 15:11:27
【问题描述】:

我已经在 stackoverflow 上搜索并搜索了超过 4 小时的问题的解决方案,阅读并尝试了解正在发生的事情,但我还没有遇到与我的问题相关的解决方案,如果这听起来像一个问题,我深表歉意重复,我会尽力解释到底发生了什么,这样我就可以深入了解 c3p0 的内部工作原理。

我有一个 SpringMVC Web 应用程序在 Tomcat 上运行,并使用 Hibernate、JPA 和 C3P0 作为我的池资源。在页面重新加载时,应用程序对数据库进行几次调用以获取随机图像并将其显示在新页面上。该应用程序在大约 30 次左右的页面重新加载时运行良好,但最终总是崩溃,我必须重新启动 mysql 才能使应用程序再次工作,它给出了以下错误:

2015 年 4 月 4 日下午 12:21:55 com.mchange.v2.resourcepool.BasicResourcePool$AcquireTask 运行 警告:com.mchange.v2.resourcepool.BasicResourcePool$AcquireTask@634d8e3d -- 获取尝试失败!!!清除挂起的获取。在尝试获取所需的新资源时,我们未能成功超过允许的最大获取尝试次数 (30)。上次获取尝试异常: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:数据源拒绝建立连接,来自服务器的消息:“连接太多” 在 sun.reflect.GeneratedConstructorAccessor101.newInstance(未知来源) 在 sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 在 java.lang.reflect.Constructor.newInstance(Constructor.java:526) 在 com.mysql.jdbc.Util.handleNewInstance(Util.java:411) 在 com.mysql.jdbc.Util.getInstance(Util.java:386) 在 com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015) 在 com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989) 在 com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975) 在 com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1114) 在 com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2493) 在 com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2526) 在 com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2311) 在 com.mysql.jdbc.ConnectionImpl.(ConnectionImpl.java:834) 在 com.mysql.jdbc.JDBC4Connection.(JDBC4Connection.java:47) 在 sun.reflect.GeneratedConstructorAccessor43.newInstance(未知来源) 在 sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 在 java.lang.reflect.Constructor.newInstance(Constructor.java:526) 在 com.mysql.jdbc.Util.handleNewInstance(Util.java:411) 在 com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:416) 在 com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:347) 在 com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:135) 在 com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:182) 在 com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:171) 在 com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.java:137) 在 com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.java:1014) 在 com.mchange.v2.resourcepool.BasicResourcePool.access$800(BasicResourcePool.java:32) 在 com.mchange.v2.resourcepool.BasicResourcePool$AcquireTask.run(BasicResourcePool.java:1810) 在 com.mchange.v2.async.ThreadPerTaskAsynchronousRunner$TaskThread.run(ThreadPerTaskAsynchronousRunner.java:255)

以下是相关文件/配置,以提供问题的上下文:

spring.xml:

<context:component-scan base-package="com.clathrop.infographyl.dao" />

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="infographylPU" />
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <!-- Connection properties -->
    <property name="driverClass" value="com.mysql.jdbc.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/infographyl_db" />
    <property name="user" value="user" />
    <property name="password" value="passwd" />
    <!-- Pool properties -->
    <property name="minPoolSize" value="5" />
    <property name="maxPoolSize" value="20" />
    <property name="acquireIncrement" value="1" />
    <property name="maxStatements" value="0" />
    <property name="idleConnectionTestPeriod" value="3000" />
    <property name="loginTimeout" value="300" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

persistence.xml:

<persistence-unit name="infographylPU" transaction-type="RESOURCE_LOCAL">
    <class>com.clathrop.infographyl.model.Infographic</class>
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
    </properties>
</persistence-unit>

这是一个基本查询的样子,在这个类中,我有一个 EntityManager 作为实例变量,并且在每个查询完成后我关闭() entityManager,但它对数据库中建立的连接数没有影响。

InfographicDaoImpl.java:

@Repository
public class InfographicDaoImpl implements InfographicDao{

@PersistenceContext
private EntityManager entityManager;

@Override
@Transactional
public void insertInfographic(Infographic infographic){
    try{
        entityManager.persist(infographic);
    } catch (Exception e){
        e.printStackTrace();
    } finally {
        entityManager.close();
    }

}

@Override
public List<Infographic> findAllInfographics(){
    try{
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Infographic> cq = builder.createQuery(Infographic.class);

        Root<Infographic> root = cq.from(Infographic.class);
        cq.select(root);
        List<Infographic> igList = entityManager.createQuery(cq).getResultList();
        return igList;
    } catch (Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }
}

@Override
public Infographic getRandomInfographic(Integer tableSize){
    Random rand = new Random();
    int randomIndex = rand.nextInt((tableSize-1)+1) + 1;

    try{
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Infographic> cq = builder.createQuery(Infographic.class);

        Root<Infographic> root = cq.from(Infographic.class);
        cq.select(root);
        cq.where(builder.equal(root.<Integer>get("id"), randomIndex));
        Infographic randomIg = entityManager.createQuery(cq).getSingleResult();
        return randomIg;
    } catch (Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }


}

@Override
public Integer getRowCount(){
    try{
        Number result = (Number) entityManager.createNativeQuery("Select count(id) from infographics").getSingleResult();
        return result.intValue();
    } catch (Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }
}

@Override
public List<Infographic> listInfographics(Integer startIndex, Integer pageSize){
    List<Infographic> igList = new ArrayList<Infographic>();

    String sStartIndex = Integer.toString(startIndex);
    String sPageSize = Integer.toString(pageSize);

    try{
        List list = entityManager.createNativeQuery("Select * from infographics limit " + sStartIndex + ", " + sPageSize).getResultList();
        for(Object ig : list){
            igList.add((Infographic) ig);
        }
        return igList;
    } catch(Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }


}

}

在这一点上,我对我的应用程序中发生的事情有几个问题。我理解的方式是 c3p0 正在处理与数据库建立的连接,我认为通过池连接,建立的连接将被重用,但是当我查看 mysql 中的进程列表时,我得到的连接列表越来越多。连接数一直在增长,直到应用程序最终抛出上面显示的连接过多警告。我的问题是为什么 c3p0 不重用连接?我是否在某处遗漏了配置来告诉它重用预先存在的连接?为什么 entityManager.close() 似乎没有影响?如果我误解了如何使用 hibernate 和 c3p0,请让我知道我缺少什么。非常感谢任何帮助,在此先感谢。

更新:

严重:Web 应用程序 [/infographyl] 似乎已经启动了一个名为 [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0] 的线程,但未能停止它。这很可能会造成内存泄漏。

我已将问题范围缩小到当 tomcat 启动并部署相关应用程序时识别出的内存泄漏。我现在的问题是试图找出导致问题的可能配置(或缺少配置)。

我目前的解决方法是在 my.cnf 中设置 wait_timeout 以终止超过 5 秒的进程/连接,目前这似乎没问题,但这不是一个可持续的解决方案,我想知道正确的关闭我的连接的方法。

更新2: com.mchange.* 信息日志:

2015 年 4 月 5 日晚上 10:57:30 org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator instantiateExplicitConnectionProvider 信息:HHH000130:实例化显式连接提供程序:org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider 2015 年 4 月 5 日晚上 10:57:30 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager 信息:初始化 c3p0 池... com.mchange.v2.c3p0.ComboPooledDataSource [acquireIncrement -> 1,acquireRetryAttempts -> 30,acquireRetryDelay -> 1000,autoCommitOnClose -> false,automaticTestTable -> null,breakAfterAcquireFailure -> false,checkoutTimeout - > 0,connectionCustomizerClassName -> null,connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester,dataSourceName -> z8kfsx98137ghpr10fde73|6aa74262,debugUnreturnedConnectionStackTraces -> false,描述 -> null,driverClass -> com.mysql.jdbc.Driver , factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> z8kfsx98137ghpr10fde73|6aa74262, idleConnectionTestPeriod -> 3000, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/infographyl_db, lastAcquisitionFailureDefaultTaskTime -> null, maxAdministrative > 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 5, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin - > false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]

【问题讨论】:

  • 如何获取对象'entityManager'?
  • @SteveWaldman,感谢您的回复。 entityManager 对象是在实例化 InfographicDaoImp 对象时创建的。我将更新代码示例,以便为您提供更好的想法。这是创建/访问 entityManager 的错误方法吗?
  • 好的 - 这是否总是发生,或者这是一个生产环境,您发现此类问题偶尔会发生?
  • @clathrop 是的。错误的方法是创建 One 并跨线程使用它,或者在抛出异常等之后仍然使用它。EntityManager.close() 肯定有效。还有其他例外吗?
  • @Antoniossss,这发生在我的本地测试环境中,而且总是这样。我必须重新启动 mysql 才能继续使用该应用程序。

标签: java mysql spring hibernate c3p0


【解决方案1】:

因此,从您所描述的所有内容来看,您的应用程序似乎正在创建然后放弃多个 c3p0 池。您不会遇到单个池耗尽(应用程序冻结)的常见症状。相反,您的应用会打开比 maxPoolSize 更多的连接,然后在达到服务器端连接限制时失败。除非您的服务器有 ~20 个连接,否则您可能会创建多个池。设置wait_timeout 隐藏了问题,因为废弃数据源的连接会自动关闭(),但这不是一个好的解决方案。如果您为每个客户端创建新的数据源,您将大大减慢而不是加速您的应用程序,并且如果这些数据源未关闭()(似乎它们不是,或者您不会累积打开的连接) ,您将创建线程和内存泄漏。

所以。

首先,你是如何记录事情的?请确保com.mchange.* 类记录在 INFO 中,并检查查找池启动消息。 c3p0 在数据源初始化的 INFO 处转储一个大型池配置消息。确保您至少看到其中一条消息。你见过他们很多次吗?那么问题来了。

如果我是对的并且您正在打开然后放弃多个 c3p0 数据源,那么下一个问题就是为什么。应用程序中的池化数据源嵌入在 EntityManagerFactory 对象中,在应用程序的整个生命周期中应该只有一个。 Spring 让事情看起来很简单,但它隐藏了如何/何时构建、销毁等细节。我认为您可能需要回答的关键问题是 Spring 为什么/是否创建多个 EntityManagerFactory 实例(或重新创建多个 c3p0 DataSource单个 EntityManagerFactory 中的时间)。

附言c3p0 不提供“loginTimeout”配置参数。

【讨论】:

  • 非常感谢您提供信息丰富的回复。是的,我确实看到了来自 com.mchange.* 的许多池配置消息,这些消息在我每次重新加载页面时都会出现。当我重新加载页面时,应用程序会查询数据库以获取要在前端显示的新图像,因此理想情况下,我希望能够根据需要多次重新加载页面,如果我理解正确,则不必创建新的c3p0 池连接每次。我将在我的问题中粘贴 c3p0 信息日志的日志,以确保我们谈论的是同一件事。您建议如何找出根本问题?
  • 您看到上面粘贴的日志消息在每次重新加载页面时都会重复出现?
  • 是的,看起来绝对不是一件好事,现在我知道这绝对不是一件好事哈哈。我不确定是否还有其他文件/配置我没有显示我应该显示,我想我粘贴了所有相关的内容,如果我遗漏了可以在我的 [github](github.com/ clathrop/infographyl)
  • 没有。这是一件非常糟糕的事情。您应该在每次应用部署时看到一次。即在 Tomcat 应用程序中,您应该在第一次启动应用程序时看到它(可能延迟到第一页加载),然后在更新 war 文件之前再也不会看到它。
  • 我接受你的回答是最好的,因为你很可能正确地提供了导致错误的区域,但我还没有找到解决方案。我会继续浏览日志并阅读 spring/c3p0 以查看是否可以找到我做错了什么,感谢您的帮助!
【解决方案2】:

我找到了解决问题的方法,但我不太确定这是最好的方法。

我通过以下方式将 my.cnf 中的 wait_timeout 设置为 5 秒:

[mysqld]
wait_timeout=5

这似乎可以有效地破坏 entityManager 正在创建的睡眠进程,并允许我长时间运行应用程序而无需重新启动 mysql。

我现在的问题是,这真的是处理由 c3p0 + EntityManager 创建的连接的最佳且唯一的方法吗?为什么 c3p0 不自行破坏连接或删除它们?这是故意的吗?任何澄清或讨论表示赞赏。谢谢大家:)

【讨论】:

  • C3p0 工作得很好,在几个生产环境中进行了测试。您必须修改整个 DB 相关代码,以防止涉及 EntityMnagers 的 try-catch-finally 块,因为这是您耗尽连接的唯一方法。
  • 至于规范wait_timetout默认值是5到5.0.51和10从5.0.52开始。所以你没有改变默认情况下不存在的任何东西。
  • 在没有指定wait_timeout的情况下,entityManager创建的连接进程处于睡眠状态超过10秒,当我去更新它时,my.cnf中没有默认的wait_timeout属性。我不太清楚你的意思是“你必须修改整个数据库相关代码,以防止涉及 EntityMnagers 的 try-catch-finally 块,因为这是你可以用完连接的唯一方法。”一开始你说我需要try-catch-finally,现在你说我不应该?
  • dev.mysql.com/doc/refman/5.0/en/…。默认值设置为上述值(默认值未在 cfg 文件中定义)。不,你误解了,修改你的代码并在任何地方添加try-catch-finally,或者至少在涉及实体经理的try-finally
  • 我编辑了我的代码以显示我的完整 InfographicDaoImpl,这是我的应用程序中唯一使用 entityManager 的地方,我已经按照你的建议关闭了 entityManager 的所有实例,但这没有任何影响,这是有道理的猜猜如果像你说的那样容器正在通过@PersistenceContext 处理EntityManager 的生命周期是真的,那么我关闭它们有什么关系呢?
【解决方案3】:

我认为,某些数据库操作会导致异常。在这种情况下,抛出异常并且 EntityManager 没有按应有的方式关闭。通常你的数据库操作应该看起来像

        EntityManager em=getEntityManagerSomehow();
        try{
            do your stuff with db here
        }catch(Exception ex){ 
            handle exception here eg. log or rethrow.

          }finally{
           em.close();  // always close entity manager even if exception occures.
       }

在我看来,如果你像上面展示的那样重构你的代码,问题就会得到解决。

【讨论】:

  • 您好 Antoniossss,感谢您的回复。我尝试了你的建议,但没有解决问题,mysql连接仍然存在,没有被重用或关闭。我查找了使用 entityManager 的some examples,但他们没有在哪里明确关闭 entityManager,我不确定这是故意还是为了简洁起见?目前还不清楚最佳做法是什么。
  • 始终关闭实体管理器。但是这里你有容器管理的持久性上下文,所以它应该被容器安全关闭
  • 你正在其他地方泄漏连接
  • 我认为你是对的,在挖掘日志后,我看到了一些堆栈跟踪,如下所示:SEVERE: The web application [/infographyl] appears to have started a thread named [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#2] but has failed to stop it. This is very likely to create a memory leak. 问题是我不知道是什么原因造成的,可能是如何设置我的 persistenceContext 和 EntityManager?还有什么我应该分享的可以说明为什么会发生这种情况的吗?
猜你喜欢
  • 1970-01-01
  • 2020-04-25
  • 2014-10-14
  • 1970-01-01
  • 2022-10-05
  • 2011-07-15
  • 2013-07-24
  • 2021-11-12
  • 2019-05-20
相关资源
最近更新 更多