使用 play-ws 读取许多物体时内存不足
Out of memory when reading many bodies with play-ws
当使用 play WS standalone 从服务器读取多个主体时,出现 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(Results.scala:346)
at play.api.mvc.Codec$$$Lambda6/1241362979.apply(Unknown Source)
at play.api.http.DefaultWriteables.$anonfun$wString(Writeable.scala:171)
at play.api.http.DefaultWriteables$$Lambda9/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 客户端正在累积响应而没有清理它们。
如果我为每个请求创建并关闭一个新客户端,那么它就可以工作了。
知道如何避免这种情况吗?
您 运行 并行请求太多,尤其是当您的每个响应正文至少长度为 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)
最好的方法可能是将输入列表分块并为每个块发送请求。
当使用 play WS standalone 从服务器读取多个主体时,出现 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(Results.scala:346)
at play.api.mvc.Codec$$$Lambda6/1241362979.apply(Unknown Source)
at play.api.http.DefaultWriteables.$anonfun$wString(Writeable.scala:171)
at play.api.http.DefaultWriteables$$Lambda9/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 客户端正在累积响应而没有清理它们。 如果我为每个请求创建并关闭一个新客户端,那么它就可以工作了。
知道如何避免这种情况吗?
您 运行 并行请求太多,尤其是当您的每个响应正文至少长度为 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)
最好的方法可能是将输入列表分块并为每个块发送请求。