Я нахожу какое-то странное поведение с 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
Я провел простое исследование и нашел это:
- Play2 использует модель актера с akka.
- Play2 используетbonecp для пула соединений с базой данных.
Для обработки запроса @Transaction Play2 использует как минимум 2 отдельных актора:
Один актер для получения соединения из пула и обработки вызова.
Соответствующий код:
- https://github.com/playframework/playframework/blob/master/framework/src/play-java-jpa/src/main/java/play/db/jpa/TransactionalAction.java
- https://github.com/playframework/playframework/blob/master/framework/src/play-java-jpa/src/main/java/play/db/jpa/JPA.java#L180 а>
- https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/core/j/JavaAction.scala#L61
Второй актер для фиксации или отката транзакции. Соединение разорвано в данный момент.
Соответствующий код:
Оба актора выполняются из одного исполнителя akka и пула потоков. Посмотрите: http://www.playframework.com/documentation/2.2.x/ThreadPools а>
По умолчанию существует 24 потока.
- По умолчанию для пула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 мс (анализ параметров + одна вставка в базу данных). Вам нужно только отправить больше одновременных запросов, чем размер пула соединений. Теперь я пишу то, что я думаю, что происходит.
- Многие акторы захватили потоки и соединения для обработки запроса.
Многие актеры пытаются подключиться из пула. Но свободных соединений, доступных в данный момент, нет (все остальные акторы их захватили). Этот пул соединений основан на BlockingQueue. Код: https://github.com/wwadge/bonecp/blob/master/bonecp/src/main/java/com/jolbox/bonecp/DefaultConnectionStrategy.java#L82
Таким образом, текущий поток блокируется на время ожидания в этой строке (по умолчанию одна секунда).
- Если некоторые потоки заблокированы этими акторами, то некоторые актор не могут быть выполнены с этими потоками.
- Таким образом, меньшее количество соединений возвращается в пул соединений, потому что меньшее количество акторов может быть выполнено с меньшим количеством потоков.
- Когда-нибудь все темы будут заблокированы. И нет доступных потоков для исполняющих акторов, которые должны возвращать соединения в пул.
Это похоже на тупик.
Может кто-нибудь дать мне совет, как я могу избежать этой проблемы?