文档

§协调关闭

Play 集成了 Pekko 的 协调关闭,但仍然没有完全依赖它。虽然协调关闭负责 Play 的完全关闭,但仍然存在 ApplicationLifecycle API,Play 负责退出 JVM。

在生产环境中,触发干净关闭的可能是 SIGTERM,或者如果您的 Play 进程是 Pekko 集群的一部分,则可能是 Downing 事件。

注意:如果您使用的是嵌入式 Play,或者在测试中手动处理 ApplicationServer,那么 Play 内部迁移到协调关闭可能会影响您的关闭过程,因为使用协调关闭会在 ApplicationServer 的依赖生命周期中引入一些小的变化。
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 实例。在 ServerApplication 共享 ActorSystem 的环境中,当其中一个停止时,ServerApplication 都会停止。你可以在 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 仍然会按创建顺序反向运行,并且它们将在 CoordinatedShutdownservice-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 服务器后端,尽可能遵循这些步骤。

  1. 首先,服务器端口被解除绑定,不再接受新的连接(也适用于 Netty 后端)
  2. 如果请求处于“飞行中”(正在由用户代码处理),则会给它一个硬性截止时间来完成。对于 Pekko HTTP 和 Netty 后端,都可以使用play.server.terminationTimeout配置截止时间(有关更多详细信息,请参阅Pekko HTTP 设置Netty 设置中的通用服务器配置)。
  3. 如果连接没有“飞行中”请求,则立即终止连接
  4. 如果用户代码在超时时间内发出响应,则该响应将与 Connection: close 标头一起发送到客户端,并且连接将关闭。
  5. 如果它是流式响应,则也强制要求它必须在截止时间内完成,如果未完成,则无论流式响应的状态如何,连接都将被终止。
    注意:Pekko HTTP 包含 一个错误,导致它在优雅终止期间不考虑响应实体流。作为解决方法,您可以将 play.server.waitBeforeTermination 设置为所需的延迟,以使这些响应有时间完成。有关此配置的更多详细信息,请参阅 Pekko HTTP 设置Netty 设置 中的通用服务器配置。
  6. 如果用户代码在截止时间内没有回复响应,则会自动发送一个响应,其状态由 pekko.http.server.termination-deadline-exceeded-response 配置。该值必须是有效的 HTTP 状态代码

下一步:与 Pekko Typed 和 Cluster Sharding 集成


发现此文档中的错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。