Неэффективный тонкий сервер / Как работают четные веб-серверы?

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

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

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

Какое здесь узкое место? Разве мой тонкий сервер не должен обрабатывать запросы, пока процессор не будет на 100%? Нужно ли делать что-то другое в моем приложении, чтобы оно работало лучше на сервере со встроенными функциями?

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's jetty, netty и т. Д. Вы можете написать свой собственный в C/C++). В то время как один поток на чем-то заблокирован, другие потоки могут выполнять цикл обработки событий. Но при достаточно большой нагрузке даже они не смогут работать. Так что НИКОГДА НЕ БЛОКИРУЙТЕ на сервере с четными записями

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

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

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

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

Для "более" четного веб-сервера вы должны проверить Rainbows.

Другие вопросы по тегам