0Pricing
Scala for Backend Engineering & Functional Programming · Lezione

Supervisione e tolleranza ai guasti

Esplori le strategie di supervisione di Akka per creare sistemi resilienti, in grado di recuperare elegantemente dai guasti.

Supervisione e tolleranza ai guasti è una lezione Scala for Backend Engineering & Functional Programming gratuita su CoddyKit. Questa è la lezione 3 di 3. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Scala for Backend Engineering & Functional Programming, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Scala for Backend Engineering & Functional Programming include 3 lezioni in totale.

Creazione di sistemi resilienti

Nelle applicazioni concorrenti possono verificarsi dei problemi. Un actor potrebbe andare in crash, una chiamata di rete potrebbe non riuscire oppure un database potrebbe non essere raggiungibile.

La tolleranza agli errori è la capacità di un sistema di continuare a funzionare correttamente anche quando alcune sue parti si guastano. Akka mette a disposizione potenti meccanismi per ottenere questo risultato.

Supervisione parent-figlio

Gli actor di Akka sono organizzati in una gerarchia, molto simile a un albero genealogico. Quando un actor ne crea un altro, diventa il suo parent.

  • I parent sono responsabili della supervisione dei propri figli.
  • Se un actor figlio ha un errore, il parent riceve una notifica.
  • Il parent decide quindi come gestire l'errore del figlio. Questa viene chiamata strategia di supervisione.

Decisioni in caso di errore

Quando un actor ha un errore, il parent può impartire una delle quattro direttive:

  • Resume: l'actor continua a elaborare i messaggi, ignorando l'errore.
  • Restart: l'actor viene arrestato, ricreato e quindi riprende l'elaborazione. Il suo stato interno viene reimpostato.
  • Stop: l'actor viene terminato definitivamente.
  • Escalate: l'errore viene inoltrato al supervisor del parent (il nonno dell'actor).

Strategia OneForOne

OneForOneStrategy è una strategia di supervisione comune. Significa che una direttiva viene applicata solo all'actor figlio che ha avuto l'errore.

Ad esempio, se un actor figlio va in crash, il parent può decidere di riavviare solo quel figlio specifico, lasciando invariati gli altri.

OneForOne in azione

Vediamo OneForOneStrategy. Creeremo un parent che riavvia un figlio in caso di ArithmeticException, ma lo arresta in caso di altre eccezioni.

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()
}

Osservazione di OneForOne

Ha eseguito il codice precedente? Ecco cosa è successo:

  • Quando è stato inviato "divideByZero", il figlio ha generato un'ArithmeticException. Il parent ha applicato Restart. Sono stati visualizzati i log "Pre-restart" e "Post-restart".
  • Il figlio ha quindi elaborato "Hello again".
  • Quando è stato inviato "nullPointer", il figlio ha generato una NullPointerException. Il parent ha applicato Stop. Il figlio è stato terminato.
  • È stato inviato "Hello after null", ma il figlio era già arrestato e non ha potuto elaborarlo.

Strategia AllForOne

A volte, un errore in un figlio significa che anche tutti i figli fratelli potrebbero essere compromessi o non funzionare correttamente.

AllForOneStrategy applica la stessa direttiva a tutti i figli del supervisor, non solo a quello che ha avuto l'errore. Se un figlio ha un errore, tutti i figli sono interessati dalla direttiva scelta.

AllForOne in azione

Modifichiamo il parent per utilizzare AllForOneStrategy. Osservi come l'errore di un figlio influisce sul fratello.

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()
}

Personalizzazione dei decider

È possibile creare una logica di supervisione altamente personalizzata utilizzando una funzione decider. Questa funzione riceve un Throwable (l'eccezione) e restituisce una Directive.

Ciò consente di implementare regole complesse basate sul tipo di eccezione, sul messaggio che l'ha causata o persino sullo stato del supervisor.

Supervisione avanzata

Ecco come definire un decider personalizzato all'interno della strategia del supervisor per ottenere un controllo più preciso.

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()
}

Verifichi le Sue conoscenze

Consideri un actor parent che supervisiona due actor figli, childA e childB. Il parent utilizza una AllForOneStrategy con la direttiva Restart per tutte le eccezioni.

Che cosa succederà se childA genera un'eccezione?

Riepilogo: tolleranza agli errori

Ottimo lavoro! In questa lezione ha appreso i potenti meccanismi di supervisione di Akka:

  • Gerarchia di supervisione: i parent supervisionano i figli.
  • Direttive: Resume, Restart, Stop, Escalate.
  • OneForOneStrategy: applica le direttive solo al figlio che ha avuto l'errore.
  • AllForOneStrategy: applica le direttive a tutti i figli.
  • Decider personalizzati: consentono un controllo preciso della gestione degli errori.

Questi strumenti sono essenziali per creare applicazioni concorrenti robuste e resilienti con gli actor di Akka!

Domande Frequenti

La lezione «Supervisione e tolleranza ai guasti» è gratuita?

Sì — il testo completo di «Supervisione e tolleranza ai guasti» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Scala for Backend Engineering & Functional Programming, passa a CoddyKit PRO. Il corso Scala for Backend Engineering & Functional Programming include 3 lezioni in totale.

Cosa imparerò in «Supervisione e tolleranza ai guasti»?

Esplori le strategie di supervisione di Akka per creare sistemi resilienti, in grado di recuperare elegantemente dai guasti. Eserciti Scala for Backend Engineering & Functional Programming con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Scala for Backend Engineering & Functional Programming?

Non è richiesta alcuna esperienza precedente. Scala for Backend Engineering & Functional Programming su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 3.

Quanto tempo richiede la lezione «Supervisione e tolleranza ai guasti»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Scala for Backend Engineering & Functional Programming?

Sì. Ogni lezione Scala for Backend Engineering & Functional Programming include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Fondamenti del modello ad attori di Akka
  2. Progettazione di sistemi ad attori
  3. Supervisione e tolleranza ai guasti
← Torna a Scala for Backend Engineering & Functional Programming