액터모델은 TopLevel  아키텍처로 계층형구조로 동적으로 생성할수 있으며  부모가 자식의 생성과 유지를 책임지게 하는 관리감독(SuperVisor) 전략을 이용할수도 있습니다.

액터가 생성되고 나면 부모액터는 자식액터를 알수 있기때문에 상대경로로 접근할수 있으며~ 계층형주소 체계를 가지기 때문에 api path처럼 주소체계로 접근할수도 있습니다.


액터를 계층적으로 구성하고 관리감독하는 SuperVisor액터 생성을 시도하보겠습니다.

LLM TIP : 코드생성을 이용할때 표준이되는 기본 모델의  코드를 어시스트로 활용해 분석을 먼저 의뢰하면, 기존 코드 컨셉을 활용해 생성을 하기때문에 코드생성의 범위를 벗어나지 않게됩니다.

코드분석 프롬프트

코틀린으로 작성된 다음 액터모델을 분석해 

코파일럿을 이용하는 경우 IDE에서 참조 아닌경우 코드를 그대로 이용

코드 : https://github.com/psmon/java-labs/blob/master/KotlinBootLabs/src/main/kotlin/actor/HelloActor.kt



헬로우 액터를 동적으로 생성하고 관리감독할수 있는 부모액터 생성을 시도해 보겠습니다.

코드생성 프롬프트

HelloActor 를 관리감독하는 SuperVisor 부모액터를 만들고 자식 액터를 동적으로 만들고 싶습니다.


SuperVisorActor 

SuperVisorActor
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
    }

}


여기서 추가되는 핵심 개념은 다음과 같습니다.

  • CreateChild : 자식 액터를 동적으로 생성할수 있습니다.
  • TerminateChild : 특정 자식 노드를 동적으로 중지 할수 있습니다.
  • SupervisorStrategy : 자식 액터가 예외로 죽는경우 자동복구 옵션을 선택 할수 있습니다.
    • restart(): 자식 액터를 재시작합니다.
    • resume(): 현재 상태를 유지하면서 자식 액터의 예외를 무시하고 계속 실행합니다.
    • stop(): 자식 액터를 중지합니다.
    • restartWithBackoff(): 백오프(backoff) 전략을 사용하여 자식 액터를 재시작합니다.
    • stopWithBackoff(): 백오프 전략을 사용하여 자식 액터를 중지합니다.
  • Terminated : 자식노드의 액터가 죽는경우 모니터링을 받을수 있습니다.



코드생성 프롬프트

SupervisorActor 를 테스트하는 코드도 생성


SuperVisorActorTest

SuperVisorActorTest
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


액터의 최상위 아키텍처와 Supervisor의 특징 요약

액터 모델의 최상위 아키텍처

액터 모델은 병렬 프로그래밍과 분산 시스템에서 상태와 행위를 캡슐화하여 안전하고 효율적인 동시성을 제공하는 프로그래밍 모델입니다. Akka 프레임워크는 이러한 액터 모델을 구현한 대표적인 라이브러리로, 다음과 같은 최상위 아키텍처를 가지고 있습니다.

1. 액터 시스템 (Actor System)

  • 정의: 액터를 생성하고 관리하는 최상위 컨테이너입니다.
  • 역할:
    • 액터의 생성, 종료, 메시지 전송 등을 관리합니다.
    • 시스템 전체의 설정과 구성 정보를 담고 있습니다.
  • 특징:
    • 하나의 애플리케이션 내에 여러 개의 액터 시스템을 가질 수 있지만, 일반적으로 하나의 액터 시스템을 사용합니다.
    • 액터 시스템은 계층 구조를 가지며, 최상위 루트 액터들이 그 아래의 액터들을 관리합니다.

2. 액터 계층 구조

  • 루트 액터 (Root Actor):
    • 액터 시스템의 최상위 레벨에 존재하는 액터입니다.
    • 모든 액터는 루트 액터의 자식이거나 하위 계층에 위치합니다.
  • Supervisor 액터:
    • 자식 액터들의 생명주기와 오류 처리를 관리하는 액터입니다.
    • 액터 계층 구조에서 부모 액터는 자식 액터의 Supervisor 역할을 합니다.
  • 자식 액터:
    • 특정 기능이나 역할을 수행하기 위해 생성된 액터입니다.
    • 부모 액터의 관리 하에 있으며, 부모 액터는 자식 액터의 오류를 처리하고 라이프사이클을 관리합니다.

3. 액터의 통신 방식

  • 비동기 메시지 전달:
    • 액터 간의 통신은 비동기적으로 이루어지며, 메시지를 통해 상호 작용합니다.
    • 메시지는 액터의 메일박스에 큐(queue)로 저장되며, 하나씩 처리됩니다.
  • 캡슐화된 상태:
    • 액터의 상태는 내부적으로 캡슐화되어 있으며, 외부에서는 직접 접근할 수 없습니다.
    • 상태 변경은 메시지 처리 로직 내에서만 가능합니다.

Supervisor의 특징

Supervisor는 액터 시스템에서 자식 액터의 오류를 관리하고 시스템의 안정성을 유지하기 위한 핵심적인 역할을 합니다.

1. 오류 관리 (Error Handling)

  • Supervisor 전략 (Supervisor Strategy):
    • 자식 액터에서 예외나 오류가 발생했을 때 적용할 정책을 정의합니다.
    • 주요 전략:
      • 재시작 (Restart): 자식 액터를 초기 상태로 재시작합니다.
      • 재개 (Resume): 현재 상태를 유지한 채로 예외를 무시하고 실행을 계속합니다.
      • 중지 (Stop): 자식 액터를 종료합니다.
      • 에스컬레이션 (Escalate): 예외를 상위 Supervisor에게 전파합니다.
  • 예외 처리 흐름:
    • 자식 액터에서 예외 발생 시, Supervisor는 설정된 전략에 따라 예외를 처리합니다.
    • 이를 통해 시스템 전체의 장애를 방지하고 부분적인 복구를 가능하게 합니다.

2. 자식 액터의 라이프사이클 관리

  • 생성 및 종료:
    • Supervisor는 자식 액터를 생성하고 필요에 따라 종료할 수 있습니다.
    • 자식 액터의 생명주기를 관리하여 시스템의 리소스를 효율적으로 사용합니다.
  • 감시 (Monitoring):
    • Supervisor는 자식 액터를 감시하여 종료 또는 장애 발생 시 적절한 조치를 취합니다.
    • context.watch(childActor)를 통해 자식 액터의 라이프사이클 이벤트를 수신합니다.

3. 계층적 구조와 책임 분리

  • 계층적 오류 처리:
    • 액터 시스템은 트리 구조로 이루어져 있으며, 각 레벨에서 오류 처리가 가능합니다.
    • 하위 레벨의 오류는 상위 Supervisor에게 전파될 수 있으며, 전체 시스템의 안정성을 유지합니다.
  • 책임 분리:
    • 각 액터는 자신에게 주어진 역할과 책임만을 수행하며, 다른 액터의 내부 상태나 로직에 의존하지 않습니다.
    • Supervisor는 자식 액터의 오류 처리와 라이프사이클 관리에 집중합니다.

4. 동적 액터 관리

  • 동적 생성 및 관리:
    • Supervisor는 런타임 시점에 자식 액터를 동적으로 생성하고 관리할 수 있습니다.
    • 이를 통해 시스템의 유연성과 확장성을 높입니다.
  • 상태 및 자원 관리:
    • 필요에 따라 자식 액터를 종료하여 리소스를 회수하고, 새로운 작업에 대해 액터를 생성합니다.

결론

액터 모델의 최상위 아키텍처는 액터 시스템, 액터 계층 구조, 그리고 비동기 메시지 전달로 구성되어 있습니다. 이 구조는 동시성과 병렬성을 효율적으로 처리하기 위한 기반을 제공합니다.

Supervisor는 액터 시스템에서 핵심적인 역할을 수행하며, 자식 액터의 오류 처리, 라이프사이클 관리, 그리고 시스템의 안정성을 유지하는 데 중요한 요소입니다. 적절한 Supervisor 전략을 통해 시스템은 예외 상황에서도 안정적으로 동작할 수 있으며, 동적인 자식 액터 관리로 유연성과 확장성을 확보할 수 있습니다.



next : 라우터를 활용한 분산처리 전략




  • No labels