감독과 내결함성
장애에서 안정적으로 복구할 수 있는 회복력 있는 시스템을 만들기 위해 Akka의 감독 전략을 살펴봅니다.
감독과 내결함성은(는) CoddyKit의 무료 Scala for Backend Engineering & Functional Programming 강의입니다. 이것은 3개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 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를 적용했습니다. "Pre-restart"와 "Post-restart" 로그가 표시되었습니다.- 그다음 자식은 "Hello again"을 처리했습니다.
"nullPointer"가 전송되자 자식에서NullPointerException이 발생했습니다. 부모는Stop을 적용했습니다. 자식은 종료되었습니다.- "Hello after 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라는 두 자식 액터를 감독하는 부모 액터를 생각해 보세요. 부모는 모든 예외에 대해 Restart 지시를 사용하는 AllForOneStrategy를 적용합니다.
childA에서 예외가 발생하면 어떤 일이 일어날까요?
복습: 장애 허용
잘하셨습니다! 이번 레슨에서는 Akka의 강력한 감독 메커니즘을 배웠습니다.
- 감독 계층: 부모가 자식을 감독합니다.
- 지시:
Resume,Restart,Stop,Escalate OneForOneStrategy: 실패한 자식에게만 지시를 적용합니다.AllForOneStrategy: 모든 자식에게 지시를 적용합니다.- 사용자 지정 판정 함수: 실패 처리를 세밀하게 제어할 때 사용합니다.
이러한 도구는 Akka 액터를 사용하여 견고하고 복원력 있는 동시성 애플리케이션을 구축하는 데 필수적입니다!
자주 묻는 질문
“감독과 내결함성” 강의는 무료인가요?
네 — “감독과 내결함성” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Scala for Backend Engineering & Functional Programming 강의 전체를 잠금 해제할 수 있습니다. Scala for Backend Engineering & Functional Programming 강의에는 총 3개의 강의가 포함되어 있습니다.
“감독과 내결함성”에서 뭘 배우나요?
장애에서 안정적으로 복구할 수 있는 회복력 있는 시스템을 만들기 위해 Akka의 감독 전략을 살펴봅니다. 브라우저에서 직접 실행하는 실습 코드로 Scala for Backend Engineering & Functional Programming을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Scala for Backend Engineering & Functional Programming을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Scala for Backend Engineering & Functional Programming은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 3개 중 3번째 강의입니다.
“감독과 내결함성” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Scala for Backend Engineering & Functional Programming 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Scala for Backend Engineering & Functional Programming 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- Akka 액터 모델 기초
- 액터 시스템 설계
- 감독과 내결함성