Fiber的app.Listen()无法优雅退出,因其是阻塞调用且不暴露*http.Server实例,导致收到SIGINT/SIGTERM时进程立即终止,defer不执行、DB/Redis连接未关闭、请求中断;必须手动构造http.Server,监听信号后调用带超时context的Shutdown(),并同步关闭所有非HTTP资源及goroutine。

为什么 app.Listen() 无法优雅退出
Fiber 的 app.Listen() 是阻塞调用,内部直接封装了 http.ListenAndServe(),不暴露底层 *http.Server 实例。一旦收到 SIGINT 或 SIGTERM,Go 运行时默认立即终止进程,defer 不执行、数据库连接不关闭、正在处理的请求被中断——日志戛然而止就是典型表现。
必须手动构造 http.Server 并接管信号
核心是绕过 app.Listen(),自己创建 *http.Server,启动监听 goroutine,并监听系统信号触发 Shutdown():
- 用
fiber.New()创建 app 后,调用app.Handler()获取 handler,传给http.Server{Handler: ...} - 在单独 goroutine 中调用
srv.ListenAndServe(),避免阻塞主流程 - 用
signal.Notify()监听os.Interrupt和syscall.SIGTERM - 收到信号后,调用
srv.Shutdown(),且必须传入带超时的context.Context(如context.WithTimeout(context.Background(), 25*time.Second)) -
srv.Shutdown()返回后,再依次关闭 DB、Redis、gRPC 等资源;注意db.Close()不等事务,要用db.QueryContext(ctx, ...)配合 shutdown context
常见错误:超时 context 用错或漏关非 HTTP 资源
错误写法:srv.Shutdown(context.Background()) → 可能永久 hang 住,因为 handler 卡在无 deadline 的 DB 查询里;正确做法是显式设超时,且超时值必须小于 K8s 的 terminationGracePeriodSeconds(比如后者是 30s,这里设 25s)。
另一个高频遗漏点:HTTP 层之外的资源全得手动关,顺序还不能错——比如先关 Redis 连接池,再关 DB,但若 DB 事务依赖 Redis 缓存状态,就可能出错。更关键的是,http.Server.Shutdown() 完全不感知这些,漏一个,main 就卡死。
别忘了清理活跃 goroutine 和长连接
如果业务里用了 go func() { ... }() 启动后台任务,或挂了 SSE 流、WebSocket 连接,它们不会被 Shutdown() 自动终止。必须:
- 用
sync.WaitGroup记录活跃任务,在 shutdown 前调用wg.Wait() - 对 SSE 流,检查
stream.Flush()是否返回 error(断连检测),并在Handler中监听c.Context().Done() - 对 WebSocket,确保
conn.Close()在 shutdown 流程中被调用,且不新开 goroutine
真正决定“是否优雅”的,从来不是有没有调 Shutdown(),而是你是否把每个可能阻塞、持有资源、异步运行的路径都收口到了 shutdown context 或同步等待机制里。











