Тупик потока с play 2.2.1 и JPA

Я нахожу какое-то странное поведение с 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.
  2. Play2 используетbonecp для пула соединений с базой данных.
  3. Для обработки запроса @Transaction Play2 использует как минимум 2 отдельных актора:

    Один актер для получения соединения из пула и обработки вызова.

    Соответствующий код:

    Второй актер для фиксации или отката транзакции. Соединение разорвано в данный момент.

    Соответствующий код:

  4. Оба актора выполняются из одного исполнителя 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. Если некоторые потоки заблокированы этими акторами, то некоторые актор не могут быть выполнены с этими потоками.
  4. Таким образом, меньшее количество соединений возвращается в пул соединений, потому что меньшее количество акторов может быть выполнено с меньшим количеством потоков.
  5. Когда-нибудь все темы будут заблокированы. И нет доступных потоков для исполняющих акторов, которые должны возвращать соединения в пул.

Это похоже на тупик.

Может кто-нибудь дать мне совет, как я могу избежать этой проблемы?


person vasiliyz    schedule 29.12.2013    source источник


Ответы (2)


Может кто-нибудь дать мне совет, как я могу избежать этой проблемы?

Избегайте использования акторов Akka для прямого выполнения нескольких одновременных потенциально блокирующих запросов к одному и тому же ресурсу. Вместо этого используйте отдельный пул потоков для работы с JDBC.

person Robin Green    schedule 29.12.2013
comment
Я не могу избежать проблемы таким образом, потому что актеры akka являются частью фреймфорка play2, мой собственный код довольно прост и не запускает самих актеров. Может какой-то конфиг это исправит? - person vasiliyz; 29.12.2013

На самом деле ни один из ваших кодов не проходит здесь через акторов, но он проходит между разными потоками.

Вы должны настроить свои пулы потоков так, чтобы они были большими, как это рекомендуется для приложений Java, выполняющих блокировку в этом документе:

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

person James Roper    schedule 30.12.2013
comment
после некоторых исследований и тестов я пришел к выводу, что это ошибка, потому что ее нельзя точно исправить с помощью конфигурации, но ее можно исправить путем изменения кода (используя JPA.withTransaction вручную вместо использования аннотации @Transactional). Я создал задачу github.com/playframework/playframework/issues/2208. - person vasiliyz; 30.12.2013
comment
я установил parallelism-max на 60 и все равно получил ошибку. Я думаю, что это никак не исправить конфигом, и у нас всегда есть шанс, что какой-то актор застрянет в BlockingQueue, если мы установим maxConnectionsPerPartition меньше, чем parallelism-max. В любом случае мы получим проблему, если количество одновременных запросов больше, чем параметр maxConnectionsPerPartition. - person vasiliyz; 30.12.2013