Введение в nginx, часть 2: Другие возможности
Данная статья была опубликована в электронном приложением к журналу "Системный администратор"- "Open Source #042"
В первой части статьи я рассказал о базовых и наиболее часто применяемых возможностях nginx. Однако это малая часть того, что можно сделать с nginx. Во второй части своей статьи я расскажу о некоторых более продвинутых возможностях, которые используются в крупных и высоконагруженых проектах.
Простейший способ рещить эту задачу — dns round-robin, т.е. назначение доменному имени нескольких ip-адресов. Но это решение имеет ряд недостатков, и гораздо лучше выглядит решение балансировки запросов по бакендам на фронтенде nginx. В конфигурационном файле выглядит это примерно так:
Запросы, приходящие к nginx, распределяются по бакендам соответственно указаному весу. Кроме того, можно сделать так, чтобы запросы с одних и тех же IP-адресов отправлялись на одни и те же серверы (для этого в upstream нужно указать директиву ip_hash). Так можно решить проблему с сессиями, но все же лучше найти какой-нибудь способ их репликации или (что еще лучше) использовать RESTful-подход.
В случае, если один из серверов откажется принимать соединения или соединение к нему отвалится по таймауту, он на некоторое время будет исключен из upstream.
Для хранения принятых запросов и еще не отданных ответов nginx использует буферы в памяти, а если запрос или ответ не помещается в них, nginx записывает его во временный файл (и пишет при этом предупреждение в log-файл). Поэтому необходимо установить такие размеры, чтобы в большинстве случаев не требовалось обращаться к временному файлу, а с другой стороны — чтобы буферы не использовали слишком много памяти.
Для этого используются следующие параметры:
client_body_buffer_size (по умолчанию: 8k/16k — в зависимости от архитектуры) — задает размер буфера для чтения тела запроса клиента. Обычно стандартного значения хватает, его требуется повышать, только если ваше приложение устанавливает огромные cookies.
proxy_buffer_size (по умолчанию: 4k/8k) — задает размер буфера, в который будет читаться первая часть ответа, получаемого от проксируемого сервера. В этой части ответа находится, как правило, небольшой заголовок ответа. Стандартного значения обычно хватает.
proxy_buffers (по умолчанию: 8 4k/8k) — задает число и размер буферов для одного соединения, в которые будет читаться ответ, получаемый от проксируемого сервера. Установите этот параметр так, чтобы большинство ответов от бэкенда помещалось в буферы.
Есть одна тонкость, касающаяся механизма обработки соединений, а именно — способ получения информации о событиях на сокетах. Существуют следующие методы:
При компиляции nginx автоматически выбирается максимально эффективный найденый метод, однако скрипту configure можно насильно указать какой метод использовать. Если вы решили сделать это, то лучше использовать такие методы:
Linux 2.6: epoll;
FreeBSD: kqueue;
Solaris, HP/UX и другие: /dev/poll;
Linux 2.4 и 2.2: rtsig, не рекомендуется при больших нагрузках.
Существует очень полезный трюк, который позволяет указать разработчикам приложений, какие страницы нужно оптимизировать в первую очередь. Для этого потребуется в конфиге nginx указать новый формат лога:
Переменная $upstream_response_time содержит время ответа бэкенда, поэтому в лог попадает время обработки каждого запроса бэкендом. Далее понадобятся два скрипта:
1. /usr/local/bin/url_stats_report.sh:
2. /usr/local/bin/url_stats.py:
Они не идеальны, но задачу выполняют: запуская /usr/local/bin/url_stats_report.sh (например, в postrotate утилиты logrotate), вы получаете наглядную картину, какие запросы занимают большую часть времени бэкенда.
Однако это все еще экспериментальная функция, но я решил рассказать о ней, т.к. ее одень давно ждали многое администраторы, да и лично для меня она очень полезна.
Сейчас nginx умеет кешировать на диске ответы от http и fastcgi запросов на бакенды, указывать ключ для кеширования, учитывать заголовки "X-Accel-Expires", "Expires" и "Cache-Control" и вручную устанавливать максимальное время жизни объекта в кеше.
Обслуживанием кеша (очиста старых файлов, наблюдение за размером и т.п.) занимается специальный процесс cache manager. Положительной особенностью реализации является то, что при старте nginx cache manager начинает проверку кеша в фоне, благодаря чему nginx не делает то что называется «дает сквида», т.е. он не висит несколько минут проверяя кеш перед стартом.
Я намеренно не указываю пример конфигурации, т.к. во-первых директивы могут еще поменяться, а во-вторых нужно глубокое понимание механизма кеширования, что требует вдумчивого чтения документации (http://sysoev.ru/nginx/docs/http/ngx_http_proxy_module.html#proxy_cache) и архивов рассылки nginx-ru.
В статье освещена лишь та часть возможностей nginx, которыми я пользуюсь чаще всего. За пределами рассказа остались такие вопросы, как поддержка SSI, работа с memcached, экспериментальный встроенный Perl и сторонние модули, реализующие дополнительную функциональность.
В первой части статьи я рассказал о базовых и наиболее часто применяемых возможностях nginx. Однако это малая часть того, что можно сделать с nginx. Во второй части своей статьи я расскажу о некоторых более продвинутых возможностях, которые используются в крупных и высоконагруженых проектах.
Failover и балансировка
Крупные проекты редко состоят из одного сервера приложений. Часто их два или больше, и возникает задача балансировки клиентов по этим серверам, а также выполнения failover — необходимо чтобы выход из строя одного из серверов не был заметен для клиентов.Простейший способ рещить эту задачу — dns round-robin, т.е. назначение доменному имени нескольких ip-адресов. Но это решение имеет ряд недостатков, и гораздо лучше выглядит решение балансировки запросов по бакендам на фронтенде nginx. В конфигурационном файле выглядит это примерно так:
# объявляем upstream — список бакендов
upstream backend {
# перечисляем dns-имена или ip-адреса серверов и их «вес»
server web1 weight=5;
server 1.2.3.4:8080 weight=5;
# а так можно подключаться к бакенду через unix-сокет
server unix:/tmp/backend3 weight=1;
}
# конфигурация виртуального сервера
server {
listen <...>;
server_name myserver.com;
# отправляем все запросы из локейшена / в апстрим
location / {
proxy_pass http://backend;
}
}
Запросы, приходящие к nginx, распределяются по бакендам соответственно указаному весу. Кроме того, можно сделать так, чтобы запросы с одних и тех же IP-адресов отправлялись на одни и те же серверы (для этого в upstream нужно указать директиву ip_hash). Так можно решить проблему с сессиями, но все же лучше найти какой-нибудь способ их репликации или (что еще лучше) использовать RESTful-подход.
В случае, если один из серверов откажется принимать соединения или соединение к нему отвалится по таймауту, он на некоторое время будет исключен из upstream.
Оптимизация nginx
1. Увеличение количества и объема буферов
Для хранения принятых запросов и еще не отданных ответов nginx использует буферы в памяти, а если запрос или ответ не помещается в них, nginx записывает его во временный файл (и пишет при этом предупреждение в log-файл). Поэтому необходимо установить такие размеры, чтобы в большинстве случаев не требовалось обращаться к временному файлу, а с другой стороны — чтобы буферы не использовали слишком много памяти.
Для этого используются следующие параметры:
client_body_buffer_size (по умолчанию: 8k/16k — в зависимости от архитектуры) — задает размер буфера для чтения тела запроса клиента. Обычно стандартного значения хватает, его требуется повышать, только если ваше приложение устанавливает огромные cookies.
proxy_buffer_size (по умолчанию: 4k/8k) — задает размер буфера, в который будет читаться первая часть ответа, получаемого от проксируемого сервера. В этой части ответа находится, как правило, небольшой заголовок ответа. Стандартного значения обычно хватает.
proxy_buffers (по умолчанию: 8 4k/8k) — задает число и размер буферов для одного соединения, в которые будет читаться ответ, получаемый от проксируемого сервера. Установите этот параметр так, чтобы большинство ответов от бэкенда помещалось в буферы.
2. Механизмы обработки соединений
Есть одна тонкость, касающаяся механизма обработки соединений, а именно — способ получения информации о событиях на сокетах. Существуют следующие методы:
- select — стандартный метод. На большой нагрузке сильно нагружает процессор.
- poll — стандартный метод. Также сильно нагружает процессор.
- kqueue — эффективный метод, используемый в операционных системах FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 и Mac OS X. На 2-процессорных машинах под управлением Mac OS X использование kqueue может привести к kernel panic.
- epoll — эффективный метод, используемый в Linux 2.6+. В некоторых старых дистрибутивах есть патчи для поддержки epoll ядром 2.4.
- rtsig — real time signals, эффективный метод, используемый в Linux 2.2.19+. При больших количествах одновременных соединений (более 1024) с ним могут быть проблемы (их можно обойти, но на мой взгляд, лучше с этим не связываться).
- /dev/poll — эффективный метод, используемый в Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ и Tru64 UNIX 5.1A+.
При компиляции nginx автоматически выбирается максимально эффективный найденый метод, однако скрипту configure можно насильно указать какой метод использовать. Если вы решили сделать это, то лучше использовать такие методы:
Linux 2.6: epoll;
FreeBSD: kqueue;
Solaris, HP/UX и другие: /dev/poll;
Linux 2.4 и 2.2: rtsig, не рекомендуется при больших нагрузках.
Включение gzip позволяет сжимать ответ, отправляемый клиенту, что положительно сказывается на удовлетворенности пользователя, но требует больше времени CPU. Gzip включается директивой gzip (on|off). Кроме того, стоит обратить на следующие важные директивы модуля gzip:
gzip_comp_level 1..9 — устанавливает уровень сжатия. Опытным путем выявлено, что оптимальные значения лежат в промежутке от 3 до 5, большие значения дают маленький выигрыш, но создают существенно большую нагрузку на процессор, меньшие — дают слишком маленький коэффициент сжатия.
gzip_min_length (по умолчанию, 0) — минимальный размер ответа, который будет сжиматься. Имеет смысл поставить этот параметр в 1024, чтобы слишком малеьнике файлы не сжимались (т.к. эффективность этого будет мала).
gzip_types mime-тип [mime-тип ...] - разрешает сжатие ответа методом gzip для указанных MIME-типов в дополнение к "text/html". "text/html" сжимается всегда. Имеет смысл добавить такие mime-типы как text/css, text/javascript и подобные. Разумеется, сжимать gif, jpg и прочие уже компрессированые форматы не имеет смысла.
Кроме того, существует модуль gzip_static, который позволяет раздавать уже сжатые статические файлы. В конфирурационном файле это выглядит так:
location /files/ {
gzip on;
gzip_min_length 1024;
gzip_types text/css text/javascript;
gzip_comp_level 5;
gzip_static on;
}
При использовании такой конфигурации в случае запроса «/files/test.html» nginx будет проверять наличие «/files/test.html.gz», и, если этот файл существует и дата его последнего изменения больше, чем дата последнего изменения файла test.html, будет отдан уже сжатый файл, что сохранит ресурсы процессора, которые потребовались бы для сжатия оригинального файла.
gzip on;
gzip_min_length 1024;
gzip_types text/css text/javascript;
gzip_comp_level 5;
gzip_static on;
}
Оптимизация приложений
Существует очень полезный трюк, который позволяет указать разработчикам приложений, какие страницы нужно оптимизировать в первую очередь. Для этого потребуется в конфиге nginx указать новый формат лога:
log_format my_combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$upstream_response_time "$host"'
access_log /var/log/nginx/access_log my_combined;
Переменная $upstream_response_time содержит время ответа бэкенда, поэтому в лог попадает время обработки каждого запроса бэкендом. Далее понадобятся два скрипта:
1. /usr/local/bin/url_stats_report.sh:
#!/bin/sh
echo "=== Requests which took most of the time ===" > /tmp/report.txt
echo "overall time - number of requests - average time - url" >> /tmp/report.txt
cat /var/log/nginx/*access.log | /usr/local/bin/url_stats.py >> /tmp/report.txt
cat /tmp/report.txt | mail -s "url performance report" root
2. /usr/local/bin/url_stats.py:
#!/usr/bin/env python
import sys
urls = {}
try:
while 1:
line = raw_input()
line_arr = line.split(" ")
try:
host = line_arr[-1]
host = host[1:]
host = host[:-1]
url = line_arr[6]
t = float(line_arr[-2])
#print host, url, t
try:
urls[host + url] = (urls[host + url][0] + t, urls[host + url][1] + 1)
except KeyError, e:
urls[host + url] = (t, 1)
except ValueError, e:
pass
except EOFError, e:
pass
def sort_by_value(d):
""" Returns the keys of dictionary d sorted by their values """
items=d.items()
backitems=[ [v[1],v[0]] for v in items]
backitems.sort(reverse=True)
return [backitems[i][1] for i in range(0,len(backitems))]
if (len(sys.argv) > 1):
f = open(sys.argv[1], 'r')
for k in f.readlines():
k = k.strip()
try:
print urls[k][0], urls[k][1], urls[k][0] / urls[k][1], k
except:
print 0, 0, k
else:
i = 0
for k in sort_by_value(urls):
print urls[k][0], urls[k][1], urls[k][0] / urls[k][1], k
i += 1
if i > 100: break
Они не идеальны, но задачу выполняют: запуская /usr/local/bin/url_stats_report.sh (например, в postrotate утилиты logrotate), вы получаете наглядную картину, какие запросы занимают большую часть времени бэкенда.
Кеширование
Незадолго до выхода этой статьи, Игорь Сысоев выпустил версию nginx 0.7.44 с экспериментальной поддержкой кеширования. Из-за большого количества багов, за которкое время было выпущено несколько версий, и в настоящее время последняя версия — 0.7.50 уже достаточно хорошо (хотя и не идеально) работает с кешированием.Однако это все еще экспериментальная функция, но я решил рассказать о ней, т.к. ее одень давно ждали многое администраторы, да и лично для меня она очень полезна.
Сейчас nginx умеет кешировать на диске ответы от http и fastcgi запросов на бакенды, указывать ключ для кеширования, учитывать заголовки "X-Accel-Expires", "Expires" и "Cache-Control" и вручную устанавливать максимальное время жизни объекта в кеше.
Обслуживанием кеша (очиста старых файлов, наблюдение за размером и т.п.) занимается специальный процесс cache manager. Положительной особенностью реализации является то, что при старте nginx cache manager начинает проверку кеша в фоне, благодаря чему nginx не делает то что называется «дает сквида», т.е. он не висит несколько минут проверяя кеш перед стартом.
Я намеренно не указываю пример конфигурации, т.к. во-первых директивы могут еще поменяться, а во-вторых нужно глубокое понимание механизма кеширования, что требует вдумчивого чтения документации (http://sysoev.ru/nginx/docs/http/ngx_http_proxy_module.html#proxy_cache) и архивов рассылки nginx-ru.
За кадром
В статье освещена лишь та часть возможностей nginx, которыми я пользуюсь чаще всего. За пределами рассказа остались такие вопросы, как поддержка SSI, работа с memcached, экспериментальный встроенный Perl и сторонние модули, реализующие дополнительную функциональность.
Комментарии