액터는 매우 가벼운 동시성  엔터티들이다.  event-driven receive loop를 이용하여 비동기적 메시지를 처리한다.

동시 처리성을 높이려면 Dispatcher 를이용 ,분산처리 확장은 Cluster가 활용될수 있으며 로컬용으로 작성된 개발방법이 크게 달라지지 않습니다.

AKKA는 액터를 전반적으로 활용하기때문에 액터패턴에 대한 컨셉을 먼저 살펴보겠습니다.

간단한 용어설명

  • Active Object : 항상 작동중이고 명령 가능한 능동적 객체란 점에서 액터 패턴과 유사
  • 명령가능 : 액터는 메시지를 통해서만 명령이전달된다는 점에서 차이가있습니다.
  • 비동기 : 다른 녀석에게 일을 넘기고 내일을 한다. ( 논블럭킹 )
  • 동기 : 다른 녀석이 일을 마칠때까지 기다린다. ( 블럭킹 )
  • 능동적 : 나만의 큐와 스케쥴러를 가지고 있음
  • 패턴 : 이하 참고


소프트웨어 설계에 있어서 경험만큼 확실한 것은 없다. 그러나 문제는 모든 사람들이 충분한 경험을 갖고 있지는 않다는 사실이다.

그렇다면 충분한 경험을 갖지 못한 사람들이 많은 경험을 가진 전문가 못지않게 소프트웨어 설계를 잘 할 수 있도록 만들어 줄수 있는 방법은 무엇일까?


소프트웨어 설계 방법론과 지침을 통한 공유

=> 접근의 한계가 있음, 방법론이나 지침은 일반화된 것이어서 구체적인 문제에 적용하기 위해서는 또다른 지식이필요


사례를 통한 공유

=> 사례는 너무 구체적이고 특정 문제의 가정에 의존


위 두가지 모두 한계점이있으며


일반적이지도, 또 너무 구체적이지도 않은 형태의 소프트웨어 설계를 위한 지침을 고민하면서

생긴게 디자인 패턴이다. ( Design Patterns : Elements of Reusable Object-Oriented Software)

- 여기에 포함되는 패턴이 GoF(이분야의 4인방)  디자인 패턴이다.


Erich Gamma외 3명은 다음과 같은 절차로 디자인 패턴을 정립화 하였다.  ( 새로운 디자인 패턴도 아래와같은 과정으로 생겨난다.)

  1. 여러가지 설계 사례를 분류
  2. 각 유형별로 가장 적합한 설계를 일반화 시켜 패턴으로 정립
  3. 이중 다시 쉽게 재사용가능한 객체 지향 개념에 따른 설계만 패턴으로 지정

디자인 패턴이 휼륭한 원서임에도 불구하고 다음과 같은 약점이 있음

  1. 해결하고자하는 문제의 유형이 적고
  2. 디자인 패턴이 설계되기까지 어떤 측면에서 고민이 이루어졌는지에대한 유도과정이 확실치 않으며
  3. 디자인 패턴에서 제시하는 설계가 일반적인 개발자들의 설계와 비교할때 어떠한 장단점을 가지는지 언급이 안됨

여기까지 Gof 디자인 패턴 이렇게 활용한다 -C++로 배우는 패턴의 이해와활용에서 발췌

디자인 패턴은 이러한거다란 정도 알고 넘어갑시다.

OOP와 비교해본 Actor Model

OOP(Object-oriented programming)


일반적 OOP에서 스레드 세이프한 객체 작성법
    public class DeepThought
    {
        private int state;

        public int State
        {
            get
            {
                lock (this)
                {
                    Thread.Sleep(3000); //나의 상태에대한 깊은 생각에 빠짐..나는누군가? 여긴어딘가?
                    return state;
                }                
            }

            set
            {
                lock (this)
                {
                    //깊은 생각에 빠질땐 누구도 건들지 말기를 바람,그대가 최고우선순위 스레드라할지라도
                    state = value;
                }                
            }
        }        
    }
var dt = new DeepThought()

Thread.new { console.log( dt.State ) }
Thread.new { dt.State=1 }

 자신의 상태변경및 제공을 멀티스레드에 안전한 방식으로 OOP방식으로 설계한다고 가정하자

이 클래스는 이야기한다, '자신의 속성을 Lock(metex)를 걸어 , 누군가 나를  접근하고 있을때, 해당 오브젝트를 건들지마(또는 기달려야해)' 라고 이야기하고 있다.


일반적으로 OOP 관점에서는 스레드 세이프하다란 의미는 메스드사용시 공유객체를 Lock을 걸거나? 별도의 독립된 객체를 추가로 생성하여

동시처리,동시접근에대한 문제를 풀려고 한다. 공통점은 특정한 속성값이 멀티쓰레드에서 동시접근 제한 방지를 위해 공유가된다는 점이다.




==> Object1이 먼저 Object2를 접근하고 Lock을걸기때문에, Object3는 Object1의 메서드A 가 끝날때까지 Object3는 대기상태에빠짐


접근공유문제와  동시성 문제 해결하기위해, 복잡한 쓰레드 모델을(CriticalSection,SpinLock,Mutex,Semaphore) 익혀야하며

클러스터 구성하기도 전에, 단일노드에서 효율적 동시성 접근문제에 대해 많은 고민을 해야합니다.

Actor

액터의 기본철학은, 액터가 보유하고 있는 상태 메모리 공간는 어느 액터 혹은 심지어 부모가 액터가 자식 액터의  속성도 접근할수 없습니다.

( Protected 는 자식이 접근할수 있다란 OOP의 상속 개념을 사용할수 없습니다.)

다시말해 액터내부에 일어나는 일은 어느 누구와도 '공유'되지 않으며 죽음의 칵테일에서  공유라는 속성을 제거함으로 멀티쓰레드와 관련된 문제의 대부분을 제거 하고 시작한다.

공유되지 않기때문에, 임계영역 처리를 위해  'lock' 'synchronized' 와 같은 부자연스러운 키워드가 필요없으며 어려운 스레드 모델을 사용해야할 필요가 없어집니다.

메시지를 통해서만 상대에게 명령할수 있고 값을 알아낼수 있다란 단순한 방법만을 사용합니다.

예제를 통해 어떻게 작동되는지 살펴봅시다.

Actor 설계 코드
public class MyActor: ReceiveActor
{
  //Actor모델 에서는 안전한 동시성처리를 위해 어떠한 프로퍼티(속성)도 공유가 필요없음으로, 속성 접근제한에 priave만으로 충분합니다.
  private readonly ILoggingAdapter log = Context.GetLogger();
  private int state =0;

  public MyActor()
  {
    Receive<string>(message => {
      log.Info("Received String message: {0}", message);
      Sender.Tell(state );
    });
    Receive<SomeMessage>(message => {...});
  }
}

//코드 변경 최소로 옵션만으로 로컬,원격,클러스터등러등의 작동 선택 전략이 가능하다.
var remoteActor = system.ActorSelection("akka.tcp://MyServer@127.0.0.1:8001/user/someActor"); 

// 질의를 하던지?(Ask), 단지 말만한다던지?(Tell), 결과를 기다리던지(Result)? 
// 객체 접근 기반이아닌 메시지 전송기반의 인터페이스를 사용하는 조금더 추상화된 모델이다.
// Tell의 의미를 상세히 분석하면, 내가 한말에 결과값이 필요없으니 처리만해달라란 의미이며 
// 네트워크상의 의미로 수행결과가 있어도 어떠한 값을 리턴한다고 해도, 결과메시지 자체가 발송되지 않습니다.
var state = remoteActor.Ask("Hello").Result; 



Actor는 오로지 메시지만 주고 받으며 동시성을 달성하며,  결과값을 얻기위해서는 객체접근이아닌 질의를 해야한다.

이와같은 처리방식은 동일한 컨셉으로 로컬뿐아니라 리모트처리,분산처리등으로 확장이 될수가있다.


 메시지 전송에의해서만 처리를 하게되며, 이러한 방식은 리모트로의 전환시 구현체의 변경이 없음을 의미합니다.

액터는 메시징을 받는 스레드와,이것을 하니씩 꺼내에 순차적으로 처리하는 작업 스레드가 분리되어 있음으로

연속성을 보증함과 동시에, 작업이 블락킹으로 구현 되었다고 하더라도, 항상 메시지를 받을수 있습니다.

Actor2는 그 누구와도 자신의 상태에대한 메모리를 공유하지 않기때문에 상태변경에 필요한 속성에 Lock이 필요가 없습니다.


액터의 특징을 살펴 보고자 반대편에 있는 OOP를 이용한 스레드 직접 제어의 단점을 예를 들었지만

액터 시스템도 Dispatcher 란 튜닝가능 객체가 스레드 풀을 이용을 하며, OOP로 작성된 추상화 객체입니다.

도메인 메시지 처리에 있어 스레드를 이용하는 OOP적 설계방식이 단순성을 유지하고 확장에 유연한 개발능력을 가지고 있는가?

스스로 질문을 던저볼수 있으며, 메시징 처리에 있어 동시성 처리를 포함한 다음 3가지 주제는  액터나 아카와 상관없는

공통주제로 , 액터를 연마하기전 추천 선행 자료입니다.