Scala for Backend Engineering & Functional Programming · Aula

Supervisão e tolerância a falhas

Explore as estratégias de supervisão do Akka para criar sistemas resilientes, capazes de se recuperar de falhas de forma adequada.

Aula 3 de 312 etapas

Supervisão e tolerância a falhas é uma aula grátis de Scala for Backend Engineering & Functional Programming no CoddyKit. Esta é a aula 3 de 3. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Scala for Backend Engineering & Functional Programming, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Scala for Backend Engineering & Functional Programming inclui 3 aulas no total.

Criando sistemas resilientes

Em aplicações concorrentes, as coisas podem dar errado. Um ator pode falhar, uma chamada de rede pode falhar ou um banco de dados pode ficar inacessível.

Tolerância a falhas é a capacidade de um sistema continuar funcionando corretamente mesmo quando partes dele falham. O Akka fornece mecanismos poderosos para alcançar isso.

Supervisão entre pai e filho

Os atores do Akka são organizados em uma hierarquia, muito semelhante a uma árvore genealógica. Quando um ator cria outro ator, ele se torna seu pai.

  • Os pais são responsáveis por supervisionar seus filhos.
  • Se um ator filho falhar, seu pai receberá uma notificação.
  • O pai então decide como lidar com a falha do filho. Isso é chamado de estratégia de supervisão.

Decidindo o que fazer em caso de falha

Quando um ator falha, seu pai pode emitir uma de quatro diretivas:

  • Resume: o ator continua processando mensagens, ignorando a falha.
  • Restart: o ator é interrompido, recriado e então retoma o processamento. Seu estado interno é redefinido.
  • Stop: o ator é terminado permanentemente.
  • Escalate: a falha é encaminhada ao supervisor do pai (seu avô).

Estratégia OneForOne

A OneForOneStrategy é uma estratégia de supervisão comum. Isso significa que uma diretiva se aplica apenas ao ator filho que falhou.

Por exemplo, se um ator filho falhar, o pai poderá decidir reiniciar somente esse filho específico, sem afetar os demais.

OneForOne em ação

Vamos ver a OneForOneStrategy. Criaremos um pai que reinicia um filho diante de uma ArithmeticException, mas o interrompe diante de outras exceções.

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

Observando o OneForOne

Você executou o código anterior? Veja o que aconteceu:

  • Quando "divideByZero" foi enviado, o filho lançou uma ArithmeticException. O pai aplicou Restart. Você viu os registros "Pre-restart" e "Post-restart".
  • Em seguida, o filho processou "Hello again".
  • Quando "nullPointer" foi enviado, o filho lançou uma NullPointerException. O pai aplicou Stop. O filho foi terminado.
  • "Hello after null" foi enviado, mas o filho já havia sido interrompido e não pôde processá-lo.

Estratégia AllForOne

Às vezes, uma falha em um filho significa que todos os filhos irmãos também podem estar comprometidos ou incapazes de funcionar corretamente.

A AllForOneStrategy aplica a mesma diretiva a todos os filhos do supervisor, não apenas àquele que falhou. Se um filho falhar, todos os filhos serão afetados pela diretiva escolhida.

AllForOne em ação

Vamos modificar nosso pai para usar a AllForOneStrategy. Observe como a falha de um filho afeta seu irmão.

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

Personalizando os decisores

Você pode criar uma lógica de supervisão altamente personalizada usando uma função decisora. Essa função recebe um Throwable (a exceção) e retorna uma Directive.

Isso permite implementar regras complexas com base no tipo de exceção, na mensagem que a causou ou até mesmo no estado do supervisor.

Supervisão avançada

Veja como definir um decisor personalizado na estratégia do supervisor para obter um controle detalhado.

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

Verifique seus conhecimentos

Considere um ator pai supervisionando dois atores filhos, childA e childB. O pai usa uma AllForOneStrategy com a diretiva Restart para todas as exceções.

Se childA lançar uma exceção, o que acontecerá?

Recapitulação: tolerância a falhas

Excelente trabalho! Nesta lição, você aprendeu sobre os poderosos mecanismos de supervisão do Akka:

  • Hierarquia de supervisão: os pais supervisionam os filhos.
  • Diretivas: Resume, Restart, Stop, Escalate.
  • OneForOneStrategy: aplica diretivas somente ao filho que falhou.
  • AllForOneStrategy: aplica diretivas a todos os filhos.
  • Decisores personalizados: oferecem controle detalhado sobre o tratamento de falhas.

Essas ferramentas são essenciais para criar aplicações concorrentes robustas e resilientes com atores do Akka!

Grátis para começar

Aprenda Scala com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
39
Aulas
143

Perguntas Frequentes

A aula “Supervisão e tolerância a falhas” é grátis?

Sim — o texto completo de “Supervisão e tolerância a falhas” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Scala for Backend Engineering & Functional Programming, atualize para CoddyKit PRO. O curso de Scala for Backend Engineering & Functional Programming inclui 3 aulas no total.

O que vou aprender em “Supervisão e tolerância a falhas”?

Explore as estratégias de supervisão do Akka para criar sistemas resilientes, capazes de se recuperar de falhas de forma adequada. Você pratica Scala for Backend Engineering & Functional Programming com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Scala for Backend Engineering & Functional Programming?

Nenhuma experiência prévia é necessária. Scala for Backend Engineering & Functional Programming no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 3.

Quanto tempo leva a aula “Supervisão e tolerância a falhas”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Scala for Backend Engineering & Functional Programming?

Sim. Cada aula de Scala for Backend Engineering & Functional Programming inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Fundamentos do modelo de atores do Akka
  2. Projetando sistemas de atores
  3. Supervisão e tolerância a falhas
← Voltar para Scala for Backend Engineering & Functional Programming