下载工作台
Gin Web 开发

连接池与性能优化

试读上半部分 · 解锁后可读全文

第 12 章 · 连接池与性能优化

本章目标:调优 GORM/MySQL 连接池;配置 Redis 连接池;设置 HTTP Server 超时;使用 pprof 定位 CPU/内存热点;识别并修复 N+1 查询;建立 api-go-demo 性能清单;对照 go-dev ch10 限流与 spring-boot-web ch17 HikariCP/N+1。

学时建议:4~5 小时(含 2 小时压测)

前置:完成 gin-web ch11;ch09 Redis 缓存已启用。


12.1 场景说明:高 QPS 读商品

api-go-demo 定位高 QPS 商品读服务。单实例瓶颈常在:

层级风险本章手段
MySQL连接耗尽、慢查询、N+1连接池 + 索引 + Preload
Redis连接数、大 valuePoolSize、按 slug 粒度
HTTPgoroutine 堆积Read/Write 超时
JSON反射开销复用 DTO
日志IO 阻塞prod 级别 Warn+

优化顺序:度量(hey + pprof)→ 连接池 → 缓存(ch09)→ SQL/N+1 → 微优化

hey ──► /api/v1/products/go-handbook
              │
              ├── pprof CPU profile
              ├── GORM logger 慢查询
              └── redis hit ratio

12.2 MySQL 连接池(完整)

GORM 底层 database/sql 连接池:

// internal/database/mysql.go
package database

import (
    "time"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type PoolConfig struct {
    MaxOpen     int
    MaxIdle     int
    MaxLifetime time.Duration
    MaxIdleTime time.Duration
}

func DefaultPoolConfig() PoolConfig {
    return PoolConfig{
        MaxOpen:     50,
        MaxIdle:     10,
        MaxLifetime: time.Hour,
        MaxIdleTime: 10 * time.Minute,
    }
}

func OpenMySQL(dsn string, env string, pool PoolConfig) (*gorm.DB, error) {
    logLevel := logger.Info
    if env == "prod" {
        logLevel = logger.Warn
    }
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logLevel),
        PrepareStmt: true, // 预编译,高 QPS 下减少解析
    })
    if err != nil {
        return nil, err
    }
    return db, ConfigurePool(db, pool)
}

func ConfigurePool(db *gorm.DB, pool PoolConfig) error {
    sqlDB, err := db.DB()
    if err != nil {
        return err
    }
    if pool.MaxOpen > 0 {
        sqlDB.SetMaxOpenConns(pool.MaxOpen)
    }
    if pool.MaxIdle > 0 {
        sqlDB.SetMaxIdleConns(pool.MaxIdle)
    }
    if pool.MaxLifetime > 0 {
        sqlDB.SetConnMaxLifetime(pool.MaxLifetime)
    }
    if pool.MaxIdleTime > 0 {
        sqlDB.SetConnMaxIdleTime(pool.MaxIdleTime)
    }
    return nil
}
参数含义起点公式
MaxOpenConns最大打开连接CPU 核数 × 2~4,按压测调
MaxIdleConns空闲连接MaxOpen 的 20%~50%
ConnMaxLifetime连接最大存活< MySQL wait_timeout
ConnMaxIdleTime空闲回收5~15 分钟

监控:

SHOW STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';

对照 spring-boot-web ch17 HikariCPmaximum-pool-size 等价 MaxOpenConnsmax-lifetime 等价 ConnMaxLifetime


12.3 Redis 客户端池

// internal/cache/redis.go(扩展)
rdb := redis.NewClient(&redis.Options{
    Addr:         cfg.RedisAddr,
    PoolSize:     20,
    MinIdleConns: 5,
    PoolTimeout:  4 * time.Second,
    DialTimeout:  3 * time.Second,
    ReadTimeout:  2 * time.Second,
    WriteTimeout: 2 * time.Second,
})
信号调整
redis: connection pool timeout增大 PoolSize 或查慢命令
空闲过多减小 MinIdleConns

12.4 HTTP Server 超时

Gin 默认 Run() 无精细超时;生产改用 http.Server

// cmd/server/main.go
func runHTTPServer(cfg *config.Config, handler http.Handler) error {
    srv := &http.Server{
        Addr:              ":" + cfg.Port,
        Handler:           handler,
        ReadHeaderTimeout: 5 * time.Second,
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      30 * time.Second,
        IdleTimeout:       60 * time.Second,
        MaxHeaderBytes:    1 << 20,
    }
    return srv.ListenAndServe()
}
超时防什么
ReadTimeout慢客户端占满 goroutine
WriteTimeout大响应/client 不读
IdleTimeoutkeep-alive 泄漏

Listen 地址用 :8080 而非 127.0.0.1:8080,否则 Docker 健康检查失败(ch15)。


12.5 pprof 剖析

注册 pprof(仅 dev/staging)

import _ "net/http/pprof"

func startPprof(addr string) {
    go func() {
        log.Println("pprof listening on", addr)
        if err := http.ListenAndServe(addr, nil); err != nil {
            log.Println("pprof:", err)
        }
    }()
}

// main: if cfg.Env != "prod" { startPprof("127.0.0.1:6060") }

CPU profile

go tool pprof -http=:8081 http://127.0.0.1:6060/debug/pprof/profile?seconds=30

压测同时采样:

hey -n 10000 -c 50 -m GET http://127.0.0.1:8080/api/v1/products/go-handbook

其他 endpoint

URL用途
/debug/pprof/heap内存分配
/debug/pprof/goroutinegoroutine 栈
/debug/pprof/block阻塞
/debug/pprof/mutex锁竞争

安全:pprof 禁止暴露公网;K8s 内用 port-forward。


以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。