Comprendre les serveurs web asynchrones tels que Node.js ou Tornado : cas d'utilisation, contraintes et styles de programmation.
Calculs dans le process web : fonctions de crypto, encodage de données, etc. --> l'asynchrone ne sert à rien
Pour aller plus vite, optimiser les algorithmes, rajouter des processus, etc.
Requêtes base de données, services REST externes, fork de processus externes. --> l'asynchrone peut être utile
WebSockets --> l'asynchrone est vite indispensable
Nos processus passent leur temps à attendre ? Plus assez d'espace (mémoire) pour en ajouter d'autres ? Les passer en asynchrone peut aider.
Nos processus travaillent ? Les passer en asynchrone ne va pas aider... On peut par contre envisager d'externaliser ce travail pour les passer en asynchrone.
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
print("Serve http://127.0.0.1:8888/")
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
10 000 requêtes par lots de 100 :
$ ab -n 10000 -c 100 http://127.0.0.1:8888/
Time taken for tests: 6.706 seconds
Complete requests: 10000
Requests per second: 1491.25 [#/sec] (mean)
Time per request: 67.058 [ms] (mean)
class MainHandler(tornado.web.RequestHandler):
def get(self):
cur = self.application.db.cursor()
cur.execute("SELECT 42, pg_sleep(0.300);")
result = cur.fetchone()
self.write("Result: %s" % result[0])
20 requêtes par lots de 10 :
$ ab -n 20 -c 10 http://127.0.0.1:8888/
Time taken for tests: 6.201 seconds
Complete requests: 20
Requests per second: 3.23 [#/sec] (mean)
Time per request: 3100.513 [ms] (mean)
var http = require('http');
var pg = require('pg');
var conString = "postgres://al:al@localhost/al";
var slowQuery = 'SELECT 42 as number, pg_sleep(0.300);';
var server = http.createServer(function(req, res) {
pg.connect(conString, function(err, client, done) {
client.query(slowQuery, [], function(err, result) {
done();
res.writeHead(200, {'content-type': 'text/plain'});
res.end("Result: " + result.rows[0].number);
});
});
})
console.log("Serve http://127.0.0.1:3001/")
server.listen(3001)
20 requêtes par lots de 10 :
$ ab -n 20 -c 10 http://127.0.0.1:3001/
Time taken for tests: 0.678 seconds
Complete requests: 20
Requests per second: 29.49 [#/sec] (mean)
Time per request: 339.116 [ms] (mean)
from tornado import web, ioloop
import momoko
class MainHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
self.application.db.execute(
'SELECT 42, pg_sleep(0.300)', callback=self._done)
def _done(self, cursor, error):
result = cursor.fetchone()
self.write("Result: %s" % result[0])
self.finish()
if __name__ == "__main__":
application = tornado.web.Application([(r"/", MainHandler)])
application.db = momoko.Pool(dsn='dbname=al user=al', size=10)
application.listen(8888)
ioloop.IOLoop.instance().start()
20 requêtes par lots de 10 :
$ ab -n 20 -c 10 http://127.0.0.1:8888/
Time taken for tests: 0.622 seconds
Complete requests: 20
Requests per second: 32.18 [#/sec] (mean)
Time per request: 310.756 [ms] (mean)
Version extrêmement simplifiée de l'IOLoop de Tornado :
def start(self):
while True:
# Appelle la fonction de polling de la plateforme
# (epoll sous Linux, kqueue sous BSD). Celle-ci
# renvoie les événements survenus sur les
# descripteurs de fichiers (sockets, etc.) surveillés
event_pairs = self._impl.poll(poll_timeout)
self._events.update(event_pairs)
while self._events:
# Appelle les handler d'événements enregistrés
fd, events = self._events.popitem()
fd_obj, handler_func = self._handlers[fd]
handler_func(fd_obj, events)
Enregistrement des handlers :
def add_handler(self, fd, handler, events):
fd, obj = self.split_fd(fd)
self._handlers[fd] = (obj, stack_context.wrap(handler))
self._impl.register(fd, events | self.ERROR)
L'IO loop de Node.js est fournie par la bibliothèque libuv.
var server = http.createServer(function(req, res) {
pg.connect(conString, function(err, client, done) {
client.query(slowQuery, [], function(err, result) {
var dbValue = result.rows[0].number;
done();
http.get("http://127.0.0.1:8000/", function(response) {
response.on("data", function(chunk) {
/* Le service met 300 ms à répondre
et renvoie {"value": 19} */
var responseObj = JSON.parse(chunk),
value = dbValue - responseObj.value;
res.writeHead(200, {'content-type': 'text/plain'});
res.end("Result: " + value);
});
});
});
});
});
20 requêtes par lots de 10 :
$ ab -c 10 -n 20 http://127.0.0.1:3001/
Time taken for tests: 1.253 seconds
Complete requests: 20
Requests per second: 15.96 [#/sec] (mean)
Time per request: 626.434 [ms] (mean)
class MainHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
def handle_db(cursor, error):
db_value = cursor.fetchone()[0]
def handle_http(response):
json_data = json.loads(response.body.decode())
result = db_value - json_data['value']
self.write("Result: %s" % result)
self.finish()
http_client = tornado.httpclient.AsyncHTTPClient()
http_client.fetch('http://127.0.0.1:8000/', handle_http)
self.application.db.execute(
'SELECT 42, pg_sleep(0.300)', callback=handle_db)
20 requêtes par lots de 10 :
$ ab -c 10 -n 20 http://127.0.0.1:8888/
Time taken for tests: 1.260 seconds
Complete requests: 20
Requests per second: 15.88 [#/sec] (mean)
Time per request: 629.834 [ms] (mean)
class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
cursor = yield momoko.Op(self.application.db.execute,
'SELECT 42, pg_sleep(0.300)')
db_value = cursor.fetchone()[0]
http_client = tornado.httpclient.AsyncHTTPClient()
response = yield http_client.fetch('http://127.0.0.1:8000/')
json_data = json.loads(response.body.decode())
result = db_value - json_data['value']
self.write("Result: %s" % result)
self.finish()
20 requêtes par lots de 10 :
$ ab -c 10 -n 20 http://127.0.0.1:8888/
Time taken for tests: 1.281 seconds
Complete requests: 20
Requests per second: 15.61 [#/sec] (mean)
Time per request: 640.527 [ms] (mean)
class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
http_client = tornado.httpclient.AsyncHTTPClient()
# Lancement des requêtes en parallèle
cursor, response = yield [
momoko.Op(self.application.db.execute,
'SELECT 42, pg_sleep(0.300)'),
http_client.fetch('http://127.0.0.1:8000/'),
]
db_value = cursor.fetchone()[0]
json_data = json.loads(response.body.decode())
result = db_value - json_data['value']
self.write("Result: %s" % result)
self.finish()
20 requêtes par lots de 10 :
$ ab -c 10 -n 20 http://127.0.0.1:8888/
Time taken for tests: 0.663 seconds
Complete requests: 20
Requests per second: 30.15 [#/sec] (mean)
Time per request: 331.638 [ms] (mean)
var koa = require('koa');
var koaPg = require('koa-pg');
var request = require('co-request');
var app = koa();
app.use(koaPg('postgres://al:al@localhost/al'))
app.use(function *(){
var sql = 'SELECT 42 as number, pg_sleep(0.300)';
/* Lancement des requêtes en parallèle */
var results = yield [
this.pg.db.client.query_(sql),
request('http://127.0.0.1:8000')
];
var dbValue = results[0].rows[0].number;
var responseObj = JSON.parse(results[1].body);
this.body = "Result: " + (dbValue - responseObj.value);
});
app.listen(3000);
20 requêtes par lots de 10 :
$ ab -c 10 -n 20 http://127.0.0.1:3000/
Time taken for tests: 0.685 seconds
Complete requests: 20
Requests per second: 29.22 [#/sec] (mean)
Time per request: 342.268 [ms] (mean)
Quand utiliser de l'asynchrone ?
Comment coder en asynchrone ?
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |