优雅关闭

OxPHP 会处理 SIGTERMSIGINT,以便进程退出前先让正在处理的请求完成。这对于零停机部署以及容器编排中的滚动更新至关重要。

信号处理

OxPHP 响应两种关闭信号:

信号 来源 行为
SIGTERM 容器编排器、docker stopkill 发起优雅关闭
SIGINT 终端 Ctrl+C 发起优雅关闭

两种信号都会触发相同的关闭流程。只需要第一个信号即可,服务器会立即开始排空。

关闭流程

收到关闭信号后,OxPHP 会遵循以下流程:

  1. 停止接受新连接 —— 服务器停止在主端口上接受新的 TCP 连接。PHP 工作进程继续运行,以处理正在进行中的请求。
  2. 收尾活跃连接 —— HTTP/2 客户端会收到一个 GOAWAY 帧,空闲的 HTTP/1.1 keep-alive 连接会被关闭,这样客户端就会转移到健康的实例,而不是把新请求多路复用到即将关闭的实例上。已打开的流会被迅速且干净地结束 —— 任何已经开始刷新分块输出的响应都算在内,既包括有限长度的下载,也包括 SSE —— 参见 Server-Sent Events
  3. 排空正在处理的请求 —— 普通的活跃请求不受打扰,可以带着完整响应正常结束。服务器每 100ms 检查一次是否完成。内部的健康/指标服务器在整个排空期间保持可用,因此就绪探针能够继续工作。
  4. 强制执行排空截止时间 —— 超过 DRAIN_TIMEOUT_SECONDS 后仍在运行的请求会被取消(它们的 register_shutdown_function() 回调仍会执行),并被给予约 2 秒来收尾,之后服务器继续推进。
  5. 刷新插件 —— 排空窗口期间缓冲的访问日志条目和 APM span 会被刷新。
  6. 关闭异步池 —— 后台异步任务池被停止。
  7. 中止内部服务器 —— 排空完成后,健康/指标服务器被停止。
  8. 退出 —— 进程以状态码 0 退出。
Note

PHP 工作进程会在第 1 步显式停止 —— 停止主服务器的过程中会调用执行器的 shutdown(),它会通知工作线程在完成任何正在进行的请求后退出。

配置

变量 默认值 说明
DRAIN_TIMEOUT_SECONDS 25 正在处理的请求被取消前可用于完成的最大秒数;截止时间之后,进程会在约 2 秒内退出。默认值为截止后的收尾和遥测刷新预留了余量,使其能落在 Kubernetes 默认的 30 秒终止宽限期之内

DRAIN_TIMEOUT_SECONDS 设置为能容纳你预期最慢的请求:

  • API 服务器,响应较快:1015
  • 应用程序,涉及文件上传或长查询:3060
  • 工作进程模式,涉及后台处理:与你预期最长的操作相匹配

Kubernetes

在 Kubernetes 中,滚动更新期间的关闭流程如下:

  1. Kubernetes 向 pod 发送 SIGTERM
  2. pod 从 Service 端点列表中被移除。
  3. OxPHP 在 DRAIN_TIMEOUT_SECONDS 之内排空正在处理的连接,然后取消掉落队者并在约 2 秒后退出。
  4. 如果 pod 在 terminationGracePeriodSeconds 之后仍在运行,Kubernetes 会发送 SIGKILL

terminationGracePeriodSeconds 设置为高于 DRAIN_TIMEOUT_SECONDS + 2,这样排空 —— 包括截止后的收尾和遥测刷新 —— 就能在强制终止前完成:

yaml
apiVersion: apps/v1 kind: Deployment spec: template: spec: terminationGracePeriodSeconds: 45 containers: - name: oxphp image: ghcr.io/oxphp/oxphp:0.10.0 env: - name: DRAIN_TIMEOUT_SECONDS value: "30"

预停止钩子

如果你的服务从传播端点变更较慢的外部负载均衡器接收流量,可以添加一个预停止钩子来延迟关闭流程:

yaml
lifecycle: preStop: exec: command: ["sleep", "5"]

这样负载均衡器就有时间在 OxPHP 停止接受连接之前,把该 pod 从其目标列表中移除。

Docker

运行 docker stop 时,Docker 会发送 SIGTERM。Docker 默认的停止超时为 10 秒,超过后 Docker 会发送 SIGKILL

为了给 OxPHP 足够的时间排空,请增大停止超时:

bash
docker stop --time 45 my-oxphp-container

或者在你的 Compose 文件中设置:

compose.yaml
services: oxphp: image: ghcr.io/oxphp/oxphp:0.10.0 stop_grace_period: 45s environment: DRAIN_TIMEOUT_SECONDS: "30"

日志消息

在优雅关闭期间,OxPHP 会输出结构化日志消息,你可以对其进行监控:

排空成功:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"INFO","message":"Draining in-flight connections","active_connections":3} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

达到排空截止时间:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

如果你经常看到 “Drain timeout reached” 警告,请增大 DRAIN_TIMEOUT_SECONDS,或使用 oxphp_request_duration_us 直方图排查长时间运行的请求。

另请参阅

  • 健康检查 —— 就绪探针与关闭的交互
  • 配置参考 —— 包括 DRAIN_TIMEOUT_SECONDS 在内的所有环境变量
  • 指标 —— oxphp_active_connections 追踪排空期间的连接数