Настройка производительности 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 быстрее отдавать статическое содержимое с меньшей загрузкой ЦП.
Настройка во время выполнения
| Связанные модули | Связанные директивы |
|---|---|
HostnameLookups и другие вопросы DNS
До Apache 1.3 для HostnameLookups по умолчанию использовалось значение On. Это увеличивает задержку каждого запроса, поскольку перед его завершением требуется выполнить поиск DNS. В Apache 1.3 для этого параметра по умолчанию используется значение Off. Если вам нужно, чтобы адреса в файлах журналов разрешались в имена узлов, используйте программу logresolve, поставляемую с Apache, или один из многочисленных доступных пакетов для составления отчётов по журналам.
Рекомендуется выполнять такую постобработку файлов журналов на компьютере, отличном от рабочего веб-сервера, чтобы эта деятельность не оказывала отрицательного влияния на производительность сервера.
Если вы используете директивы или Allow from domain (то есть указываете имя узла или доменное имя, а не IP-адрес), потребуется выполнить два поиска DNS (обратный, а затем прямой, чтобы убедиться, что обратный поиск не подделан). Поэтому для наилучшей производительности, если возможно, указывайте в этих директивах IP-адреса, а не имена.Deny from domain
Обратите внимание, что директивы можно ограничить областью действия, например, поместив их в раздел <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 или даже нарушить его стабильность:
-
В некоторых операционных системах
mmapмасштабируется хуже, чем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 почти не требуется. Если за секунду создаётся более четырёх дочерних процессов, в ErrorLog выводится сообщение. Если таких ошибок много, рассмотрите возможность настройки этих параметров. Используйте вывод mod_status в качестве ориентира.
С созданием процессов связана их остановка, вызванная параметром MaxConnectionsPerChild. По умолчанию используется значение 0, означающее отсутствие ограничения на число соединений, обрабатываемых одним дочерним процессом. Если сейчас для этого параметра задано очень низкое значение, например 30, возможно, стоит значительно его увеличить. Если вы используете SunOS или старую версию Solaris, из-за утечек памяти ограничьте его значением около 10000.
При использовании постоянных соединений дочерние процессы будут заняты ожиданием новых запросов по уже открытому соединению. Значение 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, а также последние сборки MPM worker в разработке используют атомарный API APR. Этот API предоставляет атомарные операции, которые можно применять для облегчённой синхронизации потоков.
По умолчанию APR реализует эти операции наиболее эффективным способом, доступным на каждой целевой платформе ОС и ЦП. Например, во многих современных ЦП есть инструкция для аппаратной атомарной операции сравнения и обмена (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-atomicsAPR создаёт код, использующий инструкцию SPARC v8plus для быстрой аппаратной операции сравнения и обмена. Если настроить Apache с этим параметром, атомарные операции будут выполняться эффективнее (с меньшей загрузкой ЦП и более высокой степенью параллелизма), но полученный исполняемый файл будет работать только на чипах UltraSPARC. - Linux на x86
По умолчанию APR использует атомарные операции на основе мьютексов в Linux. Однако при настройке с параметром--enable-nonportable-atomicsAPR создаёт код, использующий инструкцию 486 для быстрой аппаратной операции сравнения и обмена. Это повысит эффективность атомарных операций, но полученный исполняемый файл будет работать только на чипах 486 и новее (но не на 386).
mod_status и ExtendedStatus On
Если подключить mod_status и при сборке и запуске Apache задать ExtendedStatus On, то для каждого запроса 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, после чего серверы с одним сокетом не будут использовать сериализацию.
Корректное закрытие соединения
Как сказано в разделе 8 документа draft-ietf-http-connection-00.txt, чтобы 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. (И, пожалуйста, пришлите нам исправление.)
DYNAMIC_MODULE_LIMIT
Если вы не собираетесь использовать динамически загружаемые модули (а скорее всего, так и есть, раз вы читаете этот документ и настраиваете сервер ради каждой крупицы производительности), добавьте -DDYNAMIC_MODULE_LIMIT=0 при сборке сервера. Это позволит сэкономить ОЗУ, которая выделяется исключительно для поддержки динамически загружаемых модулей.
Приложение: подробный разбор трассировки
Ниже приведена трассировка системных вызовов Apache 2.0.38 с MPM worker в Solaris 8. Трассировка собрана с помощью:
truss -l -p httpd_child_pid.
Параметр -l указывает truss записывать идентификатор LWP (облегчённого процесса — разновидности потока на уровне ядра в Solaris), который вызывает каждый системный вызов.
В других системах для трассировки системных вызовов могут использоваться другие инструменты, например strace, ktrace или par. Все они выдают похожие результаты.
В этой трассировке клиент запросил у httpd статический файл размером 10 КБ. Трассировки динамических запросов или запросов с согласованием содержимого выглядят совершенно иначе (а иногда и довольно неприглядно).
/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) являются побочным эффектом обработки fcntl(2) для сокетов в libc Solaris.
/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 различается в разных операционных системах. В некоторых других системах для отправки заголовков перед вызовом sendfile(2) необходимо выполнить вызов write(2) или writev(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...)
Тем временем поток-слушатель может принять другое соединение сразу после того, как передаст это соединение рабочему потоку (с учетом логики управления потоком в MPM worker, которая ограничивает работу потока-слушателя, если все доступные рабочие потоки заняты). Хотя из этой трассировки это не очевидно, следующий вызов 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