Posts Tagged zeromq

Nodecast : backend asynchrone avec zeromq

Depuis mon dernier article sur Nodecast il y a 6 mois, de nouvelles idées ont germés qui ont données lieux à une nouvelle et sans aucun doute dernière (on y croit) réécriture du backend.

En effet dans mon dernier article je me sers d’un logiciel propriétaire pour présenter ce que je crois être l’avenir de services sur Internet via des technologies en Peer to Peer. Dans cette architecture P2P je pense que l’auto-hébergement est alors crédible car les données sont répliquées et leur accès répartie.

C’est pourquoi j’ai à nouveau réécris le backend, car dans un contexte où chaque utilisateur est encouragé à s’auto-héberger, il est nécessaire que le logiciel soit léger, simple à packager et donc avoir le moins de dépendance possible. Lorsque j’ai découvert Zeromq j’ai décidé d’implémenter avec, la gestion de la file d’attente et de répartition des jobs pour remplacer le serveur Qpid. Tous les serveurs de file comme Qpid, RabbitMQ ou Redis sont excellent dans un environnement centralisé mais sont à mon avis une contrainte s’ils sont imposés dans un environnement décentralisé ou réparti.

Zeromq me permet de migrer dans le dispatcher la gestion des files, grâce à des sockets ZMQ de type PUSH/PULL gérées dans des threads. L’usage de Zeromq est in fine plus complexe puisque plus bas niveau qu’une API fourni avec un serveur de file, mais les possibilités sont quasiment infinie, voir les différents exemples dans de nombreux langages et surtout les nombreuses spécifications de protocoles. A noter que Zeromq ne permet pas de persistance disque comme le font Qpid ou RabbitMQ, cependant une implémentation du protocole Titanic le permettrait, et il n’est sans doute pas sorcier de les ajouter en option.

A propos de la persistance des messages, tout dépend du type d’application gérée, pour un système bancaire il ne peut y avoir de perte de message bien entendu. Au contraire, il me semble avoir lu que les dev du  backend de last.fm avaient choisi une technologie sans persistance pour privilégier la performance.

Schémas

Voici le schéma de la nouvelle architecture et du dispatcher

Description

Ce schéma décrit le fonctionnement interne du processus dispatcher. Les numéros en rouge indiquent le workflow suivi par un job. Même s’il n’y a plus de serveur de file dans l’architecture nodecast, le dispatcher permet d’en conserver les propriétés grâce d’une part aux threads et aux envois non bloquant :  la directive ZMQ::NOBLOCK indique à la socket zmq de ne pas attendre de confirmation du destinataire.

  1. L’API géré par Thin (externe au processus dispatcher) réceptionne les données XML envoyées par les clients nodecast. Le serveur Thin a bindé le port 5555 local, stocke le XML dans GridFS, construit un hash et le push dans la socket ZMQ_PUSH.
  2. Le thread Zreceive est connecté sur le port 5555 via une socket ZMQ:PULL. Avec ce protocole il peut y avoir plusieurs instance du dispatcher connectés, mais un seul recevra un même job. A contrario en ZMQ PUB/SUB tous les subscribers recoivent une copie du job.
  3. Zreceive a créé un ZMQ_DEVICE. Celui-ci relie la socket ZMQ_PULL vers une socket ZMQ_PUSH. Le device transmet tout ce que reçoit la socket de PULL vers la socket de PUSH. A noter que la socket ZMQ_PUSH est ici de type ZMQ_INPROC, ce n’est donc pas une socket TCP mais une zone mémoire partagée avec une autre thread.
  4. Un thread Zdispatch est connecté en ZMQ_PULL sur la zone mémoire inproc. Il désérialize les jobs reçu dans un objet BSON puis émet un signal Qt avec la charge créée.
  5. L’objet Payload reçoit la charge dans un slot. A noter que cet objet possède à une référence vers un objet Nosql ce qui lui permet d’interroger MongoDB. Dans ce cas il est impératif que le slot Payload ne soit pas appelé alors qu’il n’a pas fini ses tâches en cours. Pour cela le QObject::connect qui relie le signal de Zdispatch avec le slot de Payload doit impérativement posséder la directive  Qt::BlockingQueuedConnection. Payload découpe la charge reçue, effectue des traitements, sérialize des objects puis emet des signaux pour chaque worker.
  6. Le thread worker_push possède un slot par signal emit par Payload. Lors de sa construction il a bindé chaque socket ZMQ_PUSH par worker. Il transmet alors la charge en mode non bloquant ZMQ_NOBLOCK.
  7. Chaque worker ZMQ_PULL sur sa socket TCP dédié. Traite puis stocke dans MongoDB.

Les cadences.

Elles rythmes le workflow, s’il n’y avait qu’une seule cadence ce backend serait synchrone. Cependant on voit très bien plusieurs.

  1. Tout d’abord le serveur Thin, qui transmet le job et retourne aussitôt une réponse au client.
  2. Ensuite le thread Zreceive, retransmet à son rythme les jobs dans une zone mémoire
  3. Le thread Zdispatch lit la zone mémoire puis faire suivre les jobs dans des signaux Qt.
  4. L’objet Payload transmet les bouts de job dans des signaux vers des slots.
  5. Le thread worker_push envoie à chaque worker sa charge
L’entrée et la sortie du dispatcher sont non bloquante. Si ce dernier plante, le serveur Thin et le worker se rendront compte de rien. De plus contrairement à un serveur classique, les sockets zeromq permettent de lancer des clients même si le serveur n’est pas lancé. Si le dispatcher tombe il pourra être relancé sans problème et les workers recevront à nouveau leur jobs.

Conclusions

L’architecture du dispatcher n’est pas encore optimisée, par exemple les positions 4, 5 et 6 pourraient fusionner. Cependant le Proof Of Concept est concluant, ce backend devrait pouvoir s’exécuter correctement sur une machine recyclée. Une dernière tâche serait de migrer le serveur Thin dans un thread en Qt afin de simplifier et optimiser encore l’architecture, ou bien d’utiliser Mongrel2 à la place de Nginx et Thin comme me le conseillait un certain Zed Shaw :)
La cible d’obtenir un backend asynchrone brokerless étant atteinte la prochaine étape est l’intégration d’un worker qui permette la synchronisation en P2P des données du backend. L’objectif est qu’une instance Nodecast se synchronise avec l’instance d’un contact de l’utilisateur. Gros travail en prespective, les solutions sont nombreuses comme telehash mais qui ne fourni pas pour l’instant d’implémentation en C/C++, ou bien la lib bitdht utilisée par entre autre par retroshare.
Bonnes vacances (ou bon courage) aux lecteurs !

, ,

4 Commentaires

Suivre

Get every new post delivered to your Inbox.

Joignez-vous à 189 followers