Параллелизм в стойке — Rack.multithread, async.callback или оба?

Я пытаюсь полностью понять варианты параллельной обработки запросов в Rack. Я использовал async_sinatra для создания приложения с длительным опросом, и теперь я экспериментирую с пустой стойкой, используя флаг throw :async и/или Thin --threaded. Мне нравится эта тема, но есть некоторые вещи, которые я просто не могу понять. (Нет, я не путаю параллелизм с параллелизмом, и да, я понимаю ограничения, налагаемые 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. Это не относится к делу, но приветствуется любое понимание.)


person bioneuralnet    schedule 15.08.2011    source источник
comment
Проблема с epoll должна быть исправлена, как указано в этом билете, это коммит, который они указывают на.   -  person Bitterzoet    schedule 15.08.2011
comment
Если я уберу флаг --no-epoll, то мои потоковые запросы увеличатся с миллисекунд до минут. EM 0.12.10, Рубин 1.9.2-p180. Я полагаю, я мог бы попробовать скомпилировать p290...   -  person bioneuralnet    schedule 15.08.2011
comment
Хороший вопрос. Я задал очень похожий вопрос здесь: stackoverflow.com/questions/8146851/ и провели некоторые эксперименты здесь: github.com/jjb/threaded-rails-example (обратите внимание, что, хотя threaded thin успешно работает асинхронно, он работает медленнее)   -  person John Bachir    schedule 16.11.2011


Ответы (1)


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

Q1. Верно. Генерация ответа (он же call) будет заключена в EventMachine.defer { ... }, что заставит EventMachine передать его во встроенный пул потоков.

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

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

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

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

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

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

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

person Community    schedule 04.01.2012