Je voudrais construire un système basé sur Erlang / OTP, qui résout un problème «extrêmement déroutant».
J’ai déjà lu / parcouru:
J’ai l’essentiel des processus, de la messagerie, des superviseurs, des serveurs génériques, de la journalisation, etc.
Je comprends que certains choix d’architecture dépendent de l’application concernée, mais j’aimerais quand même connaître quelques principes généraux de la conception du système ERlang / OTP.
Dois-je simplement commencer par quelques serveurs génériques avec un superviseur et en tirer progressivement parti?
Combien de superviseurs devrais-je avoir? Comment est-ce que je décide quelles parties du système devraient être basées sur le processus? Comment devrais-je éviter les goulots d’étranglement?
Devrais-je append la journalisation plus tard?
Quelle est l’approche générale de l’architecture des systèmes multiprocesseurs à tolérance de pannes dissortingbués d’Erlang / OTP?
Vous manquez un composant clé dans les architectures Erlang ici: les applications! (C’est-à-dire le concept d’applications OTP, pas 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 d’un élément abstrait important ou complexe du système.
La première étape de la conception d’un système Erlang consiste à décider quelles applications sont nécessaires. Certaines peuvent être extraites du Web telles qu’elles sont appelées bibliothèques. D’autres vous aurez besoin d’écrire vous-même (sinon vous n’auriez pas besoin de ce système particulier). Ces applications, que nous appelons généralement la logique métier (vous devez souvent écrire certaines bibliothèques vous-même, mais il est utile de garder la distinction entre les bibliothèques et les applications métier de base qui lient tout).
Vous devriez avoir un superviseur pour chaque type de processus que vous souhaitez surveiller.
Une bande de travailleurs temporaires identiques? Un superviseur pour les gouverner tous.
Processus différent avec différentes responsabilités et stratégies de redémarrage? Un superviseur pour chaque type de processus, dans une hiérarchie correcte (selon le moment où les choses doivent redémarrer et quel autre processus doit être interrompu avec eux?).
Parfois, il est possible de placer plusieurs types de processus sous le même superviseur. C’est généralement le cas lorsque vous avez quelques processus singleton (par exemple, un superviseur de serveur HTTP, un processus propriétaire de table ETS, un collecteur de statistiques) qui sera toujours exécuté. Dans ce cas, il peut être trop difficile d’avoir un superviseur pour chacun, il est donc courant d’append le superviseur sous un. Soyez simplement conscient des implications de l’utilisation d’une stratégie de redémarrage particulière lorsque vous faites cela, par exemple, si votre serveur Web se bloque ( one_for_one
est la stratégie la plus courante à utiliser dans de tels cas). Veillez à ne pas avoir de dépendance entre les processus dans un superviseur one_for_one
. Si un processus dépend d’un autre processus en panne, il peut également tomber en panne, ce qui déclenche trop souvent l’intensité du redémarrage des superviseurs et provoque le crash du superviseur lui-même trop tôt. Cela peut être évité en ayant deux superviseurs différents, qui contrôleraient complètement les redémarrages en fonction de l’intensité et de la période configurées ( explication plus longue ).
Chaque activité concurrente dans votre système doit être dans son propre processus. Avoir la 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 processus ou quelques gigantesques qui exécutent tout en séquence. Ces systèmes sont généralement pleins d’odeur de code et le code est très rigide et difficile à refactoriser. Cela les ralentit également, car ils ne peuvent pas utiliser 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 à abuser du concept de processus, ce qui rend de nombreux processus inactifs en attente d’autres qui travaillent. Ces systèmes ont tendance à être inutilement complexes et difficiles à déboguer.
Essentiellement, dans les deux variantes, vous rencontrez le même problème, vous n’utilisez pas toute la concurrence disponible et vous n’obtenez pas les meilleures performances du système.
Si vous vous en tenez au principe de la responsabilité unique et que vous respectez la règle pour avoir un processus pour chaque activité réellement concurrente dans votre système, vous devriez vous en sortir.
Il y a des raisons valables d’avoir des processus inactifs. Parfois, ils conservent un état important, parfois vous souhaitez conserver certaines données temporairement et plus tard, ignorer le processus, parfois ils attendent des événements externes. Le plus gros inconvénient est de transmettre des messages importants à travers une longue chaîne de processus largement inactifs, car cela ralentira votre système avec beaucoup de copies et utilisera plus de mémoire.
Difficile à dire, cela dépend beaucoup de votre système et de ce qu’il fait. En règle générale, cependant, si vous répartissez les responsabilités entre les applications, vous devriez pouvoir adapter l’application qui semble être le goulot d’étranglement distinct du rest du système.
La règle d’or ici est de mesurer, mesurer, mesurer ! Ne pensez pas que vous avez quelque chose à améliorer jusqu’à ce que vous ayez mesuré.
Erlang est génial en ce sens qu’il vous permet de masquer la concurrence derrière des interfaces (connues sous le nom de simultanéité implicite). Par exemple, vous utilisez une API de module fonctionnel, une interface normale de module:function(Arguments)
, qui peut à son tour générer des milliers de processus sans que l’appelant le sache. Si vous avez vos abstractions et votre API correctement, vous pouvez toujours paralléliser ou optimiser une bibliothèque après l’avoir utilisée.
Cela étant dit, voici quelques lignes direcsortingces générales:
Et un conseil de bonus: ne pas réutiliser les processus. Créer un processus à Erlang est tellement bon marché et rapide qu’il est inutile de réutiliser un processus une fois sa durée de vie écasting. Dans certains cas, il peut être judicieux de réutiliser l’état (par exemple, l’parsing complexe d’un fichier), mais il est préférable de le conserver de façon canonique ailleurs (dans une table ETS, une firebase database, etc.).
Vous devriez append la journalisation maintenant! Il existe une excellente API intégrée appelée Logger fournie 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 possède plusieurs fonctionnalités avancées et devrait couvrir la plupart des cas nécessitant une journalisation. Il y a aussi la bibliothèque Lager plus ancienne mais encore largement utilisée.
Pour résumer ce qui a été dit ci-dessus:
Pièges courants: