관리 메뉴

HAMA 블로그

[Play2] Akka 의 결합 - (번역) 본문

PlayFramework2

[Play2] Akka 의 결합 - (번역)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 10. 10. 22:12

Akka 와 함께 사용하기


아래 내용을 먼저 읽어서 Play 와 Akka 에 대한 관계를 먼저 파악을..

  @ In Play 2.0, Play delegated all requests to go through an actor. It heavily depended on Akka's future API and other parts.

  @ In Play 2.1, with the move of Akka's future API into Scala 2.10, Play started depending less directly on Akka. It gets all it's execution contexts from Akka, and provides integration with Akka, but that's about the extent of it.

  @ In Play 2.3, we're adding new features to aid Akka integration, particularly around WebSockets.

  @ In Play 2.4, Play will be ported to the new akka-http (formerly known as spray), at which point, Play will be as built on Akka as you can get.



Akka 는  확장성 높고 병행성있는 어플리케이션을 만들기 위해 더 나은 플랫폼을 제공하고 추상층을 높힌 액터모델을 사용하고 있다. 실패나 예외에 대해 대응하기위해서 ‘고장나게 냅둬라’ 모델을 채택하고있는데 , 절대 멈추지 않고 스스로 치유될 수 있는 어플리케이션을 만듬으로써  텔레콤 산업에서 큰 성공을 거두었다. 액터는 또한 위치투명성을 제공하여 손쉬운 분산프로그래밍, 클러스터링에 적합하다.

§액터시스템 어플리케이션

아카는 액터 시스템이라는 컨테이너에서 사용된다. 액터 시스템은 그것이 포함하고 있는 리소스들을 관리한다. 플레이 어플리케이션은 특별한 액터 시스템을 정의하여 사용한다. 이 액터 시스템은 어플리케이션 라이프 사이클을 관리하고 어플리케이션이 재시작되었을때 자동적으로 다시 시작하게 해준다.


액터 작성하기

일단 액터하나를 간단히 만들어 보자.  "Hello" 에 이름을 붙여서 다시 돌려주는 액터이다. 

import akka.actor._

object HelloActor {
def props = Props[HelloActor]

case class SayHello(name: String)
}

class HelloActor extends Actor {
import HelloActor._

def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}

이 액터는 다음과 같은 아카 관례를 따른다:

  • - 메세지를 보내고 받을 수 있으며  , 그 프로토콜 (SayHello ) 은 동반 객체에 정의되있다. 
  • - Props 메소드를 동반 객체에 가지고 있다. 액터를 Props 팩토리 메소드를 이용하여 생성한다.


액터 생성하고 사용하기 

ActorSystem 이  액터를 생성하고 사용하기 위해 필요하다. 다음과 같이 만든다.  

import play.api.mvc._
import akka.actor._
import javax.inject._

import actors.HelloActor

@Singleton
class Application @Inject() (system: ActorSystem) extends Controller {

val helloActor = system.actorOf(HelloActor.props, "hello-actor")

//...
}

actorOf 는 새로운 액터를 생성하기 위해 사용된다.  싱글턴으로 선언한것을 눈여겨 보시라. 우린 액터와 그 레퍼런스들을 저장할 것 이기 때문에 필요한 부분이다.  만약 그렇지 않다면 컨트롤러가 만들어질때마다 새로운 액터가 만들어질 거란 의미이기 때문이다.  동일한 시스템에 동일한 액터를 가질 수 없다. 


액터에게 요청하기 

가장 기본적인 일은  액터에게 메세지를 전송하는 일이다. 액터에게 메세지를 보내면 반응이 없을 것인데 기본적으로  "보내고 잊기"  방식이기 때문이다. 보통 tell 패턴으로 알려져있다.

웹어플리케이션에서 tell 패턴 말고 다른 전력이 필요할때가 많이 있다. HTTP 는 요청하고 반응하는 프로토콜인데 이 경우에 ask 패턴을 사용할 가능성이 클 것이다. ask 패턴은 Future 를 리턴하는데 아래 예를 살펴보자. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds

def sayHello(name: String) = Action.async {
    (helloActor ? SayHello(name)).mapTo[String].map { message =>
     Ok(message)
    }
}

설명 해 보면 :

  • 먼저 ask 패턴은 임포트 되야하고, 이것은  ? 연산자를 액터에서 제공한다. (tell 은 ! 이다) 
    즉 helloActor 에게 SayHello(name) 메세지를 보내는것이다. 보낸 후에 바로 Future[Any] 를 받는다. 그 후에 mapTo 메소드로  기대하는 타입으로 매핑을 하고 , 최종적으로 돌려 받은 메세지를 Ok(message) 로 돌려준다. 
    타임아웃도 스코프내에 필요한데 타임아웃내에 반응이 오지 않으면 타임아웃 에러를 내보내면 무시한다. 

 액터 의존성 주입

원한다면 Guice  를 가지고 액터와 액터 설정을 바인드 할 수도 있다.
플레이 설정을 활용하는 액터를 원한다면 이렇게 하면 된다. 

import akka.actor._
import javax.inject._
import play.api.Configuration

object ConfiguredActor {
case object GetConfig
}

class ConfiguredActor @Inject() (configuration: Configuration) extends Actor {
import ConfiguredActor._

val config = configuration.getString("my.config").getOrElse("none")

def receive = {
case GetConfig =>
sender() ! config
}
}


플레이는 액터바인딩을 제공하는데 도움을 주는 몇몇 헬퍼를 제공한다. 이것들은 액터 스스로이 DI 되게 하며, 다른 컴포넌트들 안으로 주입되는 액터에 대한 액터 참조를 허용한다. 이런 헬퍼들을 사용하는 액터를 바인드 하기위해 다음 문서에서 나타내는 모듈을 만든다.  dependency injection documentation, 그리고 AkkaGuiceSupport 트레잇과 믹스하고  bindActor 메소드를 액터와 바인드 하기위해 사용한다:

import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport

import actors.ConfiguredActor

class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ConfiguredActor]("configured-actor")
}
}


이 액터는 configured-actor 로 이름 붙혀지고 주입을 위한 자격이 부여될 것이다.
이제 다른 컴포넌트들과 당신의 컨트롤러 안에서 액터를 활용 할 수 있다. 

import play.api.mvc._
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import javax.inject._
import actors.ConfiguredActor._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

@Singleton
class Application @Inject() (@Named("configured-actor") configuredActor: ActorRef)
(implicit ec: ExecutionContext) extends Controller {

implicit val timeout: Timeout = 5.seconds

def getConfig = Action.async {
(configuredActor ? GetConfig).mapTo[String].map { message =>
Ok(message)
}
}
}

자식 액터에게 의존성 주입하기 

루트 액터에게 주입하는 방법은 위에 보았지만 많은 액터들은 자식 액터들일 것이다. 그들은 플레이 앱의 라이프 싸이클에 묶여있지 않기 때문에 그들에게 상태를 전달해줘야한다.

그러기 위해서 플레이는  Guice’s AssistedInject 를 지원한다.

주입될때 설정과 함께 작동하는 키가 추가 될 수 있다.  

import akka.actor._
import javax.inject._
import com.google.inject.assistedinject.Assisted
import play.api.Configuration

object ConfiguredChildActor {
case object GetConfig

trait Factory {
def apply(key: String): Actor
}
}

class ConfiguredChildActor @Inject() (configuration: Configuration,
@Assisted key: String) extends Actor {
import ConfiguredChildActor._

val config = configuration.getString(key).getOrElse("none")

def receive = {
case GetConfig =>
sender() ! config
}
}

key 파라미터가 @Assisted 으로 선언된것을 확인하자. 이것은 수동으로 제공되었다는것을 말한다. 


우리는 Factory 트레잇을 정의했다. 이것은 key 를 가지고 액터를 리턴한다. 우린 이것을 구현하지 않을 것인데 Guice 가 우리를 위해 구현 할 것이다. 키 파라미터를 전달하는것 뿐 만 아니라 설정 디펜던시를 위치시키는것 그리고 저것을 주입하는일 등의 구현을 제공한다. 트레잇은 그저 액터를 리턴한다.  이 액터를 테스팅 할때 어떤 액터라도 리턴 할 수 있는 팩토리를 주입 할 수 있을 것이다.예를들어 우리에게 가짜 자식 액터를 주입할 수 있게 할 것이다. 

이제 액터는 InjectedActorSupport  를 확장하며 우리가 만든 팩토리를 사용할 것이다.

import akka.actor._
import javax.inject._
import play.api.libs.concurrent.InjectedActorSupport

object ParentActor {
case class GetChild(key: String)
}

class ParentActor @Inject() (
childFactory: ConfiguredChildActor.Factory
) extends Actor with InjectedActorSupport {
import ParentActor._

def receive = {
case GetChild(key: String) =>
val child: ActorRef = injectedChild(childFactory(key), key)
sender() ! child
}
}

 injectedChild 를 이용하여 자식 액터에 대한 참조를 얻는다. 

마지막으로 우리의 액터를 바인드 할 필요로 해졌다. 우리의 모듈상에서   bindActorFactory 메소드를 사용하여  부모 액터를 바인드 한다.. 그리고 또한 자식 팩토리를 자식 구현을 위해 바인드 한다.

import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport

import actors._

class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ParentActor]("parent-actor")
bindActorFactory[ConfiguredChildActor, ConfiguredChildActor.Factory]
}
}

ConfiguredChildActor.Factory인스턴스를 자동으로 바인드하기위해 Guice 를 얻는다.그것은  ConfiguredChildActor를 위한 설정 인스턴스를 제공 할 것이다.

설정하기 

기본 액터 시스템 설정은 플레이 어플리케이션 설정 파일에서 읽혀진다. 예를들어 어플리케이션 액터 시스템의 디폴트 디스패처 를 설정한다고 치면 conf/application.conf에 이걸 추가한다

akka.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
akka.actor.debug.receive = on

아카 로깅 설정은 이것을 참고 하라 ->  configuring logging.

설정 prefix 바꾸기

또 다른 아카 액터 시스템을 위해 akka.* 세팅을 이용하길 원하면 다음 처럼

play.akka.config = "my-akka"

이제 앞으로  akka 대신해서  my-akka 에서 읽혀질 것이다. 

my-akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64
my-akka.actor.debug.receive = on

빌트인 액터 시스템 이름 

플레이 액터 시스템의 기본 이름은 application. 이며 변경 가능하다.

play.akka.actor-system = "custom-name"

Note: 아카 클러스터 내에서 플레이 아카를 사용 하는데 매우 유용하다. 

비동기 태스크 스케쥴링하기.

액터로 task 를 실행하거나 메세지를 전송하는 것을 스케쥴링 할 수 있다.  (펑션 or Runnable). Cancellable 돌려 받고 , 스케쥴 연산을 실행한것을 취소하기위해 cancel 를 사용한다.

예를들어 testActor 매  300 microseconds 동안 메세지를 보낸다.

import scala.concurrent.duration._

val cancellable = system.scheduler.schedule(
0.microseconds, 300.microseconds, testActor, "tick")

Note: This example uses implicit conversions defined in scala.concurrent.durationto convert numbers to Duration objects with various time units.

유사하게 지금 부터 10밀리세컨드초에 다음 코드를 실행 시킨다.  

import play.api.libs.concurrent.Execution.Implicits.defaultContext
system.scheduler.scheduleOnce(10.milliseconds) {
file.delete()
}

자신만의 액터 시스템 사용하기 

빌트인 액터 시스템을 사용하길 추천하지만 , 바르게 설정된 클래스로더, 라이프싸이클 훅 등 같은 것을 셋 업하는것으로 당신의 액터 시스템을 사용 할 수 도 있다. 다음을 꼭 참조하세요. 

  • stop hook 을 등록하세요. 플레이가 종료되면 액터 시스템도 종료 되도록 말이죠.
  • * 정확한 클래스로더를 Play Environment 로 부터 전달하세요. 그렇지 않으면 아카는 당신의 어플리케이션 클래스들을 찾을 수 없을테니까요. 
  • * play.akka.config 를 사용하여  플레이가 아카 설정을 읽는 위치를 변경 시키든지, 시스템이 동일한 리모트 포트를 바인드하려 할때 문제를 일으킬 수 있으므로 디폴트 아카 config 로부터 아카 설정을 읽지 않는다.  



'PlayFramework2' 카테고리의 다른 글

[Play2] WS API (번역)  (0) 2016.10.11
[Play2] 마이크로서비스 (microservices)  (0) 2016.10.11
[Play2] Remote Akka 연결하기 (번역)  (0) 2016.10.10
[Play2] DI (의존성 주입)  (0) 2016.10.10
[Play2] Cookie 와 Session  (0) 2016.09.29
Comments