优雅关闭 #

一、优雅关闭概述 #

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