복붙노트

[SCALA] Akka HTTP : 서버 미래의 블록 차단

SCALA

Akka HTTP : 서버 미래의 블록 차단

나는 기본 인증하고 내 요청에 Akka HTTP를 사용하는 것을 시도하고있다. 내가이 자원에 휴식 전화를 걸 그래서 내가 통해 인증하기 위해 외부 자원을 가지고 발생합니다.

이것은 시간이 좀 걸립니다, 그리고 그것을 처리가있는 동안,이 전화를 기다리고, 내 API의 나머지가 차단 된 것 같다. 난 아주 간단한 예제와 함께이 재현 :

// used dispatcher:
implicit val system = ActorSystem()
implicit val executor = system.dispatcher
implicit val materializer = ActorMaterializer()


val routes = 
  (post & entity(as[String])) { e =>
    complete {
      Future{
        Thread.sleep(5000)
        e
      }
    }
  } ~
  (get & path(Segment)) { r =>
    complete {
      "get"
    }
  }

내가 로그 엔드 포인트에 게시 할 경우, 내 GET 엔드 ​​포인트는 로그 엔드 포인트가 지시 5 초에 붙어 기다리고 있습니다.

이 예상되는 동작이며, 경우, 어떻게 내 전체 API를 차단하지 않고 작업을 차단해야합니까?

해결법

  1. ==============================

    1.당신은 관찰 무엇 예상되는 동작 - 아직 물론 매우 나쁘다. 알려진 솔루션과 모범 사례가 방지하기 위해 존재하는 것이 좋은. 이 대답에 나는 짧은 문제를 설명하는 시간을 보내고 싶습니다 오래하고 깊이 - 읽기를 즐겨보세요!

    당신은 관찰 무엇 예상되는 동작 - 아직 물론 매우 나쁘다. 알려진 솔루션과 모범 사례가 방지하기 위해 존재하는 것이 좋은. 이 대답에 나는 짧은 문제를 설명하는 시간을 보내고 싶습니다 오래하고 깊이 - 읽기를 즐겨보세요!

    짧은 대답은 : 항상! "라우팅 인프라를 차단하지 않는다"작업을 차단하기위한 전용 디스패처를 사용!

    관찰 된 증상의 원인 : 문제는 당신이 차단 미래가 실행 발송자로 context.dispatcher를 사용하고 있다는 점이다. (간단히 말해서 단지 "스레드의 무리"에) 같은 디스패처가 실제로 들어오는 요청을 처리하기 위해 라우팅 인프라에서 사용하는 - 당신이 사용할 수있는 모든 스레드를 차단 그렇다면, 당신은 라우팅 인프라를 굶주리는 끝. (A 일까지 토론과 벤치마킹이 경우 Akka HTTP이로부터 보호 할 수있다, 나는 나의 연구 할 일 목록에 추가 할 것입니다).

    주의 깊은 관리 요구를 차단하십시오 Akka의 문서 섹션에서 설명한 바와 같이, (우리는 다른 사람 위에 별도의 실행에 간단한 있도록 만들 이유입니다) 같은 발송자의 다른 사용자에게 영향을주지에 특별한주의로 치료해야 블로킹.

    당신의 긴 실행 작업은 정말 하나의 작업이 아니라 일련의 그, 당신은 다른 배우, 또는 시퀀스 선물 위에 사람들을 분리 한 수 있다면 - 뭔가 다른 내가 여기에서 모든 가능한 경우에 API를 막지 않도록해야한다는 것입니다주의를 불어 넣고 싶었습니다. 어쨌든, 단지 지적하고 싶었 - 이러한 차단 호출을 피하기 가능하다면, 아직 당신이있는 경우 - 그 다음은 제대로 그 처리하는 방법에 대해 설명합니다.

    심층 분석 및 솔루션 :

    이제 우리는 잘못이 무엇인지 알고, 개념적,하자 정확히 위의 코드에서 깨진 것을보고 있고, 방법 등이 문제의 외모에 대한 권리 솔루션 :

    색상 스레드 상태를 =

    이제 코드와 방법에 미치는 영향은 디스패처 및 응용 프로그램의 성능을 3 개를 조사 할 수 있습니다. 앱이 다음로드 넣어왔다이 동작을 강제로 :

    1) [나쁜] 나쁜 코드에 디스패처 행동 :

    // BAD! (due to the blocking in Future):
    implicit val defaultDispatcher = system.dispatcher
    
    val routes: Route = post { 
      complete {
        Future { // uses defaultDispatcher
          Thread.sleep(5000)                    // will block on the default dispatcher,
          System.currentTimeMillis().toString   // starving the routing infra
        }
      }
    }
    

    우리는 [A] 부하에 우리의 응용 프로그램을 폭로하고, 이미 akka.actor.default - 디스패처 스레드의 수를 볼 수 있도록 - 그들은 요청 처리하고 - 작은 녹색 조각을, 오렌지는 다른 사람을 의미 실제로 유휴있다.

    그리고 우리는 이러한 스레드의 차단의 원인이되는 [B] 부하를 시작 - 당신은 초기 스레드 "기본-디스패처-2,3,4는"전에 유휴 상태 인 후 차단으로가는 볼 수 있습니다. 우리는 또한 풀의 성장 관찰 - 새로운 스레드 "기본-디스패처-18,19,20,21는 ..."그러나 그들이 바로 수면에 들어갈 시작된다 (!) - 우리는 여기에 귀중한 자원을 낭비하고!

    같은 시작 스레드의 수는 기본 디스패처 구성에 따라 다르지만 가능성이 50 정도를 초과하지 않습니다. 우리가 2K 차단 작전을 발사하기 때문에, 우리는 전체 스레드를 굶어 - 차단 작업 지배 등의 라우팅 인프라가 다른 요청을 처리하는 데 사용할 수 스레드가 없다고 - 아주 나쁜!

    의는 (BTW Akka 가장 좋습니다 - 아래 그림처럼 항상 분리 차단 행동이) 그것에 대해 뭔가를하자 :

    2) 좋은!] 디스패처 행동 좋은 구조 코드 / 운영자 :

    당신의 application.conf에서 동작을 차단 전용이 디스패처를 구성 :

    my-blocking-dispatcher {
      type = Dispatcher
      executor = "thread-pool-executor"
      thread-pool-executor {
        // in Akka previous to 2.4.2:
        core-pool-size-min = 16
        core-pool-size-max = 16
        max-pool-size-min = 16
        max-pool-size-max = 16
        // or in Akka 2.4.2+
        fixed-pool-size = 16
      }
      throughput = 100
    }
    

    당신은 여기에 다양한 옵션을 이해하기 Akka 디스패처 문서에서 자세한 내용을 읽어야합니다. 주요 포인트는하지만 우리가 차단 작전에 대한이 사용할 수 유지 스레드의 하드 제한이있는 ThreadPoolExecutor입니다 고른 것입니다. 크기 설정은 앱이 무엇을하는지에 따라, 얼마나 많은 코어 서버가 있습니다.

    우리가 대신 기본 하나, 그것을 사용할 필요가 다음 :

    // GOOD (due to the blocking in Future):
    implicit val blockingDispatcher = system.dispatchers.lookup("my-blocking-dispatcher")
    
    val routes: Route = post { 
      complete {
        Future { // uses the good "blocking dispatcher" that we configured, 
                 // instead of the default dispatcher – the blocking is isolated.
          Thread.sleep(5000)
          System.currentTimeMillis().toString
        }
      }
    }
    

    우리는 먼저 같은 부하를 사용하여 응용 프로그램, 일반 요청에 약간의 압력을 한 후 우리는 차단을 추가 할. 이것은 스레드 풀이 경우에 동작하는 방법입니다 :

    (그래서 대부분 유휴 상태입니다, 정말 부하가 서버를두고 있지 않다) 실제 실행이다 - 그래서 처음에는 정상적인 요청이 쉽게 기본 디스패처에 의해 처리됩니다, 당신은 거기에 몇 녹색 선을 볼 수 있습니다.

    이제 우리는에, 내 차단-dispatcher- *의 차기 차단 작전을 발행 시작하고 구성 스레드의 수를 시작할 때. 그것은 거기에있는 모든 수면을 처리합니다. 또한, 그 스레드에서 일어나는 아무것도 일정 기간 후에는 그들을 종료됩니다. 우리가 잠 () 처리됩니다 새로운 스레드를 시작할 것 풀을 차단하는 또 다른 무리와 함께 서버를 공격한다면 -을 보내고, 그러나 그 사이에 - 우리가 그냥 거기있어 "에 우리의 소중한 스레드을 낭비하지 않는 및 아무것도하지 마세요".

    이 설정을 사용하는 경우, 일반 GET 요청의 처리량들은 여전히 ​​행복하게 (여전히 꽤 무료) 기본 디스패처에 봉사하고, 영향을받지했다.

    이 반응 애플리케이션에서 차단의 모든 종류를 다루는 권장되는 방법입니다. 그것은 종종 차단 /이 경우에는 나쁜 행동을 자고, 응용 프로그램의 나쁜 행동하는 부분을 "bulkheading"(또는 "분리")라고합니다.

    3) [해결 틱] 적절히 적용 디스패처 블로킹 동작 :

    이 예에서 우리는 차단 작전에 직면했을 때 도움을 줄 수 방법을 scala.concurrent.blocking에 대한 scaladoc를 사용합니다. 그것은 일반적으로 더 많은 스레드가 차단 작업을 생존을 위해 최대 회전되도록한다.

    // OK, default dispatcher but we'll use `blocking`
    implicit val dispatcher = system.dispatcher
    
    val routes: Route = post { 
      complete {
        Future { // uses the default dispatcher (it's a Fork-Join Pool)
          blocking { // will cause much more threads to be spun-up, avoiding starvation somewhat, 
                     // but at the cost of exploding the number of threads (which eventually
                     // may also lead to starvation problems, but on a different layer)
            Thread.sleep(5000)
            System.currentTimeMillis().toString
           }
        }
      }
    }
    

    응용 프로그램은 다음과 같이 작동합니다 :

    에서 힌트를 차단 "오,이 차단됩니다, 그래서 우리는 더 많은 스레드가 필요"때문에 새로운 스레드가 많이 생성되는 것을 알 수 있습니다,이입니다. 이것은 그러나 우리가 차단 작전이 끝난 후 ... 물론, 그들은 결국 종료됩니다 아무것도하지 않고 수백 개의 스레드가, 우리는 1) 예보다 작게 차단하고있는 총 시간이 발생합니다 (FJP이 작업을 수행합니다 ), 그러나 우리는 우리가 우리가 차단 동작을 위해 전용으로 정확히 얼마나 많은 스레드 알고있는 2) 솔루션과 달리, 실행 스레드의 큰 (제어되지 않는) 양을해야합니다 동안합니다.

    합산 : 기본 디스패처를 차단하지 마십시오 :-)

    가장 좋은 방법은 가능한 차단 작업을위한 디스패처를 가지고, 2의 패턴)를 사용하고, 거기에 그들을 실행하는 것입니다.

    행복 해킹, 희망이 도움이!

    화제의 Akka의 HTTP 버전 : 2.0.1

    프로파일 러를 사용 : 많은 사람들이 그래서 여기에이 정보를 추가, 나는 위의 사진에서 스레드 상태를 시각화하는 데 사용 프로파일 러 어떤 개인이 답변에 대한 응답으로 저를 요구했다 : 나는하지만, 멋진 상업 프로파일 (OSS에 대한 무료)입니다 YourKit를 사용 당신은 오픈 JDK에서 무료 VisualVM과를 사용하여 동일한 결과를 얻을 수 있습니다.

  2. ==============================

    2.이상한,하지만 나를 위해 모든 것을 잘 (NO 차단) 작동합니다. 여기에 코드입니다 :

    이상한,하지만 나를 위해 모든 것을 잘 (NO 차단) 작동합니다. 여기에 코드입니다 :

    import akka.actor.ActorSystem
    import akka.http.scaladsl.Http
    import akka.http.scaladsl.server.Directives._
    import akka.http.scaladsl.server.Route
    import akka.stream.ActorMaterializer
    
    import scala.concurrent.Future
    
    
    object Main {
    
      implicit val system = ActorSystem()
      implicit val executor = system.dispatcher
      implicit val materializer = ActorMaterializer()
    
      val routes: Route = (post & entity(as[String])) { e =>
        complete {
          Future {
            Thread.sleep(5000)
            e
          }
        }
      } ~
        (get & path(Segment)) { r =>
          complete {
            "get"
          }
        }
    
      def main(args: Array[String]) {
    
        Http().bindAndHandle(routes, "0.0.0.0", 9000).onFailure {
          case e =>
            system.shutdown()
        }
      }
    }
    

    또한 당신은 onComplete를 또는으로 onSuccess 지침에 코드를 비동기을 포장 할 수 있습니다 :

    onComplete(Future{Thread.sleep(5000)}){e} 
    
    onSuccess(Future{Thread.sleep(5000)}){complete(e)}
    
  3. from https://stackoverflow.com/questions/34641861/akka-http-blocking-in-a-future-blocks-the-server by cc-by-sa and MIT license