액터모델의 특징 요약
- 동시및 분산 시스템작성을 위해 높은수준의 추상화제공
- 개발자가 명시적 잠금처리및 스레드 관리 안해도됨
- 이로인해 병렬 시스템 작성에 용이
Actor 정의(+메시지설계)
public class SomeMessage { public string message { set; get; } } public class MyActor : ReceiveActor { public MyActor() { Receive<string>(message => { Sender.Tell("RE:" + message); }); Receive<SomeMessage>(message => { Sender.Tell("RE:" + message.message); }); } }
public class MyActorSame : UntypedActor //MyActor와 동일한 기능을 하는 Actor로 OnReceive 이벤트 발생 함수를 통해 조금더 명시적(덜추상적)으로 메시지를 처리합니다. { protected override void OnReceive(object message) { if(message is string) { if (message as string == "createChild") { Context.ActorOf<MyActor>("myChild"); Sender.Tell("Create Child Succed:myChild"); } else { Sender.Tell("RE:" + message); } } else if(message is SomeMessage) { Sender.Tell("RE:" + (message as SomeMessage).message ); } } }
Actor는, 메시지를 송수신을 정의하는 가장작은 객체의 정의 이며, 능동적 객체라고 볼수 있다.
Actor설계시 기본적으로 다음을 고려하면 된다. 이것은 마치 특정API 설계시 하는 고민과 똑같다.
- 어떠한 메시지(구조체)를 전달할것인가?
- 특정한 메시지(요청)에 어떻게 반응할것인가?
위 정의 코드는 그러한 설계에대해 구현을 한것이며, 다음과 같은 스펙을 가진다.
- 메시지를 받게 될시 , 접두어 "RE:" 를 붙여 해당 메시지를 그대로 돌려줌
- 응답해야할 Type이 정해지지 않고 다양한 요청에 대해 다양한 Type으로 응답가능
위 코드에서 조금더 추상적인 ReceiveActor 생성자에서 , Type 매칭하여 메시지 처리기가 가능한것은 , Receive의 추상화덕분이며
IF문이나 스위치문없이 생성자에서 , 처리할 패턴별로 처리기가 등록이 가능한것은 C#의 문법지원때문인데
C# 언어학적으로 파악하고자 하면 (템플릿/람다/함수형/델리게이트 특성) 참조하여 더 깊은 학습이 가능합니다.
어쨋든, 우리의 아주 짧은 액터 메시지 처리기 설계가 끝이 났습니다.
액터 생성
protected void SomeTest1() { IActorRef myActor = actorSystem.ActorOf<MyActor>("myactor"); Console.WriteLine(myActor.Ask("Hello Akka").Result); }
처음에 설계한 MyActor를 myActor란 이름으로 생성하고
Heelo Akka란 메시지를 전송하여 그 결과값을 출력하는 예제입니다.
보낼수 있는 명령 Type
- Ask : 결과값을 기대할때 사용합니다. 비동기로 수신하며, 동기처리시 .Result를 붙입니다. ( C#의 Async 프로그래밍 )
- Tell : 결과값이 필요 없을때 , 단지 전송만 하고 대기처리가 없습니다.
Child 액터 생성과 액트선택
자식 노드 생성
Receive<string>(message => { if (message == "createChild") { Context.ActorOf<MyActor>("myChild"); Sender.Tell("Create Child Succed:myChild"); } else { Sender.Tell("RE:" + message); } });
문자열 수신부분을 수정을 하여, createChild 란 문자열을 받을시 'myChild' 란 하위 노드의 액트를 생성하고
자식액트(하위노드) 의 생성을 반환하는 메시지를 응답하는 예제입니다.
이것을 작동시키는 코드는 아래와 같이 하면 되겠습니다.
특정노드를 선택하여, 자식 노드 동적으로만들고, 자식노드에게 메시지 보내기
protected void SomeTest2() { IActorRef myActor = actorSystem.ActorOf<MyActor>("myactor"); Console.WriteLine(myActor.Ask("createChild").Result); Console.WriteLine(actorSystem.ActorSelection("/user/myactor").Ask("Hello Akka1").Result); Console.WriteLine( actorSystem.ActorSelection("/user/myactor/myChild").Ask("Hello Akka2").Result ); }
주로 어떠한 메시지를 받았을때, 동적으로 액터를 생성시 활용을 합니다. REST API의 Endpoint체계는 다소 정적이지만
Actor의 EndPoint체계는 이러한 이유로 , 뎁스 추가 삭제가 자유롭습니다.
사용자가 만든 액터 루트의 최상위는 'user' 이며 만든 계층에따라 하위 뎁스로 가거나 수평으로 뻗어 가게 됩니다.
actorSystem에 의해 어떠한 이름(myactor))으로 만들어졌다면 'user/myactor'
myactor가 어떠한 이름(myChild) 하위 노드를 만들었다면 'user/myactor/myChild' 이렇게 계층화가 됩니다.
그리고 , 액트시스템은 어떠한 액터라도 선택할수 있으며
actorSystem.ActorSelection( 액터주소).어떠한액션() 이러한 접근방식으로 메시지 전송이 가능합니다.
주소체계
잠시 실습없이, Actor를 접근하는 주소체계에대해 설명 드리겠습니다. ActorPath는 총 4가지로 구분이 되며
http의 RestAPI의 주소체계와 거의 흡사하다고 볼수 있습니다.
4요소
- Protocol : AKKA를 사용하는 프로토콜임을 알림
- ActorSystem : 주로 어플리케이션 하나의 이름으로, ActorSystem 최초 생성시 이름지정
- Address : 네트워크 주소로 ip:port 로 표현
- Path : 서비스 개발자가, 계층적으로 배치한 Actor 이름
여기서 의문을 가질수 있습니다. RestAPI와 흡사한데, 그냥 RestAPI를 사용할래~ 라구요
주소체계와 사용법은 흡사하나, 애시당초 사용목적이 틀립니다. 위 예는 동일장비에서 액트 메시지 처리능력 성능그래프이며
초당 50만메시지 처리가 가능합니다. 원격API뿐 아니라 , 로컬 시뮬레이션에 사용하기에도 충분한 동시적 처리능력을 가지고 있습니다.
네트워크가 적용되면 네트워크 지연이 있어서, 위와같은 결과를 내지 못할테지만, ACTOR메시지 처리가 RESTAPI비교하여 100배 이상 고성능일것으로 추측합니다.
더욱이 RAMPUP(지속적으로 증가) 테스트를 하게된다면, RESTAPI는 1000tps이상에서 3분이상 못버티고 CPU100,메모리풀 이 날것으로 예상합니다.
이것은 정확하게 ACTOR는 소켓 연결지속적 실시간처리이며 , RESTAPI가 전송1번에 연결을 맺고/끊고를 반복하는 프로토콜의 차이에 있습니다.
AKKA의 주목적은 사용자에게 제공하는 REST를 걷어내는게 아니라, 미들웨어간 소통이 없는 서비스를 소통하게하여 DB 호출의 최소화
미들웨어간 RestAPI사용을 하였다면 변경으로 인한 성능향상의 목적이 될것입니다.
다음 RemoteActor실습시 가상의 서비스 시나리오를 만들어 극명한 성능 차이를 프로파일링 비교해보겠습니다.
- RESTAPI 호출 VS RemoteActor 호출
- 항상 DB Read 가 발생하는 RESTAPI VS Actor를 사용하여 DB Read가 발생하지않는 RESTAPI