Versions Compared

Key

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

...

Info

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

메세지에 대응되는 패턴매칭은 액터의 행동을 나타내는 굉장히 편리한 방법이며

추상레벨을 높혀서 Akka 를 가져다가 사용하는 개발자들이  분산/병렬  코드를 작성하기 굉장히 쉽게 해준다.

  • 유사 패턴 : Active Object 패턴
  • 간단한 설명 : 비동기 메시지를 처리할수 있는 능동적 객체

간단한 용어설명

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

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

간단한 용어설명

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

...

  • 패턴 : 이하 참고
Expand
title패턴의 탄생이유로 알아보는 패턴의 의미


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

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


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

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


사례를 통한 공유

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


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


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

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

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


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

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

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

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

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

그 이후 위 약점을 보완하려고, 다양한 언어로 디자인 패턴 활용방법에 대한 책이 쏟아져 나왔지만

개인적인 디자인패턴에대한 경험은 다음과 같습니다.

  1. 바이블 같은책이 없고,개발패턴을 이야하는 사람은 있으나 주위에 잘 활용하는 사람이 없음, 여전히 이해하기 어렵거나, 직접구현해서 적용하기 어려움
  2. 결국 사람들이 많이 사용하여 일반화되는 패턴의 특성상  라이브러리화가 되거나 툴킷화되거나 프레임워크에 녹아듬 ( 애매한  라이이브러리-툴킷-프레임워크의 차이 )
  3. 패턴을 직접 개발하기보다, 패턴자체가 적용된 개발 라이브러리또는 툴킷을 이용하게 되면서 해당 패턴의 습관이 생김
  4. 자신이 사용중인 개발패턴이 무엇이고? 고민하지 않으면? (What-Why-How)  그냥 몸속에 습관으로만 남아있음   

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

OOP와 비교해본 Actor Model

...

OOP(Object-oriented programming)

Image Added

Code Block
languagec#
themeEmacs
title일반적 OOP에서 스레드 세이프한 객체 작성법
linenumberstrue
    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를 OOP방식으로 설계한다고 가정하자

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

...

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

Actor

액터의 기본철학은, 액터가 차지하는 보유하고 있는 상태 메모리 공간은 어느 다른 쓰레드혹은 동일한 액터에서도 자기자신의 속성을 접근할수 없다공간는 어느 액터 혹은 심지어 부모가 액터가 자식 액터의  속성도 접근할수 없습니다.

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

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

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

 배워야 되지 않는다는 의미는 아님,오히려

기존 방식의 어려운점을 알아야 더 잘활용할수 있습니다. 

하지만 멀티스레드 구현에 집중하지말고 관련 스레드 모델 컨셉 정도만 익히는것으로 충분합니다.

비동기형 능동적 객체인 Actor를 어떻게  최적화해서 다중스레드로 관리할것인가의 문제는

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

예제를 통해 어떻게 작동되는지 살펴봅시다.ActorSystem이 해야할일이며, 개발코드와 분리하여 옵션화를 통한 튜닝의 문제입니다.   ( http://doc.akka.io/docs/akka/2.5.3/scala/dispatchers.html )

Code Block
languagec#
themeEmacs
titleActor 설계 코드
linenumberstrue
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; 


...

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


 메시지 전송에의해서만 , 동시성에대한 처리를하기때문에 네트워크로의 확장에 유연합니다.

Actor2는 MessageA 처리중, MessageB요청을 받게되며 처리후 다음 메시지를 처리합니다.

쓰레드 모델과 다른점은 Actor3는 MessageB를 요청하고 대기상태에 빠질필요가 없고

Actor2의 내부에서는 Lock자체가 필요가 없게됩니다.

 위 그림을 보면, 이러한 의문을 가질수 있습니다.  액터하나당? Thread를 하나사용하는것인가?

그러면  액터수 생성에 제한이 있지 않는가? 실제 아무일을 하지 않는 액터는 스레드를 점유하지 않으며

동시 처리가능수에대한 제한도 가능합니다. ( dispatcher가 액터들을 스레드풀에의해 효율적으로 관리함)

만약 어떠한 액터가 처리 시간이 많이걸리는 액터라고 가정하면..,

빠르게 작동되는 액터와 스레드사용 전략을 분리할 필요가 있습니다.

AKKA에서는  이러한 문제를 복잡한 스레드 모델이 아닌 튜닝가능한 옵션으로 해결하려고 시도합니다.

dispatcher-behaviour-on-good-code.pngImage Removed

멀티스레드 측정을 위해 Tace하거나 중단을 통해 작동의 연관성을 찾는것은 어렵기때문에, 스레드 프로파일러 시각화 툴의 도움을 받습니다.

만약 위와같은 스레드모델를  직접 작성하게될시 서비스코드의 복잡도증가및 숙련도에따라 서비스의 불안정으로 이어질수 있습니다.

AKKA에서는 단지 튜닝을 위한 목적으로 스레드사용에대한 전략을 설정화로 이루어냅니다.

목적과 성능에따라 액터를 관리할 dispatcher를 분리하게되면 Actor의 성능에 대한 문제 파악이 용이해집니다.

-번외 문서 : 서버 개발자의 성능 튜닝에대한 고뇌

Code Block
languagescala
themeEmacs
linenumberstrue
my-blocking-dispatcher {
  type = Dispatcher
  executor = "thread-pool-executor"
  thread-pool-executor {
    fixed-pool-size = 16
  }
  throughput = 1
}
//이것은 런타임 코드가 아니라, 설정사항입니다. AKKA에서는 Thread를 직접 생성하는 코드를 작성하지 않습니다.
//장비 1개 성능 최적화와 관련된 튜닝사항으로 스케일업과 관련이있습니다.

관련 액터들만, 스레드를 특정개수로 제한할수가 있으며  처리시간이 짧은 액터일경우 훨씬더 작은 수의 스레드로도 충분히 빠르게 작동을 합니다.

실제로 내부 장비에서 액터간 초당 50만 메시지가 처리가능하다라고 함 :  http://letitcrash.com/post/20397701710/50-million-messages-per-second-on-a-single

OOP설계방식으로 패킷을설계하고, TCP전송 프로토콜을 사용하여 멀티스레드 기법으로 고성능 전송을 구현한다고 했을시

1:1 안정적인 고성능 전송기능이될때까지 엄청난 노력을 기울여야할것입니다. (그것이 c++이던 java이던간에)

AKKA에서는 이 고민의 시간을 줄이고 , 우리의 개발 서비스를  어떠한 라우팅전략을 써고 클러스터링으로 잘 구성할것인가에대해서  집중하도록합니다. 

...

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

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

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

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


Note

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

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

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

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

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