VisualStudio/C#서버

[C#서버] Akka.net과 Actor모델 Part.1

usingsystem 2024. 9. 12. 15:52
728x90

주요내용

  • Akka.net 이란?
  • 액터관리와 ActorSystem 
  • 액터간 메세지 전달 Tell
  • 액터참조와 IActorRef
  • 액터생성과 Props
  • 액터간 감독과 예외처리 supervision
  • 주소로 액터 찾기 ActorSelection
  • ActorSelection과 IActorRef 차이점
  • 액터 라이프사이

1. Akka.net이란?

.NET 플랫폼에서 사용할 수 있는 오픈 소스 프레임워크로, Actor 모델을 구현하여 병렬성과 분산 시스템을 쉽게 구축할 수 있도록 도와줍니다.

2. Actor모델 이란?

Actor 모델은 큰 프로젝트에서 여러 작업을 동시에 처리하거나, 많은 사용자가 동시에 접속하는 시스템을 만들 때 매우 유용한 패턴입니다. 이 모델을 이해하기 위해 먼저, 우리가 흔히 사용하는 객체 지향 프로그래밍(OOP)과 비교해볼게요.

객체 지향 프로그래밍에서는 메서드를 호출하고, 객체의 상태를 변경하는 것이 기본입니다. 그러나 동시성 문제를 처리하려면 여러 스레드가 공유하는 메모리와 이를 관리하는 락(lock)이라는 장치를 사용해야 합니다. 하지만 락은 복잡할 뿐만 아니라, 잘못 사용하면 시스템이 멈추거나 성능이 떨어지는 문제가 발생할 수 있어요.

여기서 Actor 모델이 등장합니다. Actor 모델은 이러한 문제를 해결하기 위해 개발된 패턴으로, 핵심 개념은 '모든 것이 독립적으로 동작하는 작은 단위로 나누어져 있고, 이들이 메시지를 통해 서로 소통한다'는 것입니다.

1. Actor의 독립성과 메시지 기반 동작:

Actor는 스스로 동작하는 작은 단위입니다. 각 Actor는 자신만의 상태와 행동을 가지고 있으며, 다른 Actor와 직접적인 상호작용 없이 '메시지'라는 형태로 소통합니다. 예를 들어, Actor가 어떤 작업을 하고 싶다면, 직접 다른 Actor의 메서드를 호출하는 대신, '이거 해줘'라는 메시지를 보냅니다. 이 방식은 서로의 상태를 직접 변경하지 않고, 요청을 보낸 후 처리 결과를 기다리거나 다른 일을 처리할 수 있게 해줍니다.

2. 병렬성과 비동기 처리:

Actor 모델의 또 다른 중요한 개념은 비동기적 동작입니다. 비동기란 작업을 요청한 후 그 작업이 완료될 때까지 기다리는 대신, 다른 일을 계속할 수 있다는 것을 의미합니다. Actor 모델에서는 각 Actor가 메시지를 비동기적으로 받아서 처리하기 때문에, 여러 작업이 병렬로 진행될 수 있습니다. 이때 각 Actor는 독립적인 스레드로 동작하거나 스레드 풀이라는 것을 이용해 효율적으로 관리됩니다.

3. 상태와 동기화:

각 Actor는 자신만의 상태를 가지고 있고, 이 상태는 해당 Actor 자신만 변경할 수 있습니다. 상태를 공유하지 않고 메시지로만 소통하기 때문에, 공유 메모리 문제나 복잡한 동기화(락 사용 등)를 신경 쓸 필요가 없어요. 예를 들어, 게임에서 각 캐릭터가 독립적으로 움직이고 행동하는 것을 Actor라고 생각할 수 있습니다. 이 캐릭터들은 서로의 상태를 직접 변경할 수 없고, 메시지를 보내 어떤 행동을 유도할 수 있습니다.

4. 에러 처리와 계층 구조:

Actor 모델에서는 에러를 처리하는 방식도 체계적입니다. Actor는 부모-자식 관계로 계층 구조를 형성하고, 부모 Actor는 자식 Actor의 상태를 감시하고 관리할 수 있습니다. 자식 Actor에서 문제가 발생하면, 부모가 이를 감지하고 적절한 조치를 취할 수 있죠. 예를 들어, 자식을 재시작하거나, 중지할 수 있습니다. 이를 통해 시스템이 안정적으로 작동하도록 돕습니다.

이런 구조 덕분에 Actor 모델은 복잡한 동시성 문제를 해결할 수 있고, 시스템의 성능과 안정성을 높이는 데 큰 도움이 됩니다. 병렬 작업이 많거나, 시스템의 확장이 중요한 경우에 특히 유용합니다. Actor 모델은 마치 각기 다른 악기를 연주하는 오케스트라 단원들이 서로 간섭하지 않고 지휘자의 지시에 따라 연주하는 것과 비슷하다고 생각하면 이해가 쉬울 것입니다. 각 Actor가 자신의 역할을 하고, 지시에 따라 움직이며, 메시지라는 악보를 통해 조화롭게 동작하는 거죠.

이렇게 Actor 모델을 통해 복잡한 동시성 문제를 풀어나갈 수 있답니다!

  • 비동기적 메서드 호출: 객체 지향 패턴에서 동기적 메서드 호출이 이루어진다면, Actor 모델에서는 메서드 호출이 비동기로 변경됩니다. 이를 통해 병렬성과 비동기 처리를 자연스럽게 구현할 수 있습니다.
  • Thread 기반 동작: Actor는 독립적인 실행 단위로, 각 Actor는 독립된 스레드 기반 객체입니다. 하지만 반드시 모든 Actor가 하나의 스레드에 할당되는 것은 아니며, 스레드 풀을 통해 관리되어 더 많은 Actor를 효율적으로 실행할 수 있습니다.
  • 메시지 기반 동기화: Actor 모델에서는 상태 공유와 직접적인 동기화 대신 메시지 전달을 통해 동기화 문제를 처리합니다. 이는 각 Actor가 메시지를 수신하고 처리하는 방식으로 내부 상태를 관리하도록 합니다.

Actor 모델이 해결하려는 문제

  • 공유 메모리와 동시성 문제: OOP에서 메서드를 호출하여 내부 객체의 상태를 변경할 때, 공유 메모리에 접근하는 여러 스레드가 존재할 수 있습니다. 이로 인해 데이터 레이스, 동기화 문제, 복잡한 락 관리가 필요하게 됩니다.
  • 락의 문제점: 락(lock)을 사용하면 다음과 같은 문제가 발생합니다:
    • 동시성 제한: 락은 한 번에 하나의 스레드만 자원에 접근하도록 제한하여 동시성을 감소시킵니다.
    • 비용이 큰 작업: 락과 관련된 스레드의 일시 정지와 복원은 운영체제(OS) 차원에서 높은 비용이 듭니다.
    • Deadlock: 잘못된 락 관리로 인해 시스템이 교착 상태에 빠질 위험이 있습니다.
  • 위임된 작업의 에러 처리: 기존의 병렬 처리 방식인 Task-delegate concurrency에서는 작업을 위임할 때 발생하는 에러를 작업을 넘겨준 쪽에서 받기 어렵습니다. Actor 모델은 이러한 문제를 언어 수준에서 해결하고자 합니다.

Actor 모델의 주요 구조와 동작

  • MailBox(메시지 큐): 각 Actor는 자신만의 메시지 큐를 가지고 있습니다. 이 큐는 비동기적으로 메시지를 수신하고 저장합니다.
  • 메시지: Actor는 메시지를 통해 서로 통신합니다. 특정 메서드를 직접 호출하는 대신, 특정 Actor에게 메시지를 전달하여 행동을 유도합니다. 모든 POCO( Plain Old CLR Object )는 메세지가 될 수 있다.
  • Behavior: Actor는 수신한 메시지에 따라 특정 행동을 결정하고 실행합니다. 예를 들어:
    • 자신의 상태를 변경할 수 있습니다.
    • 새로운 자식 Actor를 생성하거나 기존 자식을 제거할 수 있습니다.
    • 다른 Actor에게 메시지를 보낼 수 있습니다.
  • State: Actor의 상태는 Actor 자신만 변경할 수 있습니다. 상태는 init, ready, closed 등의 단계로 나타낼 수 있습니다.
  • 동기화 대신 메시지 전달: Actor 모델에서는 락이나 블로킹 대신 메시지 전달 방식을 사용하여 동기화를 처리합니다. 이는 특정 Actor에게 메시지를 보내는 것으로, 직접적으로 스레드를 실행하는 것을 의미하지 않습니다.

에러 처리와 계층 구조

  • 계층적 구조: Actor 간의 호출 관계를 통해 계층 구조(Hierarchy)가 형성됩니다. 예를 들어, 부모 Actor는 자식 Actor의 상태를 감시할 수 있으며, 자식 Actor에서 오류가 발생하면 이를 감지하고 대응할 수 있습니다.
  • 에러 전파와 관리: 부모 Actor는 자식 Actor의 에러를 관리할 수 있으며, 이를 통해 에러의 전파를 제어할 수 있습니다.

3. 액터 관리 ( ActorSystem )

ActorSystem은 하나의 프로세스 내에서 동작하는 여러 액터들을 조직화하고, 메시지 전달, 액터 생명주기 관리, 스케줄링 등을 담당합니다.

기능

  1. 액터 생성 및 관리: ActorSystem은 액터의 부모 역할을 하며, 액터 트리 구조를 유지 관리합니다.
  2. 메시지 전달: 액터 간의 메시지 전달을 중개하고, 비동기 메시지 큐를 관리합니다.
  3. 설정 및 구성: 시스템 설정 파일을 통해 액터 시스템의 동작을 구성할 수 있습니다.
  4. 생명주기 관리: 액터의 시작, 종료, 재시작 등의 생명주기를 관리하여 내결함성을 지원합니다.

ActorSystem은 하나의 애플리케이션에서 여러 개 생성할 수 있지만, 일반적으로 한 개만 사용하는 것이 일반적입니다. 이를 통해 애플리케이션 내의 모든 액터가 같은 컨텍스트 내에서 동작하게 됩니다.

public class HelloActor : UntypedActor
{
    protected override void OnReceive(object message)
    {
        if (message is string msg)
        {
            Console.WriteLine($"Received: {msg}");
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        // ActorSystem 생성
        ActorSystem MyActorSystem = ActorSystem.Create("MyActorSystem");

        // UntypedActor 생성
        // IActorRef는 Akka.NET에서 액터를 참조하기 위한 인터페이스입니다.
        // 액터 시스템에서 생성된 액터는 직접 참조되는 것이 아니라, IActorRef를 통해 참조됩니다.
        IActorRef helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "helloActor");

        // 메시지 전송
        helloActor.Tell("Hello, Akka.NET!");

        // 시스템 종료
        Console.ReadLine();
        system.Terminate().Wait();
    }
}

4. 메시지 정의 및 처리 ( Tell )

Tell()을 사용하여 Actor에게 메시지를 전달할 수 있다. UntypedActor를 상속받아 Actor를 만들경우 Actor가 처리 방법을 모르는 메시지를 전달받는 다면 무시하거나 무시한 메세지를 Unhandled()를 통해 처리할 수 있다. ReceiveActor를 사용하면 Unhandled 로깅이 자동으로 수행된다.

 

메세지를 전달받은 Actor는 Sender를 통해 다른 Actor에게 답장을 하거나 메세지를 전달 할 수 있다.

// MyActor 클래스는 UntypedActor를 상속하여 메시지를 처리하는 액터입니다.
public class MyActor : UntypedActor
{
    // 메시지를 수신할 때 호출되는 메서드
    protected override void OnReceive(object message)
    {
        // 메시지가 Messages.InputError 타입일 경우 처리
        if (message is Messages.InputError msg)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(msg.Reason); // 에러 메시지 출력
        }
        else
        {
            // 처리할 수 없는 메시지는 Unhandled() 메서드를 호출하여 무시
            Unhandled(message);
        }
    }
}

5. IActorRef와 Props

액터 참조 핸들러 IActorRef ( http://api.getakka.net/docs/stable/html/56C46846.htm )

IActorRef는 액터에 대한 참조 또는 핸들 역할을 하며, ActorSystem을 통해 액터에게 메시지를 보낼 수 있도록 지원합니다. 액터와 직접 소통하지 않고, IActorRef에 메시지를 보내면 ActorSystem이 이를 전달합니다. IActorRef는 액터가 로컬이든 원격이든 관계없이 메시지를 보낼 수 있는 참조를 제공하며, 해당 액터가 과거에 존재했음을 보장합니다. 액터가 종료될 수 있기 때문에, 종료 알림을 받으려면 IActorContext.Watch를 사용하여 감시할 수 있고, 종료 시 Terminated 메시지를 받을 수 있습니다.

 

Actor간 직접적인 통신을 하지 않는다.

직접적으로 하는 것이 아니라 ActorSystem을 통해서 이루어집니다. 

  1. 메시지 관리와 전달 방식: ActorSystem은 모든 메시지를 Envelope로 감싸서 전달하며, 이 Envelope에는 메시지에 대한 메타데이터가 포함되어 있습니다. 액터는 이 메타데이터를 활용해 메시지를 보다 효과적으로 처리할 수 있습니다.
  2. 위치 투명성: 액터가 어느 프로세스나 머신에 있는지 신경 쓰지 않아도 됩니다. ActorSystem은 액터의 위치를 자동으로 관리하며, 이를 통해 원격 액터를 지원할 수 있습니다. 이렇게 하면 여러 머신에서 분산 처리하여 시스템의 확장성을 높일 수 있습니다.

메시지가 액터에게 전달되었는지 확인하는 것 역시 ActorSystem이 관리하는 부분입니다. Akka.NET은 메시지 전달을 보장하는 다양한 메커니즘을 제공하므로, 이를 직접 관리할 필요는 없습니다. 현재로서는 메시지 전달이 ActorSystem의 역할이라는 점을 신뢰하면 됩니다.

 

Actor의 계층 구조

액터는 계층을 형성합니다. 즉, 본질적으로 자신에게 직접 보고하는 "최상위" 액터가 있고 ActorSystem다른 액터에게 보고하는 "자식" 액터가 있습니다. 자식 액터를 만드는 이유는 시스템의 복잡성을 관리하고, 역할과 책임을 분리하여 더 구조적이고 유연한 액터 시스템을 설계하기 위함입니다. 자식 액터는 부모 액터의 일부 작업을 분담하여 처리하거나, 특정 기능을 전담하도록 설계됩니다. 이를 통해 부모 액터는 더 간단한 역할을 유지하며, 자식 액터는 자신만의 상태와 로직을 관리할 수 있습니다. 또한, 자식 액터는 부모 액터가 실패하거나 재시작할 때 함께 관리될 수 있어, 내결함성과 안정성을 향상시킵니다.

계층에 따른 경로가 변경된다.

class MyActorClass : UntypedActor{
    // PreStart는 액터가 시작될 때 호출되는 초기화 메서드이다. 보통 부모 자식 관계를 설정함.
	protected override void PreStart(){
		IActorRef myFirstChildActor = Context.ActorOf(Props.Create(() =>
        new MyChildActorClass()), "myFirstChildActor");
	}
}

 

IActorRef와 context

Context는 액터의 생명 주기, 자식 액터 관리, 메시지 전달, 로깅, 스케줄링 등의 작업을 지원합니다.

 

  • Self: 현재 액터 자신에 대한 참조.
  • Sender: 현재 메시지를 보낸 액터에 대한 참조.
  • Parent: 부모 액터에 대한 참조.
  • Children: 자식 액터들에 대한 참조 목록.
  • ActorOf: 새로운 자식 액터를 생성.
  • Become/Unbecome: 행동을 변경하거나 복원.
Self.Tell("Hello, Self!");

 

액터 설정 Props

액터를 생성하기 위한 설정 정보를 담는 객체입니다. Props는 액터의 타입, 생성 방식, 필요한 인자 등을 정의하며, 액터 시스템에서 새로운 액터 인스턴스를 만들 때 사용됩니다.

new를 통한 인스턴스 객체생성 금지.

Props props = Props.Create(() => new MyActor(..), "...");//람다형식

Props props2 = Props.Create<MyActor>();

 


6. 액터 계층과 감독

자식 액터 계층 (Child Actor Hierarchy)

액터는 부모-자식 관계를 통해 계층적으로 구성됩니다. 각 액터는 다른 액터를 생성할 수 있으며, 생성된 액터는 생성한 액터의 자식이 됩니다. 

  • 부모 액터: 새로운 액터를 생성한 액터로, 자식 액터의 생명주기와 일부 관리 책임을 갖습니다.
  • 자식 액터: 부모 액터에 의해 생성된 액터로, 특정 작업을 수행하거나 부모로부터 특정 역할을 위임받아 행동합니다.

액터 계층은 트리 구조를 형성하며, 루트 액터부터 최하위 자식 액터까지 연결됩니다. 이 계층 구조는 시스템을 더 모듈화하고, 각 액터의 역할을 명확하게 정의하며, 오류를 더 잘 처리할 수 있도록 돕습니다.

감독 (Supervision)

감독은 액터 계층의 부모-자식 관계에서 발생하는 오류를 관리하는 메커니즘입니다. 감독 모델은 부모 액터가 자식 액터의 실패를 감지하고, 해당 실패를 처리하는 방식을 정의합니다. 이러한 전략을 통해, 부모 액터는 자식 액터의 오류에 대응할 수 있으며, 전체 시스템이 하나의 실패로 인해 무너지는 것을 방지할 수 있습니다.

각 액터는 부모 액터가 감독하며, 오류가 발생할 경우 이를 복구하는 데 도움을 줍니다. 계층 구조에서 부모 액터는 자식 액터의 오류를 관리합니다. 자식 액터에 예외가 발생하면 부모가 오류 메시지를 받고, 적절한 지시를 내립니다(재시작, 중지 등) 감독의 기본 전략은 "One-For-One"으로, 특정 자식에만 적용됩니다. "All-For-One" 전략은 해당 자식과 모든 형제 액터에 적용됩니다.

 

SupervisorStrategy를 통한 감독

부모 액터가 자식 액터의 실패(예외 발생 등)를 감지하고, 그에 따라 자식 액터를 어떻게 처리할지를 결정하는 정보를 제공합니다. OneForOneStrategy( One-For-One )또는 AllForOneStrategy( All-For-One ) 같은 전략을 설정할 수 있습니다:

  • OneForOneStrategy: 실패한 자식 액터에만 적용
  • AllForOneStrategy: 한 자식의 실패가 다른 자식들에게도 영향을 미치도록 설정

SupervisorStrategy에서 사용되는 지시문

  • Resume: 실패한 자식 액터를 그대로 계속 실행합니다. (예외를 무시하고 상태를 유지)
  • Restart: 자식 액터를 재시작합니다. (상태 초기화)
  • Stop: 자식 액터를 중지합니다. (액터의 생명주기 종료)
  • Escalate: 실패를 상위 부모에게 전달하여 처리하도록 합니다.
    public class TailCoordinatorActor : UntypedActor
    {
        protected override void OnReceive(object message)
        {
        }
        // 여기서 기본 SupervisorStrategy를 재정의하고 있습니다
        // 기본 전략은 Restart 지시문이 있는 One-For-One 전략입니다
        protected override SupervisorStrategy SupervisorStrategy()
        {
            return new OneForOneStrategy(
                10, // 최대 재시도 횟수
                TimeSpan.FromSeconds(30), // 시간 범위
                x =>
                {
                    // ArithmeticException은 애플리케이션에 치명적이지 않다고 간주할 수 있습니다
                    // 따라서 오류를 무시하고 계속 진행합니다.
                    if (x is ArithmeticException) return Directive.Resume;

                    // 복구할 수 없는 오류인 경우, 실패한 액터를 중지합니다
                    else if (x is NotSupportedException) return Directive.Stop;

                    // 그 외의 모든 경우에는, 실패한 액터를 재시작합니다
                    else return Directive.Restart;
                });
        }
    }

7. 주소로 액터 찾기 ActorSelection

ActorSelection은 Akka.NET에서 특정 액터를 직접 참조하지 않고, 경로(path)를 통해 액터를 선택하고 메시지를 전송할 수 있는 기능입니다. 이를 통해 액터 시스템 내에서 액터를 유연하게 접근하고, 동적으로 메시지를 전달할 수 있습니다. ActorSelection은 액터의 위치나 정확한 참조를 알 필요 없이 경로를 통해 액터와 통신할 수 있게 해주기 때문에, 분산 시스템이나 액터 구조가 동적으로 변화하는 환경에서 매우 유용합니다.

ActorSelection은 실제 ActorRef를 사용하는 것보다 약간의 성능 비용이 추가됩니다. 따라서 참조를 명확히 알고 있는 경우, 직접 ActorRef를 사용하는 것이 더 효율적일 수 있습니다.

ActorSelection이 생겨나게 된 이유

  1. 동적 액터 경로 접근: 액터 시스템에서는 액터가 동적으로 생성, 삭제될 수 있습니다. 특정 액터에 접근하려면 ActorRef가 필요하지만, 모든 액터의 ActorRef를 항상 가지고 있는 것은 어렵습니다. 특히, 분산된 환경에서 특정 액터의 ActorRef를 유지하는 것은 비현실적입니다.
  2. 유연한 메시지 전송: 액터 시스템에서 모든 액터의 위치를 명확히 알고 있지 않더라도, 특정 경로를 따라 액터에게 메시지를 전송할 수 있어야 합니다. ActorSelection은 이러한 요구를 충족하기 위해 설계되었습니다.
  3. 위치 투명성: 액터 시스템의 중요한 개념 중 하나는 위치 투명성입니다. 즉, 액터가 어디에 있든지 상관없이 동일한 방식으로 접근하고 통신할 수 있어야 합니다. ActorSelection은 경로 기반의 접근 방식을 통해 이 위치 투명성을 지원합니다.

ActorSelection 사용 이유

  1. 동적 참조 관리: 액터의 정확한 참조(ActorRef)를 알 필요 없이 경로를 통해 액터를 선택할 수 있습니다. 예를 들어, 자식 액터들이 동적으로 생성되거나 삭제되는 경우, 경로를 통해 유연하게 접근할 수 있습니다.
  2. 분산 시스템 지원: 분산된 액터 시스템에서 네트워크를 넘어 원격 액터에게 메시지를 보낼 때 ActorSelection이 유용합니다. 경로를 통해 원격 액터의 위치를 추상화할 수 있습니다.
  3. 유연성: 액터 구조가 고정되지 않고 동적으로 변화할 때, ActorSelection은 액터의 경로를 통해 메시지를 보내기 때문에, 구조가 변화하더라도 액터 참조를 지속적으로 관리하지 않아도 됩니다.
  4. 브로드캐스트: 특정 경로를 따라 여러 액터가 존재할 경우, ActorSelection을 사용하여 한 번에 여러 액터에게 메시지를 전송할 수 있습니다.

ActorSelection과 IActorRef의 차이점

IActorRef:

  • 액터에 대한 고유 참조입니다.
  • 액터를 생성할 때 반환되며, 이를 통해 해당 액터와 직접 통신할 수 있습니다.

ActorSelection:

  • 특정 경로에 있는 하나 이상의 액터를 선택하는 메커니즘입니다.
  • 경로를 통해 액터를 동적으로 탐색하여 접근합니다.
// 부모 액터 아래에 있는 모든 자식 액터를 선택하는 ActorSelection을 생성합니다.
var childSelection = Context.ActorSelection("/user/parent/*");

// 새로운 ChildActor를 생성하고, 그에 대한 IActorRef를 얻습니다.
IActorRef childRef = Context.ActorOf(Props.Create(() => new ChildActor()), "child1");
using Akka.Actor;

public class ParentActor : UntypedActor
{
    protected override void OnReceive(object message)
    {
        if (message is string msg && msg == "start")
        {
            // 자식 액터를 생성하고, 특정 경로에 액터를 배치
            Context.ActorOf(Props.Create(() => new ChildActor()), "child1");

            // ActorSelection을 사용하여 경로를 통해 자식 액터에 접근
            var childSelection = Context.ActorSelection("child1");

            // ActorSelection을 통해 자식 액터에 메시지 전송
            childSelection.Tell("hello from parent");
        }
    }
}

public class ChildActor : UntypedActor
{
    protected override void OnReceive(object message)
    {
        if (message is string msg)
        {
            Console.WriteLine($"ChildActor received message: {msg}");
        }
    }
}

 


8. 액터 라이프사이클

Akka.NET의 액터 라이프 사이클은 5단계로 구성됩니다.

 

액터 라이프 사이클 5단계

1. Starting (시작 중)

이 단계는 액터가 ActorSystem에 의해 초기화되는 상태입니다. 액터가 생성되고 준비되며, 메시지를 받을 준비를 하는 단계입니다.

2. Receiving (메시지 수신 중)

이 단계에서는 액터가 메시지를 받을 수 있는 상태가 됩니다. 액터의 우편함(Mailbox)에 쌓인 메시지들이 차례로 OnReceive 메서드로 전달되어 처리됩니다. 이 단계에서 액터는 주로 메시지 처리 작업을 수행합니다.

3. Stopping (중지 중)

이 단계에서는 액터가 자신의 상태를 정리하는 중입니다. 액터가 중지되는 이유에 따라 이 단계의 행동이 달라집니다:

  • 재시작 중인 경우: 액터가 재시작될 때는, 상태나 메시지를 저장하여 재시작 후 다시 사용할 수 있도록 준비할 수 있습니다. 재시작 후에는 이전의 상태를 이어받아 다시 메시지를 처리할 준비를 합니다.
  • 종료 중인 경우: 액터가 완전히 종료되는 경우, 우편함에 남아 있는 모든 메시지는 ActorSystem의 DeadLetters 우편함으로 보내집니다. DeadLetters는 더 이상 전달할 수 없는 메시지를 저장하는 곳으로, 보통 액터가 이미 종료된 경우에 발생합니다.

4. Terminated (종료됨)

액터가 완전히 종료된 상태입니다. 이 상태에서는 액터가 더 이상 존재하지 않으며, 해당 액터의 IActorRef로 메시지를 보내면 모두 DeadLetters로 전달됩니다. 액터는 재시작될 수 없으며, 만약 동일한 위치에 새로운 액터가 생성된다면, 새로운 IActorRef를 가지지만, 동일한 ActorPath를 가질 수 있습니다.

5. Restarting (재시작 중)

이 단계는 액터가 재시작될 때를 의미하며, 다시 Starting 상태로 돌아갑니다. 액터는 오류나 예외 발생 시 재시작될 수 있으며, 이때 기존 상태를 초기화하고 새롭게 시작합니다.

 

액터 생명주기 재정의 메소드

  1. PreStart - PreStart는 액터가 메시지를 받기 전에 실행되는 초기화 로직을 넣을 수 있는 메서드입니다. 이곳에 액터의 초기 상태를 설정하거나 필요한 초기화 작업을 수행할 수 있습니다. 액터가 재시작될 때도 호출됩니다. 가장 많이 사용되는 후크 메서드입니다. 액터의 초기 상태 설정과 초기화 로직 실행에 사용됩니다. 새로운 액터가 시작될 때마다 필요한 준비 작업을 수행할 수 있습니다.
    • 사용 예: 데이터베이스 연결 설정, 자원 할당, 초기 메시지 전송 등.
  2. PreRestart - 액터가 실패하여(예: 처리되지 않은 예외 발생) 부모 액터에 의해 재시작될 때, PreRestart 메서드가 호출됩니다. 이 메서드에서는 액터가 재시작되기 전에 필요한 정리 작업을 수행하거나, 현재 처리 중인 메시지를 저장해 두어 나중에 다시 처리할 수 있도록 할 수 있습니다. 세 번째로 많이 사용되며, 재시작 전 작업이 필요할 때 사용됩니다. 예를 들어, 메시지를 임시 저장하거나 재처리를 위해 준비하는 작업을 할 수 있습니다. 액터의 작업에 따라 사용 빈도와 방법이 달라질 수 있습니다.
    • 사용 예: 현재 상태 저장, 정리 작업 수행, 재시작 전 자원 해제.
  3. PostStop - PostStop은 액터가 종료된 후 호출됩니다. 액터가 더 이상 메시지를 받지 않을 때, 정리 작업을 수행하기에 적합한 곳입니다. 액터가 종료될 때 파일 핸들이나 다른 시스템 자원을 해제해야 할 때 주로 사용됩니다. 이 메서드는 PreRestart 중에도 호출될 수 있지만, 필요에 따라 PreRestart에서 base.PreRestart를 호출하지 않아 이 동작을 피할 수 있습니다. 두 번째로 많이 사용되는 후크 메서드입니다. 액터가 종료될 때, 시스템 자원을 해제하거나 정리 작업을 수행하는 데 사용됩니다. 예를 들어, 파일 시스템 핸들 해제, 네트워크 연결 종료 등을 처리할 수 있습니다.
    • 사용 예: 자원 해제, 로그 기록, 네트워크 연결 종료.
  4. PostRestart - PostRestart는 액터가 재시작된 후 호출되며, PreRestart와 PreStart 사이에 호출됩니다. 이 메서드는 재시작의 원인이 된 오류를 추가로 분석하거나, 보고 작업을 수행하는 데 적합합니다. Akka.NET에서 기본적으로 제공하는 오류 처리 외에 추가적인 진단을 수행할 수 있는 기회입니다.
    • 사용 예: 오류 원인 분석, 진단 로그 작성, 재시작 후 상태 초기화.

감독과의 관계 (Supervision)

액터가 예기치 않게 충돌하거나 예외를 던지면, 액터의 감독자(supervisor)는 자동으로 액터의 생명주기를 처음부터 다시 시작하게 만듭니다. 이 과정에서 액터의 메일박스에 남아 있는 메시지들은 유지되며, 새로 시작된 액터가 다시 처리할 수 있게 됩니다.

이 때, 부모의 감독 지시(SupervisionDirective)에 따라 액터의 행동이 결정됩니다. 부모는 자식 액터에게 종료, 재시작, 오류 무시 후 계속 작업 등의 지시를 내릴 수 있습니다. 기본 설정은 재시작이며, 이를 통해 문제가 되는 상태를 초기화하고 액터를 깨끗하게 새로 시작할 수 있게 됩니다. 재시작은 비용이 적게 들기 때문에 Akka.NET에서는 이러한 방식이 기본으로 사용됩니다.

using Akka.Actor;
using System;

public class MyActor : UntypedActor
{
    // PreStart: 액터가 시작될 때 호출됩니다.
    protected override void PreStart()
    {
        base.PreStart();
        Console.WriteLine("MyActor is starting.");
        // 초기화 작업 예: 데이터베이스 연결 설정 등
    }
    // PreRestart: 액터가 재시작되기 전에 호출됩니다.
    protected override void PreRestart(Exception reason, object message)
    {
        base.PreRestart(reason, message);
        Console.WriteLine($"MyActor is restarting due to: {reason.Message}");
        // 재시작 전 작업 예: 현재 상태 저장, 메시지 임시 저장 등
    }
    // PostStop: 액터가 종료된 후 호출됩니다.
    protected override void PostStop()
    {
        base.PostStop();
        Console.WriteLine("MyActor has stopped.");
        // 종료 후 자원 해제 예: 파일 핸들 닫기, 네트워크 연결 종료 등
    }
    // PostRestart: 액터가 재시작된 후 호출됩니다.
    protected override void PostRestart(Exception reason)
    {
        base.PostRestart(reason);
        Console.WriteLine("MyActor has restarted.");
        Console.WriteLine($"Reason for restart: {reason.Message}");
        // 재시작 후 추가 작업 예: 상태 초기화, 추가 로깅 등
    }
    // 메시지를 처리하는 메서드
    protected override void OnReceive(object message)
    {
        // 메시지 처리 로직을 여기에 작성합니다.
        Console.WriteLine($"Received message: {message}");
    }
}

 


참고자료

 

https://petabridge.com/blog/when-should-I-use-actor-selection/

 

When Should I Use Actor Selection?

When Should I Use Actor Selection?

petabridge.com

 

https://github.com/petabridge/akka-bootcamp

 

GitHub - petabridge/akka-bootcamp: Self-paced training course to learn Akka.NET fundamentals from scratch

Self-paced training course to learn Akka.NET fundamentals from scratch - petabridge/akka-bootcamp

github.com

https://github.com/akkadotnet/akka.net?tab=readme-ov-file

 

GitHub - akkadotnet/akka.net: Canonical actor model implementation for .NET with local + distributed actors in C# and F#.

Canonical actor model implementation for .NET with local + distributed actors in C# and F#. - akkadotnet/akka.net

github.com

https://www.youtube.com/watch?v=BzTAdSxtrq0

 

 

728x90