在 Play 中,每个请求都会产生一个 Akka actor 吗?
In Play, does every request spawn an Akka actor?
我读到 Play 是基于 Akka 构建的,所以我想知道是否对于每个传入请求,都会产生一个 actor 来提供服务。
以控制器动作为例:
def upload = Action(parse.multipartFormData) { implicit request =>
request.body.file("picture").map { picture =>
val client = new AmazonS3Client
client.putObject("my-bucket", picture.filename, picture.ref.file)
}.getOrElse {
BadRequest("File missing")
}
}
上传是同步进行的,我经常看到示例试图在 Future 中包装这样的代码块。我认为如果这个请求是由 Akka actor 服务的,那么就不需要这样做了。
请让我知道我是对还是错,以及您对使用阻塞服务的建议。
我认为 Play 不会为每个请求生成一个新的 actor,而是使用一个 actor 池来处理它们。如果可以为数百万个请求生成数百万个 actor,而所有这些都是为了处理一个请求,那么利用 actor 的消息队列来处理请求的目的就有点背离了。在某些时候应该有一个上限。
不过,Play 是否真的这样做并不重要。默认情况下,所有 Action
都是异步处理的。您的代码与使用 Action.async
包装在 Future
中的代码之间的唯一区别在于,它将使用一种便捷的方法来处理 Future
。最后,两者都是像 Request => Future[Result]
.
这样的函数
I think that if this request is being served by an Akka actor, doing so is not needed.
这不是真的,部分原因是上述原因。 Play 使用可配置的线程池(演员使用)来处理请求。默认情况下,它的大小为每个核心一个线程。演员共享线程以完成工作。这意味着如果你有 4 个核心/4 个线程和 100 个参与者(随机数),如果你有 4 个阻塞上传,你将有 4 个阻塞线程。不管有多少演员,你仍然会遇到线程饥饿,而其他 96 个都没用。更糟糕的是,这意味着您的服务器将无法再处理任何请求,直到其中一个上传完成并且其中一个线程不再被阻止。
如果将代码包装在 Future
中, 和 使用单独的 ExecutionContext
来阻止,则可以缓解这种情况。包装在 Future
中还不够,因为代码仍在阻塞。您将不得不在某处进行阻塞,但不要在 Play 用于处理请求的默认 ExecutionContext
中执行此操作。相反,您可以配置一个专门用于处理上传。
application.conf
# Configures a pool with 10 threads per core, with a maximum of 40 total
upload-context {
fork-join-executor {
parallelism-factor = 10.0
parallelism-max = 40
}
}
用法:
implicit val uploadContext: ExecutionContext = Akka.system.dispatchers.lookup("upload-context")
def upload = Action.async(parse.multipartFormData) { implicit request =>
request.body.file("picture").map { picture =>
val client = new AmazonS3Client
Future(client.putObject("my-bucket", picture.filename, picture.ref.file))(uploadContext)
}.getOrElse {
Future.successful(BadRequest("File missing"))
}
}
您可以阅读有关线程池的更多信息here。
我读到 Play 是基于 Akka 构建的,所以我想知道是否对于每个传入请求,都会产生一个 actor 来提供服务。
以控制器动作为例:
def upload = Action(parse.multipartFormData) { implicit request =>
request.body.file("picture").map { picture =>
val client = new AmazonS3Client
client.putObject("my-bucket", picture.filename, picture.ref.file)
}.getOrElse {
BadRequest("File missing")
}
}
上传是同步进行的,我经常看到示例试图在 Future 中包装这样的代码块。我认为如果这个请求是由 Akka actor 服务的,那么就不需要这样做了。
请让我知道我是对还是错,以及您对使用阻塞服务的建议。
我认为 Play 不会为每个请求生成一个新的 actor,而是使用一个 actor 池来处理它们。如果可以为数百万个请求生成数百万个 actor,而所有这些都是为了处理一个请求,那么利用 actor 的消息队列来处理请求的目的就有点背离了。在某些时候应该有一个上限。
不过,Play 是否真的这样做并不重要。默认情况下,所有 Action
都是异步处理的。您的代码与使用 Action.async
包装在 Future
中的代码之间的唯一区别在于,它将使用一种便捷的方法来处理 Future
。最后,两者都是像 Request => Future[Result]
.
I think that if this request is being served by an Akka actor, doing so is not needed.
这不是真的,部分原因是上述原因。 Play 使用可配置的线程池(演员使用)来处理请求。默认情况下,它的大小为每个核心一个线程。演员共享线程以完成工作。这意味着如果你有 4 个核心/4 个线程和 100 个参与者(随机数),如果你有 4 个阻塞上传,你将有 4 个阻塞线程。不管有多少演员,你仍然会遇到线程饥饿,而其他 96 个都没用。更糟糕的是,这意味着您的服务器将无法再处理任何请求,直到其中一个上传完成并且其中一个线程不再被阻止。
如果将代码包装在 Future
中, 和 使用单独的 ExecutionContext
来阻止,则可以缓解这种情况。包装在 Future
中还不够,因为代码仍在阻塞。您将不得不在某处进行阻塞,但不要在 Play 用于处理请求的默认 ExecutionContext
中执行此操作。相反,您可以配置一个专门用于处理上传。
application.conf
# Configures a pool with 10 threads per core, with a maximum of 40 total
upload-context {
fork-join-executor {
parallelism-factor = 10.0
parallelism-max = 40
}
}
用法:
implicit val uploadContext: ExecutionContext = Akka.system.dispatchers.lookup("upload-context")
def upload = Action.async(parse.multipartFormData) { implicit request =>
request.body.file("picture").map { picture =>
val client = new AmazonS3Client
Future(client.putObject("my-bucket", picture.filename, picture.ref.file))(uploadContext)
}.getOrElse {
Future.successful(BadRequest("File missing"))
}
}
您可以阅读有关线程池的更多信息here。