§协调关闭
Play 集成了 Pekko 的 协调关闭,但仍然没有完全依赖它。虽然协调关闭负责 Play 的完全关闭,但仍然存在 ApplicationLifecycle
API,Play 负责退出 JVM。
在生产环境中,触发干净关闭的可能是 SIGTERM
,或者如果您的 Play 进程是 Pekko 集群的一部分,则可能是 Downing
事件。
注意:如果您使用的是嵌入式 Play,或者在测试中手动处理
Application
和Server
,那么 Play 内部迁移到协调关闭可能会影响您的关闭过程,因为使用协调关闭会在Application
和Server
的依赖生命周期中引入一些小的变化。
1. 调用Server#stop
必须停止Server
,并且必须停止在该Server
上运行的Application
。
2. 调用Application#stop
必须停止Application
,并且可以停止运行应用程序的Server
。
§它与 ApplicationLifecycle
相比如何?
协调关闭提供了多个阶段,您可以在其中注册任务,而 ApplicationLifecycle
仅提供一种注册停止钩子的方法。传统的 ApplicationLifecycle
停止钩子是一系列操作,Play 将决定它们运行的顺序。协调关闭使用一个有向无环图 (DAG) 中组织的阶段集合,其中单个阶段中的所有任务并行运行。这意味着,如果您将清理代码从 ApplicationLifecycle
迁移到协调关闭,那么您现在还必须指定代码应该在哪个阶段运行。如果您希望对看似无关的应用程序部分的关闭顺序进行细粒度控制,则可以使用协调关闭而不是 ApplicationLifecycle
。例如,如果您想通过打开的 WebSockets 信号应用程序正在关闭,则可以在名为 before-service-unbind
的阶段注册一个任务,并让该任务将信号消息推送到 WebSocket 客户端。before-service-unbind
必须在 service-unbind
之前发生,service-unbind
是服务器连接被解绑的阶段。但是,解绑连接仍然允许正在进行的请求完成。
Play 的 DI 公开了 CoordinatedShutdown
的一个实例。如果你想从 ApplicationLifecycle
迁移到 Coordinated Shutdown,无论你在哪里请求注入 ApplicationLifecycle
的实例,现在都可以请求 CoordinatedShutdown
的实例。
CoordinatedShutdown
实例绑定到 ActorSystem
实例。在 Server
和 Application
共享 ActorSystem
的环境中,当其中一个停止时,Server
和 Application
都会停止。你可以在 Play 手册的 Coordinated Shutdown 新部分 中找到更多详细信息,或者查看 Pekko 的 Coordinated Shutdown 参考文档。
§关闭顺序
Coordinated Shutdown 发布了一组默认阶段,这些阶段组织成有向无环图 (DAG)。你可以创建新的阶段并覆盖默认值,以便现有阶段依赖于你的阶段。以下是 Pekko 中最相关的阶段列表,Play 默认使用这些阶段
before-service-unbind
service-unbind
service-requests-done
service-stop
// few cluster-related phases only meant for internal use
before-actor-system-terminate
actor-system-terminate
上面的列表按默认运行顺序提到了相关的阶段。请遵循 Pekko 文档 来更改此行为。
请注意,你没有迁移到 Coordinated Shutdown 任务的 ApplicationLifecycle#stopHooks
仍然会按创建顺序反向运行,并且它们将在 CoordinatedShutdown
的 service-stop
阶段内运行。也就是说,Coordinated Shutdown 将所有 ApplicationLifecycle#stopHooks
视为单个任务。Coordinated Shutdown 使你能够灵活地在不同的阶段运行关闭任务。你当前使用 ApplicationLifecycle#stopHooks
的代码应该没问题,但请考虑审查它如何以及何时被调用。例如,如果你有一个定期执行某些数据库操作的 actor,那么该 actor 需要一个数据库连接。根据这两个的创建方式,你的数据库连接池可能在 ApplicationLifecycle#stopHook
中关闭,该连接池发生在 service-stop
阶段,但你的 actor 可能现在在 actor-system-terminate
阶段关闭,该阶段发生在稍后。
如果您在service-stop
阶段运行清理代码与您的使用方式一致,请继续使用ApplicationLifecycle#stopHooks
。
要选择使用协调关闭任务,您需要注入一个CoordinatedShutdown
实例,并使用addTask
,如下例所示
- Scala
-
class ResourceAllocatingScalaClass @Inject() (cs: CoordinatedShutdown) { // Some resource allocation happens here: A connection // pool is created, some client library is started, ... val resources = Resources.allocate() // Register a shutdown task as soon as possible. cs.addTask(CoordinatedShutdown.PhaseServiceUnbind, "free-some-resource") { () => resources.release() } // ... some more code }
- Java
-
public class ResourceAllocatingJavaClass { private final Resources resources; @Inject public ResourceAllocatingJavaClass(CoordinatedShutdown cs) { // Some resource allocation happens here: A connection // pool is created, some client library is started, ... resources = Resources.allocate(); // Register a shutdown task as soon as possible. cs.addTask( CoordinatedShutdown.PhaseServiceUnbind(), "free-some-resource", () -> resources.release()); } // ... some more code }
§关闭触发器
Play 进程通常通过SIGTERM
信号终止。当 Play 进程收到信号时,会运行一个 JVM 关闭钩子,导致服务器通过调用协调关闭来停止。
其他可能的触发器与SIGTERM
略有不同。虽然SIGTERM
以从外到内的方式处理,但您可以从代码中触发关闭(或库可能会检测到触发关闭的原因)。例如,当您将 Play 进程作为 Pekko 集群的一部分运行,或在您的 API 上添加一个端点,允许管理员或编排器触发程序关闭时。在这些情况下,关闭是从内到外的:协调关闭列表的所有阶段都按适当的顺序运行,但 Actor 系统将在 JVM 关闭钩子运行之前终止。
在开发 Play 应用程序时,您应该考虑所有终止触发器以及它们将按什么步骤和顺序运行。
§限制
Pekko 协调关闭附带一些设置,使其非常可配置。尽管如此,在 Play 生命周期中使用 Pekko 协调关闭会使其中一些设置无效。其中一个设置是pekko.coordinated-shutdown.exit-jvm
。在 Play 项目中启用pekko.coordinated-shutdown.exit-jvm
会导致关闭时出现死锁,阻止您的进程完成。一般来说,调整 Pekko 协调关闭的默认值在所有生产、开发和测试模式下都应该没问题。
§优雅地关闭服务器
服务器后端关闭会优雅地进行,并遵循Pekko HTTP 文档中描述的步骤。以下摘要适用于 Pekko HTTP 服务器后端,但 Play 设置了 Netty 服务器后端,尽可能遵循这些步骤。
- 首先,服务器端口被解除绑定,不再接受新的连接(也适用于 Netty 后端)
- 如果请求处于“飞行中”(正在由用户代码处理),则会给它一个硬性截止时间来完成。对于 Pekko HTTP 和 Netty 后端,都可以使用
play.server.terminationTimeout
配置截止时间(有关更多详细信息,请参阅Pekko HTTP 设置或Netty 设置中的通用服务器配置)。 - 如果连接没有“飞行中”请求,则立即终止连接
- 如果用户代码在超时时间内发出响应,则该响应将与
Connection: close
标头一起发送到客户端,并且连接将关闭。 - 如果它是流式响应,则也强制要求它必须在截止时间内完成,如果未完成,则无论流式响应的状态如何,连接都将被终止。
注意:Pekko HTTP 包含 一个错误,导致它在优雅终止期间不考虑响应实体流。作为解决方法,您可以将play.server.waitBeforeTermination
设置为所需的延迟,以使这些响应有时间完成。有关此配置的更多详细信息,请参阅 Pekko HTTP 设置 或 Netty 设置 中的通用服务器配置。 - 如果用户代码在截止时间内没有回复响应,则会自动发送一个响应,其状态由
pekko.http.server.termination-deadline-exceeded-response
配置。该值必须是有效的 HTTP 状态代码。
发现此文档中的错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。