Services asynchrones
avec Mongrel2 et ZeroMQ

Loïc d'Anterroches
Novembre 2011

Tout ce que Node.JS, RoR, Django, la dernière techno à la mode peuvent faire, PHP le peut aussi.

Donc ne succombez pas aux cris des sirènes.

Loïc et Céondo

Aeron une chaise superbe en polymères

Source: design-corps.co.uk

Propriétés physiques et chimiques

Les polymères de l'Aeron sont produits par DSM.

Besoin de propriétés comme la pression de vapeur, densité, capacité thermique, enthalpy de formation, la viscosité, le point de fusion etc.

Sources: DSM, Cheméo

Recherche : serveur Nginx, PHP-fastcgi avec un framework léger, MongoDB..

Science : Python, OpenBabel, R et même du Fortran pour prédire les propriétés de nouvelles molécules.

Problème

Passage du desktop au client web, calcul immédiat au batch... pas possible.

Prédiction Instantannée

Source: CAPEC/DTU

La même chose sur le web

Chercher une solution...

Deux approches entre le client et le serveur

Et toujours au niveau serveur

Comment mettre à disposition facilement un service codé en Python à mon application PHP sans faire un fork ?

Buzzword: Realtime

Avant de plonger

Source: The Matrix

ØMQ

ØMQ — Sockets aux stéroïdes

import zmq
import science 

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://localhost:5560")

while True:
    message = socket.recv()
    result = science.predict(message)
    socket.send(result)

C'est du Python, c'est un serveur robuste, c'est rapide comme pas deux.

ØMQ — Aussi pour PHP

<?php

$context = new ZMQContext();
//  Socket pour se connecter au serveur
$requester = new ZMQSocket($context, ZMQ::SOCKET_REQ);
$requester->connect("tcp://localhost:5560");
$requester->send("Nc1ccccc1O");
printf("La réponse est : %s.", $requester->recv());

C'est du PHP, c'est aussi robuste et rapide comme pas deux.

Un serveur, un client, deux langages, haute performance et asynchrone.

ØMQ — Les primitives

Le squelette de votre système. Les primitives de ØMQ sont très bien pensées.

ØMQ — PUB/SUB simple et efficace

$context = new ZMQContext();
$sub = new ZMQSocket($context, ZMQ::SOCKET_SUB);
$sub->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, 'CHOCOLAT');
$sub->connect("tcp://localhost:5560");
while (true) {
    printf("Reçu : %s.", $sub->recv());
}
$context = new ZMQContext();
$sub = new ZMQSocket($context, ZMQ::SOCKET_PUB);
$sub->bind("tcp://localhost:5560");
$quoi = array('CHOCOLAT', 'FRAISE', 'BANANE'); 
for ($i=0; $i<100000; $i++) {
    $sub->send(array_rand($quoi) . ' ' . microtime()); usleep(1000);
}

Pour PUSH/PULL pas d'abonnement côté client.

ØMQ — Transports

Le lien entre les éléments.

ØMQ — Messages

Un blob c'est du JSON, BSON, un jpg, de l'utf-8 ou pas.

Les enveloppes pour les abonnements (SUB) ou rassembler des messages en un gros message (multipart).

Source: Ian Barber

Résumé de ØMQ

Mongrel2

PULL ou PUSH ? Bienvenue à Mongrel2

Rappel du problème

Mongrel2 peut faire du PULL/PUSH, pour différents protocoles — HTTP, JsSocket, Websocket, XML — sur le même port, sur un unique serveur.

Buzz words: asynchrone, coroutines, event driven, ZeroMQ, performance, you name it.

Mongrel2 — Simple et élégant

Mongrel2 — Science + Web

from mongrel2 import handler 
import json 
import science
from uuid import uuid4 

conn = handler.Connection(uuid4().hex, 
                          "tcp://127.0.0.1:9997", 
                          "tcp://127.0.0.1:9996") 
while True: 
    req = conn.recv() 
    if req.is_disconnect(): 
        continue 
    result = science.predict(req.body)
    conn.reply_http(req, result)

Le même service fournit à un client web. science peut lui même être livré via ØMQ... le marteau.

Mongrel 2 — Flux d'une requête

Le génie de ce design : toutes les étapes sont asynchrones, seule la connexion avec le client ne l'est pas.

Mongrel2 — Protocole

Requête : UUID ID PATH SIZE:HEADERS,SIZE:BODY,

34f9ceee-cd52-4b7f-b197-88bf2f0ec378  # Mongrel2 id
6 # Client id
/path/foo # HTTP Path
422:{"PATH":"/path/foo","PATTERN":"/path","user-agent":"curl/7.19.7..."},\
7:"HELLO" # Payload

Réponse : UUID SIZE:ID ID ID, BODY

34f9ceee-cd52-4b7f-b197-88bf2f0ec378  # Mongrel2 id
3:6 9, # Clients id
<html ...

Une réponse, plusieurs clients

Pourquoi cela change la donne pour PHP

PHP plus rapide que PHP : Hello World! plus rapide avec Photon qu'avec Apache/mod_php

Applications possibles

Toute votre infrastructure peut communiquer de manière simple et homogène.

Exemple, je spam mes amis

class MailSender extends \photon\task\AsyncTask
{
    public $name = 'mailsender';
    public function work($socket)
    {
        list($taskname, $client, $payload) = explode(' ', $socket->recv(), 3);
        $payload = json_decode($payload);
        $emails = $payload['emails'];
        $subject = $payload['subject'];
        $body = $payload['body'];
        $headers = 'From: ' . Conf::f('from_email') . "\r\n" .
                   'X-Mailer: Photon/' . \photon\VERSION;
        foreach ($payload['emails'] as $to) {
            mail($to, $payload['subject'], $payload['body'], $headers);
        }
    }
}

Réception en PULL d'un travail émis en asychrone.

Source: photon-project.com

Exemple, je spam mes amis

class MyViews
{
    public function updateTicket($request, $match)
    {
        // Do a lot of work to update the ticket
        $payload = array(
                 'emails' => array('foo1@example.com',
                                   'foo2@example.com',
                                   'foo3@example.com',
                                   'foo4@example.com'),
                 'subject' => 'Ticket 123 has been updated.',
                 'body' => 'Hello, Photon is great and you too.');
        $runner = new \photon\task\Runner();
        $runner->run('mailsender', $payload);
        return new \photon\http\Response('Ticket updated!', 'text/plain');
    }
}

Le run() sans avoir besoin d'une queue pour les emails en sortie. Tout se fait dans le thread I/O de ØMQ.

Source: photon-project.com

Thread I/O, c'est quoi ?

$context = new ZMQContext($io_threads=1, $is_persistent=true);
$sub = new ZMQSocket($context, ZMQ::SOCKET_PUB);

Stats temps réel

Stats temps réel

Photon fait dans le durable

Photon fait dans le durable

Système complexe mais simple à développer puis déployer

À la fin la marmotte

Source: zguide.zeromq.org

Le bon

la brute et le truand

Mais les signaux vont dans le bon sens...

Performance et complexité

Au début tout allait bien

Page statique, le livre d'or modifiait une page statique.

Source: Benchmark impossible à reproduire

Puis la Java a mal tourné

Framework, base de données, beaucoup de "boilerplate" mais pourquoi ?

Source: Benchmark impossible à reproduire

Ne pas oublier l'essentiel

Il y a dedans et dehors

Source: Benchmark impossible à reproduire
ØMQ's error handling philosophy is a mix of fail-fast and resilience. Processes, we believe, should be as vulnerable as possible to internal errors, and as robust as possible against external attacks and errors.
zguide.zeromq.org

Scaling — Déployer

Simplement lancer de nouveaux handlers

$ php monprojet.phar serve

les .phar c'est chouette

Source: photon-project.com

Pourquoi tout ça ?

Source: heroku.com

Merci