Golang使用Gin框架实现REST API开发指南

Golang使用Gin框架实现REST API开发指南 我们有一个运行正常的 Golang Gin REST API,但在运行 24 小时后会停止响应。

5 回复

好的

更多关于Golang使用Gin框架实现REST API开发指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


不知道。

可能是数据库连接池的问题。

为什么它停止工作了?

所以从日志中你发现是 Gin 导致了问题?

问题分析

根据描述,您的 Gin REST API 在运行 24 小时后停止响应,这通常是由于资源泄漏或连接管理问题导致的。以下是常见原因和解决方案:

1. 数据库连接泄漏

// 错误的示例 - 连接未关闭
func getUser(c *gin.Context) {
    db, _ := sql.Open("mysql", "user:pass@/dbname")
    defer db.Close() // 只关闭了连接池,但查询的连接可能未释放
    
    rows, err := db.Query("SELECT * FROM users")
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    // 缺少 rows.Close() 会导致连接泄漏
}
// 正确的示例
func getUser(c *gin.Context) {
    db, err := sql.Open("mysql", "user:pass@/dbname")
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    defer db.Close()
    
    rows, err := db.Query("SELECT * FROM users")
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    defer rows.Close() // 确保关闭结果集
    
    // 处理数据...
}

2. HTTP 连接未正确关闭

// 设置连接超时和保活参数
func main() {
    router := gin.Default()
    
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      router,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }
    
    // 优雅关闭
    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
    
    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)
    }
}

3. 内存泄漏监控

// 添加性能监控中间件
func monitorMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        // 记录请求前的内存状态
        var m runtime.MemStats
        runtime.ReadMemStats(&m)
        beforeAlloc := m.Alloc
        
        c.Next()
        
        // 记录请求后的内存状态
        runtime.ReadMemStats(&m)
        afterAlloc := m.Alloc
        
        log.Printf("Request: %s | Duration: %v | Memory delta: %v bytes",
            c.Request.URL.Path,
            time.Since(start),
            afterAlloc-beforeAlloc)
    }
}

func main() {
    router := gin.Default()
    router.Use(monitorMiddleware())
    
    // 定期输出内存统计
    go func() {
        for {
            time.Sleep(1 * time.Hour)
            var m runtime.MemStats
            runtime.ReadMemStats(&m)
            log.Printf("Memory Stats: Alloc=%v, TotalAlloc=%v, Sys=%v, NumGC=%v",
                m.Alloc, m.TotalAlloc, m.Sys, m.NumGC)
        }
    }()
    
    router.Run(":8080")
}

4. 协程泄漏检查

// 监控协程数量
func monitorGoroutines() {
    go func() {
        for {
            time.Sleep(30 * time.Second)
            count := runtime.NumGoroutine()
            log.Printf("Current goroutines: %d", count)
            
            if count > 1000 { // 设置阈值
                log.Printf("WARNING: High goroutine count: %d", count)
                
                // 输出堆栈信息
                buf := make([]byte, 1<<16)
                stackSize := runtime.Stack(buf, true)
                log.Printf("Stack trace:\n%s", buf[:stackSize])
            }
        }
    }()
}

func main() {
    monitorGoroutines()
    
    router := gin.Default()
    
    // 使用带超时的上下文处理请求
    router.GET("/long-task", func(c *gin.Context) {
        ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
        defer cancel()
        
        done := make(chan bool)
        
        go func() {
            // 模拟长时间任务
            time.Sleep(15 * time.Second)
            done <- true
        }()
        
        select {
        case <-ctx.Done():
            c.JSON(408, gin.H{"error": "request timeout"})
        case <-done:
            c.JSON(200, gin.H{"status": "completed"})
        }
    })
    
    router.Run(":8080")
}

5. 完整的健康检查端点

func healthCheck(c *gin.Context) {
    // 检查数据库连接
    db, err := sql.Open("mysql", "user:pass@/dbname")
    if err != nil {
        c.JSON(503, gin.H{"status": "unhealthy", "error": "database connection failed"})
        return
    }
    defer db.Close()
    
    err = db.Ping()
    if err != nil {
        c.JSON(503, gin.H{"status": "unhealthy", "error": "database ping failed"})
        return
    }
    
    // 检查内存状态
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    
    health := gin.H{
        "status": "healthy",
        "timestamp": time.Now().Unix(),
        "memory": gin.H{
            "alloc":      m.Alloc,
            "total_alloc": m.TotalAlloc,
            "sys":        m.Sys,
            "num_gc":     m.NumGC,
        },
        "goroutines": runtime.NumGoroutine(),
    }
    
    c.JSON(200, health)
}

func main() {
    router := gin.Default()
    router.GET("/health", healthCheck)
    router.Run(":8080")
}

6. 使用 pprof 进行性能分析

import (
    _ "net/http/pprof"
    "net/http"
)

func main() {
    // 启动 pprof 服务器
    go func() {
        http.ListenAndServe("localhost:6060", nil)
    }()
    
    router := gin.Default()
    router.Run(":8080")
}

运行后可以通过以下命令获取性能数据:

# 获取 30 秒的 CPU 性能分析
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 获取内存快照
go tool pprof http://localhost:6060/debug/pprof/heap

# 获取协程信息
go tool pprof http://localhost:6060/debug/pprof/goroutine

建议按以下步骤排查:

  1. 首先添加健康检查端点监控服务状态
  2. 启用 pprof 分析内存和协程泄漏
  3. 检查所有数据库连接是否正确关闭
  4. 确保 HTTP 连接配置了合理的超时时间
  5. 监控日志中是否有异常错误信息

这些代码示例可以直接集成到您的项目中,帮助诊断和解决服务停止响应的问题。

回到顶部