Programovanie neblokujúcich I/O pomocou greenletov a v node.js

22.12.2013 | 18:54 | Mirecove dristy | Miroslav Bendík

V dnešnom blogu sa pozrieme na dva rôzne spôsoby zápisu neblokujúcich I/O. Jeden bude použitím callback funkcií používaných v node.js a druhý pomocou greenletov v pyhtone.

Príklad

Náš ukážkový program bude veľmi jednoduchý. Náš program bude zobrazovať výrok dňa, ktorý bude uložený v memcache (kvôli jednoduchosti). Kontaktovať memcache budeme vždy 2x - prvým prístupom zistíme kľúč v ktorom je výrok uložený a druhým prístupom získame samotný výrok.

Node.js

// server.js

var http = require('http');
var Memcached = require('memcached');
var memcached = new Memcached('127.0.0.1:11211'); // pripojenie na memcache
 
// Nastavenie premenných, toto by mal riešiť externý skript
memcached.set("msg_var", "vyrok_2013-07-12")
memcached.set("vyrok_2013-07-12", "vyrok pre dnesok")
 
http.createServer(function (req, res) {
  // IO operácia - načítanie msg var
  memcached.get('msg_var', function(err, msg_var) {
    // callback po načítaní msg_var
    if (err) {
      sys.puts('Error!');
      return;
    }
    // IO operácia - načítanie konkrétneho výroku
    memcached.get(msg_var, function(err, msg) {
      if (err) {
        sys.puts('Error!');
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(msg); // výpis výroku
    });
  });
}).listen(8888, '127.0.0.1');

Príklad sa spúšťa príkazom node server.js. Pred spustením samozrejme musíme nainštalovať a spustiť memcached a nainštalovať npm modul príkazom npm install memcached.

gevent

# server.py

from gevent.wsgi import WSGIServer
import umemcache as memcache
 
mc = memcache.Client('127.0.0.1:11211')
mc.connect()
mc.set("msg_var", "vyrok_2013-07-12")
mc.set("vyrok_2013-07-12", "vyrok pre dnesok")
 
def application(environ, start_response):
    start_response("200 OK", [('Content-Type', 'text/plain')])
    msg_var = mc.get("msg_var")[0]
    msg = mc.get(msg_var)[0]
    yield msg
 
WSGIServer(('127.0.0.1', 8888), application, log=None).serve_forever()

Server sa spustí príkazom python server.py. Závislosti ako gevent a umemcache musia byť samozrejme pred spustením nainštalované.

Výsledky

Oba servery boli testované príkazom ab2 -n 10000 -c 100 127.0.0.1:8888.

Node:

Concurrency Level:      100
Time taken for tests:   16.881 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      1180000 bytes
HTML transferred:       170000 bytes
Requests per second:    592.38 [#/sec] (mean)
Time per request:       168.811 [ms] (mean)
Time per request:       1.688 [ms] (mean, across all concurrent requests)
Transfer rate:          68.26 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   3.1      0      46
Processing:    20  168  65.8    179     413
Waiting:       19  168  65.7    179     412
Total:         65  168  65.9    179     413

Percentage of the requests served within a certain time (ms)
  50%    179
  66%    204
  75%    216
  80%    220
  90%    254
  95%    281
  98%    303
  99%    389
 100%    413 (longest request)

Gevent:

Concurrency Level:      100
Time taken for tests:   19.746 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      1180000 bytes
HTML transferred:       170000 bytes
Requests per second:    506.42 [#/sec] (mean)
Time per request:       197.464 [ms] (mean)
Time per request:       1.975 [ms] (mean, across all concurrent requests)
Transfer rate:          58.36 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   1.6      0      24
Processing:    16  196  13.9    197     219
Waiting:       15  196  13.9    197     219
Total:         32  196  12.7    198     219

Percentage of the requests served within a certain time (ms)
  50%    198
  66%    201
  75%    203
  80%    204
  90%    207
  95%    209
  98%    211
  99%    212
 100%    219 (longest request)

Výsledok

Žiaden zázrak v podobe zdrvujúcej prehry geventu sa nekonal. Dobre implementovaný kooperatívny multitasking je prakticky ekvivalentný s eventloopom v node.js. Rozdiel je prakticky len v zápise. Kvôli katastrofálnemu zápisu príkazov v node.js vznikli knižnice ako async, v tomto prípade sú však neaplikovateľné. Node.js má samozrejme svoje výhody v podobe knižníc pripravených na async I/O, ale rovnaká funkcionalita sa dá dosiahnuť aj inými spôsobmi.