0Pricing
Scala for Backend Engineering & Functional Programming · Leçon

Supervision et tolérance aux pannes

Découvrez les stratégies de supervision d’Akka pour créer des systèmes résilients capables de se rétablir élégamment après des défaillances.

Supervision et tolérance aux pannes est une leçon Scala for Backend Engineering & Functional Programming gratuite sur CoddyKit. Ceci est la leçon 3 sur 3. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Scala for Backend Engineering & Functional Programming, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Scala for Backend Engineering & Functional Programming comprend 3 leçons au total.

Créer des systèmes résilients

Dans les applications concurrentes, des problèmes peuvent survenir. Un acteur peut se bloquer, un appel réseau peut échouer ou une base de données peut être inaccessible.

La tolérance aux pannes est la capacité d’un système à continuer de fonctionner correctement même lorsque certaines de ses parties échouent. Akka fournit de puissants mécanismes pour y parvenir.

Supervision parent-enfant

Les acteurs Akka sont organisés selon une hiérarchie, à la manière d’un arbre généalogique. Lorsqu’un acteur en crée un autre, il devient son parent.

  • Les parents sont responsables de la supervision de leurs enfants.
  • Si un acteur enfant échoue, son parent reçoit une notification.
  • Le parent décide ensuite de la manière de gérer l’échec de l’enfant. C’est ce que l’on appelle une stratégie de supervision.

Décider en cas d’échec

Lorsqu’un acteur échoue, son parent peut appliquer l’une des quatre directives suivantes :

  • Resume : l’acteur continue à traiter les messages en ignorant l’échec.
  • Restart : l’acteur est arrêté, recréé, puis reprend son traitement. Son état interne est réinitialisé.
  • Stop : l’acteur est définitivement arrêté.
  • Escalate : l’échec est transmis au superviseur du parent (son grand-parent).

Stratégie OneForOne

La OneForOneStrategy est une stratégie de supervision courante. Cela signifie qu’une directive s’applique uniquement à l’acteur enfant qui a échoué.

Par exemple, si un acteur enfant se bloque, le parent peut décider de redémarrer uniquement cet enfant précis, sans affecter les autres.

OneForOne en action

Examinons OneForOneStrategy. Nous allons créer un parent qui redémarre un enfant en cas d’ArithmeticException, mais l’arrête en cas d’autres exceptions.

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object OneForOneExample extends App {
  class Child extends Actor {
    override def preStart(): Unit = {
      println(s"Child ${self.path.name}: Started!")
    }
    override def postStop(): Unit = {
      println(s"Child ${self.path.name}: Stopped!")
    }
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
      println(s"Child ${self.path.name}: Pre-restart due to ${reason.getMessage}")
    }
    override def postRestart(reason: Throwable): Unit = {
      println(s"Child ${self.path.name}: Post-restart!")
    }
    def receive = {
      case "divideByZero" => throw new ArithmeticException("Oops, dividing by zero!")
      case "nullPointer"  => throw new NullPointerException("Something is null!")
      case msg            => println(s"Child ${self.path.name}: Received $msg")
    }
  }

  class Parent extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
        case _: ArithmeticException => Restart
        case _: NullPointerException => Stop
        case _                      => Escalate
      }

    val child = context.actorOf(Props[Child], "myChild")

    def receive = {
      case msg => child ! msg
    }
  }

  val system = ActorSystem("OneForOneSystem")
  val parent = system.actorOf(Props[Parent], "myParent")

  parent ! "Hello"
  parent ! "divideByZero" // Should cause a restart
  Thread.sleep(100)
  parent ! "Hello again"
  Thread.sleep(1000) // Give time for restart to complete

  parent ! "nullPointer"  // Should cause a stop
  Thread.sleep(100)
  parent ! "Hello after null" // This message will not be processed by child
  Thread.sleep(2000)

  system.terminate()
}

Observer OneForOne

Avez-vous exécuté le code précédent ? Voici ce qui s’est produit :

  • Lorsque "divideByZero" a été envoyé, l’enfant a levé une ArithmeticException. Le parent a appliqué Restart. Vous avez vu les journaux « Pré-redémarrage » et « Post-redémarrage ».
  • L’enfant a ensuite traité « Bonjour à nouveau ».
  • Lorsque "nullPointer" a été envoyé, l’enfant a levé une NullPointerException. Le parent a appliqué Stop. L’enfant a été arrêté.
  • « Bonjour après null » a été envoyé, mais l’enfant était déjà arrêté et ne pouvait pas le traiter.

Stratégie AllForOne

Parfois, l’échec d’un enfant signifie que tous ses enfants frères peuvent également être compromis ou incapables de fonctionner correctement.

La AllForOneStrategy applique la même directive à tous les enfants du superviseur, et pas seulement à celui qui a échoué. Si un enfant échoue, tous les enfants sont affectés par la directive choisie.

AllForOne en action

Modifions notre parent pour utiliser AllForOneStrategy. Remarquez comment l’échec d’un enfant affecte son frère.

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object AllForOneExample extends App {
  class Child extends Actor {
    override def preStart(): Unit = {
      println(s"Child ${self.path.name}: Started!")
    }
    override def postStop(): Unit = {
      println(s"Child ${self.path.name}: Stopped!")
    }
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
      println(s"Child ${self.path.name}: Pre-restart due to ${reason.getMessage}")
    }
    override def postRestart(reason: Throwable): Unit = {
      println(s"Child ${self.path.name}: Post-restart!")
    }
    def receive = {
      case "fail" => throw new RuntimeException("Child failed!")
      case msg    => println(s"Child ${self.path.name}: Received $msg")
    }
  }

  class Parent extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      AllForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
        case _: RuntimeException => Restart
        case _                   => Escalate
      }

    val childA = context.actorOf(Props[Child], "childA")
    val childB = context.actorOf(Props[Child], "childB")

    def receive = {
      case "failA" => childA ! "fail"
      case "msgB"  => childB ! "Hello from B"
      case msg     => println(s"Parent received: $msg")
    }
  }

  val system = ActorSystem("AllForOneSystem")
  val parent = system.actorOf(Props[Parent], "myParent")

  parent ! "msgB" // Child B receives a message
  Thread.sleep(100)

  parent ! "failA" // Child A fails, causing all children to restart
  Thread.sleep(1000)

  parent ! "msgB" // Child B (restarted) receives another message
  Thread.sleep(2000)

  system.terminate()
}

Personnaliser les décideurs

Vous pouvez créer une logique de supervision hautement personnalisée à l’aide d’une fonction de décision. Cette fonction prend un Throwable (l’exception) et renvoie une Directive.

Cela vous permet d’implémenter des règles complexes fondées sur le type d’exception, le message qui l’a provoquée ou même l’état du superviseur.

Supervision avancée

Voici comment définir un décideur personnalisé dans votre stratégie de supervision afin d’exercer un contrôle précis.

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object CustomDeciderExample extends App {
  class Worker extends Actor {
    def receive = {
      case "criticalError" => throw new IllegalStateException("Critical state!")
      case "minorError"    => throw new IllegalArgumentException("Bad input!")
      case msg             => println(s"Worker: Received $msg")
    }
  }

  class Supervisor extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 10.seconds) {
        case _: IllegalArgumentException => Resume // Minor error, just resume
        case _: IllegalStateException    => Restart // Critical, restart
        case _: Exception                => Stop    // Unknown, stop
        case _                           => Escalate // Others, escalate
      }

    val worker = context.actorOf(Props[Worker], "myWorker")

    def receive = {
      case msg => worker ! msg
    }
  }

  val system = ActorSystem("CustomDeciderSystem")
  val supervisor = system.actorOf(Props[Supervisor], "mySupervisor")

  supervisor ! "Hello"
  supervisor ! "minorError" // Should Resume
  Thread.sleep(100)
  supervisor ! "Hello again" // Worker should still be active
  Thread.sleep(500)

  supervisor ! "criticalError" // Should Restart
  Thread.sleep(100)
  supervisor ! "After critical" // Worker should be restarted
  Thread.sleep(1500)

  system.terminate()
}

Vérifier vos connaissances

Imaginez un acteur parent supervisant deux acteurs enfants, childA et childB. Le parent utilise une AllForOneStrategy avec la directive Restart pour toutes les exceptions.

Si childA lève une exception, que se passe-t-il ?

Récapitulatif : tolérance aux pannes

Excellent travail ! Dans cette leçon, vous avez découvert les puissants mécanismes de supervision d’Akka :

  • Hiérarchie de supervision : les parents supervisent les enfants.
  • Directives : Resume, Restart, Stop, Escalate.
  • OneForOneStrategy : applique les directives uniquement à l’enfant ayant échoué.
  • AllForOneStrategy : applique les directives à tous les enfants.
  • Décideurs personnalisés : ils permettent un contrôle précis de la gestion des échecs.

Ces outils sont essentiels pour créer des applications concurrentes robustes et résilientes avec les acteurs Akka !

Questions Fréquemment Posées

La leçon « Supervision et tolérance aux pannes » est-elle gratuite ?

Oui — le texte complet de « Supervision et tolérance aux pannes » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Scala for Backend Engineering & Functional Programming, passe à CoddyKit PRO. Le cours Scala for Backend Engineering & Functional Programming comprend 3 leçons au total.

Qu'est-ce que j'apprendrai dans « Supervision et tolérance aux pannes » ?

Découvrez les stratégies de supervision d’Akka pour créer des systèmes résilients capables de se rétablir élégamment après des défaillances. Tu pratiques Scala for Backend Engineering & Functional Programming avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Scala for Backend Engineering & Functional Programming ?

Aucune expérience préalable n'est requise. Scala for Backend Engineering & Functional Programming sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 3.

Combien de temps prend la leçon « Supervision et tolérance aux pannes » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Scala for Backend Engineering & Functional Programming ?

Oui. Chaque leçon Scala for Backend Engineering & Functional Programming inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Principes fondamentaux du modèle d’acteurs Akka
  2. Conception de systèmes d’acteurs
  3. Supervision et tolérance aux pannes
← Retour à Scala for Backend Engineering & Functional Programming