grails - 防止保留未使用的数据库连接

标签 grails transactions connection-pooling

问题描述:

让我们有一个从 Controller 调用的服务方法:

class PaymentService {
    static transactional = false

    public void pay(long id) {
        Member member = Member.get(id)
        //long running task executing HTTP request
        requestPayment(member)
    }
}

问题是,如果 8 个用户同时访问同一个服务,并且执行 requestPayment(member) 方法的时间为 30 秒,则整个应用程序将卡住 30 秒。

问题比看上去还要严重,因为如果 HTTP 请求执行良好,没有人会意识到有什么问题。严重的问题是我们的网络服务的可用性取决于我们的外部合作伙伴/组件(在我们的用例支付网关中)的可用性。因此,当您的合作伙伴开始出现性能问题时,您也会遇到这些问题,更糟糕的是,它会影响您应用程序的所有部分。

评价:

问题的原因是 Member.get(id) 从池中保留了一个数据库连接,并将其保留以供进一步使用,尽管 requestPayment(member) 方法从不需要访问数据库。当下一个(第 9 个)请求到达需要数据库连接的应用程序的任何其他部分(事务服务、数据库选择等)时,它会继续等待(如果 maxWait 设置为较低的持续时间,则超时),直到池中有可用的连接为止。连接,在我们的用例中甚至可以持续 30 秒。

等待线程的堆栈跟踪是:

at java.lang.Object.wait(Object.java:-1)
      at java.lang.Object.wait(Object.java:485)
      at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1115)
      at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
      at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
      at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)

或者超时:

JDBC begin failed
org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:114)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
        at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1167)
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
        ... 7 more

显然,事务服务也会发生同样的问题,但它更有意义,因为连接是为事务保留的。

作为临时解决方案,可以使用数据源上的 maxActive 属性增加池大小,但它并不能解决保持未使用连接的真正问题。

作为永久解决方案,可以将所有数据库操作包含在事务行为中(withTransaction{..}@Transactional),这提交后将连接返回到池(或者令我惊讶的是 withNewSession{..} 也有效)。但我们需要确保从 Controller 到 requestPayment(member) 方法的整个调用链不会泄漏连接。

如果连接“泄漏”,我希望能够在 requestPayment(member) 方法中引发异常(类似于 Propagation.NEVER 事务行为) ),这样我就可以在测试阶段尽早揭示问题。

最佳答案

深入研究源代码后,我找到了解决方案:

class PaymentService {
    static transactional = false
    def sessionFactory

    public void pay(long id) {
        Member member = Member.get(id)
        sessionFactory.currentSession.disconnect()
        //long running task executing HTTP request
        requestPayment(member)
    }
}

上述语句将连接释放回池中。

如果从事务上下文执行,则会抛出异常(org.hibernate.HibernateException 连接代理在事务完成后不可用),因为我们无法释放这样的连接(这正是我想要的)需要)。

Java 文档:

Disconnect the Session from the current JDBC connection. If the connection was obtained by Hibernate close it and return it to the connection pool; otherwise, return it to the application.

This is used by applications which supply JDBC connections to Hibernate and which require long-sessions (or long-conversations)

关于grails - 防止保留未使用的数据库连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35364394/

相关文章:

java - Grails 和 Java : Proper way to unmarshall Json from Grails in Java app

c# - TransactionScope 问题和 EF6 异步调用

java - 多线程 Java 程序中的 Oracle DB 连接池

php - 如何使用 PHP PDO 函数提取获取 SQL 事务中特定查询的结果?

.net - ADO.NET Entity Framework 中事务的 MSDTC 问题

java - 如何在启动时预初始化 DBCP 连接池?

java - Struts 1 和连接池

spring - 如何在 Grails 3 中将 ServletForwardingController 添加到 GrailsDispatcherServlet?

grails - 如何处理CommonsMultipartFile

sql - 使用SQL语句填充Grails选择框时遇到问题