Scala for Backend Engineering & Functional Programming · Lección

Supervisión y tolerancia a fallos

Explore las estrategias de supervisión de Akka para crear sistemas resilientes capaces de recuperarse correctamente de los fallos.

Lección 3 de 312 pasos

Supervisión y tolerancia a fallos es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 3 de 3. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Scala for Backend Engineering & Functional Programming, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Scala for Backend Engineering & Functional Programming incluye 3 lecciones en total.

Creación de sistemas resilientes

En las aplicaciones concurrentes pueden ocurrir problemas. Un actor podría bloquearse, una llamada de red podría fallar o una base de datos podría no estar disponible.

La tolerancia a fallos es la capacidad de un sistema para seguir funcionando correctamente aunque fallen algunas de sus partes. Akka proporciona mecanismos potentes para conseguirlo.

Supervisión entre actores principales y secundarios

Los actores de Akka están organizados en una jerarquía, como un árbol genealógico. Cuando un actor crea otro, se convierte en su actor principal.

  • Los actores principales son responsables de supervisar a sus actores secundarios.
  • Si un actor secundario falla, su actor principal recibe una notificación.
  • El actor principal decide entonces cómo gestionar el fallo del actor secundario. Esto se denomina estrategia de supervisión.

Decisión ante un fallo

Cuando un actor falla, su actor principal puede emitir una de cuatro directivas:

  • Resume: el actor continúa procesando mensajes e ignora el fallo.
  • Restart: el actor se detiene, se vuelve a crear y después reanuda el procesamiento. Su estado interno se restablece.
  • Stop: el actor finaliza de forma permanente.
  • Escalate: el fallo se transmite al supervisor del actor principal (su abuelo).

Estrategia OneForOne

OneForOneStrategy es una estrategia de supervisión habitual. Significa que la directiva se aplica únicamente al actor secundario que ha fallado.

Por ejemplo, si un actor secundario se bloquea, el actor principal puede decidir reiniciar solo ese actor, sin afectar a los demás.

OneForOne en acción

Veamos OneForOneStrategy. Crearemos un actor principal que reinicie un actor secundario cuando se produzca una ArithmeticException, pero lo detenga ante otras excepciones.

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

Observación de OneForOne

¿Ha ejecutado el código anterior? Esto es lo que ocurrió:

  • Al enviar "divideByZero", el actor secundario lanzó una ArithmeticException. El actor principal aplicó Restart. Se mostraron los registros «Pre-restart» y «Post-restart».
  • A continuación, el actor secundario procesó «Hello again».
  • Al enviar "nullPointer", el actor secundario lanzó una NullPointerException. El actor principal aplicó Stop. El actor secundario finalizó.
  • Se envió «Hello after null», pero el actor secundario ya estaba detenido y no pudo procesarlo.

Estrategia AllForOne

A veces, el fallo de un actor secundario significa que sus hermanos también podrían estar afectados o no poder funcionar correctamente.

AllForOneStrategy aplica la misma directiva a todos los actores secundarios del supervisor, no solo al que ha fallado. Si un actor secundario falla, todos se ven afectados por la directiva elegida.

AllForOne en acción

Modifiquemos el actor principal para que use AllForOneStrategy. Observe cómo el fallo de un actor secundario afecta a su hermano.

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

Personalización de los decisores

Puede crear una lógica de supervisión muy personalizada mediante una función decisora. Esta función recibe un Throwable (la excepción) y devuelve una Directive.

Esto permite implementar reglas complejas basadas en el tipo de excepción, el mensaje que la provocó o incluso el estado del supervisor.

Supervisión avanzada

Así se define un decisor personalizado dentro de la estrategia del supervisor para obtener un control 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()
}

Compruebe sus conocimientos

Considere un actor principal que supervisa dos actores secundarios, childA y childB. El actor principal utiliza una AllForOneStrategy con la directiva Restart para todas las excepciones.

¿Qué ocurrirá si childA lanza una excepción?

Recapitulación: tolerancia a fallos

¡Buen trabajo! En esta lección ha aprendido sobre los potentes mecanismos de supervisión de Akka:

  • Jerarquía de supervisión: los actores principales supervisan a los actores secundarios.
  • Directivas: Resume, Restart, Stop, Escalate.
  • OneForOneStrategy: aplica las directivas únicamente al actor que ha fallado.
  • AllForOneStrategy: aplica las directivas a todos los actores secundarios.
  • Decisores personalizados: permiten controlar con precisión la gestión de fallos.

Estas herramientas son esenciales para crear aplicaciones concurrentes sólidas y resilientes con Akka Actors.

Gratis para empezar

Aprende Scala con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
39
Lecciones
143

Preguntas frecuentes

¿La lección «Supervisión y tolerancia a fallos» es gratis?

Sí — el texto completo de «Supervisión y tolerancia a fallos» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Scala for Backend Engineering & Functional Programming, actualiza a CoddyKit PRO. El curso de Scala for Backend Engineering & Functional Programming incluye 3 lecciones en total.

¿Qué aprenderé en «Supervisión y tolerancia a fallos»?

Explore las estrategias de supervisión de Akka para crear sistemas resilientes capaces de recuperarse correctamente de los fallos. Practicas Scala for Backend Engineering & Functional Programming con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Scala for Backend Engineering & Functional Programming?

No se requiere experiencia previa. Scala for Backend Engineering & Functional Programming en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 3.

¿Cuánto tiempo toma la lección «Supervisión y tolerancia a fallos»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Scala for Backend Engineering & Functional Programming?

Sí. Cada lección de Scala for Backend Engineering & Functional Programming incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Fundamentos del modelo de actores de Akka
  2. Diseño de sistemas de actores
  3. Supervisión y tolerancia a fallos
← Volver a Scala for Backend Engineering & Functional Programming