Nginx - Apache killer
Posted by Jarosław Zabiełło Tue, 07 Nov 2006 23:47:00 GMT
W ostatnim artykule (Railsy: Lighttpd czy Apache 2.2.x?) porównywałem najbardziej popularne serwery HTTP dla Railsów. Zaintrygowany paroma wpisami w blogu postanowiłem przyjrzeć się dosyć mało znanemu serwerowi HTTP który zaczyna zdobywać coraz więcej uwagi na Zachodzie. Chodzi o ultraszybki serwer nginx napisany przez rosyjskiego programistę Igora Sysojewa.
Na pierwszy bój poszedł prosty test wyświetlenia “Hello World!” Na używanym przeze mnie serwerze dedykowanym (Athlon 64 3000+, 1GB RAM, Linux Ubuntu 64bit) dla 100 tys. zapytań (musiałem użyć aż tyle, bo serwer jest za szybki na mniejszą liczbę zapytań) uzyskałem następujące wyniki (dla polecenia “ab -n 100000 -c 1 http://localhost”):
- Apache 2.2.3 = 4439 req/s
- Lighttpd 1.4.11 = 7150 req/s
- Nginx 0.4.12 = 8700 req/s
Co prawda Nginx wykazuje miażdzącą przewagę wydajności nad Apachem ale, z racji tego, że używam Lighttpd, który co prawda odstaje od Nginxa ale nie aż tak, postanowiłem na razie zaczekać z ewentualną migracją.
Okazało się jednak, że będę musiał przeprowadzić taką migrację szybciej niż bym chciał. Coś złego zaczęło się dziać z Ligthttpd. Po paru godzinach pracy, przestawał odpowiadać na zapytania a nawet w ogóle proces znikał z pamięci. Ki diabeł? Trudno powiedzieć, nie mam czasu aby analizować dokładniej problem. Jedyne co pomagało to regularny restart Lighttpd. Trochę głupie rozwiązanie. Postanowiłem zatem zrobić wcześniejszą migrację do Nginxa. Wg statystyk Netcraftu z Nginxa korzysta już ponad 90 tys. domen. Wydaje się to wystarczającą ilością aby można było uznać ten serwer za stabilny.
Jednakże moja migracja ma pewną trudność. Używam bowiem równocześnie PHP, Django, Rails i Zope (ściślej: Plone). Czyli całkiem niezła mieszanka aplikacji. Z PHP i Railsami było najmniej problemów, bo przykłady konfiguracji są z grubsza podane w Wiki.
Z Plone było troszkę gorzej. Musiałem bowiem znaleźć odpowiednik mniej więcej takiego kodu w Apache:
RewriteRule "^/(.*)$"
"http://88.198.15.160:6001/VirtualHostBase/http/apologetyka.com:80/app/VirtualHostRoot/$1" [P,L]To nie jest zwykły rewrite, to jest połączenie proxy z rewrite.
W Lighttpd (też się swego czasu namęczyłem aby to uzyskać) uzyskuje się to tak:
url.rewrite-once = (
"^/(.*)$" => "/VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1"
)
proxy.server = (
"/VirtualHostBase/" => (
( "host" => "88.198.15.160" , "port" => 6001 ) )
)location / {
rewrite ^/(.*)$ /VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1;
}
location /VirtualHostBase/ {
include /opt/nginx/conf/proxy.conf;
proxy_pass http://88.198.15.160:6001;
}Najtrudniej było z konfiguracją Django bo opisu do Django na Nginx nie ma ani w dokumentacji do Django, ani w dokumentacji do NGinxa. Zmęczony eksperymentowaniem napisałem na listę dyskusyjną Django i dostałem całkiem pożyteczną wskazówkę odnośnie strony http://www.python.rk.edu.pl/w/p/django-pod-serwerem-nginx/. Niestety miałem pecha, bo akurat trafiłem na zmianę wpisów w DNS i artykuł był niedostępny. Udało mi się na szczęście wyłuskać jego kopię z cache Googli. Autor międzyczasie podesłał mi też pliki z artykułami. Zauważyłem że napotkał pewne problemy ze zmuszeniem Django do wyświetlania statycznej treści. Trochę podłubałem w kodzie i problem rozwiązałem. :)
Zauważyłem że Django wyświetlał mi pliki statyczne w trybie debug. Wynikało to pewnie z tego, że w urls.py stosuję zawsze taki wpis:
...
if DEBUG:
urlpatterns += patterns('',
(r'^images/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/images', 'show_indexes': True}),
(r'^stylesheets/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/stylesheets', 'show_indexes': True}),
(r'^javascripts/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/javascripts', 'show_indexes': True}),
)Dla trybu produkcyjnego (settings.DEBUG=False) trzeba zmusić serwer HTTP aby się zajmował plikami statycznymi. Django ma tylko przetwarzać Pythona. Mozna to zrobić np. tak:
...
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
access_log off; # po co mi logi obrazków :)
expires 30d;
}
location / {
include /opt/nginx/conf/fastcgi.conf;
fastcgi_pass 127.0.0.1:6002;
fastcgi_pass_header Authorization;
fastcgi_intercept_errors off;
}Gdzie plik fastcgi.conf:
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param PATH_INFO $fastcgi_script_name;Plik startowy napisałem sobie już w Pythonie:
#!/usr/bin/env python
import os, sys, time
DEBUG = True
# All Django project are inside /home/app/django/
path = '/home/app/django'
projects = {
'biblia' : {
'project': 'searchers',
'port':6002,
'pidfile': '/var/run/django_searchers.pid',
'children': 4,
},
'koran': {
'project': 'django_project',
'port':6003,
'pidfile': '/var/run/django_koran.pid',
'children': 4
},
}
def start(name):
project = projects[name]['project']
os.chdir('%s/%s/' % (path, project))
appl = './manage.py runfcgi host=127.0.0.1'
cmd = '%s port=%s minspare=1 maxspare=%s pidfile=%s --settings=%s.settings' \
% (appl, projects[name]['port'], projects[name]['children'], projects[name]['pidfile'], project)
if DEBUG: print cmd
os.system(cmd)
def stop(name):
pidfile = projects[name]['pidfile']
if os.path.exists(pidfile):
cmd = '/bin/kill -TERM %s' % open(pidfile).read()
if DEBUG: print cmd
os.system(cmd)
os.unlink(pidfile)
def restart(name):
stop(name)
time.sleep(1)
start(name)
if __name__ == '__main__':
try:
action, project = sys.argv[1], sys.argv[2]
if action in ['start','stop', 'restart'] and project in projects:
if action == 'start':
start(project)
elif action == 'stop':
stop(project)
elif action == 'restart':
restart(project)
else:
raise IndexError
else:
raise IndexError
except IndexError:
print "Usage: %s {start|stop|restart} {%s}" % (sys.argv[0], '|'.join(projects.keys()))
Migracja się udała. Plone, PHP, Django i Rails śmigają mi teraz na ultraszybkim (i zajmującym mało pamięci!) serwerze Nginx. Acha, zapomniałem dodać: Nginx to nie tylko duża wydajność i oszczędność pamięci. Nginx ma dużo modułów. Może nie tyle, co Apache, ale znacznie lepiej niż Lighttpd.
BTW, ciekawie wygląda także serwer Cherokee. Nginx działa tylko pod systemami POSIX (Unix, MacOS-X, Linux, FreeBSD). Cherokee natomiast posiada… binarną instalację pod Windows! Ale o tym może napiszę coś innym razem. :)


Kanały IRC![[Dilber w Onecie]](/images/larry.png)


czyli jak rozumiem dla Railsow uzywasz w tej chwili: nginx + mongrel (bezposrednio) ?
yeap
“Po paru godzinach pracy, przestawał odpowiadać na zapytania a nawet w ogóle proces znikał z pamięci.”
Miałem to przy testowaniu lighttpd na archu – padał pod dużym obciążeniem. Niedawne próby zabicia go tym samym na gentoo amd64 nie powiodły się :)
no to gr8 ;)
Jakie wersje lighttpd maja takie problemy?
Świetna robota z opisaniem konfiguracji, Nginx potrzebuje takich przykładów z życia wziętych ! Na pewno przyda się wielu osobom, które przechodzą na Nginx’a.
Tak BTW, nie wiem czy to ten dedykowany serwer czy Nginx, ale teraz Twój blog pojawia się rzeczywiście błyskawicznie :)
@Riklaunim: w takim razie polecam arch64:-)
@Jarosław Zabiełło: hmm, na stronie nginx’a czytalem że ichnie reverse-proxy używa tylko HTTP/1.0 do komunikacji z backendami, czy to poważna wada jeśli chodzi o frameworki takie jak nitro, rails czy django? może pytanie wygląda głupio, ale jestem zielony w tej kwestii:-)
Te problemy miałem z Lighttpd 4.11 (instalowany za pomocą aptitude pod Ubuntu 6) i zbiegło to się w czasie z jakimś przeciążeniem VPS’a.
Blog się szybciej wyświetla głównie dzięki serwerowi dedykowanemu. Zauważyłem tak “na oko” że Plone też mi działa jakieś 2x szybciej.
Co do reverse-proxy – nie wiem. Rails i Django mi działa bez problemu, ale uzywam tam tylko odpowiednio: load-balancera do procesów mongrela (dla RoR) i fastcgi/wsgi (dla Pythona). Jedynie dla Plone używam proxy ale problemów nie widzę.
a jest jakas mozliwosc na stworzenie malego howto? jak to wszystko polaczyc do tzw. “kupy” :) ?
a zrobił ktoś w nginxie katalogi userów (/home/user/public_html) ? bez dodawania wirtualnych hostów – chodzi mi o jeden wpis…
A jak sie ma wydajnosc Nginx’a do np. http://www.mathopd.org/ ? Ktos z was porownywal wyniki? Wiadomo ze apache to kobyla i do prostych requestow nie jest potrzebna.
@gaber wydajność mathopd jest tragiczna, już przy średnim obciążeniu zaczyna gubić połączenia z klientami. Jeżeli szukasz czegoś w tej klasie to polecam boa.org – jest poprostu zabójczy a przy serwowaniu statycznych plików potrafi być niejednokrotnie szybszy niż lighttpd, CGI obsługuje w takim samym czasie co Apache2 – do szcześcia brakuje tylko FastCGI
@Jarosław mam pytanie odnośnie przeładowania konfiguracji nginxa – czy podczas tego procesu zrywa on połączenia tak jak robi to lighttpd, czy też jest to niewidoczne dla klienta?
nginx moze przeladowac konfiguracje w sposob niewidoczny dla klinetow, kill -HUP, a nawet potrafi zrobic update w ten sposob, jest to opisnae na wiki, szukaj command line