Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

분리합니다. 장애처리 코드가 서비스 코드에 썩이지 않음으로 서비스의 흐름을 방해하지 않습니다.

장애처리에대한 플랜은 부모가 모니터링하고  지는구조로 모니터링하고 책임지는 구조로 그것을 직접 설계에 반영해야합니다.


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

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


Test Plan 은 아래와같습니다.

...

Expand
titleResult

[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


AKKA뿐아니라, 동시성 메시지 처리가되는 라이브러리를 이해하려면 로그를 잘 파악해야 합니다.

위 로그는 붉은색일때 어떠한 장애가 에러가 발생하였으며, Null일때만 파란색 즉 복구처리가 되었고인자값 예외일때는예외의 범위중 Null 참조에러일경우 파란색부분, 즉 객체리셋을 통한 복구 플랜을 작동하고

인자값 예외일 경우... 그냥 Child 노드가 정지되게 놔두는 것을 로그로 확인이 가능합니다.

이것은 우리의 의도대로 잘 작동됨을 의미 합니다.

...

Info

결함 처리를 이렇게 어렵게해? 의문을 제기할수 있습니다.

하지만 위와 같은 플랜을 가진 예외처리를 일반적인 OOP클래스로 코드로 직접 작성을 해보십시오

물론 위 케이스에한해서, 개발자에 따라 더 구조적이고 깔끔한 예외처리가가능할것입니다.

하지만 계속 유지보수가되고 있는 상황과 기존 모델이 할수 없는 문제점이 분명있으며

기존 예외처리모델(try/catch)는 다음과 같은 두가지 문제를 가지고 있습니다, 전통적인 익센셥처리인 Try / Catch를 사용하게되는경우

다음과 피호출자에게 책임을 떠맡는 다음과 같은 문제가 발생하게 됩니다.

호출자에게 예외를 던지면, 대부분 호출자는 어떻게 처리해야할지 모름

다음과 같이, 로컬에 파일이 추가 되면 DB까지 자동으로 적는 FileWatcher가 있다고 가정해봅시다.

  • A : 새파일이 추가되면 FileWatcher가 감지하고 , 스레드가 작동됨
  • B: 파일 리드기는 파일을 Row단위로 읽어서 로그 Write에게 정보를 넘김
  • C: 로그 Witer기는 Row단위로 정보를 받아서 DB에 적재하려고함
  • D: DBWrite는 DB에 연결을 하여, Row단위로 적재를함


진행Type
예외발생시
AFileWatcher

예외처리여기서 예외잡지못하면 스레드 종료됨
BFileReader

예외처리C어디까지 기록했는지 모름
CLogWriter

예외처리DDBWrite를 재시도할수있는 정보가 없음
DDBWriter


기존 예외처리모델의 문제는, 호출자에게 책임을 계속 떠넘기는데 있습니다.

호출구조상 Stack의 윗부분에는 에러핸들링을 처리 하기위한 ,  에러핸들링 처리를 위한 충분한 정보가 없습니다.


즉 D과정에 DbBrockenConnectionException 이 발생하고 이과정에서

예외가 발생하여 C의 예외처리기에 갔다고 했을시, C가가진 예외처보및 LogWrite기는

기존 DB접속에대한 정보가 없어서 재시도할 충분한 정보가 없습니다.

이것을 처리하기위해 LogWrite기에게 정보접속을 제공하고 객체를 공유하는것은

단순한 설계를 깨는것이며, 그렇게해서 처리한다고 해도.....

try{ 시도} catch{ 또시도 } 이러한 부적절한 코드가 될것입니다물론 피호출자에게 에러처리를 위한 정보를 계속 전달할수도 있습니다.

하지만 예외 처리를 위해서 LogWrite기에서 DB재시도를 위해  DB 정보접속을 전달하는것은 설계원칙을 깰수가 있습니다.

LogWriter기는 DB의 접속정보를 가지고 있지 말아야합니다.


책임을 지지않는 전역 Catch

과거 예외 핸들링이 지원안되었을시, 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에서는 부모가 AKKA에서는 부모가 자식(정확하게는 호출자)에게 예외를 전가시키지 않는 관리감독 전략을 통해 에러핸들링을

서비스 코드에서 분리하고, 최대한 서비스코드에서 에러처리를 분리하여 상위 부모에서 해결하려는 전략을 사용합니다.

생성에 관여한 부모액터가 가장 적절한 복구전략을 세울수 있기때문입니다.적절한 복구전략을 세울수 있기때문입니다.


물론 액터는 트리형태의 구조적인 형태로 구성이 되기때문에, 이러한 관리자 감독에 의한 에러처리가 가능하며

모든경우 다 사용할수 있는 방법이 아니기때문에 익셉션처리 프로그래밍은 여전히 중요하게 사용되고 있으며,

액터의 복구전략과 예외처리 프로그래밍을 비교하는것은 예외처리 프로그래밍을 더 고찰할수 있게합니다. 

Info

Catch ALL?

서비스를 죽이지 않기위해, 모든 예외를 캐치하는것, 스택의 구조상

캐치된 소스의 위치는 복구를 할수 있는 충분한 정보가 없음


예외란?

예외는 복구를 위한 수단이라기보다, 에러처리에대한 일관성 없는 상태를 방지하고자 함수호출을

빠르게 거슬러 올라가기위한 수단이다.


Let It Crash?

부적절하게 안죽게 하려고, 처리하지도 못하는 예외를 서비스코드에 썩지말고

그냥 죽게두고, 부모의 복구 플랜을 통해 장애처리를 서비스코드와 분리하는 전략

그리고 전략에따라, 백업서비스가 있다고하면 그냥 죽게놔두는것이 유용한 전략

예외란?

예외는 복구를 위한 수단이라기보다 일관성 없는 상태를 방지하고자 함수호출을 빠르게 거슬러 올라가기위한 수단이다.참고: http://wiki.c2.com/?LetItCrash