Play框架转发大请求/响应:性能慢及内存溢出问题咨询
解决Play框架转发大响应体的性能与OOM问题
你的问题核心在于当前代码对请求和响应体做了不必要的JSON序列化/反序列化:
- 请求端:
request.body.asJson解析请求体为JsValue,post(body)又将其序列化为JSON字符串 - 响应端:
res.json解析响应体为JsValue,Ok(res.json)再次序列化为JSON字节流
5MB的JSON经历两次解析+序列化,既大幅增加耗时(3倍延迟的直接原因),又会占用大量堆内存,最终触发OutOfMemoryError。
优化方案:直接转发原始字节流(无JSON解析)
下面提供两种实现方式,根据你的响应体大小选择:
1. 小/中等响应体:Strict模式(一次性加载到内存)
适合响应体在内存可承受范围内的场景,代码更简洁:
def process = Authorized().async { request => // 直接获取原始请求体,跳过JSON解析 request.body.asRaw.flatMap { rawBody => val wsRequest = ws.url("internal-service/process") // 继承原请求的所有HTTP头(包括Content-Type) .addHttpHeaders(request.headers.toSimpleMap.toSeq: _*) .addHttpHeaders("Accept-Encoding" -> "gzip, deflate") // 直接发送原始字节,避免JSON序列化 wsRequest.post(rawBody.asBytes()).map { res => if (res.status >= 200 && res.status < 300) { // 直接返回响应的原始字节,跳过JSON解析 Ok.sendEntity(HttpEntity.Strict(res.bodyAsBytes, res.header("Content-Type"))) // 继承响应的所有HTTP头(如Content-Encoding) .withHeaders(res.headers.toSimpleMap.toSeq: _*) } else { // 避免加载大错误响应体到内存,仅返回状态码 throw SimpleServiceException(s"Failed to process datasets. Reason ${res.status}") } } }.getOrElse(Future.successful(BadRequest("Invalid request body"))) }
2. 超大响应体:流式转发(Chunked模式)
如果响应体超过内存承载能力,用流式处理彻底避免OOM,性能最优:
def process = Authorized().async { request => request.body.asRaw.map { rawBody => val wsRequest = ws.url("internal-service/process") .addHttpHeaders(request.headers.toSimpleMap.toSeq: _*) .addHttpHeaders("Accept-Encoding" -> "gzip, deflate") // 流式发送请求体,流式接收响应 wsRequest.stream(rawBody.asBytes()).map { response => if (response.status >= 200 && response.status < 300) { // 分块转发响应体,不加载整个响应到内存 Ok.chunked(response.bodyAsSource) .withHeaders(response.headers.toSimpleMap.toSeq: _*) } else { // 忽略错误响应流后抛出异常 response.bodyAsSource.runWith(Sink.ignore).flatMap { _ => Future.failed(SimpleServiceException(s"Failed to process datasets. Reason ${response.status}")) } } } }.getOrElse(Future.successful(BadRequest("Invalid request body"))) }
关键优化点
- 跳过JSON解析/序列化:全程操作原始字节,避免不必要的CPU和内存开销
- 继承HTTP头:确保请求的Content-Type、响应的Content-Encoding等头信息正确传递,避免手动配置出错
- 按需处理错误响应:非成功状态下尽量不加载完整响应体,减少内存占用
内容的提问来源于stack exchange,提问作者perotom
相关产品推荐
相关产品推荐

