Spec-Zone.ru › Apache HTTP Server

Настройка производительности Apache

Apache 2.x — это универсальный веб-сервер, разработанный для обеспечения баланса гибкости, портативности и производительности. Хотя он не был разработан специально для установления эталонных показателей, Apache 2.x способен обеспечивать высокую производительность во многих реальных ситуациях.

По сравнению с Apache 1.3, релиз 2.x содержит множество дополнительных оптимизаций для повышения пропускной способности и масштабируемости. Большинство этих улучшений включены по умолчанию. Однако существуют параметры конфигурации на этапе компиляции и во время выполнения, которые могут существенно повлиять на производительность. В данном документе описываются параметры, которые администратор сервера может настроить для настройки производительности установки Apache 2.x. Некоторые из этих параметров конфигурации позволяют httpd лучше использовать возможности оборудования и ОС, в то время как другие позволяют администратору пожертвовать функциональностью ради скорости.

Проблемы с оборудованием и операционной системой

Самой большой проблемой оборудования, влияющей на производительность веб-сервера, является оперативная память. Веб-сервер никогда не должен использовать подкачку, так как подкачка увеличивает задержку каждого запроса до уровня, который пользователи считают недостаточно быстрым. Это заставляет пользователей нажимать кнопку «Стоп» и перезагружать страницу, что ещё больше увеличивает нагрузку. Вы можете и должны контролировать параметр MaxRequestWorkers таким образом, чтобы ваш сервер не создавал слишком много дочерних процессов, что приведёт к началу подкачки. Процедура выполнения этого проста: определите размер среднего процесса Apache, посмотрев список процессов с помощью инструмента, такого как top, и разделите эту величину на общий объём доступной памяти, оставив некоторое пространство для других процессов.

Помимо этого, всё остальное стандартно: получите достаточно быстрый процессор, достаточно быструю сетевую карту и достаточно быстрые диски, где «достаточно быстро» — это понятие, которое необходимо определить экспериментально.

Выбор операционной системы в значительной степени зависит от местных условий. Но некоторые рекомендации, которые оказались полезными в общем случае, таковы:

  • Используйте последнюю стабильную версию и уровень патчей операционной системы, которую вы выбрали. Многие поставщики ОС внедрили значительные улучшения производительности в свои стеки TCP и библиотеки потоков в последние годы.

  • Если ваша ОС поддерживает вызов системы sendfile(2), убедитесь, что установлены необходимые релизы и/или исправления для его включения. (Например, в Linux это означает использование Linux 2.4 или более поздней версии. Для ранних релизов Solaris 8 может потребоваться применение исправления.) В системах, где он доступен, sendfile позволяет Apache 2 доставлять статический контент быстрее и с меньшей загрузкой процессора.

Проблемы с конфигурацией во время выполнения

Связанные модули Связанные директивы
  • mod_dir
  • mpm_common
  • mod_status
  • AllowOverride
  • DirectoryIndex
  • HostnameLookups
  • EnableMMAP
  • EnableSendfile
  • KeepAliveTimeout
  • MaxSpareServers
  • MinSpareServers
  • Options
  • StartServers

Проверка имен хостов и другие соображения по DNS

До Apache 1.3, HostnameLookups по умолчанию было On. Это добавляет задержку к каждому запросу, так как требуется завершить поиск DNS перед завершением запроса. В Apache 1.3 этот параметр по умолчанию Off. Если вам нужно, чтобы адреса в ваших файлах журнала разрешались в имена хостов, используйте программу logresolve, которая поставляется с Apache, или один из многочисленных пакетов отчётности о журналах.

Рекомендуется выполнять такую постобработку файлов журналов на машине, отличной от рабочей машины веб-сервера, чтобы данная деятельность не отрицательно повлияла на производительность сервера.

Если вы используете любые директивы Allow from domain или Deny from domain (т.е., используете имя хоста или доменное имя вместо IP-адреса), вы заплатите за два поиска DNS (обратный, а затем прямой поиск, чтобы убедиться, что обратный не подделан). Поэтому для наилучшей производительности используйте IP-адреса вместо имён, если это возможно, при использовании этих директив.

Обратите внимание, что возможно ограничить область действия директив, например, в разделе <Location "/server-status">. В этом случае поиск DNS выполняется только для запросов, соответствующих критериям. Вот пример, который отключает поиск, за исключением файлов .html и .cgi.

HostnameLookups off
<Files ~ "\.(html|cgi)$">
  HostnameLookups on
</Files>

Но даже если вам нужны имена DNS только в некоторых CGI, вы можете рассмотреть возможность выполнения вызова gethostbyname в конкретных CGI, которые его требуют.

FollowSymLinks и SymLinksIfOwnerMatch

В любом месте вашего URL-пространства, где у вас нет Options FollowSymLinks, или у вас есть Options SymLinksIfOwnerMatch, Apache потребуется выполнить дополнительные системные вызовы для проверки символьных ссылок. (Один дополнительный вызов на каждый компонент имени файла). Например, если у вас есть:

DocumentRoot "/www/htdocs"
<Directory "/">
  Options SymLinksIfOwnerMatch
</Directory>

и производится запрос для URI /index.html, тогда Apache выполнит lstat(2) для /www, /www/htdocs, и /www/htdocs/index.html. Результаты этих lstats никогда не кэшируются, поэтому они будут происходить при каждом запросе. Если вам действительно необходима проверка безопасности символьных ссылок, вы можете сделать что-то вроде этого:

DocumentRoot "/www/htdocs"
<Directory "/">
  Options FollowSymLinks
</Directory>

<Directory "/www/htdocs">
  Options -FollowSymLinks +SymLinksIfOwnerMatch
</Directory>

Это по крайней мере позволит избежать дополнительных проверок для пути DocumentRoot. Обратите внимание, что вам нужно добавить аналогичные разделы, если у вас есть пути Alias или RewriteRule за пределами корня документа. Для наилучшей производительности и отсутствия защиты символьных ссылок установите FollowSymLinks везде и никогда не устанавливайте SymLinksIfOwnerMatch.

AllowOverride

В любом месте вашего URL-пространства, где вы разрешаете переопределения (обычно файлы .htaccess ), Apache попытается открыть .htaccess для каждого компонента имени файла. Например,

DocumentRoot "/www/htdocs"
<Directory "/">
  AllowOverride all
</Directory>

и производится запрос для URI /index.html. Тогда Apache попытается открыть /.htaccess, /www/.htaccess, и /www/htdocs/.htaccess. Решения аналогичны предыдущему случаю с Options FollowSymLinks. Для наилучшей производительности используйте AllowOverride None во всей вашей файловой системе.

Переговоры

Если это вообще возможно, избегайте переговоров о содержимом, если вам действительно нужна каждая последняя доля производительности. На практике преимущества переговоров перевешивают потери производительности. Есть один случай, когда вы можете ускорить работу сервера. Вместо использования подстановочных знаков, таких как:

DirectoryIndex index

Используйте полный список вариантов:

DirectoryIndex index.cgi index.pl index.shtml index.html

где вы сначала перечисляете наиболее распространённый выбор.

Также обратите внимание, что создание файла type-map явно обеспечивает лучшую производительность, чем использование MultiViews, так как необходимую информацию можно определить, прочитав этот единственный файл, а не сканируя каталог в поисках файлов.

Если вашему сайту необходимы переговоры о содержимом, рассмотрите возможность использования файлов type-map вместо директивы Options MultiViews для выполнения переговоров. Обратитесь к документации Переговоры о содержимом для получения подробной информации о методах переговоров и инструкций по созданию файлов type-map.

Картирование памяти

В ситуациях, когда Apache 2.x необходимо просмотреть содержимое файла, который передаётся, например, при обработке включений на стороне сервера, он обычно отображает файл в памяти, если ОС поддерживает некоторую форму mmap(2).

На некоторых платформах это улучшает производительность. Однако есть случаи, когда отображение в памяти может навредить производительности или даже стабильности httpd:

  • На некоторых операционных системах отображение в памяти не масштабируется так же хорошо, как read(2), когда увеличивается количество процессоров. Например, на многопроцессорных серверах Solaris Apache 2.x иногда быстрее передает файлы, обработанные на сервере, когда mmap отключен.

  • Если вы отобразите в памяти файл, расположенный на файловой системе, смонтированной через NFS, и процесс на другой машине-клиенте NFS удалит или обнулит файл, ваш процесс может получить ошибку «ошибка шины» при следующем доступе к содержимому отображенного файла.

Для установок, где любой из этих факторов применим, вы должны использовать EnableMMAP off для отключения отображения в памяти передаваемых файлов. (Примечание: данную директиву можно переопределить на уровне каждого каталога.)

Sendfile

В ситуациях, когда Apache 2.x может игнорировать содержимое передаваемого файла, например, при передаче статического содержимого файла, он обычно использует поддержку sendfile ядра для файла, если ОС поддерживает операцию sendfile(2).

На большинстве платформ использование sendfile улучшает производительность, устраняя отдельные механизмы чтения и отправки. Однако есть случаи, когда использование sendfile может навредить стабильности httpd:

  • Некоторые платформы могут иметь неисправную поддержку sendfile, которую система сборки не обнаружила, особенно если двоичные файлы были скомпилированы на другом компьютере и перенесены на машину с неисправной поддержкой sendfile.

  • При файловой системе, смонтированной через NFS, ядро может не иметь возможности надёжно обслуживать сетевой файл через собственный кэш.

Для установок, где любой из этих факторов применим, вы должны использовать EnableSendfile off для отключения доставки содержимого файла с помощью sendfile. (Примечание: данную директиву можно переопределить на уровне каждого каталога.)

Создание процессов

До Apache 1.3 параметры MinSpareServers, MaxSpareServers, и StartServers оказывали значительное влияние на результаты бенчмарков. В частности, Apache требовал периода «разогрева», чтобы достигнуть достаточного количества дочерних процессов для обслуживания нагрузки. После начального создания StartServers дочерних процессов, только один дочерний процесс в секунду создавался для удовлетворения параметра MinSpareServers. Таким образом, серверу, к которому обращаются 100 одновременных клиентов, используя значение StartServers по умолчанию, 5, потребовалось бы порядка 95 секунд, чтобы создать достаточно дочерних процессов для обработки нагрузки. Это работает хорошо на реальных серверах, поскольку они не перезапускаются часто. Но это очень плохо сказывается на бенчмарках, которые могут выполняться всего десять минут.

Правило одного в секунду было реализовано для предотвращения перегрузки машины началом новых дочерних процессов. Если машина занята созданием дочерних процессов, она не может обслуживать запросы. Но это так сильно влияет на воспринятую производительность Apache, что пришлось его заменить. Начиная с Apache 1.3, код смягчит правило одного в секунду. Он создаст один, подождёт секунду, затем два, подождёт секунду, затем четыре, и он будет продолжать экспоненциально, пока не создаст 32 дочерних процесса в секунду. Он остановится, когда удовлетворит параметр MinSpareServers.

Это, похоже, достаточно отзывчиво, что делает регулировку MinSpareServers, MaxSpareServers и StartServers почти ненужной. Когда создаётся более 4 дочерних процессов в секунду, в ErrorLog будет выведено сообщение. Если вы видите много таких ошибок, то рассмотрите настройку этих параметров. Используйте вывод mod_status в качестве руководства.

Связано с созданием процесса, есть смерть процесса, вызванная настройкой MaxConnectionsPerChild. По умолчанию это 0, что означает, что нет ограничений на количество обработанных соединений на дочерний процесс. Если в вашей конфигурации это значение установлено на очень низкое число, например, 30, вам может потребоваться значительно увеличить его. Если вы работаете под SunOS или старой версией Solaris, ограничьте это значение 10000 или около того из-за утечек памяти.

Когда используются keep-alive, дочерние процессы будут простаивать, ожидая новых запросов по уже открытому соединению. Значение по умолчанию KeepAliveTimeout в 5 секунд пытается минимизировать этот эффект. Здесь происходит компромисс между пропускной способностью сети и ресурсами сервера. В любом случае не следует увеличивать это значение выше примерно 60 секунд, так как большинство преимуществ теряется.

Проблемы конфигурации во время компиляции

Выбор MPM

Apache 2.x поддерживает подключаемые модели одновременности, называемые Модулями многопроцессорной обработки (MPM). При построении Apache необходимо выбрать используемый MPM. Для некоторых платформ существуют платформенно-специфичные MPM: mpm_netware, mpmt_os2, и mpm_winnt. Для общих систем Unix-типа существует несколько MPM на выбор. Выбор MPM может повлиять на скорость и масштабируемость httpd:

  • MPM worker использует несколько дочерних процессов с множеством потоков каждый. Каждый поток обрабатывает одно соединение за раз. Worker, как правило, является хорошим выбором для серверов с высокой нагрузкой, так как он имеет меньший размер занимаемой памяти, чем MPM prefork.
  • MPM event работает с потоками, как и MPM Worker, но разработан для одновременного обслуживания большего количества запросов, передавая часть работы по обработке вспомогательным потокам, освобождая главные потоки для работы с новыми запросами.
  • MPM prefork использует несколько дочерних процессов с одним потоком каждый. Каждый процесс обрабатывает одно соединение за раз. Во многих системах prefork сопоставим по скорости с worker, но он использует больше памяти. Беспотоковая архитектура prefork имеет преимущества над worker в некоторых ситуациях: его можно использовать с модулями сторонних разработчиков, не поддерживающими потоки, и его проще отлаживать на платформах с плохой поддержкой отладки потоков.

Дополнительную информацию об этих и других MPM см. в документации по MPM.

Модули

Поскольку использование памяти является важным фактором производительности, вы должны попытаться исключить модули, которые вы фактически не используете. Если вы скомпилировали модули как DSO, удаление модулей сводится к комментарию соответствующей директивы LoadModule для данного модуля. Это позволяет экспериментировать с удалением модулей и проверять, продолжает ли сайт работать без них.

Если же модули статически связаны с вашим бинарным файлом Apache, для удаления нежелательных модулей вам потребуется перекомпилировать Apache.

Сопутствующий вопрос, который возникает здесь, конечно, какие модули вам нужны, а какие нет. Ответ, конечно, будет разным для разных веб-сайтов. Однако, минимальный список модулей, с которым можно обойтись, обычно включает mod_mime, mod_dir, и mod_log_config. mod_log_config, конечно, необязателен, так как можно запустить веб-сайт без лог-файлов. Однако это не рекомендуется.

Атомарные операции

Некоторые модули, такие как mod_cache и недавние сборки worker MPM, используют атомарный API APR. Этот API предоставляет атомарные операции, которые могут использоваться для лёгкой синхронизации потоков.

По умолчанию APR реализует эти операции с использованием наиболее эффективного механизма, доступного на каждой целевой платформе OS/CPU. Многие современные процессоры, например, имеют инструкцию, которая выполняет атомарную операцию сравнения и обмена (CAS) в аппаратном обеспечении. Однако на некоторых платформах APR по умолчанию использует более медленную реализацию атомарного API на основе мьютексов для обеспечения совместимости со старыми моделями процессоров, не имеющими таких инструкций. Если вы собираете Apache для одной из таких платформ и планируете использовать только более новые процессоры, вы можете выбрать более быструю реализацию атомарных операций во время сборки, сконфигурировав Apache с опцией --enable-nonportable-atomics.

./buildconf
./configure --with-mpm=worker --enable-nonportable-atomics=yes

Опция --enable-nonportable-atomics актуальна для следующих платформ:

  • Solaris на SPARC
    По умолчанию APR использует мьютексы для атомарных операций на Solaris/SPARC. Однако, если вы сконфигурируете с --enable-nonportable-atomics, APR сгенерирует код, который использует opcode SPARC v8plus для быстрой аппаратной операции сравнения и обмена. Если вы сконфигурируете Apache с этой опцией, атомарные операции будут более эффективными (позволяя снизить использование ЦП и увеличить одновременность), но полученный исполняемый файл будет работать только на процессорах UltraSPARC.
  • Linux на x86
    По умолчанию APR использует мьютексы для атомарных операций на Linux. Однако, если вы сконфигурируете с --enable-nonportable-atomics, APR сгенерирует код, который использует opcode 486 для быстрой аппаратной операции сравнения и обмена. Это приведёт к более эффективным атомарным операциям, но полученный исполняемый файл будет работать только на процессорах 486 и новее (а не на 386).

mod_status и ExtendedStatus On

Если вы включите mod_status и также установите ExtendedStatus On при сборке и запуске Apache, то при каждом запросе Apache выполнит два вызова gettimeofday(2) (или times(2) в зависимости от вашей операционной системы), и (до версии 1.3) несколько дополнительных вызовов time(2). Всё это делается для того, чтобы отчёт о состоянии содержал временные показатели. Для максимальной производительности установите ExtendedStatus off (что является значением по умолчанию).

Приоритезация accept - несколько сокетов

Предупреждение:

Этот раздел не был полностью обновлён для учёта изменений, внесённых в версию 2.x Apache HTTP Server. Часть информации может быть всё ещё актуальной, но используйте её с осторожностью.

Здесь обсуждается недостаток в API сокетов Unix. Предположим, ваш веб-сервер использует несколько инструкций Listen для прослушивания на нескольких портах или нескольких адресах. Для проверки каждого сокета на наличие готового соединения Apache использует select(2). select(2) указывает, что в сокете есть нулевое или хотя бы одно ожидающее соединение. Модель Apache включает несколько дочерних процессов, и все бездействующие из них проверяют наличие новых соединений одновременно. Примитивная реализация выглядит примерно так (эти примеры не соответствуют коду, они искусственно созданы для целей обучения):

        for (;;) {
          for (;;) {
            fd_set accept_fds;

            FD_ZERO (&accept_fds);
            for (i = first_socket; i <= last_socket; ++i) {
              FD_SET (i, &accept_fds);
            }
            rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
            if (rc < 1) continue;
            new_connection = -1;
            for (i = first_socket; i <= last_socket; ++i) {
              if (FD_ISSET (i, &accept_fds)) {
                new_connection = accept (i, NULL, NULL);
                if (new_connection != -1) break;
              }
            }
            if (new_connection != -1) break;
          }
          process_the(new_connection);
        }

Но в этой примитивной реализации есть серьёзная проблема голодания. Вспомните, что несколько дочерних процессов выполняют этот цикл одновременно, и поэтому несколько дочерних процессов будут блокироваться в select, когда они находятся между запросами. Все эти заблокированные дочерние процессы проснутся и вернутся из select, когда появится один запрос на любой сокет. (Количество дочерних процессов, которые проснутся, зависит от операционной системы и временных проблем.) Все они затем попадут в цикл и попытаются accept соединение. Но только один преуспеет (предполагая, что всё ещё есть только одно готовое соединение). Остальные будут заблокированы в accept. Это эффективно заставляет эти дочерние процессы обслуживать запросы только с одного сокета и ни с каких других, и они будут застрять там, пока не появится достаточно новых запросов на этом сокете, чтобы разбудить их всех. Эта проблема голодания была впервые задокументирована в PR#467. Есть, по крайней мере, два решения.

Одним из решений является использование неблокирующих сокетов. В этом случае accept не будет блокировать дочерние процессы, и они смогут продолжить работу немедленно. Но это тратит время процессора. Предположим, у вас есть десять бездействующих дочерних процессов в select, и приходит одно соединение. Тогда девять из этих дочерних процессов проснутся, попытаются accept соединение, потерпят неудачу и вернутся в select, ничего не добившись. Тем временем ни один из этих дочерних процессов не обслуживает запросы, которые появились на других сокетах, пока они не вернутся в select снова. В целом, это решение не кажется очень плодотворным, если у вас не так же много бездействующих процессоров (в многопроцессорном ящике), как у вас есть бездействующих дочерних процессов (ситуация, которая маловероятна).

Другое решение, используемое Apache, заключается в упорядочивании входа в внутренний цикл. Цикл выглядит так (различия выделены):

        for (;;) {
          accept_mutex_on ();
          for (;;) {
            fd_set accept_fds;
            
            FD_ZERO (&accept_fds);
            for (i = first_socket; i <= last_socket; ++i) {
              FD_SET (i, &accept_fds);
            }
            rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
            if (rc < 1) continue;
            new_connection = -1;
            for (i = first_socket; i <= last_socket; ++i) {
              if (FD_ISSET (i, &accept_fds)) {
                new_connection = accept (i, NULL, NULL);
                if (new_connection != -1) break;
              }
            }
            if (new_connection != -1) break;
          }
          accept_mutex_off ();
          process the new_connection;
        }

Функции accept_mutex_on и accept_mutex_off реализуют мьютекс-семафор. Только один дочерний процесс может иметь мьютекс в любой момент времени. Есть несколько вариантов реализации этих мьютексов. Выбор определяется в src/conf.h (до версии 1.3) или src/include/ap_config.h (версия 1.3 или более поздняя). На некоторых архитектурах выбор блокировки не осуществляется, на этих архитектурах небезопасно использовать несколько Listen директив.

Директива Mutex может быть использована для изменения реализации мьютекса mpm-accept мьютекса во время выполнения. Специальные соображения для разных реализаций мьютексов документированы с этой директивой.

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

В идеале вы должны запускать серверы без множественных Listen инструкций, если хотите максимальной производительности. Но продолжайте читать.

Приоритезация accept - единственный сокет

Всё вышесказанное хорошо подходит для серверов с несколькими сокетами, но что насчёт серверов с одним сокетом? Теоретически они не должны испытывать никаких аналогичных проблем, потому что все дочерние процессы могут просто блокироваться в accept(2) до прибытия соединения, и никакого голодания не происходит. На практике это скрывает почти такое же поведение «вращения», которое обсуждалось выше в решении с неблокирующими сокетами для нескольких сокетов. Так как большинство стеков TCP реализуются, ядро фактически разбуживает все процессы, заблокированные в accept, когда приходит одно соединение. Один из этих процессов получает соединение и возвращается в пользовательское пространство. Остальные вращаются в ядре и снова засыпают, когда обнаруживают, что для них нет соединения. Это вращение скрыто от кода пользовательского пространства, но оно есть. Это может привести к тому же поведению перегрузки, что и решение с неблокирующими сокетами в случае нескольких сокетов.

По этой причине мы обнаружили, что многие архитектуры ведут себя более «вежливо», если мы сериализуем даже случай с одним сокетом. Поэтому это фактически является значением по умолчанию во многих случаях. Грубые эксперименты под Linux (2.0.30 на двухъядерном Pentium Pro 166 с 128 МБ ОЗУ) показали, что сериализация случая с одним сокетом приводит к снижению количества запросов в секунду менее чем на 3% по сравнению с несериализованным случаем с одним сокетом. Но несериализованный случай с одним сокетом показал дополнительную задержку 100 мс при каждом запросе. Эта задержка, вероятно, нивелируется на длинных линиях связи и является проблемой только в локальных сетях. Если вы хотите переопределить сериализацию для случая с одним сокетом, вы можете определить SINGLE_LISTEN_UNSERIALIZED_ACCEPT, и тогда серверы с одним сокетом не будут сериализоваться вообще.

Задержка закрытия

Как обсуждалось в draft-ietf-http-connection-00.txt разделе 8, для того, чтобы HTTP-сервер надежно реализовывал протокол, ему необходимо независимо закрывать каждое направление связи. (Напомним, что TCP-соединение двунаправленное. Каждая половина независима от другой.)

Когда эта функция была добавлена в Apache, она вызвала множество проблем на различных версиях Unix из-за недальновидности. Спецификация TCP не указывает, что состояние FIN_WAIT_2 имеет таймаут, но и не запрещает его. На системах без таймаута Apache 1.2 вызывает множество сокетов, застрявших навсегда в состоянии FIN_WAIT_2. В многих случаях этого можно избежать, просто обновившись до последних патчей TCP/IP, предоставленных поставщиком. В случаях, когда поставщик никогда не выпускал патчи (например, SunOS4 — хотя люди с лицензией на исходный код могут сами их исправить), мы решили отключить эту функцию.

Есть два способа достижения этого. Один — опция сокета SO_LINGER. Но, как известно, это никогда не реализовывалось должным образом в большинстве стеков TCP/IP. Даже на тех стеках, где реализация правильная (например, Linux 2.0.31), этот метод оказывается более затратным (время ЦП) по сравнению со следующим решением.

В основном Apache реализует это в функции, называемой lingering_close (в http_main.c). Функция примерно выглядит так:

        void lingering_close (int s)
        {
          char junk_buffer[2048];
          
          /* shutdown the sending side */
          shutdown (s, 1);

          signal (SIGALRM, lingering_death);
          alarm (30);

          for (;;) {
            select (s for reading, 2 second timeout);
            if (error) break;
            if (s is ready for reading) {
              if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {
                break;
              }
              /* just toss away whatever is here */
            }
          }
          
          close (s);
        }

Это, естественно, добавляет некоторую нагрузку в конце соединения, но это необходимо для надежной реализации. По мере того, как HTTP/1.1 становится более распространённым, а все соединения персистентными, эти затраты будут амортизированы за счёт большего числа запросов. Если вы хотите поиграть с огнём и отключить эту функцию, вы можете определить NO_LINGCLOSE, но это совершенно не рекомендуется. В частности, по мере использования HTTP/1.1 с персистентными конвейерными соединениями lingering_close является абсолютной необходимостью (и конвейерные соединения быстрее, поэтому вы хотите их поддерживать).

Файл «табло»

Родительский и дочерние процессы Apache взаимодействуют друг с другом через нечто, называемое «табло». В идеале это должно быть реализовано в общей памяти. Для тех операционных систем, к которым у нас есть доступ или для которых есть подробные порты, это, как правило, реализуется с использованием общей памяти. Остальные по умолчанию используют файл на диске. Файл на диске не только медленный, но и ненадежный (и менее функциональный). Просмотрите файл src/main/conf.h для вашей архитектуры и найдите либо USE_MMAP_SCOREBOARD, либо USE_SHMGET_SCOREBOARD. Определение одного из этих двух (а также их компаньонов HAVE_MMAP и HAVE_SHMGET соответственно) позволяет использовать предоставленный код общей памяти. Если ваша система имеет другой тип общей памяти, отредактируйте файл src/main/http_main.c и добавьте необходимые крючки для его использования в Apache. (Пожалуйста, присылай нам также патч.)

Историческая справка: Порт Apache для Linux начал использовать общую память только с версии 1.2 Apache. Этот просчёт привёл к очень плохой и ненадежной работе более ранних версий Apache под Linux.

DYNAMIC_MODULE_LIMIT

Если у вас нет намерения использовать динамически загружаемые модули (вероятно, нет, если вы читаете это и настраиваете свой сервер для максимальной производительности), то вы должны добавить -DDYNAMIC_MODULE_LIMIT=0 при построении вашего сервера. Это позволит сэкономить оперативную память, выделенную только для поддержки динамически загружаемых модулей.

Приложение: Детальный анализ трассировки

Вот трассировка системных вызовов Apache 2.0.38 с MPM worker на Solaris 8. Эта трассировка была собрана с помощью:

truss -l -p httpd_child_pid.

Опция -l сообщает утилите truss о регистрации идентификатора LWP (lightweight process — лёгкий процесс, Solaris' форма потоков ядра), который вызывает каждый системный вызов.

Другие системы могут иметь разные утилиты для трассировки системных вызовов, такие как strace, ktrace, или par. Они все производят похожий вывод.

В этой трассировке клиент запросил статический файл размером 10 КБ из httpd. Трассировки нестатических запросов или запросов с обработкой кодировки выглядят совершенно по-другому (и в некоторых случаях довольно некрасиво).

/67:    accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
/67:    accept(3, 0x00200BEC, 0x00200C0C, 1)            = 9

В этой трассировке поток-слушатель работает в рамках LWP #67.

Обратите внимание на отсутствие accept(2) сериализации. На этой конкретной платформе MPM worker по умолчанию использует несериализованный accept, если он не прослушивает несколько портов.
/65:    lwp_park(0x00000000, 0)                         = 0
/67:    lwp_unpark(65, 1)                               = 0

При приёме соединения поток-слушатель разбуживает рабочий поток для обработки запроса. В этой трассировке рабочий поток, обрабатывающий запрос, сопоставлен с LWP #65.

/65:    getsockname(9, 0x00200BA4, 0x00200BC4, 1)       = 0

Для реализации виртуальных хостов Apache необходимо знать локальный адрес сокета, используемый для приёма соединения. В многих ситуациях (например, при отсутствии виртуальных хостов или использовании директив Listen, которые не имеют адресов подстановочных знаков) этот вызов можно устранить. Однако оптимизации ещё не производились.

/65:    brk(0x002170E8)                                 = 0
/65:    brk(0x002190E8)                                 = 0

Вызовы brk(2) выделяют память из кучи. Встретить их в трассировке системных вызовов редко, так как httpd использует собственные менеджеры памяти (apr_pool и apr_bucket_alloc) для большей части обработки запросов. В этой трассировке httpd только что был запущен, поэтому ему необходимо вызвать malloc(3), чтобы получить блоки необработанной памяти для создания собственных менеджеров памяти.

/65:    fcntl(9, F_GETFL, 0x00000000)                   = 2
/65:    fstat64(9, 0xFAF7B818)                          = 0
/65:    getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0
/65:    fstat64(9, 0xFAF7B818)                          = 0
/65:    getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0
/65:    setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0
/65:    fcntl(9, F_SETFL, 0x00000082)                   = 0

Затем рабочий поток переводит соединение с клиентом (дескриптор файла 9) в режим «без блокировки». Вызовы setsockopt(2) и getsockopt(2) — побочный эффект работы Solaris' libc при обработке fcntl(2) для сокетов.

/65:    read(9, " G E T   / 1 0 k . h t m".., 8000)     = 97

Рабочий поток считывает запрос от клиента.

/65:    stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0
/65:    open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10

Этот httpd был настроен с Options FollowSymLinks и AllowOverride None. Таким образом, ему не нужно lstat(2) каждый каталог в пути до запрошенного файла, а также проверять наличие .htaccess файлов. Он просто вызывает stat(2) для проверки, что файл: 1) существует и 2) является обычным файлом, а не каталогом.

/65:    sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C)      = 10269

В этом примере httpd может отправить заголовок HTTP-ответа и запрошенный файл с помощью одного системного вызова sendfilev(2). Семантика sendfile варьируется в разных операционных системах. В некоторых других системах необходимо выполнить вызов write(2) или writev(2) для отправки заголовков перед вызовом sendfile(2).

/65:    write(4, " 1 2 7 . 0 . 0 . 1   -  ".., 78)      = 78

Этот вызов write(2) записывает запрос в журнал доступа. Обратите внимание, что в этой трассировке отсутствует вызов time(2). В отличие от Apache 1.3, Apache 2.x использует gettimeofday(3) для поиска времени. В некоторых операционных системах, таких как Linux или Solaris, gettimeofday имеет оптимизированную реализацию, которая не требует таких же накладных расходов, как типичный системный вызов.

/65:    shutdown(9, 1, 1)                               = 0
/65:    poll(0xFAF7B980, 1, 2000)                       = 1
/65:    read(9, 0xFAF7BC20, 512)                        = 0
/65:    close(9)                                        = 0

Рабочий поток выполняет завершающее закрытие соединения.

/65:    close(10)                                       = 0
/65:    lwp_park(0x00000000, 0)         (sleeping...)

Наконец, рабочий поток закрывает файл, который только что передал, и ожидает, пока слушатель назначит ему другое соединение.

/67:    accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)

Тем временем поток-слушатель может принять другое соединение, как только он передал это соединение рабочему потоку (подчиняясь некоторой логике управления потоком в worker MPM, которая регулирует слушателя, если все доступные рабочие потоки заняты). Хотя из этой трассировки это не очевидно, следующий вызов accept(2) может (и обычно происходит, в условиях высокой нагрузки) параллельно с обработкой рабочего потока только что принятого соединения.

© 2018 The Apache Software Foundation
Licensed under the Apache License, Version 2.0.
https://httpd.apache.org/docs/2.4/en/misc/perf-tuning.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API