
액터모델은 TopLevel 아키텍처로 계층형구조로 동적으로 생성할수 있으며 부모가 자식의 생성과 유지를 책임지게 하는 관리감독(SuperVisor) 전략을 이용할수도 있습니다.
액터가 생성되고 나면 부모액터는 자식액터를 알수 있기때문에 상대경로로 접근할수 있으며~ 계층형주소 체계를 가지기 때문에 api path처럼 주소체계로 접근할수도 있습니다.
액터를 계층적으로 구성하고 관리감독하는 SuperVisor액터 생성을 시도하보겠습니다.
LLM TIP : 코드생성을 이용할때 표준이되는 기본 모델의 코드를 어시스트로 활용해 분석을 먼저 의뢰하면, 기존 코드 컨셉을 활용해 생성을 하기때문에 코드생성의 범위를 벗어나지 않게됩니다.
코틀린으로 작성된 다음 액터모델을 분석해 코드 : https://github.com/psmon/java-labs/blob/master/KotlinBootLabs/src/main/kotlin/actor/HelloActor.kt |
헬로우 액터를 동적으로 생성하고 관리감독할수 있는 부모액터 생성을 시도해 보겠습니다.
HelloActor 를 관리감독하는 SuperVisor 부모액터를 만들고 자식 액터를 동적으로 만들고 싶습니다. |
package actor.supervisor
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.javadsl.AbstractBehavior
import akka.actor.typed.javadsl.ActorContext
import akka.actor.typed.javadsl.Behaviors
import akka.actor.typed.javadsl.Receive
import actor.Hello
import actor.HelloActor
import actor.HelloActorCommand
import actor.HelloActorResponse
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.Terminated
/** SupervisorActor가 처리할 수 있는 명령들 */
sealed class SupervisorCommand
data class CreateChild(val name: String) : SupervisorCommand()
data class SendHello(
val childName: String,
val message: String,
val replyTo: ActorRef<HelloActorResponse>
) : SupervisorCommand()
data class GetChildCount(val replyTo: ActorRef<Int>) : SupervisorCommand()
data class TerminateChild(val name: String) : SupervisorCommand()
class SupervisorActor private constructor(
context: ActorContext<SupervisorCommand>
) : AbstractBehavior<SupervisorCommand>(context) {
companion object {
fun create(): Behavior<SupervisorCommand> {
return Behaviors.setup { context -> SupervisorActor(context) }
}
}
private val children = mutableMapOf<String, ActorRef<HelloActorCommand>>()
override fun createReceive(): Receive<SupervisorCommand> {
return newReceiveBuilder()
.onMessage(CreateChild::class.java, this::onCreateChild)
.onMessage(SendHello::class.java, this::onSendHello)
.onMessage(GetChildCount::class.java, this::onGetChildCount)
.onMessage(TerminateChild::class.java, this::onTerminateChild)
.onSignal(Terminated::class.java, this::onChildTerminated)
.build()
}
private fun onCreateChild(command: CreateChild): Behavior<SupervisorCommand> {
val childName = command.name
val childActor = context.spawn(
Behaviors.supervise(HelloActor.create())
.onFailure(SupervisorStrategy.restart()),
childName
)
context.watch(childActor)
children[childName] = childActor
context.log.info("Created child actor with name: $childName")
return this
}
private fun onSendHello(command: SendHello): Behavior<SupervisorCommand> {
val child = children[command.childName]
if (child != null) {
child.tell(Hello(command.message, command.replyTo))
} else {
context.log.warn("Child actor [${command.childName}] does not exist.")
}
return this
}
private fun onGetChildCount(command: GetChildCount): Behavior<SupervisorCommand> {
command.replyTo.tell(children.size)
return this
}
private fun onChildTerminated(terminated: Terminated): Behavior<SupervisorCommand> {
val childActor = terminated.ref
val childName = childActor.path().name()
children.remove(childName)
context.log.info("Child actor terminated: $childName")
return this
}
private fun onTerminateChild(command: TerminateChild): Behavior<SupervisorCommand> {
val child = children[command.name]
if (child != null) {
context.stop(child)
context.log.info("Terminated child actor: ${command.name}")
} else {
context.log.warn("Attempted to terminate non-existent child actor: ${command.name}")
}
return this
}
} |
여기서 추가되는 핵심 개념은 다음과 같습니다.
restart(): 자식 액터를 재시작합니다.resume(): 현재 상태를 유지하면서 자식 액터의 예외를 무시하고 계속 실행합니다.stop(): 자식 액터를 중지합니다.restartWithBackoff(): 백오프(backoff) 전략을 사용하여 자식 액터를 재시작합니다.stopWithBackoff(): 백오프 전략을 사용하여 자식 액터를 중지합니다.SupervisorActor 를 테스트하는 코드도 생성 |
package actor.supervisor
import actor.HelloActorResponse
import actor.HelloResponse
import akka.actor.testkit.typed.javadsl.ActorTestKit
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
class SupervisorActorTest {
companion object {
private lateinit var testKit: ActorTestKit
@BeforeAll
@JvmStatic
fun setup() {
testKit = ActorTestKit.create()
}
@AfterAll
@JvmStatic
fun teardown() {
testKit.shutdownTestKit()
}
}
@Test
fun testSupervisorCreatesChildAndResponds() {
val responseProbe = testKit.createTestProbe<HelloActorResponse>()
val supervisor = testKit.spawn(SupervisorActor.create())
// 자식 액터 생성 요청
supervisor.tell(CreateChild("child1"))
// 자식 액터에게 메시지 전송 요청
supervisor.tell(SendHello("child1", "Hello", responseProbe.ref()))
// 응답 확인
responseProbe.expectMessage(HelloResponse("Kotlin"))
// 자식 액터 수 확인
val countProbe = testKit.createTestProbe<Int>()
supervisor.tell(GetChildCount(countProbe.ref()))
countProbe.expectMessage(1)
}
@Test
fun testSupervisorHandlesUnknownChild() {
val responseProbe = testKit.createTestProbe<HelloActorResponse>()
val supervisor = testKit.spawn(SupervisorActor.create())
// 존재하지 않는 자식 액터에게 메시지 전송
supervisor.tell(SendHello("unknownChild", "Hello", responseProbe.ref()))
// 응답이 없음을 확인
responseProbe.expectNoMessage()
// 자식 액터 수는 0이어야 함
val countProbe = testKit.createTestProbe<Int>()
supervisor.tell(GetChildCount(countProbe.ref()))
countProbe.expectMessage(0)
}
@Test
fun testSupervisorRestartsChildOnFailure() {
val responseProbe = testKit.createTestProbe<HelloActorResponse>()
val supervisor = testKit.spawn(SupervisorActor.create())
// 자식 액터 생성 및 의도적인 예외 발생
supervisor.tell(CreateChild("child2"))
// 자식 액터에게 잘못된 메시지 전송하여 예외 유발
supervisor.tell(SendHello("child2", "InvalidMessage", responseProbe.ref()))
// 응답이 없음을 확인 (자식 액터가 예외로 인해 재시작됨)
responseProbe.expectNoMessage()
// 올바른 메시지 재전송
supervisor.tell(SendHello("child2", "Hello", responseProbe.ref()))
// 응답 확인 (자식 액터가 재시작되어 정상 동작함)
responseProbe.expectMessage(HelloResponse("Kotlin"))
}
@Test
fun testSupervisorDetectsChildTermination() {
val supervisor = testKit.spawn(SupervisorActor.create())
// 자식 액터 생성
supervisor.tell(CreateChild("child4"))
// 자식 액터 수 확인 (1이어야 함)
val countProbe = testKit.createTestProbe<Int>()
supervisor.tell(GetChildCount(countProbe.ref()))
countProbe.expectMessage(1)
// 자식 액터 종료 요청
supervisor.tell(TerminateChild("child4"))
// 잠시 대기하여 종료 처리를 기다림
Thread.sleep(500)
// 자식 액터 수 확인 (0이어야 함)
supervisor.tell(GetChildCount(countProbe.ref()))
countProbe.expectMessage(0)
}
} |
23:05:28.555 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO actor.supervisor.SupervisorActor - Created child actor with name: child4 23:05:28.557 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO actor.supervisor.SupervisorActor - Terminated child actor: child4 23:05:28.562 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO actor.supervisor.SupervisorActor - Child actor terminated: child4 23:05:29.086 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO actor.supervisor.SupervisorActor - Created child actor with name: child1 23:05:29.092 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO actor.HelloActor - Received valid Hello message. Count incremented to 1 23:05:29.098 [SupervisorActorTest-akka.actor.default-dispatcher-3] WARN actor.supervisor.SupervisorActor - Child actor [unknownChild] does not exist. 23:05:29.214 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO actor.supervisor.SupervisorActor - Created child actor with name: child2 23:05:29.220 [SupervisorActorTest-akka.actor.default-dispatcher-5] ERROR akka.actor.typed.Behavior$ - Supervisor RestartSupervisor saw failure: Invalid message received! java.lang.RuntimeException: Invalid message received! 23:05:29.319 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO akka.actor.typed.Behavior$ - Received valid Hello message. Count incremented to 1 23:05:29.332 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO akka.actor.CoordinatedShutdown - Running CoordinatedShutdown with reason [ActorSystemTerminateReason] > Task :test |
액터 모델은 병렬 프로그래밍과 분산 시스템에서 상태와 행위를 캡슐화하여 안전하고 효율적인 동시성을 제공하는 프로그래밍 모델입니다. Akka 프레임워크는 이러한 액터 모델을 구현한 대표적인 라이브러리로, 다음과 같은 최상위 아키텍처를 가지고 있습니다.
Supervisor는 액터 시스템에서 자식 액터의 오류를 관리하고 시스템의 안정성을 유지하기 위한 핵심적인 역할을 합니다.
context.watch(childActor)를 통해 자식 액터의 라이프사이클 이벤트를 수신합니다.액터 모델의 최상위 아키텍처는 액터 시스템, 액터 계층 구조, 그리고 비동기 메시지 전달로 구성되어 있습니다. 이 구조는 동시성과 병렬성을 효율적으로 처리하기 위한 기반을 제공합니다.
Supervisor는 액터 시스템에서 핵심적인 역할을 수행하며, 자식 액터의 오류 처리, 라이프사이클 관리, 그리고 시스템의 안정성을 유지하는 데 중요한 요소입니다. 적절한 Supervisor 전략을 통해 시스템은 예외 상황에서도 안정적으로 동작할 수 있으며, 동적인 자식 액터 관리로 유연성과 확장성을 확보할 수 있습니다.
next : 라우터를 활용한 분산처리 전략