Подтвердить что ты не робот

Rack concurrency - rack.multithread, async.callback или оба?

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

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

Q2. Существует еще один, более обсуждаемый способ достижения concurrency, включающий 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. (FYI, я должен использовать флаг --no-epoll, так как давняя, предположительно-разрешенная, но не настоящая проблема с EventMachine использует epoll и Ruby 1.9.2. Это не относится к делу, но любое понимание приветствуется.)

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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