Параллелизм в стойке - rack.multithread, async.callback или оба? - PullRequest
35 голосов
/ 15 августа 2011

Я пытаюсь полностью понять варианты одновременной обработки запросов в Rack.Я использовал async_sinatra для создания приложения с длинным опросом, и сейчас я экспериментирую с голым металлом Rack, используя throw :async и / или Thin's флаг с резьбой.Я доволен этим вопросом, но есть некоторые вещи, которые я просто не могу понять.(Нет, я не принимаю параллелизм за параллелизм, и да, я понимаю ограничения, налагаемые GIL).

Q1.Мои тесты показывают, что thin --threaded (то есть rack.multithread=true) выполняет запросы одновременно в отдельных потоках (я полагаю, используя EM), что означает, что длительный запрос A не будет блокировать запрос B (кроме ввода-вывода).Это означает, что мое приложение не требует какого-либо специального кодирования (например, обратных вызовов) для достижения параллелизма (опять же, игнорируя блокировку вызовов БД, ввод-вывод и т. Д.). Это то, что я считаю, я наблюдал - это правильно?

Q2.Существует еще один, более часто обсуждаемый способ достижения параллелизма, включающий EventMachine.defer и throw :async.Строго говоря, запросы не обрабатываются с использованием потоков.Они обрабатываются поочередно, но передают свою тяжелую работу и обратный вызов в EventMachine, который использует async.callback для отправки ответа позднее.После того, как запрос A перегрузил свою работу в EM.defer, запрос B начинается. Это правильно?

Q3.Если предположить, что вышесказанное более или менее верно, есть ли какое-то конкретное преимущество одного метода перед другим? Очевидно, --threaded выглядит как волшебная пуля.Есть ли минусы?Если нет, то почему все говорят о async_sinatra / throw :async / async.callback?Возможно, первое из них гласит: «Я хочу сделать свое приложение на Rails немного более быстрым при большой нагрузке», а второе лучше подходит для приложений с большим количеством длительных запросов?Или, возможно, масштаб является фактором?Просто угадаю здесь.

Я использую Thin 1.2.11 на MRI Ruby 1.9.2.(К вашему сведению, я должен использовать флаг --no-epoll, поскольку существует давняя, предположительно, решенная, но не совсем проблема с использованием EventMachine epoll и Ruby 1.9.2.точка, но любое понимание приветствуется.)

1 Ответ

24 голосов
/ 04 января 2012

Примечание: я использую Thin как синоним для всех веб-серверов, реализующих расширение async Rack (т.е. Rainbows !, Ebb, будущие версии Puma, ...)

Q1. Правильно. Он обернет генерацию ответа (aka call) в EventMachine.defer { ... }, что приведет к тому, что EventMachine поместит его в свой встроенный пул потоков.

Q2. Использование async.callback в сочетании с EM.defer на самом деле не имеет особого смысла, так как в нем также будет использоваться пул потоков, в результате чего получится аналогичная конструкция, как описано в Q1. , Использование async.callback имеет смысл при использовании только библиотек событий для ввода-вывода. Thin отправит ответ клиенту после вызова env['async.callback'] с обычным ответом Rack в качестве аргумента.

Если тело является EM::Deferrable, Тонкий не закроет соединение, пока этот отсрочка не преуспеет. Довольно хорошо сохранившийся секрет: если вы хотите больше, чем просто длительный опрос (т.е. оставить соединение открытым после отправки частичного ответа), вы также можете вернуть EM::Deferrable в качестве объекта body напрямую, без использования throw :async или кода состояния -1.

Q3. Вы догадались правильно. Подача с резьбой может улучшить нагрузку на остальное без изменений приложение Rack. Я вижу улучшение на 20% для простых приложений Sinatra на моей машине с Ruby 1.9.3, даже больше при работе на Rubinius или JRuby, где могут использоваться все ядра. Второй подход полезен, если вы пишете свое заявление равномерно.

Вы можете бросить много магии и взломов поверх Rack, чтобы приложение, не выравненное по частоте, использовало эти механизмы (см. Em-synchrony или sinatra-synchrony), но это оставит вас в аду отладки и зависимости.

Асинхронный подход имеет смысл с приложениями, которые, как правило, лучше всего решаются с помощью четного подхода, например веб-чат . Однако я бы не рекомендовал использовать многопоточный подход для реализации длинного опроса, потому что каждое соединение опроса блокирует поток. Это оставит вас с кучей потоков или соединений, с которыми вы не можете справиться. Пул потоков EM по умолчанию имеет размер 20 потоков, что ограничивает до 20 ожидающих подключений на процесс.

Вы можете использовать сервер, который создает новый поток для каждого входящего соединения, но создание потоков стоит дорого (за исключением MacRuby, но я бы не использовал MacRuby в любом рабочем приложении). Примерами являются serv и net-http-server . В идеале, вы хотите, чтобы n: m отображало запросы и потоки. Но не существует сервера, предлагающего это.

Если вы хотите узнать больше по этой теме: я выступил с докладом об этом на Rocky Mountain Ruby (и на множестве других конференций). Видеозапись можно найти на конфроках .

...