用play-ws阅读许多尸体时内存不足

时间:2019-04-05 19:35:55

标签: scala playframework

独立使用play WS从服务器读取多个主体时,我得到一个OOM:

java.lang.OutOfMemoryError: Java heap space
    at java.lang.StringCoding$StringEncoder.encode(StringCoding.java:300)
    at java.lang.StringCoding.encode(StringCoding.java:344)
    at java.lang.String.getBytes(String.java:918)
    at akka.util.CompactByteString$.apply(ByteString.scala:872)
    at akka.util.ByteString$.apply(ByteString.scala:51)
    at play.api.mvc.Codec$.$anonfun$javaSupported$1(Results.scala:346)
    at play.api.mvc.Codec$$$Lambda$846/1241362979.apply(Unknown Source)
    at play.api.http.DefaultWriteables.$anonfun$wString$1(Writeable.scala:171)
    at play.api.http.DefaultWriteables$$Lambda$849/1109231015.apply(Unknown Source)
    at play.api.http.Writeable.toEntity(Writeable.scala:25)
    at play.api.mvc.Results$Status.apply(Results.scala:429)
    ...

您可以通过以下示例重制它:

val bigString: String = (1 to 1000000).mkString("")

val serverConfig = ServerConfig(port = Some(findFreeTcpRandomPort()))
val server = AkkaHttpServer.fromRouterWithComponents(serverConfig) { components =>
  import Results._
  import components.{defaultActionBuilder => Action}
{
  case GET(p"/big") => Action {
    Ok(bigString)
  }
}
}

val url = s"http://localhost:${server.httpPort.get}/big"
implicit val system: ActorSystem = ActorSystem()
implicit val mat: ActorMaterializer = ActorMaterializer()

val ws = StandaloneAhcWSClient()
try {
  val f = Future.traverse((1 to 1000).toList) { _ =>
    ws.url(url).get().map(_ => ())
  }
  Await.result(f, 1 hour)
} finally {
  ws.close()
  server.stop()
  system.terminate()
}

使用库:

"com.typesafe.play" %% "play-ahc-ws-standalone" % "2.0.3"
"com.typesafe.play" %% "play-akka-http-server" % "2.6.21"

似乎ws客户端正在累积响应而不清除它们。 如果我为每个请求创建并关闭一个新客户端,那么它将起作用。

有什么主意我该如何避免?

1 个答案:

答案 0 :(得分:0)

您正在并行运行太多请求,尤其是当响应的每个主体的长度至少为5888896时。

为证明问题不在ws客户端上,我将请求分为100个块,并且仅在前一个块完成后才开始下一个100个块。

  val url                             = s"http://localhost:${server.httpPort.get}/big"
  implicit val system: ActorSystem    = ActorSystem()
  implicit val mat: ActorMaterializer = ActorMaterializer()

  val ws = StandaloneAhcWSClient()
  try {
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
    run100Requests()
  } finally {
    ws.close()
    server.stop()
    system.terminate()
  }

  def run100Requests(): Unit = {
    val f = Future.traverse((1 to 100).toList) { _ =>
      ws.url(url).get().map(_ => ())
    }
    Await.result(f, 1 hour)
  }

这样做时,我不再收到OOM错误。

因此,我认为您应该对进行中的请求数量引入一些限制。 (显然不要使用Await.result)

最好的方法可能是对输入列表进行分块并为每个块发送请求。