Play Framework:文件上传——阻塞还是非阻塞?

Play Framework: File uploads - blocking or non-blocking?

给定来自 Play 文档的示例代码:

def upload = Action(parse.temporaryFile) { request =>
  request.body.moveTo(new File("/tmp/picture/uploaded"))
  Ok("File uploaded")
}
  1. 如何处理 100 个并发的慢速上传请求(线程数)?
  2. 上传的文件会缓冲在内存中还是直接流式传输到磁盘?

How 100 simultaneous slow upload requests will be handled (number of threads)?

视情况而定。实际使用的线程数并不真正相关。默认情况下,Play 使用的线程数等于 CPU 可用内核数。但这并不意味着如果你有 4 个核心,你就只能同时处理 4 个并发进程。 Play 中的 HTTP 请求在 Akka 提供的特殊内部 ExecutionContext 中异步处理。 ExecutionContext 中的进程 运行ning 可以共享线程,只要它们是非阻塞的——这被 Akka 抽象掉了。所有这些都可以用不同的方式配置。参见 Understanding Play Thread Pools

消耗客户端数据的 Iteratee 必须进行一些阻塞才能将文件块写入磁盘,但要以足够小(且快)的块进行,这不应导致其他文件上传到被封锁。

我更担心的是您的服务器可以处理的磁盘数量 I/O。 100 个慢速上传 可能 没问题,但如果没有基准测试,你就不能这么说。在某些时候,当客户端输入超过您的服务器可以写入磁盘的速率时,您将 运行 陷入困境。这在分布式环境中也不起作用。我几乎总是选择完全绕过 Play 服务器并直接上传到 Amazon S3。

Will be uploaded file buffered in memory or streamed directly to disk?

所有临时文件都流式传输到磁盘。在底层,所有从客户端发送到服务器的数据都是使用 iteratee 库异步读取的。对于分段上传,也不例外。客户端数据由 Iteratee 使用,它将文件块流式传输到磁盘上的临时文件。所以当使用 parse.temporaryFile BodyParser 时,request.body 只是磁盘上临时文件的句柄,而不是存储在内存中的文件。


值得注意的是,虽然 Play 可以以非阻塞方式处理这些请求,但移动文件一旦完成 阻塞。也就是说,request.body.moveTo(...) 将阻止控制器功能,直到移动完成。这意味着如果 100 个上传中的多个上传几乎同时完成,Play 用于处理请求的内部 ExecutionContext 会很快变得过载。 moveTo 的基础 API 在 Play 2.3 中也被弃用,因为它使用 FileInputStreamFileOutputStreamTemporaryFile 复制到永久位置。文档建议您改用 Java 7 文件 API,因为它效率更高。

这可能有点粗糙,但更像这样的东西应该可以做到:

import java.io.File
import java.nio.file.Files

def upload = Action(parse.temporaryFile) { request =>
    Files.copy(request.body.file.toPath, new File("/tmp/picture/uploaded").toPath)
    Ok("File uploaded")
}