You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 01:42:26