当前位置: 首页 > news >正文

Gin 框架进阶系列(九):优雅关闭

Gin 框架进阶系列(九):优雅关闭(Graceful Shutdown)

为什么需要优雅关闭

在生产环境中,部署新版本、扩缩容、服务器维护,都需要重启服务。如果直接 kill 进程,正在处理中的请求会被突然中断——用户看到 502 错误,更糟的是数据库写到一半的事务变成脏数据。

优雅关闭实现的是:收到关闭信号后,停止接受新请求,等待正在处理的请求全部完成,然后再退出。

参考:https://gin-gonic.com/zh-cn/docs/server-config/graceful-restart-or-stop/

完整实现

// cmd/server/main.go
package mainimport ("context""errors""log""net/http""os""os/signal""syscall""time""your-project/internal/database""your-project/internal/router"
)func main() {// 初始化数据库db, err := database.Init(os.Getenv("DB_DSN"))if err != nil {log.Fatalf("failed to connect database: %v", err)}// 初始化路由r := router.Setup(db)// 不要用 r.Run(),自己创建 http.Server 来获得更多控制权srv := &http.Server{Addr:         ":8080",Handler:      r,ReadTimeout:  10 * time.Second,WriteTimeout: 30 * time.Second,IdleTimeout:  60 * time.Second,}// 在 goroutine 中启动服务器go func() {log.Printf("server starting on %s", srv.Addr)if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {log.Fatalf("server failed: %v", err)}}()// 主 goroutine 等待关闭信号quit := make(chan os.Signal, 1)// SIGINT: Ctrl+C// SIGTERM: docker stop, k8s pod terminationsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)sig := <-quitlog.Printf("received signal: %v, shutting down gracefully...", sig)// 给正在处理的请求最多 30 秒时间完成ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()// Shutdown 做了三件事:// 1. 停止接受新连接// 2. 等待已有连接上的请求处理完毕// 3. 超时后强制关闭if err := srv.Shutdown(ctx); err != nil {log.Printf("server forced shutdown: %v", err)}// 关闭数据库连接池sqlDB, err := db.DB()if err == nil {sqlDB.Close()}log.Println("server exited")
}

逐行解析

为什么不用 r.Run() gin.Engine.Run() 内部也是创建 http.Server 然后调 ListenAndServe(),但它没有返回 *http.Server 实例,你拿不到它,也就没法调 Shutdown()。自己创建 http.Server 就拥有了完整的控制权。

三个 Timeout 的含义。 ReadTimeout 是从连接被接受到请求 body 完全读取完毕的最大时间,防止慢速客户端拖住连接。WriteTimeout 是从请求 body 读取完毕到响应写完的最大时间,防止 handler 卡死。IdleTimeout 是 keep-alive 连接在两次请求之间的最大空闲时间。

客户端建立连接 → [ReadTimeout] → 请求读完 → handler 处理 → [WriteTimeout] → 响应发完↓[IdleTimeout] → 等待下一个请求或关闭

signal.Notify 为什么用带缓冲的 channel? quit := make(chan os.Signal, 1) 的缓冲大小为 1。这是因为 signal.Notify 不会阻塞等你接收——如果 channel 满了,信号就丢了。缓冲为 1 保证在你还没执行到 <-quit 时收到的信号不会丢失。

Shutdown 的 30 秒超时。 正常情况下 Shutdown 会等所有请求处理完后返回。但如果有请求卡住了(比如一个下载大文件的请求),不能无限等下去。30 秒超时到了就强制关闭,这时那些还没完成的请求会收到一个 context canceled 的错误。

关闭顺序很重要。 先关 HTTP 服务器(停止接受新请求、等待旧请求完成),再关数据库连接。反过来的话,正在处理中的请求试图访问数据库就会报错。

与 Docker/Kubernetes 的配合

Docker 执行 docker stop 时会先发 SIGTERM,等 10 秒(默认)后发 SIGKILL。Kubernetes 的 Pod 终止流程也是先 SIGTERM 再 SIGKILL,默认宽限期(terminationGracePeriodSeconds)是 30 秒。

你的 Shutdown 超时应该比容器的宽限期短一些,留出清理的余量:

# Kubernetes Deployment 配置
spec:terminationGracePeriodSeconds: 60  # 给 60 秒宽限containers:- name: app# ...lifecycle:preStop:exec:command: ["sh", "-c", "sleep 5"]# preStop hook 等 5 秒,让服务先从负载均衡摘除
// 应用的 Shutdown 超时设为 45 秒
// 60(宽限期)- 5(preStop)- 10(余量)= 45
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)

时间线是这样的:Kubernetes 发 SIGTERM → preStop 等 5 秒(期间 Service 把 Pod 从 endpoints 摘除,新请求不再路由过来)→ 应用开始 Shutdown,最多 45 秒处理完剩余请求 → 应用退出。如果 50 秒后应用还没退出,第 60 秒 Kubernetes 会发 SIGKILL 强制杀掉。

健康检查端点

优雅关闭还需要配合健康检查。当应用开始 Shutdown 后,健康检查应该返回"不健康",让负载均衡器知道不要再发新请求过来:

// 用一个原子变量标记服务器状态
var isShuttingDown atomic.Boolfunc Health(c *gin.Context) {if isShuttingDown.Load() {c.JSON(http.StatusServiceUnavailable, gin.H{"status": "shutting_down",})return}c.JSON(http.StatusOK, gin.H{"status": "ok",})
}// 在 main 函数中,收到信号后立即标记
sig := <-quit
isShuttingDown.Store(true)
// 然后再 Shutdown...

http://www.jsqmd.com/news/593216/

相关文章:

  • eSearch全能屏幕工具:5分钟快速上手终极指南
  • 如何在5分钟内为Unity游戏添加实时翻译:XUnity.AutoTranslator完全指南
  • 即插即用模块-特征增强篇:FEM模块在遥感小目标检测中的实战解析
  • AT_arc190_c [ARC190C] Basic Grid Problem with Updates
  • 2026最权威的六大降重复率网站实测分析
  • [技术突破]解决D3D8兼容性困境:d3d8to9的API转换革命
  • 医生如何‘看片’?用DiffMIC双引导扩散网络,我复现了AI诊断的注意力机制
  • 计算机毕业设计:Python二手车全维度数据可视化与智能估价系统 Django框架 可视化 线性回归 数据分析 机器学习 深度学习 AI 大模型(建议收藏)✅
  • 3分钟快速上手:哔哩下载姬Downkyi终极使用教程
  • 从零开始:在Ubuntu 18.04上正确配置CUDA 11.7和bitsandbytes 0.38.0的完整指南
  • 2025届必备的AI写作网站实际效果
  • STM32入门——修改主频(21)
  • Gin 框架进阶系列(一):第一个路由
  • 6GB显存也能玩转AI绘画?FLUX.1-dev FP8模型让你的创意不再受限
  • 终极Windows内存优化指南:用Mem Reduct释放被浪费的RAM资源
  • CopyMultiPath Windows 右键路径复制工具支持批量复制多行文件完整路径,无乱码无残留,提升文件操作效率
  • Gin 框架进阶系列(五):Gin + GORM 连接数据库实现 CRUD
  • Gin 框架进阶系列(二):路由详解
  • 阶跃星辰 GUI-MCP 解读---(1)---论文
  • 2026年支持Turnitin的降AI工具对比:留学生场景推荐 - 还在做实验的师兄
  • 基于Logisim与Verilog HDL的运动码表计时电路设计与DE2-70开发板验证
  • 告别繁琐下载:File Browser极简方案实现20+格式文件在线预览
  • t3mujinpack胶片模拟技术解析:基于Hald CLUT算法的开源胶片仿真实现
  • 如何系统化解决戴森球计划黑雾威胁:从自动化防御到资源管理的蓝图方案
  • Gin 框架进阶系列(六):Gin 认证与授权——JWT 鉴权实战
  • 2026年教育学论文降AI工具推荐:教学设计和调研分析部分如何降 - 还在做实验的师兄
  • 计算机毕业设计:Python二手车全栈数据洞察与价格预测系统 Django框架 可视化 线性回归 数据分析 机器学习 深度学习 AI 大模型(建议收藏)✅
  • dfs序
  • 3分钟学会OpenSpeedy游戏加速:免费开源打破帧率限制的终极指南
  • 如何用SillyTavern在5分钟内创建你的第一个AI虚拟伙伴?