Как настроить приложение Play Framework с правильными пулами потоков?

Я работаю с Play Framework (Scala) версии 2.3. Из документов:

Вы не можете волшебным образом превратить синхронный ввод-вывод в асинхронный, обернув его в Future. Если вы не можете изменить архитектуру приложения, чтобы избежать блокировки операций, в какой-то момент эту операцию придется выполнить, и этот поток будет заблокирован. Таким образом, в дополнение к включению операции в Future необходимо настроить ее для запуска в отдельном контексте выполнения, который был настроен с достаточным количеством потоков для работы с ожидаемым параллелизмом.

Это меня немного смутило, как настроить мое веб-приложение. В частности, поскольку в моем приложении большое количество блокирующих вызовов: сочетание вызовов JDBC и вызовов сторонних служб с использованием блокирующих SDK, какова стратегия настройки контекста выполнения и определения количества предоставляемых потоков? Нужен ли мне отдельный контекст выполнения? Почему я не могу просто настроить пул по умолчанию, чтобы иметь достаточное количество потоков (и если я это сделаю, зачем мне по-прежнему нужно оборачивать вызовы в Future?)?

Я знаю, что в конечном итоге это будет зависеть от специфики моего приложения, но мне нужны рекомендации по стратегии и подходу. Документация по игре проповедует использование неблокирующих операций везде, но на самом деле типичное веб-приложение, обращающееся к базе данных sql, имеет много блокирующих вызовов, и после прочтения документации у меня сложилось впечатление, что этот тип приложения будет работать далеко не оптимально с конфигурациями по умолчанию.


person oym    schedule 03.10.2014    source источник


Ответы (2)


[...] какова стратегия настройки контекста выполнения и определения количества потоков для предоставления

Ну, это сложная часть, которая зависит от ваших индивидуальных требований.

  • Прежде всего, вам, вероятно, следует выбрать базовый профиль из документов (чистый асинхронный , высокосинхронный или множество конкретных пулов потоков)
  • Второй шаг — тонкая настройка вашей установки путем профилирования и сравнительного анализа вашего приложения.

Нужен ли мне отдельный контекст выполнения?

Не обязательно. Но имеет смысл использовать отдельные контексты выполнения, если вы хотите инициировать все ваши блокирующие вызовы ввода-вывода сразу, а не последовательно (таким образом, вызов базы данных B не должен ждать, пока вызов базы данных A не будет завершен).

Почему я не могу просто настроить пул по умолчанию, чтобы иметь достаточное количество потоков (и если я это сделаю, зачем мне по-прежнему нужно оборачивать вызовы в Future?)?

Вы можете проверить документы:

play {
  akka {
    akka.loggers = ["akka.event.slf4j.Slf4jLogger"]
    loglevel = WARNING
    actor {
      default-dispatcher = {
        fork-join-executor {
          parallelism-min = 300
          parallelism-max = 300
        }
      }
    }
  }
}

При таком подходе вы фактически превращаете Play в модель один поток на запрос. Это не идея Play, но если вы часто блокируете вызовы ввода-вывода, это самый простой подход. В этом случае вам не нужно оборачивать вызовы базы данных в Future.

Короче говоря, у вас есть три основных пути:

  1. Используйте только (IO-)технологии, вызовы API которых являются неблокирующими и асинхронными. Это позволяет вам использовать небольшой пул потоков/контекст выполнения по умолчанию, который соответствует природе Play.
  2. Превратите Play в платформу с одним потоком на запрос, резко увеличив контекст выполнения по умолчанию. Никаких фьючерсов не нужно, просто позвоните в свою блокирующую базу данных, как всегда
  3. Создавайте определенные контексты выполнения для ваших блокирующих вызовов ввода-вывода и получайте детальный контроль над тем, что вы делаете.
person fxfour    schedule 03.10.2014

Во-первых, прежде чем углубляться и рефакторить свое приложение, вы должны определить, действительно ли это проблема для вас. Запустите несколько тестов (gatling превосходен) и создайте несколько профилей с помощью чего-то вроде JProfiler. Если вы можете жить с текущим исполнением, то счастливых дней.

В идеале использовать реактивный драйвер, который вернет вам будущее, которое затем будет передано обратно вашему контроллеру. К сожалению, асинхронность по-прежнему остается открытым билетом для slick. Взаимодействие с REST API можно сделать реактивным с помощью библиотеки PlayWS, но если у вас есть чтобы пройти через библиотеку, которую предоставляет ваша третья сторона, вы застряли.

Итак, если предположить, что ни один из этих вариантов невозможен и что вам нужно повысить производительность, возникает вопрос, какую пользу принесет предложение Play? Я думаю, что они имеют в виду, что полезно разделить ваши потоки на те, которые блокируют, и те, которые могут использовать асинхронные методы.

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

Если, с другой стороны, вы достигаете своего предела для потоков, то, используя два пула, вы можете быстро выполнять свои быстрые неблокирующие запросы. У вас будет один пул, обслуживающий запросы в вашем контроллере и вызывающий службы, которые возвращают фьючерсы. Некоторые из этих фьючерсов фактически будут выполнять работу с использованием отдельного пула потоков, но только для блокирующих операций. Если есть какая-то часть вашего приложения, которую можно сделать реактивной, то ваш контроллер может воспользоваться этим, изолируя контроллер от блокирующих операций.

person Tompey    schedule 03.10.2014