Недостаточная производительность тонкого сервера / Как работают событийные веб-серверы?

У меня было приложение rails 3 на Nginx/Passenger, которое я только что перенес на Nginx/Thin (1.3.1). Однако теперь мое приложение работает явно медленнее, чем на Passenger. Время ожидания многих запросов тоже истекает.

Thin — это событийный веб-сервер. Из того, что я читал о событийных веб-серверах, у них нет понятия рабочих. Один «рабочий» со всем справляется. Поэтому, если один запрос ожидает ввода-вывода, тонкий просто переходит к следующему запросу и так далее. В одном из объяснений, которые я читал о событийных серверах, говорилось, что событийные серверы должны работать так же или даже лучше, чем рабочие серверы, потому что они ограничены только системными ресурсами.

Тем не менее, использование моего процессора очень мало. Мое использование памяти тоже очень мало, и операций ввода-вывода тоже немного. Мое приложение просто делает несколько запросов MySQL.

В чем здесь узкое место? Разве мой тонкий сервер не должен обрабатывать запросы, пока ЦП не будет загружен на 100%? Должен ли я делать что-то другое в своем приложении, чтобы оно лучше работало с событийным сервером?


person Coffee Bite    schedule 09.05.2012    source источник


Ответы (2)


Серджио прав. Ваше приложение на данный момент, вероятно, лучше, чем традиционная модель Apache/Passenger. Если вы выберете маршрут с событиями, особенно на однопоточных платформах, таких как Ruby, вы НИКОГДА не сможете блокировать что-либо, будь то БД, серверы кэширования, другие HTTP-запросы, которые вы можете сделать - ничего.

Это то, что усложняет асинхронное (событийное) программирование — его легко заблокировать, обычно в форме синхронного дискового ввода-вывода или разрешений DNS. Неблокирующие (событийные) фреймворки, такие как nodejs, осторожны в том, что они (почти) никогда не предоставляют вам вызов функции фреймворка, который блокирует, скорее все обрабатывается с использованием обратных вызовов (включая запросы к БД).

Это может быть легче визуализировать, если вы посмотрите на сердцевину однопоточного неблокирующего сервера:

while( wait_on_sockets( /* list<socket> */ &$sockets, /* event */ &$what, $timeout ) ) {
    foreach( $socketsThatHaveActivity as $fd in $sockets ) {
        if( $what == READ ) {   // There is data availabe to read from this socket
            $data = readFromSocket($fd);
            processDataQuicklyWithoutBlocking( $data );
        }
        elseif ($what == WRITE && $data = dataToWrite($fd)) { // This socket is ready to be written to (if we have any data)
            writeToSocket( $fd, $data );    
        }
    }
}

То, что вы видите выше, называется циклом событий. wait_on_sockets обычно предоставляется ОС в форме системного вызова, такого как select, poll, epoll или kqueue. Если processDataQuicklyWithoutBlocking занимает слишком много времени, сетевой буфер вашего приложения, поддерживаемый ОС (новые запросы, входящие данные и т. д.), в конечном итоге заполнится, что приведет к отклонению новых подключений и тайм-ауту существующих, поскольку $socketsThatHaveActivity обрабатывается недостаточно быстро. . Это отличается от многопоточного сервера (например, типичной установки Apache) тем, что каждое соединение обслуживается с использованием отдельного потока/процесса, поэтому входящие данные будут считываться в приложение, как только они поступят, а исходящие данные будут отправлены без задержки. .

Когда вы делаете (например) запрос к БД, неблокирующие фреймворки, такие как nodejs, добавляют сокетное соединение сервера БД в список отслеживаемых сокетов ($ sockets), поэтому, даже если ваш запрос занимает некоторое время, ваш (только) поток не заблокирован на этом одном сокете. Скорее они обеспечивают обратный вызов:

$db.query( "...sql...", function( $result ) { ..handle result ..} );

Как вы можете видеть выше, db.query немедленно возвращается без каких-либо блокировок на сервере db. Это также означает, что вам часто приходится писать подобный код, если только сам язык программирования не поддерживает асинхронные функции (например, новый C#):

$db.query( "...sql...", function( $result ) { $httpResponse.write( $result ); $connection.close(); } );

Правило никогда не блокировать можно несколько ослабить, если у вас есть много процессов, каждый из которых запускает цикл событий (обычно это способ запуска кластера узлов) или используете пул потоков для поддержания цикла событий (Java Jetty, Netty и т. д.). , вы можете написать свой собственный на C/C++). Пока один поток чем-то заблокирован, другие потоки все еще могут выполнять цикл обработки событий. Но при достаточно большой нагрузке даже они не будут работать. Так что НИКОГДА НЕ БЛОКИРУЙТЕ событийный сервер.

Итак, как видите, событийные серверы обычно пытаются решить другую проблему — у них может быть очень много открытых подключений. В чем они преуспевают, так это в том, что просто перемещают байты с помощью легких вычислений (например, серверы комет, кэши, такие как memcached, лак, прокси, такие как nginx, squid и т. д.). Ничего не стоит, хотя они лучше масштабируются, время отклика обычно имеет тенденцию к увеличению (нет ничего лучше, чем резервировать целый поток для соединения). Конечно, может быть экономически/вычислительно невыполнимо запускать такое же количество потоков, как количество одновременных подключений.

Теперь вернемся к вашей проблеме — я бы рекомендовал вам по-прежнему использовать Nginx, так как он отлично подходит для управления соединениями (которое основано на событиях) — обычно это означает обработку поддержки активности HTTP, SSL и т. д. Затем вы должны подключить это к своему приложению Rails. используя FastCGI, где вам по-прежнему нужно запускать воркеры, но не нужно переписывать приложение, чтобы оно было полностью событийным. Вы также должны позволить Nginx обслуживать статический контент — нет смысла привязывать ваших воркеров Rails к чему-то, что Nginx обычно может делать лучше. Этот подход обычно масштабируется намного лучше, чем Apache/Passenger, особенно если вы используете веб-сайт с высокой посещаемостью.

Если вы можете написать все свое приложение для обработки событий, то это прекрасно, но я понятия не имею, насколько просто или сложно это сделать в Ruby.

person tejas    schedule 09.05.2012
comment
Ничего себе .. спасибо за подробный ответ Tejas. Итак, бенчмарки, которые я прочитал в сети ... они для совершенно другого жанра приложений? Собственный сайт Thin предлагает приложение rails в качестве примера приложения для Thin. code.macournoyer.com/thin. У меня сложилось впечатление, что можно просто заменить пассажирский на тонкий и все будет отлично. - person Coffee Bite; 09.05.2012
comment
Пока вы нигде не блокируете, вы сможете воссоздать эти тесты. - person tejas; 09.05.2012

Да, Thin выполняет событийный ввод-вывод, но только для части HTTP. Это означает, что он может получать входящие данные HTTP во время обработки запроса. Однако все блокирующие операции ввода-вывода, которые вы выполняете во время обработки, по-прежнему блокируются. Если ваш MySQL медленно отвечает, то очередь тонких запросов заполнится.

Чтобы узнать о веб-сервере с дополнительными событиями, посетите Rainbows.

person Sergio Tulentsev    schedule 09.05.2012
comment
Привет Серхио. Простите мое банальное понимание этих понятий. Я читал, что гем mysql2 для рельсов также выполняет ввод-вывод с событиями. Кроме того, если это так, не всегда ли рабочий сервер всегда будет лучше? У меня работает только 2 экземпляра тонкого, а не 25 экземпляров пассажира, которые я запускал ранее. - person Coffee Bite; 09.05.2012
comment
@CoffeeBite: он может выполнять асинхронные вызовы, да, но это не автоматически, и вам нужно написать код, чтобы это произошло. По умолчанию он синхронный. - person Sergio Tulentsev; 09.05.2012
comment
@CoffeeBite: не всегда ли рабочие будут лучше - не уверен. Я сам использую Unicorn за nginx. Nginx обрабатывает ввод-вывод HTTP, а единороги быстро обслуживают запросы. - person Sergio Tulentsev; 09.05.2012
comment
Означает ли это, что мне нужно 25 экземпляров тонкого запуска, чтобы получить сравнимую производительность? Дело в том, что я провел небольшое исследование (чего оно стоит), прежде чем выбрать тонкий. Большинство бенчмарков, которые я видел, ставили единорога и тонкую шею к шее (часто только один тонкий экземпляр против группы рабочих-единорогов). Возьмем, к примеру, этот. /08/26/thin-vs-unicorn.html В моем приложении нет ничего особенного. Я полагаю, что оно настолько стандартно, насколько это возможно для приложения rails. Поэтому мне интересно, почему тонкий не работает. - person Coffee Bite; 09.05.2012
comment
Я думаю, причина, по которой он не работает, заключается в том, что у вас есть 2 тонких места, где раньше было 25 пассажиров. Nginx заботится о буферизации сетевого ввода-вывода, поэтому истинная мощь тонкого интерфейса остается неиспользованной. Чтобы полностью раскрыть его возможности, вам придется использовать асинхронный фреймворк. - person Sergio Tulentsev; 09.05.2012
comment
почти во всех тонких статьях говорится, что вам нужен один тонкий на каждый процессор. Вот и все. - person Coffee Bite; 09.05.2012