하마
6k
2015-05-15 18:33:52 작성 2016-11-03 11:48:34 수정됨
0
7371

ActiveObject 와 Actor with Akka


Akka 에 대한 실전경험에서 나오는 사용방법,inside 나  환경에 대한 글은 아니니 그런부분은 다른 고수님이 글을 써서 알려주시면  감사하겠습니다.


Actor 패턴 ? ActiveObject 패턴 ? 



ActiveObject  패턴의 기본을 짚어보고  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


  솔직히 나는 저 둘의 관계에 대해  깊숙히 이해하고 싶은 생각이 없다. (지금 당장은 말이다) 

따라서 깊은 이해는 없다라는걸 미리 말하며, 대략 이해한후  논문에서 말하는 대략적인 차이를

마지막 읽을거리에 요약해두었다. (반드시 읽어보길 바란다. Akka 나 Vert.x , Node.js  를 사용할때 도움이 될것이다) 



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


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


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


첫째, "비동기" 

둘째, "능동적" 



비동기란 무엇인가??  


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


- 다른녀석한테 슬쩍 넘기고 , 나는 내일을 하는게 비동기다. (논블로킹되어 작동. )

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


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

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


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


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


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


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

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


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


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


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

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


클라이언트측에서 어떤 “메소드 실행” 을  클라이언트와는 별개의 쓰레드 혹은 다른 컴퓨터의 큐에 던저서 그쪽에서

실행되게 하자!! 값은 나중에 받자!! RPC 랑도 연관되네? Vert.x/ RabbitMQ / ActiveMQ 랑도 연관되네? 


(하둡RPC :  http://okky.kr/article/276574)


근데 객체복사를 꽤 하니깐 작은규모에선 오버헤드가 좀 있고 이런 사소한것보단 저런 나름 복잡한 패턴을 실제 구현하는 피로감이..(그래서 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 에서는 Actor 패턴과  ActiveObject 패턴을 적절히 믹스했다. 




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 Actor 패턴 

// 액터 클래스 작성 

// UntypedActor 가 사용되었다. 

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();


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 홈페이지 발췌) 


public class Greeting implements Serializable {
 public final String who;
 public Greeting(String who) { this.who = who; }
}
 
public class GreetingActor extends UntypedActor {
 LoggingAdapter log = Logging.getLogger(getContext().system(), this);
 
 public void onReceive(Object message) throws Exception {
 if (message instanceof Greeting)
log.info("Hello " + ((Greeting) message).who);
 }
}
 
ActorSystem system = ActorSystem.create("MySystem");
ActorRef greeter = system.actorOf(Props.create(GreetingActor.class), "greeter");
greeter.tell(new Greeting("Charlie Parker"), ActorRef.noSender());



Akka with Scala


akka 홈페이지 발췌) 


case class Greeting(who: String)
 
class GreetingActor extends Actor with ActorLogging {
 def receive = {
 case Greeting(who) ⇒ log.info("Hello " + who)
 }
}
 
val system = ActorSystem("MySystem")
val greeter = system.actorOf(Props[GreetingActor], name = "greeter")
greeter ! Greeting("Charlie Parker")



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





p.s


관련 읽을거리 


1. Actor  vs  ActiveObject  (http://members.unine.ch/anita.sobe/res/RR-I-AS-2014.06.1.pdf  논문요약)


역사


-  멀티코어 프로그래밍의 시대가 왔고 장점에 대한 소개 
-  멀티쓰레드 프로그래밍의 어려움 소개 ( 데드락, 레이스 컨디션, 일관성깨짐등) 
-  그런 환경에서 좀더 안전하고 빠르게 개발하기위하여 Actor 모델같은 동시성 패턴이 생겨나다.
-  1973년 에 Actor 모델이 소개되었고, 인공지능의 멀티 에이전트 디자인에 의해 발전되어졌다.
-  Actor 모델은 그후  Actove Object 패턴같은 다양한 패턴들에 영향을 주었다.


차이


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

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

   작업부하를 가질수있다.

-  대부분의  AOM 구현들은 정해진 서번트와 함께 쓰레드풀에 의존적이다.  

-  양쪽 모델모두 가능한 이미 존재하는 Actor 나 ActiveObject 를 재사용한다는걸 알아둬라. 

-  보통 Actor 모델은 큐가 제한이 없으며, ActiveObject 는 큐의 크기에 제한이 있다. 


  

2. Vert.x vs Akka


외견적인 모습으로 둘은 매우 비슷하긴한데  비록 내 개인적인 생각일지 몰라도  Vert.x 는 Akka 

보다는 더  메세지큐 미들웨어들과 개념이 비슷한거 같다.  Vert.x 는 평면적, Akka 는 Tree 느낌이~

Vert.x  는 대중적인 여러 언어들사이에서 더 나은 상호운용성을 가질수있는데 ,  이것은 나한테 매우

중요한 선택포인트야.  니가 메세지큐 시스템들과 함께 액터를 같이 사용할 필요가 있을때 그리고 

좀더 복잡성을 다룰필요가있을때 Vert.x 는 그것을 좀더 간단하고 우아하게 다를수있게 한다. 


그래서 답은 흠흠 어느게 더 낫냐?  그건 그때 그때 달라요인데.. 


또한 Vert.x 가 더 완전한 솔루션이라는걸 고려해야해. tcp, http server, routing, websocket 같은거 

지원하니깐 저런건 꽤나 놀랍지 암튼 열라 full stack 이야.  api 도 매우 깔끔하지.  만약 네가 akka 를 

선택한다면 넌 play, xitrum  o spray 같은 framework 가 필요할것야,  개인적으로 난 별로지만 ~

또한 기억해둘건 vert.x 는 꽉 막힌 플랫폼이 아니란거야, 넌 vert.x 를 akka 또는 kafka 를 같이 쓸수 있

어.  예를 들어, 오버헤드없이 시스템의 부분들을  분리하는 방법을 Verticle 을 이용해   꽤나 간단하게 

만들지.  살랴살랴~


단일언어에서 Actor 패턴의 목적으로 사용하려면 Akka ,  잡다한것들 다 하려면 Vert.x.

(http://stackoverflow.com/questions/21481238/difference-in-message-passing-model-of-akka-and-vert-x)




3.  vert.x + playframework = playVertX

이 플러그인은 Play 안에 Vert.x 코어를 통합하는데  Play 에 Vert.x 이벤트버스를 노출시켜.
이건 play app 이 클러스터링 되도록 할수있지.  클러스터위에서 메세지를 전달하며 생산자&구독자
초식을 펼치며 꽤나 놀라운것들을 사용할수있게 해.

무슨의미냐고? 

자 상상해보자. 네 클러스터에  N 개의 서버가 있어. 2명의 사용자가 sockJS 와 함게 연결되었어
서로서로 다른 서버에 말이지. 그런데도 그들은 같은 채널을 공유해 , 채팅룸이나 게임공간같은곳에
말이야. 한사람이 메세지를 채널에 던지면  다른사람은 실시간으로 그것을 받을수있게 된다는~ 

좀 쩔지?? 한번 해볼래?

(https://github.com/fmasion/playVertX)



4
3
  • 댓글 0

  • 로그인을 하시면 댓글을 등록할 수 있습니다.