【问题标题】:Thread deadlock with play 2.2.1 and JPAplay 2.2.1 和 JPA 的线程死锁
【发布时间】:2014-01-16 15:10:05
【问题描述】:

我发现 JPA 有一些奇怪的行为。在某些情况下,我们遇到了错误

 "Timed out waiting for a free available connection."
 at com.jolbox.bonecp.DefaultConnectionStrategy.getConnectionInternal(DefaultConnectionStrategy.java:88) ~[bonecp.jar:na]

该行的来源:https://github.com/wwadge/bonecp/blob/master/bonecp/src/main/java/com/jolbox/bonecp/DefaultConnectionStrategy.java#L88

我做了简单的研究,发现了这个:

  1. Play2 使用带有 akka 的 actor 模型。
  2. Play2 使用 bonecp 作为数据库连接池。
  3. 对于处理@Transaction 请求,Play2 使用至少 2 个单独的参与者:

    一个从池中获取连接并处理调用的参与者。

    相关代码:

    提交或回滚事务的第二个参与者。此时已释放连接。

    相关代码:

  4. 两个actor都从一个akka执行器和线程池执行。看:http://www.playframework.com/documentation/2.2.x/ThreadPools

    默认有 24 个线程。

  5. 默认情况下,bonecp 池有 30 个连接可用。看:http://www.playframework.com/documentation/2.2.x/SettingsJDBC 的文档 和代码https://github.com/playframework/playframework/blob/master/framework/src/play-jdbc/src/main/scala/play/api/db/DB.scala#L372

假设我们有一个必须处理许多并发请求的高负载应用程序。在我的测试中,我遇到了 50 多个默认配置并发请求的问题。 您可以重复我的测试并使用非默认配置的仅 3 个并发请求来捕获此问题,如下所示:

play {
  akka {
    akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
   loglevel = DEBUG
    actor {
      default-dispatcher = {
        fork-join-executor {
         parallelism-factor = 1.0
          parallelism-min = 1
          parallelism-max = 1
        }
      }
    }
  }
}

...
db.default.minConnectionsPerPartition=2
db.default.maxConnectionsPerPartition=2
...

复杂性和处理时间对于这个测试来说并不重要。我的处理时间大约是 7 毫秒(解析参数 + 一次插入数据库)。您只需要发送比连接池大小更多的并发请求。 现在我写下我认为正在发生的事情。

  1. 许多参与者捕获线程和连接以处理请求。
  2. 许多参与者试图从池中获取连接。但是目前没有可用的免费连接(所有其他参与者都捕获了它们)。这个连接池基于 BlockingQueue。代码: https://github.com/wwadge/bonecp/blob/master/bonecp/src/main/java/com/jolbox/bonecp/DefaultConnectionStrategy.java#L82

    所以当前线程在这一行被阻塞了一个超时(默认为一秒)。

  3. 如果某些线程被此 Actor 阻塞,则某些 Actor 无法使用此线程执行。
  4. 因此返回到连接池的连接更少,因为可以用更少的线程执行更少的参与者。
  5. 有时所有线程都会被阻塞。并且没有任何线程可用于将连接返回到池的执行参与者。

这看起来像死锁。

谁能给我一个建议,我该如何避免这个问题?

【问题讨论】:

    标签: playframework playframework-2.0 akka


    【解决方案1】:

    实际上,您的代码都没有经过这里的参与者,而是在不同的线程之间传递。

    您应该将线程池配置为较大,正如本文档中为 Java 应用程序所做的阻塞所建议的那样:

    http://www.playframework.com/documentation/2.2.x/ThreadPools

    【讨论】:

    • 经过一些研究和测试后,我得出结论,这是一个错误,因为无法通过配置确定修复但可以通过代码更改修复(手动使用 JPA.withTransaction 而不是使用 @Transactional 注释) .我创建了问题github.com/playframework/playframework/issues/2208
    • 我将 parallelism-max 设置为 60,但仍然出现错误。我认为没有办法通过配置来修复它,如果我们将 maxConnectionsPerPartition 设置为小于 parallelism-max,我们总是有机会让某些参与者卡在 BlockingQueue 上。无论如何,如果并发请求的数量超过 maxConnectionsPerPartition 参数,我们会遇到问题。
    【解决方案2】:

    谁能给我一个建议,我该如何避免这个问题?

    避免使用 Akka actor 直接向同一资源发出多个同时可能阻塞的请求。改为使用单独的线程池来处理 JDBC。

    【讨论】:

    • 我无法避免这样的问题,因为 akka 演员是 play2 framefork 的一部分,我自己的代码非常简单,并且不运行演员本身。可能是一些配置可以解决这个问题?
    猜你喜欢
    • 2017-11-19
    • 1970-01-01
    • 2016-08-06
    • 1970-01-01
    • 2010-09-10
    • 2018-06-16
    • 2016-04-08
    • 1970-01-01
    相关资源
    最近更新 更多