Architectures distribuées et traitements asynchrones

Cela fait quelque temps que je réfléchis à l’idée d’écrire un article sur les architectures distribuées, et plus particulièrement sur les traitements asynchrones. J’en parlais déjà au Forum PHP en 2013, dans de ma conférence intitulée «De 0 à 10 millions de visiteurs uniques avec les moyens d’une startup» mais je vais entrer plus en profondeur dans le sujet.

Pour quoi faire ?

On peut vouloir faire des traitements asynchrones pour différentes raisons. Si on s’en tient au développement web, la principale raison est de répondre plus vite aux requêtes des visiteurs, en exécutant certaines actions de manière différée. L’exemple classique est lorsqu’une action sur votre site doit déclencher un appel sur l’API d’un service tiers : Cet appel d’API peut prendre plusieurs secondes, et il peut même échouer en cas de problème momentané du service en question ; dans ce cas, plutôt que de faire attendre notre internaute, il vaut mieux lui répondre immédiatement et traiter cet appel d’API de manière asynchrone. Pareil pour des envois d’emails (même si les serveurs SMTP opèrent déjà de cette manière), des traitements sur des fichiers audio/vidéo, des calculs lourds, etc.

Si on élargit le débat au-delà du web, c’est un bon moyen pour découpler les différentes briques qui composent un logiciel afin de mettre en place une architecture distribuée. C’est souvent un élément essentiel lorsqu’on a besoin de scalabilité et même de rapidité. Un système monolithique atteindra tôt ou tard ses limites, là où un système composé de plusieurs entités qui communiquent ensemble sera plus facile à faire évoluer − au prix d’une complexité initiale plus grande.
En éclatant le code applicatif en plusieurs morceaux, on peut distribuer son exécution sur plusieurs machines assez facilement, facilitant ainsi l’évolutivité de la plate-forme.

Concrètement, l’idée est d’avoir des entités qui s’envoient des messages au lieu de discuter entre elles directement. Quand un message est envoyé, on n’attend pas une réponse immédiate ; une fois que le message aura été traité, un autre message sera éventuellement envoyé en retour pour le notifier, à moins que le résultat ne soit par exemple stocké dans une base de données où il sera disponible pour de futurs besoins.

Le sujet des architectures distribuées n’est pas nouveau. L’idée de RPC (appel à procédure distante) a été décrite dès 1976. Dans les années 90, quand les langages orientés objet étaient à la mode, beaucoup d’énergie a été déployée dans les systèmes servant à exécuter des objets à distance. C’est dans ce contexte que sont apparus des ORB (object request brokers) comme CORBA dès 1991 et RMI (Remote Method Invocation) intégré à Java dès la version 1.1 en 1997.
D’ailleurs, la complexité de CORBA a facilité l’émergence de normes plus simples et basées sur des technos web, comme XML-RPC et SOAP (Simple Object Access Protocol) en 1998, elles-mêmes simplifiées avec l’apparition du REST en 2000 (qui s’est développé d’autant plus avec l’usage du JSON, sérialisation plus simple à manipuler que le XML).

Mais comme on va le voir, il ne suffit pas de considérer qu’il y a des procédures/méthodes/objets à appeler pour que le système soit robuste en toutes circonstances. On peut d’ailleurs remarquer que les illusions de l’informatique distribuée sont bien connues et listées depuis les années 90 :

  1. Le réseau est fiable.
  2. Le temps de latence est nul.
  3. La bande passante est infinie.
  4. Le réseau est sûr.
  5. La topologie du réseau ne change pas.
  6. Il y a un et un seul administrateur réseau.
  7. Le coût de transport est nul.
  8. Le réseau est homogène.

Effectivement, rien de tout cela n’est vrai. Découpler une application apporte beaucoup d’avantages, mais comporte aussi des contraintes et des risques qu’il faut connaître et évaluer.

Comment faire ?

Pour mettre en place ce type d’architecture, il existe un certain nombre de moyens techniques, depuis les plus simples jusqu’aux plus complexes. Je vais passer en revue quelques notions et solutions.

Quand on parle de traitements asynchrones, on a habituellement trois composants à mettre en place :

  • Une partie qui permet de donner des ordres de traitements, avec éventuellement des informations complémentaires (de quel type d’ordre il s’agit, avec parfois des données additionnelles, etc.). Cela prend habituellement la forme d’une simple bibliothèque logicielle, mais certaines technos imposent plus de contraintes − comment les ORB qui obligeaient à créer des stubs.
  • La pièce centrale du système, qui gère les messages qui sont envoyés, en les classant éventuellement par priorité ou en les rangeant dans différentes files. Cette partie peut être basée sur des technologies variées (bases de données, files de messages, tables de hachage, ou autre).
    Les technos utilisées, que ce soit pour gérer les messages ou pour communiquer entre les différentes briques de l’architecture, ont une influence directe sur les performances du système.
  • La dernière partie est constituée par des «workers», c’est-à-dire du code qui traite les messages et effectue le travail demandé. Là encore, il existe plusieurs manières de faire : soit les workers sont des programmes indépendants (qu’il faut alors gérer correctement), soit ils sont lancés par la brique centrale (au prix d’un coût d’instanciation qui ajoute de la latence dans les traitements).

Dans les notions qui me semblent importantes à avoir en tête, il y en a principalement deux :

  • La scalabilité : Est-ce que la solution retenue peut soutenir un très grand nombre de messages échangés, et est-il possible de répartir leur traitement sur plusieurs ordinateurs ?
  • La persistance : Si la partie centrale de l’architecture venait à tomber en panne, est-ce que les messages qui étaient en attente de traitement sont perdus ou bien sont-ils conservés pour être traités au redémarrage du système ?

Pour la mise en œuvre, je vais explorer 2 manières de faire, chacune avec 2 technos différentes :

  • L’utilisation d’une base de données dans laquelle on écrit les messages, avec un système de polling (“attente active” ou “scrutation” en français) pour instancier leurs traitements.
    1. De manière artisanale, avec l’utilisation de la crontab.
    2. Avec l’outil Resque.
  • L’utilisation d’un système intégrant une file de messages.
    1. Avec l’outil Gearman.
    2. Avec l’outil Beanstalkd.

Base de données ou file de messages ?

Parmi les quatre méthodes que je vais détailler, trois d’entre elles reposent d’une manière ou d’une autre sur l’utilisation d’une base de données. Continuer la lecture de « Architectures distribuées et traitements asynchrones »

Deux journées folles autour du Forum PHP

La semaine dernière a été assez mouvementée.

Comme je vous l’ai dit, j’ai donné une conférence au Forum PHP (plus une mini-conférence). Mais en fait, j’ai plein de choses à raconter. Commençons par le commencement.

J’ai rencontré Rasmus

(Frédéric dirait «J’ai rencontré Dieu»)
Lundi dernier, Epitech organisait le PHP Day, une suite de conférences dédiées au PHP, qui se terminait avec une présentation faite par Rasmus Lerdorf, le créateur de ce langage.

C’est la première fois que je rencontrais le bonhomme. Et pour tout vous dire, Rasmus, il est super cool. Il a une approche très pragmatique des choses. Pour lui, l’informatique est un outil, pas une fin en soi ; créer des outils comme le PHP est quelque chose de très intéressant, mais ça ne doit pas cacher la vraie finalité : créer des sites novateurs. Et il donne un exemple avec le projet Sahana, dont le but est d’aider le travail des organisations humanitaires en cas de catastrophes naturelles. Sa conclusion est simple : « PHP saves lives! ». À partir de là, le reste a finalement peu d’importance.

Certains étudiants lui ont posé des questions, et j’ai beaucoup apprécié ses réponses.

Concernant la “menace” que Ruby et Python font peser sur le PHP, Rasmus est très tranquille. D’un côté, il explique qu’il n’a aucun problème, qu’il s’entend très bien avec les gens qui travaillent sur ces langages, qu’il mange avec eux régulièrement lors des conventions open-source ; tous les langages rencontrent globalement les mêmes problèmes, et ils communiquent entre eux sur le meilleur moyen de les résoudre ; ceux qui déclenchent des guerres de tranchées, ce sont les utilisateurs des langages, pas leurs concepteurs.
Plus tôt dans sa conférence, Rasmus avait fait remarquer que PHP est utilisé par Facebook, Wikipedia, Yahoo!, WordPress, la majorité des blogs de la planète, la majorité des forums, la plupart des sites asiatiques, et la quasi-totalité des sites pornographiques. En toute simplicité, il expliquait que PHP est le langage le plus utilisé, et cela grâce aux efforts qui ont été fait pour permettre à PHP de s’intégrer le mieux possible aux plates-formes des hébergeurs, et pour permettre au langage de s’interfacer avec toutes les technologie actuelles ayant un semblant d’importance.

Il lui a été aussi demandé quel framework il préférait. C’était intéressant, parce qu’il est notoire que Rasmus n’aime pas particulièrement les frameworks, qui sont pour lui d’inutiles consommateurs de ressources. En fait, il n’aime pas non plus les moteurs de templates, argumentant que PHP est lui-même un moteur de templates.
Et là, il a simplement expliqué qu’un framework traditionnel, capable de tout faire, ne peut rien faire à la perfection. Et qu’il valait mieux utiliser un outil adapté au besoin (se baser sur WordPress pour les besoins de type blog, sur Drupal pour faire un CMS, etc.).

Tout cela dans une bonne humeur et une décontraction franchement agréables.
Après la conférence, un barbecue était organisé dans la cour d’Epitech. Je me suis assis à côté de Rasmus et j’ai commencé à discuter avec lui. Et là, comme ça, en mangeant nos merguez, alors que je ne suis personne, on a eu une discussion très intéressante sur l’héritage multiple et les traits (un de mes sujets favoris).

Le lendemain matin, Rasmus a donné la même conférence en ouverture du Forum PHP. Comme il y abordait un élément recoupant la conférence que j’allais donner le jour d’après, j’en ai discuté un peu avec lui. Encore une fois, il était intéressant et ouvert, il me posait des questions sur les conclusions de mes propres benchmarks, me faisait des suggestions.

Bref, c’est un grand monsieur. Si vous pouvez aller à l’une de ses conférences, n’hésitez pas, vous passerez un bon moment.

Le Forum PHP

Par le passé, je n’étais allé qu’une seule fois au Forum PHP. C’était en 2009, et j’y avais présenté le projet FineFS. J’en avais gardé un bon souvenir ; j’y avais fait des rencontres de qualité, et même si quelques rares conférences m’avaient semblées un ton en-dessous des autres, l’ensemble donnait une très bonne impression de professionnalisme.

Cette année, les choses sont un peu différente. J’ai mis un pied dans l’AFUP, l’association organisatrice, et j’ai proposé une conférence qui a été retenue. J’ai donc été amené à voir un peu l’envers du décor.

Prenons les choses dans l’ordre. Le Forum PHP, c’est :

Continuer la lecture de « Deux journées folles autour du Forum PHP »

Ma conférence au Forum PHP 2012 : Démons en PHP, de inetd à ZeroMQ

J’en parlais rapidement dans mon précédent post ; le Forum PHP est une importante manifestation informatique parisienne. Pendant deux jours, des invités prestigieux donnent des conférences techniques autour des technologies du web et de PHP.

Cette année, le Forum PHP se tiendra les 5 et 6 juin à la Cité Universitaire Internationale.

J’ai proposé une conférence qui a été retenue, et que je présenterai le mercredi 6 juin, de 14h00 à 14h45. Voici le texte de présentation de cette conférence :

Durant cette présentation, nous passerons en revue les différentes techniques servant à créer des démons en PHP, c’est-à-dire des serveurs logiciels destinés à recevoir des connexions réseau et gérer des communications client-serveur.
Nous verrons les techniques les plus simples, permettant de prototyper rapidement un démon. Ensuite nous nous intéresserons aux contraintes qui s’appliquent à ce type de développement, et les impacts que cela implique sur les choix de design logiciel. Nous aborderons alors les différentes techniques de création de démons autonomes en PHP.
Pour terminer, nous verrons comment utiliser la bibliothèque ZeroMQ pour faire communiquer des programmes en utilisant des paradigmes différents du client-serveur traditionnel.

Programme

  1. Rappel client-serveur
  2. Démons basés sur (x)inetd
  3. Démons multi-processus
  4. Démons mono-processus
  5. ZéroMQ
    1. Présentation
    2. Différents types de communications
    3. Load-balancing
    4. Polling

Ce sera la première conférence que je donnerai en dehors du milieu académique. J’ai quand même donné près d’une quinzaine de conférences dans des universités et des écoles d’ingénieurs, mais là je serai face à des personnes dont certaines sont bien plus expérimentées que moi.

Mais ça devrait bien se passer. J’ai proposé un sujet que je pense maîtriser assez bien. Le projet FineFS est un exemple pointu de développement client-serveur en PHP, et je vous ai déjà parlé plusieurs fois de ZeroMQ.

Venez nombreux !

ZeroMQ et load-balancing : un exemple concret

Il y a 2 mois, j’ai écrit un article au sujet de ZeroMQ. Si vous ne l’avez pas encore lu, je vous le conseille, je pense avoir réussi à expliquer de manière assez simple les concepts de base de cette bibliothèque réseau aux fonctionnalités très puissantes.

Pour joindre l’utile à l’agréable (comprendre : pour faire un peu de R&D, monter mes équipes en compétence sur des projets intéressants pour l’entreprise), j’ai demandé à mon administrateur système de sortir de sa «zone de confort», et de coder un petit projet en PHP utilisant ZeroMQ.

Le projet

L’idée est de mettre en place un serveur de sauvegarde centralisé. Ce serveur contient un gros disque dur (en fait, plusieurs, mais j’y reviendrai), sur lequel est stocké une copie de tous les fichiers qui doivent être sauvegardés sur les postes de travail. Tous les jours, ce serveur doit lancer une série de rsync pour synchroniser le disque dur local avec les machines à sauvegarder. C’est l’étape de “backup”.

Par la suite, nous recopions les données sur un second disque dur. Chaque dimanche, une copie complète est effectuée, alors que tous les autres jours de la semaine on se contente de faire une copie incrémentale pour ajouter les nouveaux fichiers. C’est l’étape d’“archivage”.

(oui, je sais, on pourrait utiliser un logiciel de sauvegarde comme Amanda, mais l’aspect R&D serait vachement moins évident, hein)

La réalisation

Tout cela n’a rien de bien sorcier. On pourrait faire un simple programme, lancé par crontab, qui lanceraient séquentiellement un rsync sur chaque poste de travail, puis qui effectuerait séquentiellement l’archivage de chaque machine. Le truc, c’est qu’en faisant ainsi, on perdrait un temps fou. De nos jours, on a des processeurs multi-cœurs, des réseaux gigabit ou plus… Ce serait quand même idiot de ne pas mener plusieurs sauvegardes en parallèle.

La première possibilité qu’on pourrait envisager serait simplement de lancer autant de programmes qu’il y a de machines à sauvegarder. Mais ce serait peut-être un peu violent et difficile à surveiller efficacement. Et plus le nombre de machine à sauvegarder augmente, moins cette méthode serait efficace.

On a donc décidé de mettre en place une architecture comprenant  un serveur qui coordonne le travail de plusieurs « workers », des programmes qui effectuent le boulot réel. L’idée est de démarrer un nombre fini de workers, ce qui détermine le nombre de tâches effectuées simultanément, et de leur indiquer les machines à sauvegarder.
ZeroMQ excelle dans ce genre de situation. Les workers vont se connecter au serveur, et attendre qu’il leur envoie des ordres. Le serveur, lui se contentera d’envoyer des ordres séquentiels sur sa socket ; ZeroMQ se chargera de les délivrer en les répartissant aux différents clients (c’est la fonctionnalité de load-balancing intégrée à ZeroMQ).

Petit rappel : À la base, ZeroMQ fournit 3 types de communication. Le REQ/REP sert à faire du client-serveur classique (on fait une requête, on reçoit une réponse) ; le PUSH/PULL pour envoyer des données à sens unique ; le PUB/SUB pour envoyer des données à tous ceux qui s’y sont abonnés. La principale différence entre les deux derniers est que le PUB envoie ses paquets de données − en même temps − à tous les SUB qui y sont connectés, alors que le PUSH envoie ses données successivement à chacun des PULL connectés − l’un après l’autre.

Le problème

Mon admin sys. est revenu vers moi avec un comportement étrange. Quelque soit le nombre de workers, un seul d’entre eux recevait tous les paquets de données. Aïe.

Continuer la lecture de « ZeroMQ et load-balancing : un exemple concret »

ZeroMQ, la super bibliothèque réseau

Une fois n’est pas coutume, voici un article particulièrement technique.

Le développement d’applications connectées est quelque chose d’à la fois important et délaissé. Important car cela constitue la base de l’informatique moderne, et que c’est une chose qui est enseignée dans tous les cursus informatiques. Délaissé car l’écrasante majorité des applications connectées actuelles sont basées sur le protocole HTTP.

L’omniprésence du HTTP est globalement une très bonne chose : Pas besoin de redévelopper la roue (les serveurs Apache et IIS sont là depuis longtemps, Nginx et Lighttpd cherchent à prendre la relève), les développements sont facilités (le protocole est simple, son utilisation est rapide), et un très grand choix d’applications est accessible de manière unifiée (il suffit de voir toutes les API REST disponibles). Cela a créé un écosystème applicatif dynamique.

Mais il y a aussi un inconvénient : Il devient difficile de sortir de ce modèle.

  • Soit les développeurs ne savent pas faire de développement réseau. Ils ont bien appris la théorie du client-serveur à l’école, mais ne l’ont jamais appliquée autrement qu’en faisant du code PHP, Java ou .NET qui tourne sur un serveur HTTP.
  • Soit les développeurs n’ont pas fait de développement réseau depuis tellement longtemps qu’ils ne savent même plus par où commencer. Ils ont perdu les compétences nécessaires, et doivent presque repartir de zéro.
  • Soit les développeurs deviennent fainéants et ne veulent plus s’embêter à réfléchir autrement qu’en faisant du client-serveur basique en utilisant le protocole HTTP.

Et pourtant, être capable de développer des applications qui utilisent le réseau de manière différente est vital. Cela ouvre l’esprit à de nouveaux types de programmes, ce qui est parfois nécessaire pour mettre en place une fonctionnalité qui ne peut pas se satisfaire d’un fonctionnement simpliste.

ZeroMQ

ZeroMQ est une bibliothèque permettant de créer des applications client-serveur. Elle a un triple intérêt :

  1. Elle offre des possibilités nouvelles. Plutôt que de se casser la tête dès qu’on veut sortir un peu du client-serveur habituel, ZeroMQ met à notre disposition tout un arsenal de nouveaux types de connexions. La liste est longue : du client-serveur classique ; de l’envoi de données asynchrones avec load-balacing ; du “publish-subscribe” ; de la connexion multiple simultanée et transparente ; de la communication inter-threads (au sein d’un même processus), inter-processus (au sein de la même machine) et/ou inter-machines (au sein du même réseau).
  2. Elle est très simple à utiliser. Là où il faudrait normalement des dizaines de lignes de code, les choses se développent en quelques lignes, tout en restant très faciles à comprendre et à relire.
  3. Elle est très performante. ZeroMQ génère ses propres threads pour gérer ses connexions, et intègre des optimisations qui la rende généralement plus rapide que si vous ouvrez vos propres sockets brutes (oui, j’ai eu du mal à le croire moi aussi, mais c’est véridique).

En plus de cela, ZeroMQ est disponible sur un grand nombre de langages de programmation (34 à ce jour), ce qui permet de faire communiquer facilement des programmes écrits en PHP, en C, en C++, en Java, en C#, en Lua, …

Il y a toutefois une spécificité, qui peut être un inconvénient dans certains cas : ZeroMQ ne peut communiquer qu’avec ZeroMQ. Cela veut dire qu’il n’est pas possible d’utiliser cette bibliothèque pour se connecter à un serveur HTTP, FTP ou n’importe quel autre type de serveur qui utilise des sockets TCP/IP. Cela est dû au fait que ZeroMQ utilise son propre protocole d’échange de données, qui lui donne justement ses «super-pouvoirs», mais qui empêche de l’utiliser comme une bibliothèque de connexion universelle.

Principes de base

Avec ZeroMQ, on manipule des “sockets”. C’est un peu comme les sockets BSD habituelles, sauf qu’elles offrent une abstraction permettant de manipuler aussi bien des sockets réseau que des communications inter-threads ou inter-processus. Continuer la lecture de « ZeroMQ, la super bibliothèque réseau »