Долгие вычисления в вебе

Рейтинг: 1Ответов: 2Опубликовано: 08.01.2015

Мне очень понравились ответы на мой предыдущий вопрос, поэтому я снова к вам.

Предположим, для каждого пользователя, который обратился к определенному урлу, у меня начинаются вычисления, которые могут длиться порядка 10-ти секунд (или больше). Тут же появляется вопрос, что делать, чтобы пользователь этих 10 секунд не видел пустую страницу.
Я вычитал много интересной информации, которую пока не до конца структурировал, поэтому вижу следующие пути решения:

  • Использовать очереди сообщений и синхронный бэкенд сервер. В этом случае сервер создает сокет соединение с клиентом и отдает ему html страницу (стили, скрипты и все остальное в этом духе отдает nginx), а в очередь отдает задачу на вычисления и после получения ответа от очереди пушит клиенту результат.
  • Использовать асинхронный веб-сервер (python tornado, node.js). Знаю, что tornado создает экземпляр класса для каждого клиента и при этом в цикле не забывает о других пользователях. Поэтому, как я понимаю, можно отдать пользователю html, настроив сокет соединение, и прямо в классе вьюхи вызвать функцию вычислений и пушить клиенту результат.
  • Наверное, никто не станет бить меня по рукам и губам, если я подумаю об использовании асинхронного сервера и очереди, но сам я пока не вижу с этого выгоды.

Объясните, пожалуйста, в чем преимущества каждого из способов, какие варианты еще есть и правильно ли я вообще себе все представляю. Понимаю, что объяснять это все долго, поэтому буду благодарен и если просто закидаете меня кучей ссылок :)

Ответы

▲ 1Принят

Поучаствую в олимпиаде.

В чем разница между "поставил задание в очередь" и "отдал ответ клиенту, но продолжил обработку"? В том, что второй случай абсолютно беззащитен перед прорвой клиентов. Когда есть очереди, есть и фиксированное количество обработчиков, и нагрузку можно как-то контролировать (хотя если обработчики кривые, они все равно смогут повесить сервер), в то время как асинхронный подход не будет смотреть на загрузку сервера и пытаться выполнить работу в любом случае, что легко может создать сотни процессов/потоков на сервере и просто убьет все к чертям. Этого можно избежать, если отслеживать количество выполняемых сейчас задач и ждать, пока количество выполняемых задач не упадет до приемлемого... то есть реализовать половину функционала очередей какого-нибудь gearman. Причем наверняка без синхронизации, т.е. несколько задач смогут разом увидеть свободное место и разом включиться.

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

▲ 2

Решение этой проблемы одно единственное. Два варианта указанных вами -- на самом деле одно и то же.

Вам необходимо обработать запрос, отправить задачу в очередь и ответить клиенту страницей.

А вот каким именно образом пользователь будет получать результаты выполнения это асинхронной задачи -- тут уже есть разные варианты:

  • Делать запросы на сервер с определённым интервалом, проверять выполнена задача или нет. Если да, то забрать результат, если нет, то повторить запрос через N.
  • Long polling. Браузер делает запрос на сервер, который очень долго обрабатывается, на самом деле тупо спит, ждёт результатов выполнения.
  • Websockets. Браузер устанавливает соединение с сервером, ждёт сообщений от сервера.

Первый вариант самый простой и тупой, его можно сделать даже на убогом PHP. Вторые два варианта позволяют снизить количество запросов на сервер, однако требуют чтобы процесс обрабатывающий запрос жил долго. У многих платформ (кроме Erlang/OTP разумеется) проблемы с этим -- течёт память. Не могу сказать что лучше Node.js или Tornado -- не работал с ними.

Кроме того, наверное было бы круто, если бы можно было бы прикрутить websockets интерфейс к очереди сообщений (RabbitMQ?). Тогда можно было бы просто выставить этот интерфейс во внешний мир, браузер бы смог узнать что его задача выполнена вообще не трогая ваш бэкенд.

Резюме: чтобы быстро и просто -- запрос с интервалом (первый вариант). Более грамотно и эффективно -- websockets.