问题描述:
让我们有一个从控制器调用的服务方法:
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个)请求命中应用程序的任何其他需要数据库连接的部分(事务服务,数据库选择,...)时,它会一直等待(或者如果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
显然,事务性服务会出现同样的问题,但由于为事务保留了连接,因此更有意义。
作为临时解决方案,可以使用datasource上的maxActive属性来增加池大小,但是它无法解决保持未使用连接的真正问题。
作为永久解决方案,可以将所有数据库操作包含在事务行为(withTransaction{..}
,@Transactional
)中,这会在提交后将连接返回到池(或者令我惊讶的是withNewSession{..}
也有效。但我们需要确保从控制器到requestPayment(member)
方法的整个调用链不会泄漏连接。
如果连接“泄露”(类似于requestPayment(member)
事务行为),我希望能够在Propagation.NEVER
方法中抛出异常,因此我可以在测试期间尽早揭示问题相。
答案 0 :(得分:2)
在深入挖掘源代码后,我找到了解决方案:
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:
断开Session与当前JDBC连接的连接。如果 连接是通过Hibernate获得关闭它并将其返回到 连接池;否则,将其退回申请表。
这是由提供JDBC连接的应用程序使用的 Hibernate,需要长会话(或长会话)