【问题标题】:Prevent keeping unused DB connection防止保留未使用的数据库连接
【发布时间】:2016-02-12 13:57:45
【问题描述】:

问题描述:

让我们有一个从控制器调用的服务方法:

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 请求执行良好,没有人会意识到任何问题。严重的问题是我们的 Web 服务的可用性取决于我们的外部合作伙伴/组件(在我们的用例支付网关中)的可用性。因此,当您的合作伙伴开始出现性能问题时,您也会遇到这些问题,更糟糕的是,它会影响您应用的所有部分。

评估:

问题的原因是Member.get(id)从池中保留了一个数据库连接并保留它以供进一步使用,尽管requestPayment(member)方法永远不需要访问数据库。当下一个(第 9 次)请求命中需要 DB 连接(事务服务、DB 选择等)的应用程序的任何其他部分时,它会一直等待(如果 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),这会在提交后将连接返回到池中(或者令我惊讶的是@ 987654329@ 工作)。但是我们需要确保从控制器到requestPayment(member) 方法的整个调用链不会泄漏连接。

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

【问题讨论】:

  • 您是否尝试过使用Member.read(id)?它提供只读访问;从某种意义上说,它不会写回 Hibernate,因此它可能立即释放连接。我还没有检查 Grails 源来确认。除此之外,也许使用带有轮询或推送通知的工作队列会比等待长时间运行的服务并希望获得最好的结果更好。
  • 嗨,谢谢你的回答,我刚刚试过Member.read(id),它保留了一个连接。无论如何,上面的例子是为了理解问题,通常逻辑涉及更复杂的读取和保存操作,HTTP请求不到一秒。真正的问题是您默默地依赖外部服务,这可能会使您的整个应用程序崩溃。工作队列是异步处理的好主意,但有时您需要同步响应。所以我想做的就是简单地抛出一个异常。
  • 好的。在 get 或 read 之后调用 member.discard() 怎么样。

标签: grails transactions connection-pooling


【解决方案1】:

在挖掘源代码后,我找到了解决方案:

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 connnection proxy not usable after transaction completion),因为我们无法释放这样的连接(这正是我所需要的)。

Javadoc:

从当前的 JDBC 连接断开 Session。如果 连接是通过 Hibernate 获得的,关闭它并返回给 连接池;否则,将其返回给应用程序。

这由提供 JDBC 连接的应用程序使用 休眠且需要长时间会话(或长时间对话)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-29
    • 2020-08-16
    • 2020-01-07
    • 2021-03-01
    • 2021-10-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多