42 votes

Comment concevoir l'architecture d'un système multicœur distribué tolérant aux pannes basé sur Erlang/OTP ?

Je voudrais construire un système basé sur Erlang/OTP qui résout un problème "embarrassant et parallèle".

Je l'ai déjà lu ou feuilleté :

  • Apprends-toi un peu d'Erlang ;
  • Programmer Erlang (Armstrong) ;
  • Programmation Erlang (Cesarini) ;
  • Erlang/OTP en action.

J'ai compris l'essentiel des processus, de la messagerie, des superviseurs, des gen_servers, de la journalisation, etc.

Je comprends que certains choix d'architecture dépendent de l'application concernée, mais j'aimerais tout de même connaître quelques principes généraux de conception de systèmes ERlang/OTP.

Devrais-je commencer par quelques gen_servers avec un superviseur et construire progressivement sur cette base ?

Combien de superviseurs dois-je avoir ? Comment décider quelles parties du système doivent être basées sur des processus ? Comment éviter les goulets d'étranglement ?

Devrais-je ajouter la journalisation plus tard ?

Quelle est l'approche générale de l'architecture des systèmes multiprocesseurs distribués tolérants aux pannes Erlang/OTP ?

112voto

Adam Lindberg Points 9689

Devrais-je commencer par quelques gen_servers avec un superviseur et construire progressivement sur cette base ?

Vous oubliez un élément clé des architectures Erlang : les applications ! (C'est-à-dire le concept d'applications OTP, et non d'applications logicielles).

Considérez les applications comme des composants. Un composant de votre système résout un problème particulier, est responsable d'un ensemble cohérent de ressources ou extrait quelque chose d'important ou de complexe du système.

La première étape de la conception d'un système Erlang consiste à déterminer quelles applications sont nécessaires. Certaines peuvent être tirées du web telles quelles, nous pouvons les appeler des bibliothèques. D'autres devront être écrites par vous-même (sinon vous n'auriez pas besoin de ce système particulier). Ces applications sont généralement appelées "logique métier" (souvent, vous devez également écrire vous-même certaines bibliothèques, mais il est utile de faire la distinction entre les bibliothèques et les applications métier de base qui relient tout).

Combien de superviseurs dois-je avoir ?

Vous devez avoir un superviseur pour chaque type de processus que vous voulez surveiller.

Un groupe d'intérimaires identiques ? Un seul superviseur pour les diriger tous.

Un processus différent avec des responsabilités différentes et des stratégies de redémarrage ? Un superviseur pour chaque type de processus, dans une hiérarchie correcte (en fonction du moment où les choses doivent redémarrer et des autres processus qui doivent être arrêtés avec elles).

Parfois, il est bon de placer plusieurs types de processus différents sous la responsabilité d'un même superviseur. C'est généralement le cas lorsque vous avez quelques processus uniques (par exemple, un superviseur de serveur HTTP, un processus propriétaire de table ETS, un collecteur de statistiques) qui fonctionneront toujours. Dans ce cas, il peut être trop compliqué d'avoir un superviseur pour chacun d'entre eux, il est donc courant d'ajouter les superviseurs sous un seul superviseur. Soyez simplement conscient des implications de l'utilisation d'une stratégie de redémarrage particulière lorsque vous faites cela, afin de ne pas arrêter votre processus de statistiques par exemple, au cas où votre serveur web tomberait en panne ( one_for_one est la stratégie la plus courante à utiliser dans des cas comme celui-ci). Veillez à ne pas avoir de dépendances entre les processus dans un fichier one_for_one superviseur. Si un processus dépend d'un autre processus qui s'est écrasé, il peut aussi s'écraser, déclenchant l'intensité de redémarrage du superviseur trop souvent et écrasant le superviseur lui-même trop tôt. Ceci peut être évité en ayant deux superviseurs différents, qui contrôleraient complètement les redémarrages par l'intensité et la période configurées ( plus longue explication ).

Comment décider quelles parties du système doivent être basées sur des processus ?

Chaque activité concurrente dans votre système devrait être dans son propre processus. Avoir une mauvaise abstraction de la concurrence est l'erreur la plus courante des concepteurs de systèmes Erlang au début.

Certaines personnes ne sont pas habituées à gérer la concurrence ; leurs systèmes ont tendance à en avoir trop peu. Un seul processus, ou quelques processus gigantesques, qui exécutent tout en séquence. Ces systèmes sont généralement pleins d'odeurs de code et le code est très rigide et difficile à remanier. Cela les rend également plus lents, car ils n'utilisent pas tous les cœurs disponibles pour Erlang.

D'autres personnes saisissent immédiatement les concepts de concurrence mais ne parviennent pas à les appliquer de manière optimale ; leurs systèmes ont tendance à surutiliser le concept de processus, ce qui fait que de nombreux processus restent inactifs en attendant d'autres qui sont en train de travailler. Ces systèmes ont tendance à être inutilement complexes et difficiles à déboguer.

En fait, dans les deux variantes, on rencontre le même problème : on n'utilise pas toute la concurrence disponible et on n'obtient pas les performances maximales du système.

Si vous vous en tenez à la principe de la responsabilité unique et se conformer à la règle d'avoir un processus pour chaque vraiment une activité concurrente dans votre système, ça devrait aller.

Il existe des raisons valables pour avoir des processus inactifs. Parfois, ils conservent un état important, parfois vous souhaitez conserver temporairement certaines données et abandonner le processus plus tard, parfois ils attendent des événements externes. Le plus grand piège est de faire passer des messages importants par une longue chaîne de processus largement inactifs, car cela ralentira votre système avec beaucoup de copies et utilisera plus de mémoire.

Comment éviter les goulets d'étranglement ?

Difficile à dire, cela dépend beaucoup de votre système et de ce qu'il fait. Mais en général, si vous avez une bonne répartition des responsabilités entre les applications, vous devriez pouvoir faire évoluer l'application qui semble être le goulot d'étranglement séparément du reste du système.

La règle d'or ici est de mesure, mesure, mesure ! Ne pensez pas que vous avez quelque chose à améliorer tant que vous n'avez pas mesuré.

Erlang est excellent en ce sens qu'il vous permet de cacher la concurrence derrière des interfaces (connue sous le nom de concurrence implicite). Par exemple, vous utilisez une API de module fonctionnel, une interface normale module:function(Arguments) qui pourrait à son tour engendrer des milliers de processus sans que l'appelant ne le sache. Si vos abstractions et votre API sont correctes, vous pouvez toujours paralléliser ou optimiser une bibliothèque après avoir commencé à l'utiliser.

Cela dit, voici quelques lignes directrices générales :

  • Essayez d'envoyer les messages directement au destinataire, évitez de canaliser ou d'acheminer les messages par des processus intermédiaires. Sinon, le système ne fait que passer du temps à déplacer des messages (données) sans vraiment travailler.
  • N'abusez pas des modèles de conception OTP, tels que gen_servers. Dans de nombreux cas, il suffit de lancer un processus, d'exécuter un morceau de code, puis de le quitter. Pour cela, un gen_server est superflu.

Et un conseil supplémentaire : ne réutilisez pas les processus. La création d'un processus en Erlang est si bon marché et si rapide qu'il n'est pas logique de réutiliser un processus une fois sa durée de vie terminée. Dans certains cas, il peut être utile de réutiliser l'état (par exemple, l'analyse complexe d'un fichier), mais il est préférable de le stocker canoniquement ailleurs (dans une table ETS, une base de données, etc.).

Devrais-je ajouter la journalisation plus tard ?

Vous devriez ajouter la journalisation maintenant ! Il y a une grande API intégrée appelée Enregistreur qui vient avec Erlang/OTP à partir de la version 21 :

logger:error("The file does not exist: ~ts",[Filename]),
logger:notice("Something strange happened!"),
logger:debug(#{got => connection_request, id => Id, state => State},
             #{report_cb => fun(R) -> {"~p",[R]} end}),

Cette nouvelle API dispose de plusieurs fonctionnalités avancées et devrait couvrir la plupart des cas où vous avez besoin de journalisation. Il existe également une bibliothèque tierce, plus ancienne mais toujours largement utilisée. Lager .

Quelle est l'approche générale de l'architecture des systèmes multiprocesseurs distribués tolérants aux pannes Erlang/OTP ?

Pour résumer ce qui a été dit ci-dessus :

  • Divisez votre système en applications
  • Placez vos processus dans la bonne hiérarchie de supervision, en fonction de leurs besoins et de leurs dépendances.
  • Disposez d'un processus pour chaque activité réellement concurrente dans votre système.
  • Maintenir une API fonctionnelle vers les autres composants du système. Cela vous permet de :
    • Refaire votre code sans changer le code qui l'utilise
    • Optimiser le code après coup
    • Distribuez votre système en cas de besoin (il suffit d'appeler un autre nœud derrière l'API, l'appelant ne s'en rendra pas compte).
    • Tester le code plus facilement (moins de travail pour mettre en place des harnais de test, plus facile de comprendre comment l'utiliser).
  • Commencez à utiliser les bibliothèques disponibles dans ANP jusqu'à ce que vous ayez besoin de quelque chose de différent (vous le saurez le moment venu).

Les pièges courants :

  • Trop de processus
  • Trop peu de processus
  • Trop de routage (messages transférés, processus enchaînés)
  • Trop peu de demandes (je n'ai jamais vu le cas contraire, en fait)
  • Pas assez d'abstraction (ce qui rend difficile le remaniement et le raisonnement, ainsi que les tests).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X