Максим Мошков. О создании жизнестойкого веб-сервера
---------------------------------------------------------------
Тезисы для семинара WebClub
Date: 17 Nov 1999
---------------------------------------------------------------
Обычный интелевый хост Linux или FreeBSD с апачем в состоянии
обслужить 100-150 статических реквестов в секунду.
Это 7Млн реквестов в сутки, что соответствует 200 тысяч посетителей
в сутки. Трафик генерится при этом 1-2М в секунду.
Вопрос, надо ли вам большего?
300 MaxClients соответствует 3M запросов в сутки, 60 тысяч человек.
400М RAM
Пример Lenta.Ru. День выборов.
400Мб RAM, MaxClients 512, Timeout 120, CacheTime 900
500,000 cgi-html-реквестов + 5Млн img-файлов
в час пик 35,000 запросов, 540 одновременных httpd, load 8-15
swapa не было
Диск: только SCSI.
Вот и все, что можно требовать от машины.
И НИКАКОГО swap! (Имеется ввиду, что у веб-сервера swapобласть быть должна,
но она обязана быть пустой)
ВСЕГДА СТАВИТЬ Last-Modified АТРИБУТ В ВЫДАЧУ CGI-СКРИПТОВ
- документ без временного штампа не сохраняется в локальном
кэше, и постоянно перезасасывается при просмотре
Переименовать свою директорию CGI-скриптов из cgi-bin во
что-нибудь другое
- Прокси-серверы не кэшируют URL вида
http://host.name/cgi-bin/file/name.txt и каждый раз вынуждены
обращаться к вам на сервер.
Всегда устанавливать поле Last-modified у Русского-Апача с
автоматическим угадыванием кодировки
+ Да, если не взводить это поле, то на proxy-серверах не застрянут
файлы в неккоректной кодировке.
- Но насколько напрягутся все остальные юзеры (а их >95%), и сам
веб-сервер...
CharsetDisableForcedExpires on
CacheNegotiatedDocs
Не применять авторедирект по чарсету в русском Apache
CharsetNormalizeToUrl none
CharsetAutoRedirect koi8-r none
CharsetAutoRedirect windows-1251 none
Хранить документы на сервере в кодировке windows-1251
CharsetSourceEnc koi8-r
+ Поскольку 95% посетителей живут в этой кодировке, для них серверу
не потребуется перекодировать документы.
- rus-apach _всегда_ перекодирует документ. Даже win в win
Файлам с SSI сервер сбрасывает Last-Modified, но это лечится
SSI - порождает дополнительную нагрузку. Лучше выделить их только на
отдельное расширение .shtml, и не трогать чистостатические .htm и .html.
В конфигуре сервера есть директива, возвращающая Last-Modified SSI-файлам
XBitHack full
и выполнить
chmod 755 *.shtml
Фреймы не использовать
Усложняют программирование и добавляют лишние реквесты:
(двухфреймовая страница - 3 файла вместо одного!)
Не делать суперобложек, максимум info в головную страницу
Лишний клик, потеря посетителей, снижение глубины просмотра.
НИКАКИХ ANIMATED-ГИФОВ
- Из-за ошибки в Netscape-навигаторе он постоянно перезапрашивает
animated-гиф по сети, посылая запрос на сервер каждые 10-15 секунд
Представьте, что на вашу страницу с 10 анимированными гифами зашло
двадцать Netscape и просто смотрят на нее ни во что не кликая.
Netscap'ы сами начнут слать вашему серверу IFMS-запросы в темпе
20 запросов в секунду.
Лишние имаджи = потерянные деньги
+ Многие хостеры не берут денег за траффик и размеры графики можно не
считать.
- Но часто включают счетчик на _входящий_ зарубежный траффик.
Помните, что сам HTTP-реквест от зарубежного посетителя - _входящий_
Всего-то в нем 200-300 байт. Но если у вас на каждой страничке по
20 гиф-файлов с оформлением, то один HTML-клик из-за заграницы обойдется
в 4Кб входного трафика. Помножим на 10 тысяч страничек в день, да на
30 дней - 1.2Gb - входящей зарубежки. 100-200 баксов - как с куста.
Лишние имаджи = замедленный отклик и потерянные посетители
- Много дополнительных реквестов за графикой забивают входную очередь,
переполняя MaxClients, более приоритетные запросы на обычные html
вынуждены стоять в общей очереди, задерживая отклик до 10-30 секунд.
+ Отнести всю графику на отдельный порт, и на него повесить "худой"
отдельный веб-сервер, который может только обслуживать статические
файлы и ничего кроме. В нем - сокращенный TimeOut, и меньше
жрется виртуальной памяти.
+ khttpd для Linux - работает как модуль ядра - с минимальным оверхедом.
http://www.fenrus.demon.nl/index.html
+ thttpd - держит до 2000 реквестов/сек без ограничения числа коннектов
под FreeBSD на нем сделан images.rambler.ru, под Linux глючит
http://www.acme.com -> freeware
Mathopd (на нем сделан top.list.ru)
+ Размышления/советами по поводу производительных http-серверов:
.htaccess в юзерских директориях отменить
Делаем
AllowOverride None
иначе сервер при открытии любого документа будет последовательно
шерстить все вышестоящие директории на предмет наличия в них .htaccess
- Сошедший с ума робот собирает невероятное количество 404 ошибок,
зацикливаясь в них на веки
404 код не делать cgi-скриптом
404 код не делать "красивым" - с гифчиками и указаниями на прочие разделы
robots.txt
Обязательно делать файл robots.txt, потому что он - наиболее запрашиваемый
на сервере документ, и иначе порождает массу 404 - см. выше, особенно
если 404 - cgi-скрипт
Разумные роботы слушаются запретов в файле robots.txt
# "Скажем НЕТ offline-качалкам
User-Agent: DISCo Pump, Wget, WebZIP, Teleport Pro, WebSnake, Offline Explorer, Web-By-Mail
Disallow: /
Управление доступом через httpd.conf
Пример перекрывает доступ к нашим .zip файлам если их
линкуют не с наших страниц а снаружи.
SetEnvIfNoCase Referer lib\.ru internal_referer
SetEnvIfNoCase User-Agent Teleport internal_referer
SetEnvIfNoCase User-Agent Vampire internal_referer
SetEnvIfNoCase User-Agent ReGet internal_referer
SetEnvIfNoCase User-Agent GetRight internal_referer
SetEnvIfNoCase User-Agent Wget internal_referer
<Files ~ "\.zip$">
ErrorDocument 403 http://lib.ru/books/index.htm
order deny,allow
deny from all
allow from env=internal_referer
</Files>
Развивать его можно по разным направлениям: по разному обрабатывать разных
Usн er-Agent, проверять IP-клиента и многое другое, и главное, что все это
делается не в cgi-скрипте, а на уровне базового httpd - а значит дешево
обходится серверу.
Если робот упорствует, его уничтожают
route add -host 123.456.789.1 gw localhost
Если на на mod_rewrite, как то так - по условиям -
RewriteCond %{HTTP_USER_AGENT} Teleport [NC,OR]
RewriteCond %{HTTP_USER_AGENT} MSIECrawler [NC,OR]
RewriteCond %{HTTP_USER_AGENT} DISCoFinder [NC,OR]
RewriteCond %{HTTP_USER_AGENT} WebCrawler [NC,OR]
RewriteCond %{HTTP_USER_AGENT} spider [NC,OR]
все запросы от известных роботов на динамические страницы перенаправляются
на статическую заглушку
RewriteRule ^/news.html? /static_index.html [R]
NC = No Case
R = redirect
L = Last rule
Например - переадресовка всех внешних рефероров на архивы - на морду сайта
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^http://(www\.lib\.ru/)|(lib\.ru/).*$ [NC]
RewriteBase /home/lib-www/docs/
RewriteRule ^arc/.*\.(zip)|(rar)$ http://www.lib.ru/ [R]
RewriteCond %{HTTP_REFERER} !^http://(www\.lib\.ru/)|(lib\.ru/).*$ [NC]
RewriteBase /home/lib-www/docs/
RewriteRule ^index2\.html$ http://www.lib.ru/ [R]
Или так:
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://allowed-site1.com*$ [NC]
RewriteCond %{HTTP_REFERER} !^http://www.allowed-site1.com*$ [NC]
RewriteCond %{HTTP_REFERER} !^http://allowed-site2.com*$ [NC]
RewriteCond %{HTTP_REFERER} !^http://www.allowed-site2.com.*$ [NC]
RewriteRule ^.*$ http://site.com/another_pic.gif [R,L]
Даже так:
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?domen.ru/.*$ [NC]
RewriteRule \.(gif|jpg)$ http://www.domen.ru/fuck_off.gif [R,L]
RewriteEngine on
RewriteCond %{REMOTE_ADDR} !^81.19.69.21$
RewriteRule ^(/n/.*) https://lenta.ru$1 [R,L]
RewriteEngine on
RewriteCond %{REMOTE_ADDR} !^81.19.69.28
RewriteCond %{REMOTE_ADDR} !^81.19.68.[6-9]
RewriteCond %{REMOTE_ADDR} !^81.19.68.1[012].
RewriteRule ^(/N/.*) https://lenta.ru$1 [R,L]
# Allow from 81.19.68.64/255.255.255.224
Не ставьте баннеры на самый верх
- Баннер сверху отнимает 1-2 реквеста из 4 - и в итоге грузится вперед
тормозя ваши сайтовые картинки
+ в ссылке на img src баннера вместо hostname ставьте IP - сэкономите
посетителю dns-ресолвинг - а это 2-30 секунд.
Задержка в загрузке "вашего" содержимого - держит у _вас_ лишние httpd
Ни в коем случае не делать uniq-url для баннера с помощью SSI virtual cgi
include. Потому что ps -axf покажет вам:
12858 ? S 0:00 \_ /usr/local/apache/sbin/httpd
12859 ? S 0:00 \_ /usr/local/apache/sbin/httpd
12862 ? S 0:00 \_ /usr/local/apache/sbin/httpd
13097 ? Z 0:00 | \_ (rand.cgi <zombie>)
13098 ? Z 0:00 | \_ (rb2 <zombie>)
13103 ? Z 0:00 | \_ (rb2 <zombie>)
13104 ? Z 0:00 | \_ (c4.pl <zombie>)
13105 ? Z 0:00 | \_ (random.cgi <zombie>)
12863 ? S 0:00 \_ /usr/local/apache/sbin/httpd
12868 ? S 0:00 \_ /usr/local/apache/sbin/httpd
Вместо этого использовать var - дату
<!--#config timefmt="%H%w%e%M%S"-->
<a href=http://rb2.design.ru/cgi-bin/href/nit?<!--#echo var="date_local"-->
target="_top">
<!--#config timefmt="%M%H%S%I%e"-->
<a href=http://www1.reklama.ru/cgi-bin/href/nit?<!--#echo var="date_local"-->
target=_top>
<img src=http://www1.reklama.ru/cgi-bin/banner/nit?<!--#echo var="date_local"-->
width=468 height=60 border=0 vspace=10
alt="www.reklama.ru. The Banner Network." ismap></a>
Tx3 предлагает внутреннюю подкачку баннера: это лишний cgi-скрипт,
затем из скрипта делает обращение к баннерному движку - это задержка
при генерации html, а значит - больше httpd висящих в памяти.
200 тысяч в секунду = 3 скрипта в секунду
30 static в секунду =
suexec - запуск cgi-скриптов под юзерским id - да, повышает безопасность, но
удваивает число fork+exec при запуске любого cgi-скрипта. Избегайте
насколько это возможно.
Следить, что вкомпилировано в httpd. Да, конечно код в unix реентерабельный,
но ведь у modperl и php3 огромные области инициализируемых данных - все это
жрет виртуальную память, и время на обработку одного запроса, да и просто
проверка hoock'ов, на которую подвешены модули отнимает время. Стоит ли
обрабатывать 100 статических httpd-запросов, для обслуживания которых
достаточно одного модуля default с помощью 5M монстра с вкомпилированными в
него modperl, php3, ssl httpd - которых за это же время потребуется 2-5. Из
100.
Конечно лучший язык для написания cgi-скриптов - perl. Но он безжалостен к
серверу.
Перл-скрипты - компилируются при каждом вызове. Скорость компиляции сильно
зависит, но все равно - это примерно 0.1 сек на 20Kb perl-кода. Мораль -
даже без учета на время работы собественно программы 60Kb скрипт сможет
выполниться не чаще чем 2-3 раза за секунду!
Как выкручиваться из положения?
Разбить большой скрипт на много мелких составных частей и подключать их
только когда указанный кусок кода требуется при данном случае исполнения
кода. Для этого в perl используется оператор "require" (Это грамотный аналог
include - грамотность заключена в том, что реяуире - исполнимый оператор, и
затягивает дополнительный код только когда он затребован, а при повторном
исполнении require он его НЕ перекомпилирует повторно)
Прекомпиляция перл. Perl2C. modperl. FastCGI...
Кэширование.
Можно сохранять результат работы скрипта в кэшфайле и при повторных запроса
выдавать его вместо повторной генерации.
По субъективным ощущениям кэш файл лучше выдавать не самим скриптом
open IN $file; while(){print;}
а внутренним редиректом
print "Location: http:$file\n\n";
Кэширование с помощью squid в режиме proxy-accelerator
Пожалуй, лучшее решение, если надо ускорять cgi-скриптовый сервер. Скорость
и нагрузка на машину у squid-accelerator совпадает с работой httpd отдающего
статические html и image файлы. А нагрузку на cgi-движок он снижает в 2-3
раза.
Squid сможет поддерживать директивы IfModifiedSince и REGET для содержимого
скрипта, что, понятное дело самому в скрипте делать очень невесело.
Машины стояли мордами друг к другу так, что выезжающая
подставка для кофе одного нажимала на кнопку Reset второго,
и наоборот.
Предыдущая реинкарнация моей lib.ru жила в одном корпусе с другой
машиной. Была у них внутри на коленке паяная схема-самоделка, которая
позволяла питание передернуть соседу.
А вообще для подобных вещей обычный смарт-UPS лучше всего подходит. А
компорт от УПСа надо заводить либо на киску, ибо они не дохнут, либо на
модем и звонить на него из дома.
From: Exler
Поскольку охранник раза три за ночь обходил помещение на предмет
возгорания (заходил в комнату, включал свет, обозревал помещение, выключал
свет и уходил), к выключателю на ночь присоединялась кнопка, которая при
нажатии на выключатель автоматически ресетила машину.
Конфигурационные параметры влияющие на скорость.
Options FollowSymLinks - позволяет не проверять симлинки
AllowOverride all - позволяет не искать .htaccess во всех поддиректориях
Очень важно! На сервере с большой посещаемостью: 1. Картинки снести на
выделенный сервер(порт) (или отдельный процесс сервера), и отключить
KeepAlive Off
Поскольку Alive используется только для подкачки картинок, а для хтмля
броузер все равно открывает новый коннект. С KeepAlive каждый сервер обслужив
прос еще 15 секунд болтается в памяти ожидая, не придет ли новый запрос на
картинку - увеличивая количество процессов раза в 4.
Переезд сервера, смена его IP-адреса
Старый IP-адрес сидит в кэшах DNS довольно долго (официально - до 8 часов,
реально - до двух с лишним суток). Все это время многие клиенты идут по
старому IP, на котором их уже никто не ждет - потери посетителей во время
"устаканивания DNS достигают от 20 до 60%.
Выход: двухшаговая смена ИП с использованием редиректов.
1. Шаг. За два дня до реальной смены IP поднимаем на новом IP виртуальный
вебсервер-заглушку, который быдет откликаться на www.washserver.ru, а в его
конфигуре ставим редирект всех запросов на http://washserver.ru
httpd.conf на новом IP-адресе:
<VirtualHost Новый-IP:*>
ServerName www.washserver.ru
Redirect / http://washserver.ru/
</VirtualHost>
DNS-зона домена washserver.ru:
@ ИН А старый-IP
www IN A новый-IP
После этого прописываем в DNS для www.washserver.ru новый IP,
а washserver.ru оставляем старым.
Посетители, пришедшие на www.washserver.ru будут редиректиться на
washserver.ru - т.е. мы никого не потеряем, и ждем 2 суток, пока "разойдется"
новый IP для www.washserver.ru
Через 2 суток 2 шаг. Реальная смена IP у сервера. Одновременно с этим:
На старом IP поднимаем виртуальный вебсервер-заглушку, который будет
откликаться на washserver.ru, и делать редирект всех запросов на
http://www.washserver.ru
В DNS прописываем washserver.ru на новый IP
Посетители, пришедшие по старому ИП на washserver.ru будут редиректиться на
www.washserver.ru с новым ИП - т.е. мы никого не потеряем. А через 2 суток
новый IP для имени washserver.ru разойдется по DNS и редирект можно будет
снять.
httpd.conf на старом IP-адресе:
<VirtualHost Старый-IP:*>
ServerName washserver.ru
Redirect / http://www.washserver.ru/
</VirtualHost>
DNS-зона домена washserver.ru:
@ ИН А новый-IP
www IN A новый-IP
Last-modified: Tue, 12 Apr 2005 05:24:00 GMT