관리 메뉴

HAMA 블로그

[Play2] ScalaAsync (번역) 본문

PlayFramework2

[Play2] ScalaAsync (번역)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 10. 14. 17:29



동기 결과에 대한 처리 

원문 https://www.playframework.com/documentation/2.4.x/ScalaAsync 번역 반 , 의역 반
Play2 의 가장 중요한 부분이라 본다. 현재 나도 잘 이해하고 있지 못한 Reactive Web 개발 모델과 밀접하며 

Reactive Web Applications: Covers Play, Akka, and Reactive Streams

와 

Akka in Action

을 요즘 틈틈히 읽고 있는데 짧게 짧게 블로그를 통해 이해한 것을 정리 할 생각이다.

컨트롤러를 비동기 방식으로 다루기

내부적으로 플레이프레임워크는 상향식으로 비동기적이다. 플레이는 매 요청을 비동기적이며 논블럭 방식으로 다룬다. 

기본 설정이 비동기식 컨트롤러로 바뀌었는데, 다른 말로 하면 어플리켕션 코드가 컨트롤러에서 블럭되는것을 피해야 한다는 말이다. 즉 JDBC 콜, 스트리밍 API, HTTP 요청 및 오랜 시간이 걸리는 계산 같은 연산에 관련된 코드들 말이다. 

블럭되는 컨트롤러에 의해 처리를 하기 위해 동시에 처리되는 요청들을 더 많이 수행하기 위해서는 기본 실행되는 쓰레드의 숫자를 마구 늘릴 수 도 있겠지만 컨트롤러를 비동기식으로 접근하는것이 확장성에 있어서 더 나으며  부하에 대한 시스템의 반응에도 더 유리하다. 

(역주: 이런 건 많은 수의 클라이언트 처리에 대해 멀티쓰레딩으로 처리했던 톰캣 보다 node 나 vert.x 가 훨씬 반응성이 좋다는게 입증해준다. 하나의 쓰레드로 좀 더 효율적인 i/o 를 하면 여러개의 쓰레드로는 엄청나나 효율을 가져다 줄 수 있으니..) 

논블록 액션 만들기

만약 우리가 아직 결과를 얻지 못했는데 바로 액션에 대한 결과를 처리하려면 어떻게 해야하나? 해답은 퓨쳐 이다. 리턴 받을 때 까지 기다리는게 아니라 즉시  Future[Result]  를  리턴 받아서 다른 클라이언트를 대응하는 일을 할 수 있게 된다.  Result 에 실제 리턴값이 담기면 그 때 처리 할 것이다.

웹 클라이언트는 응답을 기다리는 동안 블럭 될 것이긴 하지만 그 클라이언트에 대한 처리를 위해  서버 자체가 블럭 되진 않는 다는 것이다. 즉 서버는 다른 클라이언트들을 처리하는데 사용 될 것이며 약속이 이행되면 바로 그 때 그 결과를 돌려 줄 것이다. 

어떻게  Future[Result]를 다룰까

 실제 값을 우리에게 줄 퓨처와 그것을 통해 결과를 얻는 퓨처 : 

import play.api.libs.concurrent.Execution.Implicits.defaultContext

val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[Result] = futurePIValue.map { pi =>
  Ok("PI value computed: " + pi)
}

플레이 비동기 API 콜 모두는 퓨처를 줄 것이다. 이건 니가 외부로 웹서비스를 요청 하거나 (play.api.libs.WS API) Akka 로 비동기 업무를 수행 할 때 도 사용된 다는 뜻이다. 즉 수행이 끝 날때까지 쓰레드가 놀고 있느게 아니라 퓨처를 받은 후에 즉시 퓨처에 실제 값이 들어 올때까지 다른 일을 하게 된다는 야그..

여기 비동기 블럭을 실행하고 결과를 얻는 Future 예제가 있다. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext

val futureInt: Future[Int] = scala.concurrent.Future {
  intensiveComputation()
}

참고 : 퓨처로 실행되는 스레드 코드를 이해하는 것이 중요합니다. 위의 두 코드 블록에서 기본 실행 컨텍스트를 가져 오고 있습니다. 이것은 콜백을 허용하는 퓨처 API의 모든 메소드로 전달되는 암시 적 매개 변수입니다. 실행 컨텍스트는 종종 스레드 풀과 동일하지만 필수적이지는 않습니다.

퓨처상에서 래핑하여 동기식 IO를 마술처럼 비동기로 전환 할 수는 없습니다. 응용 프로그램의 구조를 변경하여 작업을 차단하지 못하는 경우, 어떤 시점에서 작업을 실행해야하며 해당 스레드가 차단됩니다. 따라서 Future에 작업을 포함하는 것 외에도 예상되는 동시성을 처리하기에 충분한 스레드로 구성된 별도의 실행 컨텍스트에서 실행되도록 구성해야합니다. 자세한 내용은 플레이 스레드 풀 이해를 참조하십시오.

액터를 사용하여 작업을 차단하는 것도 도움이 될 수 있습니다. 액터는 타임 아웃 및 오류 처리, 실행 컨텍스트 블로킹 설정 및 서비스와 관련된 모든 상태 관리를위한 명확한 모델을 제공합니다. 또한 액터는 동시 캐시 및 데이터베이스 요청을 처리하고 백엔드 서버 클러스터에서 원격 실행을 허용하는 ScatterGatherFirstCompletedRouter와 같은 패턴을 제공합니다. 그러나 액터는 필요에 따라 과도하게 동작 될 수 도 있습니다.

퓨쳐 되돌려주기

지금까지 액션을 만들기 위해 Action.apply 빌더 메소드를 사용 했지만 비동기 결과를 보내기 위해 Action.async 빌더도 사용된다. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  futureInt.map(i => Ok("Got result: " + i))
}

액션은 기본적으로 비동기적이다 

플레이 액션은 기본적으로 비동기적이다. 예를 들어 아래 컨트롤러 코드 처럼 말이다.  { Ok(...) } 이 부분은 컨트롤러의 메소드 바디가 아니다. 익명 함수이며 Action 객체의 apply 메소드에 전달되며 Action 타입의 객체를 만든다. 내부적으로 익명 함수가 호출 될 것이며 그것의 결과는 퓨처로 감싸질 것이다.  그럼  Action.async 는 왜 만든거지?

val echo = Action { request =>
  Ok("Got request [" + request + "]")
}

Note: Action.apply 과 Action.async 양쪽 모두  Action objects 를 만들며 내부적으로 동일하게 다루어 진다.  .async 빌더는 그냥 퓨처를 돌려주는 API 기반의 액션을 간단히 만들기 위한 장치일 뿐이다. 비동기코드를 쓰는것을 좀 더 쉽게 만들어 준다는 것일 뿐.

타임아웃 다루기

종종 타임아웃을 적절히 다루게 되는데 웹브라우저가 너무 오랫동안 블럭되면 사용자들이 짜증날테니깐...프라미스에 타임아웃을 설정해서 그 때 까지 결과가 도착 안하면 님이 만든 예러 메세지를  그냥 돌려주게 하자. 아래는 InternalServerError 를 돌려준다. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second)
  Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map {
    case i: Int => Ok("Got result: " + i)
    case t: String => InternalServerError(t)
  }
}

Next: Streaming HTTP responses


'PlayFramework2' 카테고리의 다른 글

[Play2] Action 과 Action.async 의 차이점  (0) 2017.02.26
[Play2] Iteratee & Enumerators 간단 정리  (0) 2017.02.18
[Play2] WebSockets  (0) 2016.10.13
[Play2] 외부의 원격액터와 통신  (0) 2016.10.11
[Play2] WS API (번역)  (0) 2016.10.11
Comments