Nginx - Apache killer

Opublikowane przez 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 ) )
)
Trochę prób i się udało. Nginx potrzebował takiego wpisu:
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. :)

Posted in ,  | Tagi , , , ,  | 16 comments

Comments

  1. Avatar yezooz powiedział about 23 hours later:

    czyli jak rozumiem dla Railsow uzywasz w tej chwili: nginx + mongrel (bezposrednio) ?

  2. Avatar Jarosław Zabiełło powiedział 1 day later:

    yeap

  3. Avatar Riklaunim powiedział 1 day later:

    “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ę :)

  4. Avatar yezooz powiedział 1 day later:

    no to gr8 ;)

  5. Avatar Pawel powiedział 1 day later:

    Jakie wersje lighttpd maja takie problemy?

  6. Avatar hosiawak powiedział 3 days later:

    Ś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 :)

  7. Avatar maniel powiedział 3 days later:

    @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:-)

  8. Avatar Jarosław Zabiełło powiedział 5 days later:

    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ę.

  9. Avatar PLum powiedział 14 days later:

    a jest jakas mozliwosc na stworzenie malego howto? jak to wszystko polaczyc do tzw. “kupy” :) ?

  10. Avatar envp powiedział 24 days later:

    a zrobił ktoś w nginxie katalogi userów (/home/user/public_html) ? bez dodawania wirtualnych hostów – chodzi mi o jeden wpis…

  11. Avatar gaber powiedział 2 months later:

    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.

  12. Avatar marcin powiedział 10 months later:

    @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?

  13. Avatar dsoul powiedział 10 months later:

    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

  14. Avatar AndrzejRusin powiedział over 2 years later:

    Czas leci i pojawiły się Windowsowe wersje nginxa: http://www.kevinworthington.com/tag/nginx/ i http://ngwsx.eaxi.com/

  15. Avatar Damian Kołkowski powiedział over 2 years later:

    @Paweł Ta wersja na Gentoo przy 12,24 req/s też potrafi się powiesić.

    Średnio było ~12 ale w szczycie ponad 25 req/s

  16. Avatar Paweł Olejnik powiedział over 2 years later:

    Witam, nasza strona firmowa jest uruchamiana na WebServ 2.0 pod win2003, chciałem skonfigurować serwer zope3 tak abym mógł przeglądać strony wykorzystujące skrypty napisane przy użyciu frameworku grok. Jestem amatorem, jeśli chodzi o sieci, programuję w pythonie i bardzo by mi to pomogło. Zapytam, czy jest gdzieś wyjaśnienie krok po kroku jak zainstalować serwer zope na tak skonfigurowanym serwerze, lub jeśli nie jest to skomplikowane proszę o wskazówki, dziękuję, Pozdrawiam.

(leave url/email »)

   Pomoc języka formatowania Obejrzyj komentarz