优雅关闭 #
一、优雅关闭概述 #
1.1 什么是优雅关闭 #
优雅关闭是指服务器在收到关闭信号后,完成以下操作:
text
1. 停止接收新请求
2. 处理完现有请求
3. 释放资源
4. 关闭服务器
1.2 为什么需要优雅关闭 #
| 问题 | 说明 |
|---|---|
| 请求中断 | 强制关闭会中断正在处理的请求 |
| 数据丢失 | 未完成的操作可能丢失数据 |
| 连接泄漏 | 未正确关闭连接 |
| 用户体验 | 影响用户使用体验 |
二、基本优雅关闭 #
2.1 信号处理 #
go
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello")
})
// 启动服务器
go func() {
if err := r.Run(":8080"); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
}
2.2 完整优雅关闭 #
go
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello")
})
// 创建HTTP服务器
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
// 启动服务器
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// 设置超时时间
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 优雅关闭
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
三、关闭流程 #
3.1 关闭步骤 #
text
1. 接收信号 (SIGINT/SIGTERM)
2. 停止接收新请求
3. 等待现有请求完成
4. 关闭数据库连接
5. 关闭其他资源
6. 退出程序
3.2 资源清理 #
go
func main() {
// 初始化数据库
db := initDB()
defer func() {
sqlDB, _ := db.DB()
sqlDB.Close()
}()
// 初始化Redis
redis := initRedis()
defer redis.Close()
r := gin.Default()
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 关闭服务器
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
四、超时控制 #
4.1 设置超时时间 #
go
func main() {
r := gin.Default()
srv := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
go srv.ListenAndServe()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// 设置关闭超时时间
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}
4.2 处理超时 #
go
func main() {
r := gin.Default()
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go srv.ListenAndServe()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
// 超时后强制关闭
log.Println("Timeout, forcing shutdown")
srv.Close()
}
log.Println("Server exited")
}
五、优雅重启 #
5.1 使用endless #
bash
go get github.com/fvbock/endless
go
import "github.com/fvbock/endless"
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello")
})
// 使用endless启动,支持优雅重启
endless.ListenAndServe(":8080", r)
}
5.2 重启信号 #
bash
# 发送HUP信号重启
kill -HUP <pid>
5.3 使用grace #
bash
go get github.com/facebookgo/grace/gracehttp
go
import "github.com/facebookgo/grace/gracehttp"
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello")
})
server := &http.Server{
Addr: ":8080",
Handler: r,
}
gracehttp.Serve(server)
}
六、健康检查 #
6.1 关闭前健康检查 #
go
var isShuttingDown bool
func main() {
r := gin.Default()
// 健康检查
r.GET("/health", func(c *gin.Context) {
if isShuttingDown {
c.JSON(503, gin.H{"status": "shutting down"})
return
}
c.JSON(200, gin.H{"status": "ok"})
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go srv.ListenAndServe()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// 标记正在关闭
isShuttingDown = true
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)
}
6.2 负载均衡配合 #
go
func main() {
r := gin.Default()
// 就绪检查
r.GET("/ready", func(c *gin.Context) {
if isShuttingDown {
c.JSON(503, gin.H{"ready": false})
return
}
c.JSON(200, gin.H{"ready": true})
})
// 存活检查
r.GET("/live", func(c *gin.Context) {
c.JSON(200, gin.H{"alive": true})
})
// ...
}
七、完整示例 #
7.1 生产级优雅关闭 #
go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var (
db *gorm.DB
isShuttingDown bool
)
func main() {
// 初始化
initDB()
defer closeDB()
// 创建路由
r := gin.Default()
// 健康检查
r.GET("/health", healthCheck)
r.GET("/ready", readyCheck)
// 业务路由
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello")
})
// 创建服务器
srv := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 启动服务器
go func() {
log.Printf("Server starting on %s", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to start server: %v", err)
}
}()
// 等待信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// 开始关闭
log.Println("Shutting down server...")
isShuttingDown = true
// 等待一段时间让负载均衡器感知
time.Sleep(5 * time.Second)
// 设置超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 关闭服务器
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
func healthCheck(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
}
func readyCheck(c *gin.Context) {
if isShuttingDown {
c.JSON(503, gin.H{"ready": false})
return
}
// 检查数据库连接
if err := checkDB(); err != nil {
c.JSON(503, gin.H{"ready": false, "error": err.Error()})
return
}
c.JSON(200, gin.H{"ready": true})
}
func checkDB() error {
sqlDB, err := db.DB()
if err != nil {
return err
}
return sqlDB.Ping()
}
func initDB() {
// 初始化数据库
}
func closeDB() {
sqlDB, _ := db.DB()
sqlDB.Close()
}
八、总结 #
8.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 信号处理 | 监听SIGINT和SIGTERM |
| 超时设置 | 设置合理的关闭超时 |
| 资源清理 | 关闭数据库等资源 |
| 健康检查 | 配合负载均衡 |
8.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 超时时间 | 设置合理的超时时间 |
| 健康检查 | 提供健康检查接口 |
| 日志记录 | 记录关闭过程 |
| 测试验证 | 测试优雅关闭 |
8.3 下一步 #
现在你已经掌握了优雅关闭,接下来让我们学习 性能优化,了解Gin的性能调优!
最后更新:2026-03-28