完全指南:零停机滚动发布实战)
Pingora 优雅重启与升级Graceful Restart and Shutdown完全指南零停机滚动发布实战【免费下载链接】pingoraA library for building fast, reliable and evolvable network services.项目地址: https://gitcode.com/GitHub_Trending/pi/pingoraPingora 是构建快速、可靠、可演进网络服务的 Rust 框架。本文聚焦 docs/user_guide/graceful.md 所讲的优雅重启、优雅升级与优雅关闭机制说明在发布新版本 Pingora 服务时如何做到请求不中断、连接不拒绝。读完本文你将掌握升级 socket 的配置方法、--upgrade与 SIGQUIT 的完整操作流程、监听 socket 文件描述符在进程间传递的底层原理以及如何与 systemd 集成实现一条命令完成滚动发布。优雅升级承诺的两条硬保证在互联网服务的发布场景中直接杀掉旧进程再拉起新进程会带来两类可见故障连接被拒绝connection refused与进行中的请求被强行中断。Pingora 的优雅升级机制从设计上对这两类故障做出明确保证请求必被新旧实例之一处理任何一个请求要么由旧服务实例完成要么由新服务实例完成绝不会在尝试连接服务端点时遇到 connection refused。宽限期内的请求不被终止任何能够在宽限期grace period内完成的请求保证不会被强制终止。这两条保证不是依赖运气而是由监听 socket 的跨进程移交 分阶段的优雅关闭共同实现的下文会逐步拆解。三步完成一次优雅升级官方文档给出的升级流程非常精炼共三步。Step 0配置升级 socket新旧两个服务进程需要协商同一个升级 socket 路径才能完成监听 socket 的交接。该路径在配置文件中通过upgrade_sock键指定详见 配置文件说明--- version: 1 threads: 2 pid_file: /run/pingora.pid upgrade_sock: /tmp/pingora_upgrade.sock user: nobody group: webusers注意upgrade_sock在 配置结构体ServerConf中默认值为/tmp/pingora_upgrade.sock。新旧实例必须指向同一路径这是整个交接协调的信使。除了upgrade_sockpid_file默认/tmp/pingora.pid同样关键旧进程通过 pid 文件被定位pid 文件还会在 daemon 化时被改名为{path}.old为新 pid 文件腾出位置。Step 1新实例以--upgrade启动启动新实例时加上--upgrade即-u命令行选项。此时新实例不会立即去监听服务端点而是先尝试从旧实例那里接管监听 socket。如果未加-u新实例会直接尝试自己 bind 监听地址——由于旧进程仍占用该地址将导致绑定失败。--upgrade是 Pingora 服务器默认支持的基础 CLI 参数之一Opt结构体完整的默认参数表如下参数作用默认值-d, --daemon将服务 daemon 化后台运行false-t, --test校验服务配置后退出用于升级前预检false-c, --conf配置文件路径空字符串-u, --upgrade本实例应优雅升级一个正在运行的服务false-t/--test值得特别说明官方注释明确指出该标志对于升级服务非常有用因为运维方希望在关闭旧服务进程之前先确认新服务能够正常启动见 configuration/mod.rs。在新版本部署场景下可以先-t校验配置与可启动性再执行真正的-u升级。Step 2向旧实例发送 SIGQUIT向旧实例发送SIGQUIT信号旧实例随即开始向新实例移交监听 socket。一旦移交成功新实例立即开始处理新的入站连接旧实例进入优雅关闭模式它会再等待一个短时间段给新实例充分初始化、做好承接流量的准备此后便不再接受任何新连接。这一短时间段在源码中有明确体现在 server/mod.rs 中定义了两个关键常量/* 退出前等待时间这是所有现存会话完成收尾的优雅期 */ const EXIT_TIMEOUT: u64 60 * 5; /* 关闭监听 socket 前的等待时间这是新服务就绪的优雅期 */ const CLOSE_TIMEOUT: u64 5;CLOSE_TIMEOUT 5秒正是文档所述给新实例时间初始化的默认宽限EXIT_TIMEOUT 300秒则是旧实例等待存量请求全部结束的默认上限。信号与关闭模式三套停机语义优雅升级只是 Pingora 停机体系的一部分。Pingora 服务器在 Unix 平台监听三种信号详见 启动与停止指南 与 信号监听实现信号语义行为SIGINTCtrlC快速关闭FastShutdown立即退出不等待未完成请求可能打断请求SIGTERM优雅关闭GracefulTerminate通知所有服务进入关闭流程等待预配置的宽限期后退出SIGQUIT优雅升级GracefulUpgrade与 SIGTERM 类似但额外把监听 socket 移交给新 Pingora 实例实现零停机升级从源码可以清晰看到三者的差异UnixShutdownSignalWatch::recv()用tokio::select!同时监听三个信号收到 SIGQUIT 后主循环会进入GracefulUpgradeTransferringFds阶段尝试把 fd 发送到升级 socket再进入GracefulUpgradeCloseTimeout阶段sleep(CLOSE_TIMEOUT)server/mod.rs随后才广播优雅关闭。优雅关闭的两个可调参数关闭节奏由两个配置键控制ServerConf 定义grace_period_seconds优雅关闭的总宽限期默认EXIT_TIMEOUT即 300 秒。期间旧实例继续为存量请求服务。graceful_shutdown_timeout_seconds宽限期结束后给各 tokio 运行时退出收尾的超时时间默认 5 秒。升级 socket 与 fd 移交的底层原理移交监听 socket 在 Linux 上的技术本质是通过 Unix domain socket 以SCM_RIGHTS发送文件描述符方式把监听 fd 连同其绑定的地址一起从旧进程发送给新进程。整个机制封装在 pingora-core/src/server/transfer_fd/mod.rs 中。关键实现细节包括Fds容器用HashMapString, RawFd保存绑定地址 → 监听 fd的映射序列化时把地址列表与 fd 列表一起通过sendmsg的控制消息cmsg发送transfer_fd/mod.rs。新进程先建立升级 socket 并 acceptget_fds_from()会先 unlink 旧路径、bind 并listen升级 socket权限设为0o666保证切换低权限用户后仍可连接然后阻塞等待旧进程连入并投递 fdtransfer_fd/mod.rs。旧进程带重试地 connect 与 sendmsgsend_fds_to()对ENOENT新进程还没建 socket、ECONNREFUSED、EACCES等典型对方尚未就绪错误会按 1 秒间隔重试默认最多 5 次MAX_RETRY并有最多 20 次、每次 500ms 的非阻塞轮询transfer_fd/mod.rs。重试次数也可通过配置键upgrade_sock_connect_accept_max_retries调整见 configuration/mod.rs。平台限制transfer_fd中的真实实现仅编译于 Linux非 Linux 平台上的get_fds_from()直接返回ECONNREFUSED并记录 Upgrade is not currently supported outside of Linux platformstransfer_fd/mod.rs。因此在当前仓库版本下优雅升级功能以 Linux 为前提。新实例侧Bootstrap 接管流程新实例启动后由Bootstrap组件完成接管Bootstrap::bootstrap()会广播ExecutionPhase::Bootstrap阶段随后调用load_fds()——若upgrade标志为真则从升级 socket 接收 fd 并写入共享的listen_fds各监听服务从该共享容器中取到已移交的 socket 继续 acceptbootstrap_services.rs。这解释了 Step 1 中新实例不会立即监听的原因它在等待旧实例的 fd 移交拿到之后才真正开始服务。进阶daemon_wait_for_ready与真正零窗口的滚动发布标准的-d -u流程在绝大多数场景下已足够但存在一个理论窗口systemd 等进程管理器在新进程父进程退出后立即向旧进程发 SIGQUIT而此刻新进程可能还没完成后端发现、一致性哈希环等慢初始化导致短暂 502。仓库为此提供了daemon_wait_for_ready机制配置说明daemon_wait_for_ready默认false为true且daemon为true时daemon 化的父进程会等待子进程通过SIGUSR1上报就绪后才退出从而让 systemd 延迟向旧进程发送 SIGQUIT直到新实例完全就绪。daemon_ready_timeout_seconds默认 600父进程等待SIGUSR1的超时超时则以非零码退出systemd 会中止 reload。daemon_notify_timeout_seconds默认 60子进程在因权限错误EPERM发送SIGUSR1失败时的重试窗口用于覆盖 fork 后父进程尚未完成 setuid 的短暂间隙。其实现分别落在 daemon.rs父进程侧等待循环与 bootstrap_services.rs子进程侧通知。值得注意的实现顺序Bootstrap先通知父进程再 load_fds注释明确解释——通知的目的是让进程管理器如 systemd得以继续向旧进程发 SIGQUIT而旧进程收到 SIGQUIT 后才开始投递 fd若先load_fds反而必然超时。仓库中的完整可运行示例 pingora/examples/graceful_upgrade.rs 演示了如何组合这些能力它注册BackendDiscoveryService2 秒与HashRingService3 秒两个慢初始化后台服务再通过server.bootstrap_as_a_service()注册 Bootstrap 服务并用add_dependencies([...])声明 Bootstrap 必须等两者完成才执行从而保证父进程绝不早于新进程真正就绪之前退出。该示例中的 HTTP 应用还支持GET /?sleep20参数方便你在升级过程中观察存量长请求是否被完整服务。与 systemd 集成一次 reload 完成升级Pingora 服务器并不依赖 systemd但可以非常容易地封装为 systemd 服务systemd 集成指南[Service] Typeforking PIDFile/run/pingora.pid ExecStart/bin/pingora -d -c /etc/pingora.conf ExecReloadkill -QUIT $MAINPID ExecReload/bin/pingora -u -d -c /etc/pingora.conf集成后升级流程被收敛为一条命令systemctl reload pingora.servicesystemd 会依次执行两条ExecReload先kill -QUIT让旧实例移交 socket 并优雅关闭同时启动-u -d的新实例接管监听。若结合daemon_wait_for_ready: true父进程会在新实例完全就绪后才退出Typeforking的 systemd 服务在此之后才认定 reload 进入下一步从而把 502 窗口压缩到最小。配置要点速查与优雅升级/关闭直接相关的配置键汇总默认值来自 ServerConf::default配置键作用默认值upgrade_sock新旧实例协商移交 fd 的 Unix socket 路径/tmp/pingora_upgrade.sockpid_filepid 文件路径/tmp/pingora.pidgrace_period_seconds优雅关闭宽限期秒300graceful_shutdown_timeout_seconds宽限期后运行时收尾超时秒5upgrade_sock_connect_accept_max_retries升级 socket 连接/接受的重试次数间隔 1 秒5daemon_wait_for_ready父进程等待子进程 SIGUSR1 就绪再退出falsedaemon_ready_timeout_seconds父进程等待就绪超时秒600daemon_notify_timeout_seconds子进程 SIGUSR1 因 EPERM 失败的重试窗口秒60另需注意配置文件对未知键采取忽略策略见 conf.md因此用户自定义扩展键不会破坏优雅升级相关配置的解析。小结Pingora 的优雅升级把连接不拒绝与宽限期内请求不中断两条保证落实为可操作的工程机制upgrade_sock协商 fd 移交、-u启动新实例、SIGQUIT 触发旧实例移交与优雅关闭。配合daemon_wait_for_ready与 systemdExecReload即可把新版本发布收敛为一条systemctl reload命令实现接近零停机的滚动部署。建议在正式发布前利用仓库自带的 graceful_upgrade 示例 与 transfer_fd 模块内测试涵盖 fd 收发、序列化与重试超时等场景在预发环境完整演练升级流程再推广到生产。【免费下载链接】pingoraA library for building fast, reliable and evolvable network services.项目地址: https://gitcode.com/GitHub_Trending/pi/pingora创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考