Супервизия и отказоустойчивость
Изучите стратегии супервизии Akka для создания устойчивых систем, способных корректно восстанавливаться после сбоев.
«Супервизия и отказоустойчивость» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 3 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 3 уроков всего.
Создание устойчивых систем
В параллельных приложениях могут возникать проблемы. Актор может завершиться аварийно, сетевой вызов — завершиться с ошибкой, а база данных — оказаться недоступной.
Отказоустойчивость — это способность системы продолжать корректную работу даже при сбое отдельных её частей. Akka предоставляет мощные механизмы для её обеспечения.
Надзор родителя над потомком
Акторы Akka организованы в иерархию, подобную семейному древу. Когда один актор создаёт другой, он становится его родителем.
- Родители отвечают за надзор над своими потомками.
- Если дочерний актор завершается с ошибкой, его родитель получает уведомление.
- Затем родитель решает, как обработать ошибку потомка. Это называется стратегией надзора.
Выбор действий при сбое
При сбое актора его родитель может применить одну из четырёх директив:
Resume: актор продолжает обрабатывать сообщения, игнорируя ошибку.Restart: актор останавливается, создаётся заново, а затем возобновляет обработку. Его внутреннее состояние сбрасывается.Stop: актор окончательно завершается.Escalate: ошибка передаётся надзирающему за родителем актору (дедушке).
Стратегия OneForOne
OneForOneStrategy — распространённая стратегия надзора. Она означает, что директива применяется только к дочернему актору, который завершился с ошибкой.
Например, если один дочерний актор аварийно завершился, родитель может решить перезапустить только этого потомка, не затрагивая остальных.
OneForOne в действии
Рассмотрим OneForOneStrategy. Мы создадим родителя, который перезапускает потомка при возникновении ArithmeticException, но останавливает его при других исключениях.
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()
}Наблюдение за OneForOne
Вы запустили предыдущий код? Вот что произошло:
- После отправки
"divideByZero"потомок выбросилArithmeticException. Родитель применилRestart. Вы увидели записи «Перед перезапуском» и «После перезапуска». - Затем потомок обработал «Снова здравствуйте».
- После отправки
"nullPointer"потомок выбросилNullPointerException. Родитель применилStop. Потомок был завершён. - Сообщение «Здравствуйте после null» было отправлено, но потомок уже был остановлен и не мог его обработать.
Стратегия AllForOne
Иногда сбой одного потомка означает, что все потомки-братья также могут оказаться нарушены или не способны работать корректно.
AllForOneStrategy применяет одну и ту же директиву ко всем потомкам надзирающего актора, а не только к тому, который завершился с ошибкой. Если один потомок завершается с ошибкой, выбранная директива применяется ко всем потомкам.
AllForOne в действии
Изменим нашего родителя, чтобы он использовал AllForOneStrategy. Обратите внимание, как сбой одного потомка влияет на его брата.
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()
}Настройка решающих функций
Вы можете создать тщательно настроенную логику надзора с помощью решающей функции. Эта функция принимает Throwable (исключение) и возвращает Directive.
Это позволяет реализовать сложные правила на основе типа исключения, сообщения, вызвавшего его, или даже состояния надзирающего актора.
Расширенный надзор
Ниже показано, как определить пользовательскую решающую функцию внутри стратегии надзора для более точного управления.
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()
}Проверьте свои знания
Рассмотрите родительский актор, осуществляющий надзор над двумя дочерними акторами: childA и childB. Родитель использует AllForOneStrategy с директивой Restart для всех исключений.
Что произойдёт, если childA выбросит исключение?
Повторение: отказоустойчивость
Отличная работа! В этом уроке Вы изучили мощные механизмы надзора Akka:
- Иерархия надзора: родители осуществляют надзор над потомками.
- Директивы:
Resume,Restart,Stop,Escalate. OneForOneStrategy: применяет директивы только к потомку, завершившемуся с ошибкой.AllForOneStrategy: применяет директивы ко всем потомкам.- Пользовательские решающие функции: обеспечивают точный контроль обработки сбоев.
Эти инструменты необходимы для создания надёжных и устойчивых параллельных приложений с акторами Akka!
Часто задаваемые вопросы
Урок «Супервизия и отказоустойчивость» бесплатный?
Да — полный текст урока «Супервизия и отказоустойчивость» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 3 уроков всего.
Чему я научусь в уроке «Супервизия и отказоустойчивость»?
Изучите стратегии супервизии Akka для создания устойчивых систем, способных корректно восстанавливаться после сбоев. Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?
Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 3.
Сколько времени занимает урок «Супервизия и отказоустойчивость»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?
Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Основы акторной модели Akka
- Проектирование акторных систем
- Супервизия и отказоустойчивость