Fiber不管理数据库连接,gorm.Open()后必须显式配置连接池参数;否则默认MaxOpenConns=0、MaxIdleConns=2会导致高并发下连接耗尽或超时。

gorm.Open()后不设连接池参数等于没配数据库
很多人写完 gorm.Open(mysql.Open(dsn), &gorm.Config{}) 就以为连上了,结果压测时大量 dial tcp: i/o timeout 或 too many connections。Fiber 本身不碰连接池,gorm.Open() 返回的 *gorm.DB 底层是 *sql.DB,而它的默认值是: MaxOpenConns = 0(无上限)、MaxIdleConns = 2、ConnMaxIdleTime = 0(永不回收)。这在生产环境就是定时炸弹。
- 必须立刻调用
db.DB().SetMaxOpenConns(50)和db.DB().SetMaxIdleConns(25),数值按压测结果定,别抄示例 -
SetMaxIdleConns建议设为SetMaxOpenConns的 1/2~2/3,比如 Open=50 → Idle=20~35 - 必须同步设
db.DB().SetConnMaxIdleTime(5 * time.Minute),云数据库(RDS/PolarDB)建议缩到2 * time.Minute,适配主备切换 - 漏掉
SetConnMaxLifetime(30 * time.Minute)容易积累 stale connection,报server closed the connection
Prefork 模式下每个子进程必须独立初始化 DB
开 Prefork: true 后 QPS 不升反降?大概率是所有 worker 共享了同一份 *gorm.DB 实例。Fiber 的 Prefork 是靠 fork() 复制整个进程地址空间,但数据库连接池不是线程安全的共享资源——多个 worker 抢同一个 *sql.DB 会触发内部 mutex 争抢,导致 acquire 阻塞、WaitCount 持续上涨。
- 把
gorm.Open()移到fiber.New()之后、app.Listen()之前,确保每个子进程都走一遍初始化 - 每个子进程的
SetMaxOpenConns要按「总连接数 ÷ worker 数」重新算,比如机器有 8 核、开了 8 个 worker,DB 总连接上限 400,则单个 worker 设50,不是照搬 400 - 别用全局变量存
*gorm.DB,也别依赖uber-go/dig单例注入——dig 提供的实例在 fork 后仍是同一块内存地址,不解决隔离问题 - 验证方式:启动后查
pg_stat_activity(PostgreSQL)或SHOW PROCESSLIST(MySQL),确认连接数 ≈ worker 数 × 单进程MaxOpenConns
http.Client 连接池必须手动配,不能用默认或 http.DefaultClient
Fiber handler 里调下游 HTTP 接口,如果直接写 http.Get() 或 &http.Client{},很快就会卡在 transport.roundTrip。默认 MaxIdleConnsPerHost = 2,10 个并发请求就排队;IdleConnTimeout = 0 导致空闲连接被服务端静默关闭,复用时重连;没开 TLS session cache,每次 HTTPS 都握手。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 全局定义一个复用的
*http.Client,Transport必须显式配置:MaxIdleConns = 200、MaxIdleConnsPerHost = 200、IdleConnTimeout = 90 * time.Second -
TLSClientConfig.ClientSessionCache必须设,推荐tls.NewLRUClientSessionCache(100) - 每个 HTTP 请求前必须带 context 超时:
ctx, cancel := context.WithTimeout(c.Context(), 3*time.Second),再传给client.Do(req.WithContext(ctx)) - 别在 handler 里 new client,也别用
http.DefaultClient——它和http.DefaultTransport共享连接池,其他包(如 metrics 上报)可能偷偷污染配置
db.Stats() 不看等于闭眼调参
光改数字不看监控,迟早出事。db.Stats() 返回的不是快照,是持续演化的状态信号。关键字段只有三个要盯:
立即学习“go语言免费学习笔记(深入)”;
-
WaitCount持续上升 → 连接不够用,优先加SetMaxOpenConns,不是加SetMaxIdleConns -
InUse == MaxOpenConns且长期不回落 → 连接借出去没还,查rows.Close()是否漏写、事务是否 panic 后未 rollback -
MaxIdleClosed > 0且每分钟增长 ≥ 10 → 空闲连接回收太狠,适当延长SetConnMaxIdleTime或提高SetMaxIdleConns
最容易被忽略的是:事务里混用 db.Query() 和 tx.Query()。前者从全局池拿新连接,不参与事务,还占坑;后者才真正复用事务绑定的那个连接。这种错法不会报错,但会让连接池“假忙”——监控里一切正常,接口却越来越慢。


















