What's next : "Akka: Simpler Scalability, Fault-Tolerance, Concurrency & Remoting through Actors" par Jonas Bonér
Lors de la What’s Next, Jonas Bonér a présenté Akka, un framework facilitant la réalisation d’applications concurrentes, pouvant passer à l’échelle et tolérantes aux erreurs. Cet article tente de retranscrire le contenu de cette présentation.
L’enjeu
La loi de Moore ne s’applique plus, le nombre de transistor au sein d’un microprocesseur ne double pas tous les 18 mois. Ainsi, l’on ne peut augmenter la puissance de calcul à disposition d’un programme que par deux moyens. Le premier est d’utiliser tout les cores de chacun de ses microprocesseurs (extensibilité verticale). Le second est de répartir le travail sur plusieurs machines (extensibilité horizontale).
Le problème
Les outils actuels qui permettent de gérer les aspects concurrents sont d’un niveau trop bas en terme d’abstraction. Ceci rends l’écriture de programmes corrects complexe pour les non spécialistes. Un premier obstacle par exemple est qu’il est nécessaire d’écrire du code dont le seul but est de gérer les accès concurrents (verrous, threads, etc …). Le programmeur est facilement tenté de mélanger le code de gestion de la concurrence avec le code métier. Cela non seulement conduit à une dégradation de la lisibilité du code mais introduit également une rigidité de l’application. De plus la difficulté vient elle-même du concept de concurrence. En effet, il est très difficile de raisonner sur un programme mettent en oeuvre plusieurs flux d’exécution pouvant modifier une même donnée. Entre autres, il s’avère que l’utilisation en excès de verrous conduit à des inter-blocages alors qu’en utiliser trop peu mène à des race condition. Il est trop dur d’écrire d’écrire des application concurrente, passant à l’échelle et tolérantes aux erreurs. C’est ce constat qui a poussé Jonas Bonèr à écrire Akka, un framework offrant des outils de plus haut niveau d’abstraction.
La solution offerte par Akka
Akka offre un ensemble d’outils permettant de résoudre des problèmes liés à la concurrence : les acteurs, les agents, et la mémoire transactionnelle logicielle (STM). Akka s’inspire largement du modèle de concurrence d’Erlang et propose une API pour Java et Scala,
L’acteur
Un acteur est un objet couplé doté d’une boite aux lettre (une file de messages). Un acteur ne s’exécute qu’en réaction à l’arrivée d’un message. Ainsi un acteur dont la boite aux lettres est vide ne consomme que de la mémoire. Un acteur consomme peu de ressource : environ 600 octets, soit environ 6 millions d’acteurs sur une machine équipée de 4 GO de RAM.
object SayHello class HelloActor extends Actor { def receive = { case SayHello => println("Hello world.") case _ => println("I'm not supposed to process this.") } }
Un acteur est monothreadé, vous n’avez donc pas à vous soucier de la concurrence lorsque vous traitez un message. L’état interne d’un acteur n’est pas accessible au monde extérieur, il n’y a donc nul besoin de le synchroniser. Il est possible de redéfinir à “chaud” la méthode “receive” d’un acteur en utilisant la méthode “become”.
La communication entre acteurs
Un acteur ne communique que via l’envoi de message. L’envoi de message est asynchrone et non bloquant. Les méthodes sont les même que l’on communique avec un acteur local ou distant.
- La méthode ‘!’ (“bang”) envoi un message qui n’appelle pas de réponse (“fire and forget”)
- La méthode ‘!!!’ (“bang bang bang”) envoie un message est reçoit un “Future”. Il est possible de bloquer la thread afin de récupérer le résultat immédiatement via la méthode “get”. On peut également définir une fonction qui sera appliquée sur le Future lorsque la réponse est prête.
- La méthode ‘reply’ permettre d’envoyer un message à l’acteur dont on est en train de traiter le message.
Ces caractéristiques font qu’il est d’une part facile de raisonner sur le fonctionnement d’un acteur. En effet, dans un acteur il n’y a qu’un flux d’exécution, la programmation est séquentielle. Un acteur est une entité autonome, réagissant à l’arrivé d’un message : le niveau d’abstraction est haut. Le fait qu’il n’y ait pas d’état partagé, de multiple flux d’exécution ni de communication bloquante avec d’autres acteurs élimine les famines, les inter-blocage et les ‘race conditions’.
Architecture résiliente
Dans un modèle de concurrence basé sur les acteurs, une architecture tolérante aux fautes est obtenue en supervisant les acteurs métier. Ainsi on va lier les acteurs métier à des acteurs superviseurs. Ceci signifie que le superviseur est informé de l’état de fonctionnement des acteurs qu’il gère. Une exception lancée par un acteur est reçu par son superviseur. On peut définir un ensemble d’exception qu’il est attendu de recevoir. Si le superviseur reçoit une exception inattendue, il détruira ses acteurs, se détruira lui-même et transmettra l’exception inattendue à son propre superviseur.
Stratégies
OneForOne
Dans cette stratégie, le superviseur détruit l’acteur défaillant et recréer immédiatement un nouvel acteur pour le remplacer.
AllForOne
La stratégie AllForOne se prête bien aux situations où les acteurs métier sont fortement liés. En effet le comportement est que si un acteur est défaillant, le superviseur va détruire l’ensemble des acteurs et créer un nouveau groupe.
La topologie en oignon (Fault-tolerant onion-layered Error Kernel)
Ici l’on défini un groupe d’acteurs (le noyau) dont le bon fonctionnement est impératif au fonctionnement du système. Si bien que si une erreur inattendue du sous arbre remonte l’arbre de acteurs, le superviseur est censée pouvoir la gérer ce qui va le maintenir actif. Dans cette situation, la stratégie est de recréer un superviseur fils (a poor bastard) afin de tenter de remettre en place le sous arbre qui s’est effondré. Peu importe si le sous arbre s’écroule de nouveau à court ou moyen terme, l’important est de maintenir le noyau actif afin d’amorcer une redéfinition à chaud du comportement des acteurs une fois le problème diagnostiqué et corrigé. Cette technique permet de maintenir la disponibilité du service tout en assurant sa maintenance.
Conclusion
Bien que l’acteur ne résolve pas tout les problèmes de concurrence, on peut constater qu’il élimine très bien les problèmes d’entrelacement des flux d’exécution. C’est donc une solution envisageable si l’on peut modéliser son système en s’affranchissant d’un état partagé.