Fiber的app.Shutdown()无法真正优雅关机,因其仅调用底层http.Server.Shutdown(),但未内置信号监听、未传播超时context至handler、未管理listener生命周期,导致WebSocket/SSE等长连接被强制中断,必须手动补全信号监听、带超时context及主动关闭长连接三要素。

为什么直接用 Fiber.App.Shutdown() 会丢请求
很多人以为调 app.Shutdown() 就算优雅关机了,其实它只是个空壳——Fiber 没有内置信号监听、不管理 listener 生命周期、也不自动传播 context 到 handler。现象很典型:Ctrl+C 一按,正在上传的大文件中断、WebSocket 连接立刻报 connection reset by peer、客户端收到空响应或 502。根本原因是 app.Shutdown() 只是调用了底层 http.Server.Shutdown(),但没配套信号注册和上下文控制,等于只装了刹车片,没配刹车油和刹车踏板。
必须手动补全的三块拼图:信号 + context + listener
真正不丢请求的关机,得靠这三者协同:
- 用
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)监听信号,别漏SIGTERM(systemd 默认发这个,不是SIGHUP) - 超时 context 必须干净:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second);别 defer cancel(),否则Shutdown()还没开始就超时了 -
app.Server().Shutdown()要传这个 ctx;同时确保所有 handler 里用了带 ctx 的阻塞操作,比如db.QueryContext(ctx, ...)、client.Do(req.WithContext(ctx))
Fiber 中长连接(WebSocket/SSE)怎么不被 Shutdown 中断
http.Server.Shutdown() 对已升级的连接完全无感知。Fiber 本身不接管 WebSocket 生命周期,所以你得自己管:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 在
app.Get("/ws", func(c *fiber.Ctx) error { ... })里,拿到c.Context()后立刻存进全局sync.Map,key 是 conn ID 或 remote addr - 收到关机信号后,在
app.Shutdown()前,遍历 map 主动调conn.Close()或设conn.SetReadDeadline(time.Now()) - SSE handler 里不能只写
c.SendString("data: ..."),每次写前加select { case - 避免在中间件里起异步 goroutine(比如日志上报),除非它也监听同一个
c.Context().Done()
平滑重启时 socket fd 怎么安全传给新进程
Fiber 没封装 fd 复用逻辑,得手动做。Linux 下唯一可靠方式是通过 exec.Cmd.ExtraFiles 传递:
- 老进程收到
SIGUSR2后,从app.Server().Listener提取*net.TCPListener,再调.File()得到*os.File,记下file.Fd()(通常是 3) - 启动子进程时,
cmd.ExtraFiles = []*os.File{os.Stdin, os.Stdout, os.Stderr, file} - 新进程启动后第一行代码:用
os.NewFile(3, "")恢复句柄,再net.FileListener(file)转成net.Listener -
app.Server().Addr必须设为空字符串"",否则app.Listen()会尝试重新 bind,报address already in use
最容易被忽略的是:Kubernetes 场景下别自己 fork 子进程——容器里 PID 1 几乎总是 init 或 sh,SIGUSR2 根本收不到;该用 preStop 钩子发 SIGTERM 给主进程,而不是热重启。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










