0Pricing
Scala for Backend Engineering & Functional Programming · Lekcja

Nadzorowanie i odporność na awarie

Poznaj strategie nadzorowania w Akka, aby tworzyć odporne systemy, które potrafią prawidłowo odzyskiwać sprawność po awariach.

Nadzorowanie i odporność na awarie to bezpłatna lekcja Scala for Backend Engineering & Functional Programming na CoddyKit. To lekcja 3 z 3. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Scala for Backend Engineering & Functional Programming, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Scala for Backend Engineering & Functional Programming zawiera 3 lekcji w sumie.

Budowanie odpornych systemów

W aplikacjach współbieżnych mogą wystąpić różne problemy. Aktor może ulec awarii, wywołanie sieciowe może się nie powieść, a baza danych może być niedostępna.

Tolerowanie błędów to zdolność systemu do dalszego poprawnego działania nawet wtedy, gdy jego części ulegają awariom. Akka udostępnia zaawansowane mechanizmy pozwalające to osiągnąć.

Nadzorowanie relacji rodzic–dziecko

Aktorzy Akka są zorganizowani w hierarchię przypominającą drzewo genealogiczne. Gdy aktor tworzy innego aktora, staje się jego rodzicem.

  • Rodzice odpowiadają za nadzorowanie swoich dzieci.
  • Jeśli aktor-dziecko ulegnie awarii, jego rodzic otrzymuje powiadomienie.
  • Następnie rodzic decyduje, jak obsłużyć awarię dziecka. Nazywa się to strategią nadzorowania.

Podejmowanie decyzji w razie awarii

Gdy aktor ulegnie awarii, jego rodzic może wydać jedną z czterech dyrektyw:

  • Resume: Aktor kontynuuje przetwarzanie komunikatów, ignorując awarię.
  • Restart: Aktor zostaje zatrzymany, utworzony ponownie, a następnie wznawia przetwarzanie. Jego stan wewnętrzny zostaje zresetowany.
  • Stop: Aktor zostaje trwale zakończony.
  • Escalate: Awaria zostaje przekazana nadzorcy rodzica, czyli „dziadkowi” aktora.

Strategia OneForOne

OneForOneStrategy jest często stosowaną strategią nadzorowania. Oznacza to, że dyrektywa dotyczy wyłącznie aktora-dziecka, który uległ awarii.

Jeśli na przykład jeden aktor-dziecko ulegnie awarii, rodzic może zdecydować o ponownym uruchomieniu tylko tego konkretnego dziecka, pozostawiając pozostałe dzieci bez zmian.

OneForOne w działaniu

Zobaczmy, jak działa OneForOneStrategy. Utworzymy rodzica, który ponownie uruchamia dziecko po wystąpieniu ArithmeticException, ale zatrzymuje je w przypadku innych wyjątków.

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

Obserwowanie działania OneForOne

Czy uruchomił(a) Pan/Pani poprzedni kod? Oto, co się wydarzyło:

  • Po wysłaniu komunikatu "divideByZero" dziecko zgłosiło wyjątek ArithmeticException. Rodzic zastosował dyrektywę Restart. W logach pojawiły się wpisy „Pre-restart” i „Post-restart”.
  • Następnie dziecko przetworzyło komunikat „Hello again”.
  • Po wysłaniu komunikatu "nullPointer" dziecko zgłosiło wyjątek NullPointerException. Rodzic zastosował dyrektywę Stop. Dziecko zostało zakończone.
  • Wysłano komunikat „Hello after null”, ale dziecko było już zatrzymane i nie mogło go przetworzyć.

Strategia AllForOne

Czasami awaria jednego dziecka oznacza, że wszystkie dzieci będące jego rodzeństwem również mogą być uszkodzone lub niezdolne do prawidłowego działania.

AllForOneStrategy stosuje tę samą dyrektywę do wszystkich dzieci nadzorcy, a nie tylko do tego, które uległo awarii. Jeśli jedno dziecko ulegnie awarii, wszystkie dzieci zostają objęte wybraną dyrektywą.

AllForOne w działaniu

Zmodyfikujmy rodzica tak, aby używał strategii AllForOneStrategy. Proszę zwrócić uwagę, jak awaria jednego dziecka wpływa na jego rodzeństwo.

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

Dostosowywanie decydentów

Można tworzyć bardzo niestandardową logikę nadzorowania za pomocą funkcji decydenta. Funkcja ta przyjmuje obiekt Throwable (wyjątek) i zwraca obiekt Directive.

Pozwala to implementować złożone reguły zależne od typu wyjątku, komunikatu, który go wywołał, a nawet stanu nadzorcy.

Zaawansowane nadzorowanie

Oto sposób definiowania niestandardowego decydenta w ramach strategii nadzorowania, aby uzyskać precyzyjną kontrolę.

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

Sprawdź swoją wiedzę

Proszę rozważyć aktora-rodzica nadzorującego dwóch aktorów-dzieci: childA i childB. Rodzic używa strategii AllForOneStrategy z dyrektywą Restart dla wszystkich wyjątków.

Co się stanie, jeśli childA zgłosi wyjątek?

Podsumowanie: tolerowanie błędów

Świetnie! W tej lekcji poznał(a) Pan/Pani zaawansowane mechanizmy nadzorowania w Akka:

  • Hierarchia nadzorowania: Rodzice nadzorują dzieci.
  • Dyrektywy: Resume, Restart, Stop, Escalate.
  • OneForOneStrategy: Stosuje dyrektywy wyłącznie do dziecka, które uległo awarii.
  • AllForOneStrategy: Stosuje dyrektywy do wszystkich dzieci.
  • Niestandardowi decydenci: Umożliwiają precyzyjną kontrolę obsługi awarii.

Narzędzia te są niezbędne do budowania niezawodnych i odpornych współbieżnych aplikacji z wykorzystaniem aktorów Akka!

Często zadawane pytania

Czy lekcja „Nadzorowanie i odporność na awarie” jest bezpłatna?

Tak — pełny tekst „Nadzorowanie i odporność na awarie” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Scala for Backend Engineering & Functional Programming, przejdź na CoddyKit PRO. Kurs Scala for Backend Engineering & Functional Programming zawiera 3 lekcji w sumie.

Co nauczysz się w „Nadzorowanie i odporność na awarie”?

Poznaj strategie nadzorowania w Akka, aby tworzyć odporne systemy, które potrafią prawidłowo odzyskiwać sprawność po awariach. Ćwiczysz Scala for Backend Engineering & Functional Programming z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Scala for Backend Engineering & Functional Programming?

Nie wymagamy żadnego doświadczenia. Scala for Backend Engineering & Functional Programming w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 3.

Ile czasu zajmuje lekcja „Nadzorowanie i odporność na awarie”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Scala for Backend Engineering & Functional Programming?

Tak. Każda lekcja Scala for Backend Engineering & Functional Programming zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Podstawy modelu aktorów Akka
  2. Projektowanie systemów aktorów
  3. Nadzorowanie i odporność na awarie
← Powrót do Scala for Backend Engineering & Functional Programming