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")
}
- 如何处理 100 个并发的慢速上传请求(线程数)?
- 上传的文件会缓冲在内存中还是直接流式传输到磁盘?
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 中也被弃用,因为它使用 FileInputStream
和 FileOutputStream
将 TemporaryFile
复制到永久位置。文档建议您改用 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")
}
给定来自 Play 文档的示例代码:
def upload = Action(parse.temporaryFile) { request =>
request.body.moveTo(new File("/tmp/picture/uploaded"))
Ok("File uploaded")
}
- 如何处理 100 个并发的慢速上传请求(线程数)?
- 上传的文件会缓冲在内存中还是直接流式传输到磁盘?
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 中也被弃用,因为它使用 FileInputStream
和 FileOutputStream
将 TemporaryFile
复制到永久位置。文档建议您改用 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")
}