Play框架:文件上传-阻塞或非阻塞?

7

以下是来自Play文档的示例代码:

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

10
“同时有100个上传请求的慢速处理(线程数)会如何处理?”
这要看情况。实际使用的线程数量并不重要。默认情况下,Play使用的线程数等于可用CPU核心数。但这并不意味着如果你有4个核心,就只能同时运行4个进程。在Play中,HTTP请求是异步处理的,在Akka提供的特殊内部ExecutionContext中处理。在一个ExecutionContext中运行的进程可以共享线程,只要它们是非阻塞的——这由Akka抽象出来。所有这些都可以以不同的方式进行配置。请参见了解Play线程池
消耗客户端数据的迭代器必须进行一些阻塞才能将文件块写入磁盘,但只要以足够小(且快速)的块完成操作,这就不会导致其他文件上传被阻塞。
我更担心的是你的服务器可以处理的磁盘I/O量。100个缓慢的上传也许还好,但没有基准测试很难说。当客户端输入超过服务器写入磁盘的速率时,你会遇到麻烦。这在分布式环境中也不起作用。我几乎总是选择完全绕过 Play 服务器,将上传直接指向 Amazon S3。
文件是否会缓存在内存中或直接流式传输到磁盘上?
所有临时文件都会直接流式传输到磁盘上。在幕后,从客户端发送到服务器的所有数据都是使用 iteratee 库异步读取的。对于多部分上传,情况也不会有所不同。客户端数据由 Iteratee 消耗,将文件块流式传输到磁盘上的临时文件。因此,在使用 parse.temporaryFile BodyParser 时,request.body 只是对磁盘上临时文件的句柄,而不是存储在内存中的文件。
值得注意的是,虽然Play可以以非阻塞的方式处理这些请求,但一旦文件移动完成,将会阻塞。也就是说,request.body.moveTo(...)会阻塞控制器函数直到移动完成。这意味着,如果大约同时完成了100个上传中的几个,Play用于处理请求的内部ExecutionContext可能会很快超载。在Play 2.3中,moveTo的底层API也已被弃用,因为它使用FileInputStreamFileOutputStreamTemporaryFile复制到永久位置。文档建议您改用Java 7 File 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")
}

1
@user882209 play-s3 库非常好。它提供了用于创建签名上传表单的客户端帮助程序,并提供了一个异步的服务器端 API,以处理在 S3 上上传的文件。 - Michael Zajac
这里关键并不是akka,而是它使用了netty和NIO。特别是如果moveto正在使用零拷贝,我相信它会这样做。 - Adam Gent
@AdamGent 看起来好像不行(https://github.com/playframework/playframework/blob/877ad58f2daaeb9e6e7ba0c82d4ccb11c9dcc3ba/framework/src/play/src/main/scala/play/api/libs/Files.scala#L92-106)。上传完成后,您可能需要自己使用nio而不是`moveTo`。 - Michael Zajac
@m-z 啊,这真是不幸(我没有去看,因为我不使用Play)。但是我的观点仍然存在,即它不是Akka而是Netty在抽象它,因为根据你的观点:“这只是在上传之后”。要实现非阻塞,你需要Netty(NIO),而不是Akka……你几乎可以使用没有Akka作为调度程序(即Vert.x)的完全相同的代码。 - Adam Gent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接