관리 메뉴

HAMA 블로그

Actor 패턴 ? ActiveObject 패턴 ? with AKKA 본문

소프트웨어 사색

Actor 패턴 ? ActiveObject 패턴 ? with AKKA

[하마] 이승현 (wowlsh93@gmail.com) 2015. 5. 15. 17:19

Actor 모델의 기본을 짚어보고  Akka 에 예제를 짧막하게 살펴본다. (굉장히 두서없는 글이 될것이다) 
Actor 모델이 굉장히 유명한데, 개인적으로 ActiveObject 패턴으로 알고있었다.
정확히 둘 간에 어떤  차이점이 있는지는  모르겠다.  패턴과 모델 ??  

내가 읽은 어느 책에서는 ActiveObject  패턴이 Actor 과 같다고 나오고, POSA2 편을 보면 (Pattern-Oriented Software Architecture 2 : http://www.cs.wustl.edu/~schmidt/POSA/POSA2에서는 ActiveObject 패턴과 Reactor / Proactor 패턴이 나온다.  분명히 다르긴 하다. 


정리


object :   객체로 호출하면 바로 반응한다. 

actor  :   능동적인 객체 ( 즉 자신의 쓰레드가 있고 큐를 가지고있다 )  호출하면 바로 응답하지 않는다. 

reactor : actor 에 추가적으로 해당 이벤트에 대한 핸들러가 매핑되어서 디스패치하는 구조. (selector 느낌)

proactor :  행위를 actor 에 넘겨서 그 행위에 대한 결과를 받은 구조.  ( IOCP 느낌)

activeobject : 행위할수 있는 actor 에게 자신의 요청 사항을 넘겨준후에 future 객체를 이용하여 결과 파악을 함. 


이번글을 쓰는 계기로 차이점에 관련된 읽을거리를 검색해봤다 ;;

http://www.carlgibbs.co.uk/blog/?p=237

http://members.unine.ch/anita.sobe/res/RR-I-AS-2014.06.1.pdf

Active Object pattern: http://www.dre.vanderbilt.edu/~schmidt/PDF/Active-Objects.pdf

Actor Model: http://en.wikipedia.org/wiki/Actor_model

솔직히 나는 저 둘의 관계에 대해  깊숙히 이해하고 싶은 생각이 없다. (지금 당장은 말이다) 
따라서 깊은 이해는 없다라는걸 미리 말한다. 대략 요약하면 


차이

-   구조적으로 두 패턴모두 스케쥴러와 큐를 가지고있다.
-   구조적으로 ActiveObject 는 프록시를 활용하며 , Future 객체를 사용한다.
-   구조적으로 Actor 패턴은  ActiveObject 에 비해서 자유성이 높다. 
-   메세징/통신측면에서 ActiveObject 패턴은  미리 정해진 함수호출을 통해서 잡을 전달한다.  
-   메세징/통신측면에서 Actor 패턴은 문자열을 보내며, 그 문자열 포맷은 매우 자유롭다.
    따라서 Actor 패턴에서 각각의 Actor 의 OnRecvier 구현은 매우 유연하다.  

-   Actor 모델은 더욱 확장하기 쉬운데 어떤 액터나 프록시가 될수있고 다른 액터와 분산해서 작업부하를 가질       수있다.

-  대부분의  AOM 구현들은 정해진 서번트와 함께 쓰레드풀에 의존적이다.  
-  양쪽 모델모두 가능한 이미 존재하는 Actor 나 ActiveObject 를 재사용한다는걸 알아둬라. 
-  보통 Actor 모델은 큐가 제한이 없으며, ActiveObject 는 큐의 크기에 제한이 있다. 


자 Active Object 패턴은 한마디로 말하면

"비동기 메세지를 처리할수있는 능동적 객체" 이다.

주목해야할 단어가 2개있다. 


첫째, "비동기" 

둘째, "능동적" 


비동기란 무엇인가??  

내가 어떤일을 해야하는데 내가 하기는 싫고 , 다른 녀석한테 시키고 싶을때 저놈은 게다가 그 일에는 전문가다. 내가 일을  

- 다른녀석한테 슬쩍 넘기고 , 나는 내일을 하는게 비동기다. (논블럭이라고도한다. 미묘한 차이가 있다)

- 다른녀석한테 넘기고 , 그 녀석이 일을 마칠때까지 기다리고 있는건 동기이다.(블럭킹 되었다고한다)


비동기메세지란 결국 메세지를 다른녀석한테 던저주고 나서 , 바로 나는 내일을 하는것을 말한다. 

* 윈도우즈의 IOCP에서 말하는 비동기 입출력(Overlapped IO) 은 조금 또 다른 느낌이다..(병렬의 느낌) 


그럼 능동적은 무엇인가?? 

기술적으로 간단하게 말하면, 나도 쓰레드를 가지고있고, 다른녀석도 자신의 쓰레드 혹은 프로세스를 가지고있는걸 말한다. 둘이 같은 쓰레드를 공유하는게 아니라는 얘기이다.


결국 위에서 말한 "나" 와  "다른 녀석" 은 각각 자신만의 "쓰레드" 를 가지고있고, 자신만의 "논블록킹" 큐를 가지고있게된다. 

따라서 어떤 일을 상대방한테 넘길때 메세지를 상대방의 큐에 넣고 , 돌아오면 "비동기" 가 되는것이고 

상대방은 스스로 쓰레드에서  while 을 돌면서  자신의 큐를 확인해서 일거리를 가지고 옮으로 "능동적"이다.

전체 맥락이 이해됬는가??

이 능동형 객체가 Akka에서 Actor 이며 , Actor 들은 자신만의 큐와 스케쥴러를  가지고있을것이다.

근데 메세지를 객체로?? 메세지가 명령인데  명령을 객체화 해서 보내는것은 알다시피 Command 패턴이다. 그리고  Future 패턴이나  큐에 잡을 던진다는것에서  producer-consumer  패턴을 포함한다.

패턴은 조금씩 서로와 연관된다.  

클라이언트측에서 어떤 “메소드 실행” 을  클라이언트와는 별개의 쓰레드 혹은 다른 컴퓨터의 큐에 던저서 그쪽에서실행되게 하자!! 값은 나중에 받자!! RPC 랑도 연관되네? Vert.x/ RabbitMQ / ActiveMQ 랑도 연관되네? 

(하둡RPC : http://brad2014.tistory.com/175)

근데 객체복사를 꽤 하니깐 작은규모에선 오버헤드가 좀 있고 이런 사소한것보단 저런 나름 복잡한 패턴을 실제 구현하는 피로감이..(그래서 akka  라이브러리가 나옴)

(Akka 는 비동기적으로 업무처리를 분담한다는것 이외에도 무상태를 지향하므로 , 데드락이나 레이스컨디션등 일반적인 객체지향언어의 멀티쓰레딩 코딩에서 나타나는 문제에 대한 보완을 한다. 물론 객체를 메세지로 보낼때 객체의 상태를 바꿀수있게 코딩한다면 의미없어 지겠지만) 



위의 ActiveObject UML 을 살펴보면  

1. 클라이언트가  ActiveObject 의 프록시로  메세지를 전달한다 ( 함수를 호출한다. 명령을 한다.)

2. ActiveOjbect 는 스케쥴러를 가지고 있다. ( 이게 "능동형" 라는 말인데, 쓰레드가  while 을 돈다는 말이다)

3. ActiveObject 는 MessageQueue 를 가지고있다. ( 이게 비동기를 위한 논블럭킹 큐이다) 

4. 클라이언트는 ActiveOjbect 를 통해 메세지를 만들고 메세지큐에 넣은다음에 퓨처 객체를 받고 자기할일함

5. 스케쥴러는 메세지큐에서 하나꺼내서 서번트를 통해서 작업을 하고, 퓨처객체에 완료 알림을 한다

6. 클라이언트는 퓨처객체에서 get 을 해서 실제 객체 (원하는 작업의 결과) 를 얻는다. 


첫번째 그림을 이해했으면 이것도 마찬가지라는걸 알수있다. Akka 의 Actor 는 다른 Actor의 메일박스에 메세지를 던지고 자기할일하고, QA 라는 Actor 는 디스패처를 통해서 일감을 가지와서 실행.

   

  여러개의 Actor 가 서로 다른 Actor 들에게 동시에 메세지(할일) 을 넘겨줄수도 있겠지..

Akka ( http://akka.io/ ) 에서 Actor   란 ?  (홈페이지 발췌) 

Actors

액터는 매우 가벼운 동시성  엔터티들이다. 이놈들은 메세지들을 event-driven receive loop  를 이용하여 비동기적으로 실행한다.  메세지에 대응되는 패턴매칭은 액터의 행동을 나타내는 굉장히 편리한 방법이며 , 추상레벨을 높혀서 Akka 를 가져다가 사용하는 개발자들이  분산/병렬  코드를 작성하기 굉장히 쉽게 해준다.

You focus on workflow—how the messages flow in the system—instead of low level primitives like threads, locks and socket IO. Learn More.

Akka with ActiveObject 패턴 

// 프록시와 서번트의 공통 인터페이스 
public interface  Printer {
  Future<String> printNonBlocking ( String msg) ;
  Option<String> print Blocking ( String msg) ;
}

// 공통인터페이스 기반으로 서번트를 만든다.
class PrinterImpl implements Printer {
  private String name ;
    public void PrinterImpl ( String name ) {
    this.name = name ;
  }
  public Futures<String>printNonBlocking ( String msg ) {
    System.out.println (msg) ;
    return Futures.successful(”Worked”);
  }
  public Option<String> print Blocking (String msg) {
    System.out.println (msg) ;
    return Option.some(”Worked”) ;
  }
} 


public class Main{
  public static void main (String [] args ) {
    ActorSystem system = ActorSystem.create (”testSystem ” ) ;

    // typedActor 가 사용되었다.
    // 첫번째 인자는 프록시에 사용될 인터페이스이고, 두번째 인자는 서번트가 들어간다 
    PrinterhelloWorld = TypedActor.get (system) .typedActorOf (
    new TypedProps<PrinterImpl>( Printer.class , PrinterImpl.class)) ;

    // 비동기호출을 하여 Futre 객체를 즉시 받는다.
    Future<String> resultNB = helloWorld.printNonBlocking ( ”Hello World!) ;
    Option<String> result  B = helloWorld.printBlocking (”Hello World !);
  }
} 


Akka with JAVA   

import akka.actor.UntypedActor;

public class PrintActor extends UntypedActor {

    @Override

    public void onReceive(Object message) throws Exception {

        Thread.sleep(500); // 테스트를 위해 0.5초 sleep

        System.out.println(message);

    }

}


ActorRef actor = Actors.actorOf(PrintActor.class);

actor.start();

// actor를 이용해서 액터에 메시지를 전달

ActorRef의 start() 메서드는 액터를 시작하며, 액터가 시작된 이후부터 액터에 메시지를 전달할 수 있게 된다.

액터에 메시지 전달하기

Actors.actorOf()를 이용해서 액터를 생성했다면, 이후 ActorRef가 제공하는 메서드를 이용해서 액터에 메시지를 전달할 수 있다. 

다음의 세 가지 방법으로 액터에 메시지를 전달할 수 있다.

1.Fire-And-Forget: 메시지를 전달하고 메시지에 대한 응답을 기다리지 않는다. 병행 및 확장에 적합한 메시지 전달 방식이다.

2.Send-And-Receive-Eventually: 메시지를 전달하고 응답을 받는다. 응답을 받을 때 까지 블록킹된다.

3.Send-And-Receive-Future: 메시지를 전달하고 응답을 받기 위한 Future를 리턴한다.


ActorRef actor = Actors.actorOf(PrintActor.class);

actor.start();

actor.sendOneWay("받아라");  //Fire-And-Forget 예  

actor.sendOneWay("받아라2");

actor.sendOneWay("받아라3");

System.out.println("비동기로 실행");


sendRequestReply() 메서드를 이용한 Send-And-Receive-Eventually 방식 메시지 전달

ActorRef.sendRequestReply() 메서드는 액터에 메시지를 전달하고, 그 메시지에 대한 응답이 올 때 까지 대기하고 싶을 때 사용된다. 액터 구현 클래스는 getContext().replyUnsafe() 메서드를 이용해서 메시지에 대해 응답할 수 있는데, ActorRef.sendRequestReply() 메서드는 이 응답을 리턴하게 된다. 예를 들어, 다음과 같이 메시지에 대해 응답하는 액터가 있다고 하자.

public class PingActor extends UntypedActor {

    @Override

    public void onReceive(Object message) throws Exception {

        getContext().replyUnsafe("응답: "+ message); // 메시지 sender에 응답

    }

}

이 경우 다음과 같이 sendRequestReply() 메서드를 이용함으로써 액터에 전달한 메시지에 대한 응답이 도착할 때 까지 대기할 수 있다.

ActorRef actor = Actors.actorOf(PingActor.class);

actor.start();

Object res = actor.sendRequestReply("헬로우"); // 액터로부터 응답이 도착할 때 까지 대기


sendRequestReplyFuture() 메서드를 이용한 Send-And-Receive-Future 방식 메시지 전달


sendRequestReplyFuture() 메서드는 메시지를 전달한 뒤 응답을 받기 위한 Future를 리턴한다. Future는 자바가 제공하는 Future가 아닌 Akka가 제공하는 akka.dispatch.Future 타입이다. Future는 주로 다음과 같은 형식으로 주로 사용된다.

Future future = actor.sendRequestReplyFuture("하이");

future.await(); // 응답을 대기. 대기 시간을 초과하면 예외 발생

..

..

어떤작업..

..

..

if (future.isCompleted()) { // 완료되었다면

    Option resultOption = future.result(); // 응답 구함

    if (resultOption.isDefined()) { // 응답 데이터가 있다면,

        Object result = resultOption.get(); // 응답 데이터 구함

        System.out.println(result);

    }

}


sendRequestReplyFuture()가 리턴한 Future의 await() 메서드는 시간이 초과될 때 까지 대기한다. 시간이 초과되기 전에 응답이 도착하면 다음으로 넘어가고, 시간이 초과되면 ActorTimeoutException 예외를 발생시킨다.



akka 홈페이지 발췌) 

  1. public class Greeting implements Serializable {
  2. public final String who;
  3. public Greeting(String who) { this.who = who; }
  4. }
  5.  
  6. public class GreetingActor extends UntypedActor {
  7. LoggingAdapter log = Logging.getLogger(getContext().system(), this);
  8.  
  9. public void onReceive(Object message) throws Exception {
  10. if (message instanceof Greeting)
  11. log.info("Hello " + ((Greeting) message).who);
  12. }
  13. }
  14.  
  15. ActorSystem system = ActorSystem.create("MySystem");
  16. ActorRef greeter = system.actorOf(Props.create(GreetingActor.class), "greeter");
  17. greeter.tell(new Greeting("Charlie Parker"), ActorRef.noSender());



Akka with Scala

akka 홈페이지 발췌) 

  1. case class Greeting(who: String)
  2.  
  3. class GreetingActor extends Actor with ActorLogging {
  4. def receive = {
  5. case Greeting(who) log.info("Hello " + who)
  6. }
  7. }
  8.  
  9. val system = ActorSystem("MySystem")
  10. val greeter = system.actorOf(Props[GreetingActor], name = "greeter")
  11. greeter ! Greeting("Charlie Parker")


글을 쓰고나니 좀 웃음이 나는데 양심고백을 하자면  나는 요즘도 멀티쓰레드 프로그래밍을 할때 바로 쓰레드를 만들거나 자바 executors  서비스를 이용하는데 만들고나서 한참을 데드락이 있을까봐 코드를 째려보거나 가끔은 설마 하며 운에 맞기는 코딩을 한다. 아예 akka 는 생각안한다. ;;  작은 시스템에서도 충분히 아니 강력히 써야 한다고 생각은 하는데 실천이 부족했던것같다. 이 글을 계기로  당장 개발해야 하는 수요조절기능을 위한 스케쥴러에 첫삽을 떠보려한다.


처리율(throughput)   임백준님의 Akka 시작하기에서 발췌 

아카를 이용한 리팩토링을 끝마쳤을 때, 똑같은 컴퓨터 위에서 전과 동일한 몬테 카를로 시나리오를 수행하는데 걸리는 시간이 6시간에서 2시간으로 단축되었다. 66%의 시간이 절약된 것이다. 결과를 확인한 사람들은 깜짝 놀랐다. 단순히 자바 스레드에서 아카로 라이브러리를 바꾸었을 뿐인데 그렇게 엄청난 차이가 있을 수 있냐며 고개를 갸웃거렸다. 물론 이런 차이를 일반화할 수는 없다. 이런 결과 하나를 가지고 아카가 자바 스레 드보다 3배 빠르다고 말하는 어리석은 사람은 없을 것이다. 아카도 내부적으로 자 1 아카에 대하여 - 017 바 스레드를 사용하기 때문에 그런 비교 자체가 성립하지 않는다. 하지만 일반적 인 차원에서 짚고 넘어갈만한 부분도 있다. 이렇게 커다란 차이가 어디에서 비롯 되었는지 이해하려면 우선 암달의 법칙Amdahl’s law을 생각해볼 필요가 있다. 암달의 법칙은 이렇다. “멀티코어를 사용하는 프로그램의 속도는 프로그램 내부에 존재하는 순차적sequential 부분이 사용하는 시간에 의해서 제한된다.” Thread나 Task를 만들어서 ExecutorService에게 제출하는 식으로 동시성 코드를 작성하면 여러 개의 스레드가 동시에 작업을 수행한다. 하지만 프로그램 안에는 Thread나 Task가 포함하지 않는 코드가 존재한다. 여러 개의 스레드가 동시에 작업을 수행하더라도 synchronized 블록이나 데이터베이스, 네트워크 API 호출 등을 만날 때 다른 스레드와 나란히 줄을 서서 순차적으로 작업을 수행 해야 하는 경우도 있다. 암달의 법칙은 프로그램이 낼 수 있는 속도의 상한이 이런 순차적 코드가 사용하는 시간에 의해서 제한된다고 말하는 것이다. 이러한 순차적 코드의 또 다른 이름은 블로킹blocking 콜이다. 문제는 스레드 자체 가 아니라 스레드를 사용하면서 자기도 모르게 만들어내는 블로킹 콜이다. 조금 과장해서 말하자면 자바 개발자가 스레드를 이용해서 만들어내는 ‘동시성’ 코드는 일종의 신기루다. 사실은 코드 곳곳에 존재하는 블로킹 콜, 순차적 코드 때문에 전 체적인 프로그램의 처리율은 이미 상한이 정해져 있지만 여러 개의 스레드가 ‘동 시에’ 동작한다는 사실로부터 위안을 받을 뿐이다.


Comments