Akka.NET 기본 02

Log

Akka 시스템 내부에 탑제된 로그시스템.

1
2
3
4
using Akka.Event;

ILoggingAdapter Log = Context.GetLogger();
Log.Info("메시지");

액터의 소유자.

UE에서는 특정 컴포넌트나 오브젝트의 소유자를 설정 가능했다. 그리고 해당 주인은 GetOwner함수로 불러오곤 했다. 마찬가지로 Akka액터도 계층구조기 때문에 소유자를 불러오는것이 가능하다.

 Context.Parent;

Context안에 부모가 정의되어 있다.

또한 메시지를 보낸 대상도 Context내부에 정의되어 있다.

 Context.Sender;

Become/UnbecomeStacked

액터가 메시지를 받아서 처리하는 함수는 다음 함수였다. 누군가 Tell 하면, Tell한 내용을 받아서 처리하는 부분은

1
 protected override void OnReceive(object message);

한 액터가 하나의 메시지 타입만 받으면 좋겠지만, 메시지를 분기해야 할 경우가 많이 생길 것이다. 이럴 때, Become을 사용하면, OnReceive함수가 아닌 다른 함수로 메시지를 받아 처리하도록 변경 가능하다.

1
2
3
4
5
6
7
8
9
 protected override void OnReceive(object message)
 {     
   Become(ReceiveOrderA);                          
 }

protected void ReceiveOrderA(object msg)
{

}

간단하게 다음과같이 Become함수를 이용해서 다음부터 이 함수가 메시지를 처리한다고 설정해 주면 된다. 원래 상태로 돌리고 싶다면, UnbecomeStacked로 기본 OnReceive함수가 메시지를 처리하도록 돌릴 수 있다.

1
2
3
4
5
6
7
8
9
 protected override void OnReceive(object message)
 {     
   Become(ReceiveOrderA);                          
 }

protected void ReceiveOrderA(object msg)
{
   UnbecomeStacked();
}

저번주 코드를 이용한 간단한 예제

Order class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum EOrderType
    {
        A,
        B,
        C
    }

    class Order
    {
        public Order(EOrderType a)
        {
            OrderType = a;
        }

        public Order(EOrderType a, string v) : this(a)
        {
            Msg = v;
        }

        public string Msg { get; set; }
        public EOrderType OrderType { get; set; }

    }

Player.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class Player : AActor
{
//...
   protected override void OnReceive(object message)
        {

            Log.Info("메시지를 누군가로부터 받았습니다.");
            var msg = message as Order;

            switch (msg.OrderType)
            {
                case EOrderType.A:
                    Log.Info("A가 함수를 처리하도록 합니다.");
                    Become(ReceiveOrderA);                
                    break;
                case EOrderType.B:
                    Log.Info("B가 함수를 처리하도록 합니다.");
                    Become(ReceiveOrderB);
                    break;
                case EOrderType.C:
                    Log.Info("C가 함수를 처리하도록 합니다.");
                    Become(ReceiveOrderC);
                    break;
            }
        }

    
        protected void ReceiveOrderA(object msg)
        {
            var m = msg as Order;
            Log.Info("A함수가 메시지를 처리합니다. : " + m.Msg);

            switch (m.OrderType)
            {
                case EOrderType.A:
                    Log.Info("기본 메시지 처리로 돌립니다.");
                    UnbecomeStacked();
                    break;
                case EOrderType.B:
                    Become(ReceiveOrderB);
                    break;
                case EOrderType.C:
                    Become(ReceiveOrderC);
                    break;
            };
        }

        protected void ReceiveOrderB(object msg)
        {
            var m = msg as Order;
            Log.Info("B함수가 메시지를 처리합니다. : " + m.Msg);

            switch (m.OrderType)
            {
                case EOrderType.A:
                    Become(ReceiveOrderA);
                    break;
                case EOrderType.B:
                    Log.Info("기본 메시지 처리로 돌립니다.");
                    UnbecomeStacked();
                    break;
                case EOrderType.C:
                    Become(ReceiveOrderC);
                    break;
            };
        }

        protected void ReceiveOrderC(object msg)
        {
            var m = msg as Order;
            Log.Info("C함수가 메시지를 처리합니다. : " + m.Msg);

            switch (m.OrderType)
            {
                case EOrderType.A:
                    Become(ReceiveOrderA);
                    break;
                case EOrderType.B:
                    Become(ReceiveOrderB);
                    break;
                case EOrderType.C:
                    Log.Info("기본 메시지 처리로 돌립니다.");
                    UnbecomeStacked();
                    break;
            };
        }

}

Program.cs

1
2
3
4
5
6
7
8
9
10
var PlayerCharacter = actorSystem.ActorOf<Player>("PlayerCharacter");


PlayerCharacter.Tell(new Order(EOrderType.A,"아무말"));
PlayerCharacter.Tell(new Order(EOrderType.A, "아무말2"));

PlayerCharacter.Tell(new Order(EOrderType.B, "아무말3"));
PlayerCharacter.Tell(new Order(EOrderType.B, "아무말4"));

PlayerCharacter.Tell(new Order(EOrderType.C, "아무말5"));

명령타입에 따라서 메시지를 받아 처리하는 함수가 바뀌는것을 확인 가능하다.


라이프사이클

UE 액터의 라이프사이클처럼 역시 라이프사이클이 있다. 기존에 하던식으로 함수 설명을 보고 알맞은 함수를 덮어쓰는식으로 구현하면 되겠다.

PreStart()      액터가 생성될시 호출이 됩니다.
PostRestart()   액터의 작동을 멈출시 호출이 되며, 만약 멈출 액터에게 메시지를 보내면 DeadLeeters가 발생됩니다
PostStop()      중지될 때 호출, 실패 메시지를 전달(Tell)받음
PostRestart()   재시작 이후에 왜 재시작되었는지 전달(Tell)받음
  • DeadLeeters : 존재하지 않는 액터에게 메시지를 보낼 경우 죽은 메시지라고 처리되고, DeadLeeter관련 로그가 출력됩니다.

특정 액터를 멈추는 방법

액터시스템을 이용하면 내가 필요없는 액터를 지워버릴 수 있다.

1
actorSystem.Stop(내가필요없어진액터변수);

특정 액터내부에서 자식액터를 지우는것도 가능하다.

1
Context.Stop(자식액터Ref);

또한 메시지를 전달하는것으로 제거 가능하다. 파라미터를 보니까 독약을 전달하면 죽는거 같다.

1
내가필요없어진액터변수.Tell(PoisonPill.Instance,독약을보낸액터Ref);
  • 무한루프를 돌고 있는 액터는 위 방법이 통하지 않는다는 문제가 있습니다.

오류 처리

특정 액터에서 오류가 터지면 해당 액터는 부모에게 오류가 터졌다고 알린다. 오류를 보고받은 부모는 크게 네가지 방법 중에서 하나를 택하여 오류를 처리한다.

  1. 액터를 재시작(Directive.Restart)
    • 오류가 터진 액터를 재시작한다.
    • 기존 액터가 삭제되고, 새 액터가 생성된다. (소멸자 호출 확인함)
  2. 액터를 정지(Directive.Stop)
    • 오류가 터진 액터를 중지합니다. (소멸자 호출 확인되지 않음)
  3. 부모에게 물어봅니다(Directive.Escalate)
    • 이 액터선에서는 잘 모르겠으니 내 부모에게 어떻게 처리할지 물어봅니다.
  4. 그냥 무시합니다(Directive.Resume)
    • 아무것도 안 합니다.

선택된 방법은 오류가 터진 액터와 그 하위 액터에게 모두 적용됩니다. 이 말은 즉, 해당 액터가 재시작되면 하위 액터도 모두 재시작 됨을 의미합니다. 해당 액터가 멈추면 하위 액터도 모두 멈춤을 의미합니다.

오류처리를 적용하는 두 가지 방법

오류를 처리할 방법을 골랐다면, 해당 방법을 어떻게 적용시킬것인가를 선택해야 한다.

  1. 오류가 생긴 대상만 적용 (OneForOneStrategy)
  2. 오류가 생긴 대상과 형제 모두에게 적용 (AllForOneStrategy)

이 오류 처리 적용 방법은 다음 함수로 정의 가능합니다.

1
2
3
 protected override SupervisorStrategy SupervisorStrategy(){
   return new 적용방법(최대 리트라이 횟수, 시간 범위, 문제가 생겼을  호출할 함수);
}
1
2
3
4
5
6
7
8
9
10
11
12
  protected override SupervisorStrategy SupervisorStrategy()
        {
            return new OneForOneStrategy(10, 30, localOnlyDecider);
        }

        private Directive localOnlyDecider(Exception arg)
        {
                    
            Console.WriteLine("자식에게서 문제가 터졌음");

            return Directive.Stop;
        }

이런 오류 처리 방법을 이용하면, 문제가 터질 수 있는 작업들을 자식액터에게 몰아주고, 생긴 문제들을 부모액터가 받아 처리해주는 방식으로 좀 더 안전하다(프로그램 터짐이 없음)


특정 액터를 찾아오는 ActorSelection

내가 찾고싶은 대상 액터의 경로를 알면, 해당 액터와 통신할 수 있다. 특정 액터는 다음과 같이 찾아 올 수 있다.

1
2
       ActorSelection actorSelection = Context.ActorSelection(Path);
       actorSelection.Tell("뭐든 해봐!!");

중요한 것은 경로인데… 경로를 직접 입력하는 것은 말도 안 되는 방법인 것 같고, 검색해야 할 액터의 경로를 따로 기억해두었다가 찾아오는 방법을 사용하기에는 그냥 Ref를 저장하는 것이 낫지 않나 싶다.

ActorSelection을 이용하면 원격에서도 액터를 골라 가져올 수 있습니다 서버 클라이언트 통신이 가능하다는 뜻 ㅎ.


라우터

메시지 처리 방식을 결정 여러가지 방법이 있는거 같음. 아직은 잘 모르겠음. 하나의 라우터 기능을 포함한 액터를 만들면 던져준 설정대로 내부에서 여러개의 액터를 생성하지 않나 싶다.