Une réponse à la question qui nous habite tous : "Qu'est-ce que Lexpage, Guybrush ?"    —  Roger-Dédé

Discussions

RabbitMQ et communication inter-process

Guybrush 4743 Bob
Hello,

J'en parlais sur le minichat : je suis à la recherche d'expérience avec RabbitMQ. Le use-case est le suivant : j'ai écrit une librairie qui permet d'exécuter des statecharts de façon déterministe (et de les tester, surtout, avec différentes techniques dont design-by-contract, behavior-driven-development, etc.).

Actuellement, la librairie, écrite en Python, nécessite (sauf cas particulier) de "coder" pour pouvoir exécuter le statechart : on charge le statechart depuis un yaml, on crée un interpréteur via la librairie, et ensuite on envoie les événements et on call le "execute()" sur l'interpréteur. C'est la partie "API" de la librairie. Quand des statecharts doivent communiquer entre eux, on peut les "binder" de sorte à propager les événements d'un statechart à un ou plusieurs autres. Tout cela se fait programmatiquement, même si l'API est très simple d'emploi.

L'idée serait de pouvoir s'affranchir de la partie "programmatique", surtout pour permettre aux "non-Pythonistes" de pouvoir tester/utiliser l'outil. J'ai déjà plein de routines qui me permettent de faire cela relativement facilement, mais j'aimerai vraiment qu'on puisse exécuter un statechart comme étant une simple "boîte noire" qui consomme et produit des événements, et qu'on puisse rediriger ces événements vers les consommateurs et producteurs adéquats, que ça soit des statecharts ou n'importe quel autre programme.

En première approche, l'idée était d'utiliser des sockets FS (bye bye Windows) : on lance un statechart en précisant les sockets "in" et les sockets "out", et si on veut une communication inter-statechart, il suffit de "partager" le socket in/out correctement. Le problème, c'est que la consommation d'un socket est "unique" dans le sens où dès que l'info est reçue par *un* statechart, elle ne peut pas l'être par d'autre. Ca signifie qu'il faut que je dédouble les sockets à chaque fois que j'ajoute un acteur dans mon système (et ajouter un acteur devrait être aussi simple que d'exécuter un programme en ligne de commande). Du coup, cette solution à base de sockets est embêtante : je dois multiplier les sockets pour chaque "canal" de communication entre deux statecharts. Pire : non seulement, je souhaite que les statecharts puissent communiquer avec des événements, mais j'aimerai aussi avoir une sorte de "canal de commande", permettant de contrôler/interroger l'interpréteur. Avec des sockets, ça signifie qu'il faut du bidirectionnel, et donc encore multiplier les sockets.

Bref, si l'idée des sockets FS me tentait bien au début (notamment parce que ça permettait aussi de lire/écrire les events via stdin et stdout), les limitations sont importantes, et même s'il est assez facile de les contourner, ce n'est pas idéal comme architecture. Du coup, je me dis qu'il vaudrait mieux passer par un système de publish/subscribe. Vu que chaque statechart est un processus isolé, et vu que lorsqu'on exécute un statechart, on ne veut pas explicitement renseigner les bindings de communication (parce qu'on peut dynamiquement ajouter/retirer des composants), il faudrait que je parte vers un broker centralisé, par exemple RabbitMQ.

Je ne connais pas plus que ça RabbitMQ, je sais juste que ça existe et "grosso-modo" ce que ça fait. Mais je vois que la doc est un peu kilométrique, et pas toujours très accessible. Alors avant de me lancer là-dedans, j'aimerai avoir un petit retour sur RabbitMQ, si possible dans un contexte publish/subscribe. J'aimerai également savoir si l'installation de RabbitMQ et sa mise en route sont "simples", dans le sens où je n'ai pas envie que mes utilisateurs aient à se préoccuper de tout ça. Idéalement, ils installeraient RabbitMQ via leur package manager, et ma librairie s'occuperait de lancer l'instance (si elle n'est pas encore lancée) et c'est tout (donc en gros, est-ce que RabbitMQ fonctionne "out-of-the-box").

Au niveau fonctionnalités, je pense qu'il contient tout ce dont j'ai besoin. Essentiellement, il me faudrait du support pour des canaux globaux (mais il suffit de créer des "topics" adéquats que chaque statechart rejoint), ainsi que des canaux locaux pour la communication inter-statechart (un par statechart, ou deux si jamais un émetteur qui est aussi consommateur ne peut pas ignorer facilement ses propres messages), et encore un autre canal de "commande" (un par statechart), où les réponses devraient être envoyées uniquement à l'expéditeur du message d'origine (donc communication directe, ça semble supporté).

Pour résumer, chaque statechart a un canal "in" et un canal "out" (ou simplement un seul canal si la notion d'expéditeur identifiable existe). Ces canaux servent pour la communication interprocess : chaque statechart émet ses événements sur son propre canal, et s'abonne en tant que consommateur aux canaux des autres statecharts qu'il souhaite écouter. Le "sens" de la communication n'est pas important, mais je dois pouvoir gérer tous les cas. Par exemple, un statechart A doit pouvoir écrire dans le flux de B (A est "au courant de B", mais "B ne connait pas A") et inversément, un statechart B doit pouvoir lire le flux de A ("B est au courant de A", mais "A n'est pas au courant de B"). C'est pour ça que chaque statechart doit pouvoir s'abonner en lecture et potentiellement écriture sur n'importe quel canal de communication. Pour clarifier un peu, chaque statechart X écoute sur X-in, et publie sur X-out (ce que signifie que si A veut communiquer avec B, soit A publie sur B-in, soit B écoute sur A-out, les deux scénarios devant être gérés).

Chaque statechart a aussi un canal de "commande/contrôle", sur lequel il reçoit des commandes (forcément). Les réponses à ces commandes sont envoyées uniquement à l'expéditeur de la commande (que ça soit un statechart, un binaire de contrôle ou simplement une fonction de "debug" dans RabbitMQ qui permettrait de le faire). Je suppose qu'un simple canal "A-cmd" pour chaque statechart A suffira (A lit sur A-cmd, et répond en "communication directe" à l'émetteur).


Ce message a été modifié 1 fois. Dernière modification : 11 juillet 2019 à 11:47 par Guybrush.

Fabe 392 Maitre jedi
RabbitMQ est pas super pratique à embed. Il demande de la conf et quelques process d'admin pour être configuré avec les bons topics, etc...
Ça peut sans doute se faire mais ça va être "fat".

Pour ton use case (du moins ce que j'en comprends), je serai parti sur du Go qui te permettrait de poper des channels asynchrones dynamiquement et directement dans ton executable.
Si tu as une adhérence à Python, peut être qu'il peut y avoir une forme d'orchestration du Python par le process Go, ou l'inverse.
Guybrush 4743 Bob
Je souhaite rester autant que possible en Python pour le développement, vu que la librairie est écrite dans ce langage. Je peux, par contre, sans problème intégrer un outil "tout fait" d'un autre langage, qui servirait de broker entre mes processus (ce que je souhaitais faire initialement avec RabbitMQ).

J'ai cherché brièvement en Python, mais toutes les libs que je trouve pour gérer du pub/sub ou de la communication sont limitées à un seul process (ou à un seul interpréteur Python). En général, il semble que les devs de ces librairies visent une communication inter-objets à base de signaux, plutôt qu'une communication inter-process (dans mon cas, c'est vraiment de l'inter-process que je veux, avec idéalement la possibilité de faire communiquer des processes distribués sur plusieurs machines).

Je dois encore regarder du coté de Redis ou des solutions plus "universelles et simples à déployer", car il n'est pas exclu que je puisse m'en sortir avec ça (et, accessoirement, il existe un lib Python qui permet de simuler Redis via memcache ou directement la mémoire de l'interpréteur, ce qui veut dire que je pourrai éventuellement embarquer directement dans mon package un "faux broker" tout en permettant d'en utiliser un "vrai" production-ready.

[Edit : ça a l'air possible avec Redis et son pub/sub, à priori... et supporté en Python : medium.com/@johngrant/py… ]


Ce message a été modifié 1 fois. Dernière modification : 11 juillet 2019 à 13:02 par Guybrush.

yaug 391 Maitre jedi
Alors pour la partie installation, clairement ça se fait bien et c'est utilisable outofthebox.
C'est plus ou moins comme cela que je l'utilise.

Pour ton use case, tu es clairement allez beaucoup plus loin de mon utilisation classique de RabbitMq.

Si je comprend bien, chaque Stateshart (sans avoir ce que ça représente) doit pouvoir envoyer des infos sur son canal Out, en recevoir sur son canal In, et aussi écouter un canal command.

Je ne sais pas comment sont gérés tes SS mais si c'est programmatiquement, au moment de le lancer tu peux créer les topics In, Out, Command ou un truc du genre.
ça mériterait d'être étudié.
Guybrush 4743 Bob
FabeRabbitMQ est pas super pratique à embed. Il demande de la conf et quelques process d'admin pour être configuré avec les bons topics, etc...
yaugAlors pour la partie installation, clairement ça se fait bien et c'est utilisable outofthebox. C'est plus ou moins comme cela que je l'utilise.
Mettez vous d'accord :-D
yaugSi je comprend bien, chaque Stateshart (sans avoir ce que ça représente) doit pouvoir envoyer des infos sur son canal Out, en recevoir sur son canal In, et aussi écouter un canal command.
C'est bien ça. Un statechart est juste une représentation visuelle, à la base, du comportement d'un composant. Ma librairie, Sismic, permet de définir ces statecharts via un fichier YAML (au lieu du paradigme visuel, non-exécutable, d'UML) et de les exécuter. Actuellement, l'exécution se fait via l'API. L'idée est d'avoir une commande "sismic-run" qui permette d'exécuter un statechart via Sismic sans avoir à écrire une seule ligne de code. Et vu que la communication entre statecharts est une composante importante, je veux supporter cette dernière via "sismic-run". On aurait par exemple "sismic-run statechart.yaml --name mon_statechart --in B --out C D E" qui signifierait "charge le statechart contenu dans statechart.yaml, attribue lui un identifiant unique "mon_statechart", et fait en sorte qu'il écoute (1) sur son propre canal in, (2) sur le canal out de B, et qu'il écrit (1) sur son propre canal out, et (2) sur les canaux in de C, D et E". Bien entendu, rien ne dit que B, C, D et E sont tous "actifs" (mais on s'en fou dans le cas d'une pub/sub, d'où l'idée de partir vers du pub/sub ;-).

En terme d'implémentation, j'ai déjà un runner de statecharts threadé, donc le process principal lancé par "sismic-run" peut s'occuper de subscribe les canaux qu'il faut, d'associer les callbacks aux canaux "écoutés", et d'écrire dans les canaux de sorties quand il faut. Je ne pense pas avoir besoin de plus de 50 à 100 lignes de code Python pour écrire tout ça, mais j'aimerai que l'architecture soit assez souple (notamment pour intégrer le canal de commande par la suite, parce que j'aimerai pouvoir "shutdown" proprement les statecharts, mais aussi consulter certaines variables/configurations internes pendant l'exécution et, sans doute plus tard, pouvoir "binder" des statecharts à la volée, même quand ils sont déjà "sismic-runnés" ;-)
PetitCalgon 1481 Spammeur
Pour du publish/subscribe j'utilise wamp-proto.org/ et ça marche très très bien.
Tu ouvres des boîtes aux lettres (topic) et tu publies dessus.
Tous ceux qui sont abonnés reçoivent le message.

Ça marche avec tout plein de langage de programmation (nous utilisons JS et C# en même temps) et il y a même une implémentation Python github.com/noisyboiler/w…
Donc une instance Python peut communiquer via WAMP avec une instance Java, C#, JS, etc. à partir du moment où ils sont d'accord sur le nom du topic et le format du message.
Chez nous, c'est du JSON qui est échangé, donc niveau compatibilité, on est tranquille.

C'est asynchrone dans le sens où tu publies sur une boite aux lettres, ne reçoit pas de confirmation que ça a été reçu, sauf si le recevant publie à son tour un message sur un topic de confirmation.
Tchou 1752 Spammeur
J'ai pas tout compris à la question. Perso, j'utilise (mais sans avoir développé l'appli autour, donc sans m'être trop penché dessus), et j'ai fait l'install, ça ne semblait pas complexe à installer. Actuellement en config mono-serveur, pour faire dialoguer entre eux des applis différentes, mais à terme il faut que je fasse du multi-serveur avec un rabbitmq redondant également.

Il y a quelques années j'avais dû créer une petite appli temporaire qui synchronisait entre plusieurs devices une même information en temps réel, j'avais fait du node pour, aujourd'hui je me pencherai probablement sur une solution telle que rabbitmq, mais je suis juste au stade "je sais que la techno existe", c'est insuffisant pour t'aider, désolé.
Guybrush 4743 Bob
Je vais jeter un oeil à WAMP, sait-on jamais ;-)

Coté "encodage des messages", à la base je voulais partir sur du plain text (car les "événements" reçus et émis par un statechart sont juste des strings) mais vu que les événements peuvent être paramétrisés, je dois opter pour une autre structure pour encoder les paramètres. Le plus simple et le plus probable est de partir sur du json. Pour les messages de "commande" et leurs réponses, je vais sûrement opter pour la même option, mais il faut que je définisse d'abord un protocole. La partie "commande" est surtout utile pour le débogage, mais aussi pour avoir accès à la mémoire interne des statecharts ainsi que leur configuration (état(s) actif(s)), sans quoi, mes statecharts seront juste des boîtes noires qu'on ne peut pas interroger, pas très utile ;-)

L'alternative à tout ça serait d'écrire mon propre "server/broker", via du TCP (ou des file sockets, peu importe) et de gérer la communication moi-même au niveau du serveur. Mais (1) à moins qu'un étudiant ne se présente en septembre pour bosser là-dessus, je ne compte pas perdre trop de temps à le faire moi-même, (2) c'est un peu bête de réinventer la roue s'il existe déjà quelque chose de solide pour gérer tout ça, (3) faire un "vrai serveur" correctement va m'ajouter des dépendances supplémentaires encore plus encombrantes qu'un client RabbitMQ/WAMP/Redis/..., ce qui n'est pas forcément une bonne idée.

Quoiqu'il en soit, mon implémentation sera indépendante du backend de "broking" utilisé, puisque l'outil (Sismic) est prévu pour être extrêmement générique dans son approche (sans quoi il est difficile d'attirer les chercheurs à l'utiliser. On a déjà quelques industriels, cela dit ;-))
Guybrush 4743 Bob
GuybrushJe vais jeter un oeil à WAMP, sait-on jamais
Ok, je me souviens de WAMP. Ca a fait tilt quand ils ont parlé de crossbar.io, c'était un projet que je suivais il y a quelques années. Malheureusement, l'implémentation Autobahn en Python est hyper lourde (en terme de dépendances surtout !), je ne peux pas inclure ça dans ma librairie :-/ ([Edit: oui, y a moyen en Python de définir des "groupes de dépendances" qui peuvent être facultativement installés, mais les utilisateurs oublient souvent ça, et ça suggère que certaines "features" sont en dehors du core de la lib :/]


Ce message a été modifié 1 fois. Dernière modification : 12 juillet 2019 à 08:59 par Guybrush.

Guybrush 4743 Bob
Après lecture des différentes solutions, je pense m'orienter vers RabbitMQ. Il semble en effet qu'il soit simple de le déployer et qu'il ne nécessite, dans sa forme "basique" aucune configuration particulière (je n'ai pas de besoin particulier en terme de fiabilité, sécurité, etc.).

Je dois encore bien lire la documentation pour voir comment structurer mes communications, essentiellement savoir s'il vaut mieux faire un "exchange" global (du nom de la librairie, par ex "sismic") avec différents topics ("sismic.A.in", "sismic.A.out", "sismic.B.in", etc.) où s'il vaut mieux plusieurs "exchanges" (un par statechart A, B, C, etc...) avec à chaque fois (au moins) deux topics "in" et "out". Dans les deux cas, chaque statechart va s'abonner en "lecture" à toutes ses input, et publier sur chaque "output".

Dans le premier cas (un exchange global pour l'application), cela devrait faciliter le logging/debogging (puisqu'il suffit de s'abonner à tout sur cet exchange, quelque soit le "topic") ou si on veut cibler spécifiquement une communication, de s'abonner à "sismic.A.*" (mais c'est pas "top" car si A est abonné à B.out, je ne le verrai pas, ce qui m'oblige à connaître la topologie exacte de mes communications si je veux être sûr de "tout avoir"). Cela dit cette connaissance de topologie n'est faisable que si je déclare ma topologie de façon centralisée, et je ne souhaite pas avoir cette contrainte. Chaque approche a ses avantages et ses inconvénients ;-)

Je ne vais probablement pas faire de premiers essais d'implémentation avant un petit moment. J'ai un étudiant qui est intéressé pour faire un projet d'année en rapport avec la librairie concernée (sismic), mais il ne sait pas encore "exactement quoi". Il est pour l'instant intéressé par un projet qui a été confié à un autre étudiant cette année, mais qui n'a pas du tout avancé (à ma connaissance). Du coup, s'il ne le présente pas en seconde session, le sujet sera remis dans la pool et donc l'étudiant intéressé travaillera probablement dessus, et pas sur cette histoire de RabbitMQ. A vrai dire, j'espère que l'étudiant ne prendra pas cette histoire de RabbitMQ, car ça me tente de plus en plus à expérimenter et à implémenter :-D (et en général, les productions faites par les étudiants (1) prennent du temps avant d'être fonctionnelles, et (2) ne sont pas forcément de "bonne qualité", or j'aimerai intégrer tout ça assez rapidement à la librairie ^^).

Répondre

Vous devez être inscrit et identifié.