优雅退出与平滑重启:信号处理与资源清理
面试问法:GoFrame 怎么实现优雅退出?进程被 kill 时会不会丢请求? 一句话结论:v2 从三层支持:
s.Shutdown()优雅关闭 HTTP Server;gproc统一注册退出信号回调、按序清理业务资源后再退出;另有平滑重启特性(graceful 配置,*nix 下 SIGUSR1 触发重启、SIGINT/SIGTERM 触发关闭)。不要每个组件各自监听信号,用gproc收口。
基本概念
- Linux 下常见退出信号:SIGINT(Ctrl+C)、SIGTERM(kill 默认)、SIGQUIT。
s.Run()阻塞运行直到服务关闭;任意位置拿单例g.Server()调Shutdown()即可触发关闭。- 平滑重启:官方提供 graceful 配置(或
s.SetGraceful(true),版本支持以官方文档为准),*nix 下支持信号触发重启/关闭,Windows 仅支持完整重启。 - 业务侧资源(定时任务、MQ 消费者、第三方连接)不属于 HTTP Server,需自行注册清理回调。
最短示例
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) { r.Response.Write("hello") })
s.SetPort(8199)
// 退出信号触发时,按注册顺序执行回调,全部完成后进程退出
gproc.AddSigHandlerShutdown(
func(sig os.Signal) {
gcron.Remove("job-a") // 1. 停定时任务/消费者
_ = consumer.Close() // 2. 关第三方长连接
},
func(sig os.Signal) {
_ = s.Shutdown() // 3. 最后关 HTTP Server
},
)
gproc.Listen() // 阻塞监听信号并顺序执行回调
}
资源清理清单
- HTTP Server:
s.Shutdown(),框架等待存量请求处理完再关。 - 定时任务:逐个
gcron.Remove或使用官方提供的优雅停止方法(版本能力以官方文档为准)。 - 链路追踪上报器:执行初始化返回的 shutdown 函数,保证数据落盘。
- 数据库/Redis 连接池由框架托管随进程释放;自建第三方客户端在回调里显式关闭。
注意点
- 回调里不要调
os.Exit:gproc.Listen回调全部完成会自然退出,强杀反而破坏优雅性。 - 平滑重启别用
go run/IDE 运行测试验证,父进程会干扰新老进程状态交接。 - 容器/K8s 场景:平台发 SIGTERM 并给宽限期,清理回调耗时别超过宽限,否则被 SIGKILL 兜底强杀。
常见追问
- 追问:为什么不用标准库 signal.Notify 各组件自己监听?——多个组件各自监听会“进程已退、组件没收尾”,
gproc的顺序回调保证按序析构、全部完成才退出。 - 追问:容器里平滑重启还有意义吗?——滚动发布由编排平台用“先起新实例、再退旧实例”完成,应用侧只需保证收到 SIGTERM 后优雅退出、不丢请求即可。
记忆点
- 标准动作:
gproc.AddSigHandlerShutdown注册清理 →s.Shutdown()→gproc.Listen()收尾。 - 平滑重启/SIGUSR1 的细节与平台差异以官方文档为准。