You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

결함을 허용하지 않는 시스템은, 역설적으로 결함에 대응을 하지 않겠다란 의미입니다.

ActorSystem 은 결함을 허용함으로(결함을 발생시키겠다란 의미 아님)

장애발생시 감시할수있는 장치를 제공하고 , 사용자가 직접 대처할수 있는 다양한 전략을

선택하거나 응용프로그램에 설계할수가 있습니다.


장애 대응 설계


 액터 장애처리모델은 자기자신이 아닌 자신의 부모를 통해 정의가 가능하며

어떠한 액터의 장애를 다음과 같이 설계하고 룰을 정의한다고 합시다.

OOP 예외처리 모델에서 자식이 부모를 책임질수있는 구조적 정의는 불가능합니다.


다음과 같이, 장애대응 플랜을 세웠다고 가정해봅시다.

  • 장애 발생시 1분이내에 10번만 시도할수 있다. (복구 플랜)
  • 액터 작동시 널참조로인한 예외는 재시작하여 복구한다.
  • 액터 작동시 인자값  익셉션 에러일시, 해당 액터를 Stop시킨다.


Test Plan

  • 테스트에 사용할 액터를 생성한다.  부모 : supervisor , 자식 : child1
  • child1액터에게 널 예외 유발시킨다.
  • child1액터 crash 및 복구확인
  • child1에게 메시지보내어 작동중인지 확인
  • child1액터에게 인자값 예외 유발시킨다.
  • child1가 crash가 된것 확인
  • child1에게 메시지를 보내 사살확인 


구현및 실행코드



Actor 설계
    public class Supervisor : UntypedActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();        

		//장애처리 룰을 정의합니다.
        protected override SupervisorStrategy SupervisorStrategy()
        {
            return new OneForOneStrategy(
                maxNrOfRetries: 10,	//최고 시도횟수
                withinTimeRange: TimeSpan.FromMinutes(1),	//최고 시도 시간
                localOnlyDecider: ex =>
                {
                    // Exception별  복구전략
                    if( ex is ArithmeticException )
                        return Directive.Resume;
                    else if( ex is NullReferenceException)
                        return Directive.Restart;
                    else if (ex is ArgumentException)
                        return Directive.Stop;
                    else
                        return Directive.Escalate;                    
                });
        }
       

        protected override void OnReceive(object message)
        {
            if (message is string)
            {
                string msg = message as string;

                if ( msg.Contains("create-") )
                {
                    string childName = msg.Split('-')[1];
                    log.Info("CreateChildActor:" + childName);
                    var childActor = Context.ActorOf<Child>(childName);
                    Context.Watch(childActor);
                    Sender.Tell(childActor);
                }                                                
            }
        }
    }

    public class Child : UntypedActor
    {
        private int state = 0;
        private readonly ILoggingAdapter log = Context.GetLogger();

        protected override void OnReceive(object message)
        {            
            if( message is Exception)
            {
                throw message as Exception;
            }
            else if (message is int)
            {
                state = (int)message;
            }
            else if (message is string)
            {
                switch (message as string)
                {
                    case "get":
                        log.Info("Active Check");  //메시지를 받을수 있는지 체크
                        Sender.Tell(state);
                        break;
                }
            }
        }

        protected override void PreRestart(Exception reason, object message)
        {
            log.Info("Actor Restart - reason:" + reason.GetType().Name );   //액터 재 시작된이 야기된 이유를 , 재시작시 알수 있습니다.
        }
    }
실행코드
            using (ActorSystem actorSystem = ActorSystem.Create("ServiceA"))
            {
                IActorRef supervisor = actorSystem.ActorOf<Supervisor>("supervisor");
                IActorRef child = supervisor.Ask("create-child1").Result as IActorRef;

                child.Tell(new NullReferenceException() );   //강제로 예외발생...                
                Task.Delay(1000).Wait();    //복구를 기다려준다.
                child.Tell("get");

                child.Tell(new ArgumentException() );   //강제로 예외발생...
                Task.Delay(1000).Wait();    //복구를 기다려준다.
                child.Tell("get");

                Task.Delay(3000).Wait();    //종료 지연시간

            }

[INFO][2017-09-13 오전 7:34:22][Thread 0029][[akka://ServiceA/user/supervisor#740151541]] CreateChildActor:child1
[INFO][2017-09-13 오전 7:34:22][Thread 0029][[akka://ServiceA/user/supervisor#740151541]] SomeException:
[ERROR][2017-09-13 오전 7:34:22][Thread 0029][akka://ServiceA/user/supervisor/child1] 개체 참조가 개체의 인스턴스로 설정되지 않았습니다.
Cause: System.NullReferenceException: 개체 참조가 개체의 인스턴스로 설정되지 않았습니다.
위치: ServiceA.STUDY.Child.OnReceive(Object message) 파일 D:\work\hobby\git.webnori.com\akkastudy\Solution\ServiceA\STUDY\SupervisorTestData.cs:줄 64
위치: Akka.Actor.UntypedActor.Receive(Object message)
위치: Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message)
위치: Akka.Actor.ActorCell.ReceiveMessage(Object message)
위치: Akka.Actor.ActorCell.Invoke(Envelope envelope)
[INFO][2017-09-13 오전 7:34:22][Thread 0029][[akka://ServiceA/user/supervisor/child1#716613491]] Actor Restart - reason:NullReferenceException
[INFO][2017-09-13 오전 7:34:23][Thread 0005][[akka://ServiceA/user/supervisor/child1#716613491]] Active Check
[INFO][2017-09-13 오전 7:34:23][Thread 0021][[akka://ServiceA/user/supervisor#740151541]] SomeException:
[ERROR][2017-09-13 오전 7:34:23][Thread 0021][akka://ServiceA/user/supervisor/child1] 값이 예상 범위를 벗어났습니다.
Cause: System.ArgumentException: 값이 예상 범위를 벗어났습니다.
위치: ServiceA.STUDY.Child.OnReceive(Object message) 파일 D:\work\hobby\git.webnori.com\akkastudy\Solution\ServiceA\STUDY\SupervisorTestData.cs:줄 64
위치: Akka.Actor.UntypedActor.Receive(Object message)
위치: Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message)
위치: Akka.Actor.ActorCell.ReceiveMessage(Object message)
위치: Akka.Actor.ActorCell.Invoke(Envelope envelope)
[INFO][2017-09-13 오전 7:34:23][Thread 0029][akka://ServiceA/deadLetters] Message Int32 from akka://ServiceA/user/supervisor/child1 to akka://ServiceA/deadLetters was not delivered. 1 dead letters encountered.
[INFO][2017-09-13 오전 7:34:24][Thread 0043][akka://ServiceA/user/supervisor/child1] Message String from akka://ServiceA/deadLetters to akka://ServiceA/user/supervisor/child1 was not delivered. 2 dead letters encountered.
[DEBUG][2017-09-13 오전 7:34:27][Thread 0044][ActorSystem(ServiceA)] Disposing system
[DEBUG][2017-09-13 오전 7:34:27][Thread 0044][ActorSystem(ServiceA)] System shutdown initiated

결함 처리를 이렇게 어렵게해?


의문을 제기할수 있습니다. 하지만 규모가 큰 서비스코드를 생각해봅시다.

과거 예외 핸들링이 지원안되었을시, retun -1,switch(error) 이러한 코드로 일관성없는 에러처리를 위한 로직을 작성하였고

이것을 개선하고자 예외 처리모델이 나왔습니다. 예외를 발생시키고 , 어느곳에 집결을하여 예외 핸들링을 할수가 있어서

조금더 구조적인 예외 핸들링이 가능해졌습니다. Exception을 세부적으로 설계하고 구조적으로 Catch를 하여 일관성을

유지하는것 그것이 예외처리 모델에 기본이였습니다.


하지만 협업을 하면서 서비스코드내의 예외처리가 일관성이 유지가 되었는지 생각해볼필요가 있습니다.

  • 호출자 B 가 , 자신의 부모에 해당하는 A-1 함수를 호출하였습니다. A-1은 예외를 발생시키고
  • 호출자 B는 항상 성공할줄 알았는데 예외처리가 안되어 어플리케이션이 종료되었습니다.

장애가 발생했습니다. 하지만 이 처리자는 A,B작업이 어떻게 만들어졌는지 모르는 최근 합류자입니다.

  • B에서 예외가 발생하니 그것을 호출하는 C에서 Try를 해주고 예외처리를 하니 어플리케이션은 계속 구동되었습니다.

이제는 C에서 처리하지 않은 예외가 D에 전가가 되었습니다.

  • 다시 D에서 Try를 해주고 예외처리하면서 해결되었습니다.

결국 세부적 예외처리를 포기하고...

  • A,B,C,D 호출관계도 파악안되고 결국 전역 Try를 합니다.

가상시나리오이지만, 자신의 서비스 코드에서 예외처리가 얼마나 서비스 흐름을 막고 있는지 생각해볼필요가 있습니다.

이것은 마치 GoTo 문을 고급기법으로 사용을 한것과 다름없습니다.


AKKA에서는 부모가 자식에게 예외를 전가시키지 않는 관리감독 전략을 통해 이문제가 복잡해지지 않게 일관성을 유지하려고

장치를 제공하며 후속 플랜도 개발자가 직접 할수있게 추상화를 해놓은것입니다.

이것은 액터생성시 애시당초 탑다운 방식으로 관계도 형성을 명확하게 구조화하기때문에 가능한 전략입니다.







  • No labels