Shery és RePa

2009. szeptember 16.

libevent + tornado

dyuri @ 9:22:57

Úgy kezdődött a dolog, hogy a facebookos srácok nyílt forrásúvá tették és kiadták a FriendFeed mögött álló python alapú webszervert, a Tornadot. Aranyos dolog, mert nem blokkol, sok klienst képes párhuzamosan kiszolgálni és relatíve gyors mert esemény vezérelt. Feltéve, hogy Linuxon használjuk, mert csak és kizárólag az epollt támogatja (illetve a hagyományos select()-et, nade akkor oda minden előnye).
Viszont én Solaris alatt is szeretném kihasználni a fent említett előnyöket, úgyhogy gyorsan összedobtam egy apró wrappert a libevent köré, ami működik is, és így a Solaris/FreeBSD felhasználók is örülhetnek.

Gyorsan nézzük hogyan történt a dolog.

Először a libeventet kellett letölteni és lefordítani. Szerencsére gond nélkül fordult update 7-es Solaris 10-en.
(jelenleg az 1.4.12-es változat a stabil, azt használtam)

$ ./configure --prefix=/opt/bitnet
...
checking port.h usability... yes # solaris event ports, ez kell nekünk
...
$ make install

Hogy python alól használni tudjuk, ahhoz szükségünk van a python-libevent csomagra, amit én kézzel tettem fel innen, mert a sajtboltos változat ősrégi.

$ export CFLAGS="-I/opt/bitnet/include"
$ export LDFLAGS="-L/opt/bitnet/lib"
$ python setup.py install

Ezután jött az érdemi munka, a tornadot rábeszélni, hogy a libevent-et használja. Ehhez egy ugyan olyan wrapper osztályt csináltam, amit a srácok csináltak maguknak a saját epoll, illetve a hagyományos select() támogatásához, ami igazából a standard python Poll objektum által megvalósított interface:

class _LibEvent(object):
    """A libevent based IOLoop implementation"""
 
    def __init__(self):
        self._eb = libevent.EventBase()
        self._events = {}
        self._ready_fds = {}
 
    def _fd_ready(self, fd, events, eventObj):
        eventmask = (IOLoop.READ * (events & libevent.EV_READ) / libevent.EV_READ) | \
                    (IOLoop.WRITE * (events & libevent.EV_WRITE) / libevent.EV_WRITE) | \
                    (IOLoop.ERROR * (events & libevent.EV_TIMEOUT) / libevent.EV_TIMEOUT)
 
        self._ready_fds[fd] = eventmask
 
    def register(self, fd, events):
        # ezt a reszt is at lehetne alakitani olyanna, mint az elozo fuggveny
        eventmask = 0
        if events & IOLoop.READ:
            eventmask = eventmask | libevent.EV_READ | libevent.EV_PERSIST
        if events & IOLoop.WRITE:
            eventmask = eventmask | libevent.EV_WRITE
        if events & IOLoop.ERROR:
            # does libevent has error event type?
            eventmask = eventmask | libevent.EV_TIMEOUT
 
        self._events[fd] = self._eb.create_event(fd, eventmask, self._fd_ready)
        self._events[fd].add_to_loop()
 
    def modify(self, fd, events):
        self.unregister(fd)
        self.register(fd, events)
 
    def unregister(self, fd):
        self._events[fd].remove_from_loop()
        del self._events[fd]
 
    def poll(self, timeout=10):
        self._ready_fds = {}
 
        self._eb.loop_exit(timeout)
        # libevent.EVLOOP_NONBLOCK hasznalatakor a timert sem varja meg, es megeszi a cpu-t
        self._eb.loop(libevent.EVLOOP_ONCE)
 
        return self._ready_fds.items()
 
...
 
# legvegere, illetve a mar ott levo 'try' blockba:
try:
    import libevent
    _poll = _LibEvent
except:
    _poll = _Select

Illetve ha nem akarjuk belehackolni a tornadoba a cuccot, akkor "monkey patching" technikával is bedolgozhatjuk, ekkor az alkalmazásunk ioloopjanak indítása előtt kell megmondanunk explicite, hogy libeventet használjon:

import tornado.ioloop
tornado.ioloop._poll = _LibEvent
tornado.ioloop.IOLoop.instance().start()

(remélem ezt nem kell sokáig megtenni, jeleztem a fejlesztők felé, hogy jó lenne a libevent támogatást a fő fejlesztési vonalon látni)

Csináltam is néhány gyorstesztet az egyik szerverünkön, sok párhuzamos kérés esetén látszik a fejlődés. A teszteket az apache féle ab paranccsal mértem, ahol az 'n' az összes lekérés, a 'c' pedig a konkurenciát (ennyi szálon próbálkozik párhuzamosan) jelenti. A parancsokat háromszor egymás után megismételtem, és a legjobb eredmény került ki ide.
(ulimit -n unlimited)

  1. _Select:
    • n=10000, c=100: 1048.10 [#/sec]
    • n=10000, c=1000: 683.96 [#/sec]
    • n=10000, c=2000: 672.85 [#/sec] (a háromból egyszer meghalt a lenti hibával)
    • n=10000, c=5000: háromszor egymás után: filedescriptor out of range in select()
  2. _LibEvent:
    • n=10000, c=100: 1056.57 [#/sec]
    • n=10000, c=1000: 1013.59 [#/sec]
    • n=10000, c=2000: 1004.94 [#/sec]
    • n=10000, c=5000: 954.52 [#/sec]
    • n=10000, c=10000: 781.10 [#/sec]
    • n=20000, c=10000: 934.32 [#/sec]

Első körben nekem elég is volt ennyi, hogy lássam, hogy megérte, aztán majd valami egyszerű webalkalmazással meg kéne nézni, hogy a többi megoldáshoz képest (fcgi, cherrypy, esetleg mod_wsgi) hogy muzsikál a cucc.

Használjátok egészséggel!

Egy hozzászólás érkezett eddig a(z) “libevent + tornado” c. posthoz

  1. Shery és RePa » Blog Archive » tornado performance teszt üzeni:

    [...] már megcsináltam, hogy tornado működjön rendesen Solarison is, akkor gondoltam meg is mérem, hogy mennyire rendes az - de legalábbis összehasonlítom az [...]

Ez egy blog. A velemenyunk a mienk, ezert szubjektiv, es meglehet, hogy neha csak picit fedi az egyetemes igazsagot. Mellesleg akinek nem tetszik, az nezze helyette a tvt.

Egyebkent nyugodtan lehet idezni, kepeket toltogetni, szabadok vagyunk.

Ha esetleg valami szemelyes kozolnivalod van, amit nem szeretnel kommentbe leirni, akkor tobbek kozott elerhetsz minket a [akiacikketirta] kukac horak pont hu emailcimen.