连接池设计
问题
Go 中有哪些连接池实现?如何设计和配置连接池?
答案
database/sql 连接池
database/sql 内置了连接池管理:
db, _ := sql.Open("mysql", dsn)
// 连接池配置
db.SetMaxOpenConns(100) // 最大打开连接数
db.SetMaxIdleConns(25) // 最大空闲连接数
db.SetConnMaxLifetime(5 * time.Minute) // 连接最大生存时间
db.SetConnMaxIdleTime(3 * time.Minute) // 空闲连接最大存活时间
| 参数 | 建议值 | 作用 |
|---|---|---|
| MaxOpenConns | 数据库并发上限 | 防止压垮数据库 |
| MaxIdleConns | MaxOpenConns 的 25% | 减少连接创建开销 |
| ConnMaxLifetime | 3~5 分钟 | 防止使用过期连接 |
| ConnMaxIdleTime | 1~3 分钟 | 释放长期空闲连接 |
HTTP 连接池
http.Transport 自带 TCP 连接池:
transport := &http.Transport{
MaxIdleConns: 100, // 全局最大空闲
MaxIdleConnsPerHost: 10, // 每 host 最大空闲
MaxConnsPerHost: 100, // 每 host 最大连接
IdleConnTimeout: 90 * time.Second,
}
连接池常见问题
- 连接泄漏:忘记 Close(resp.Body、rows、stmt)
- 连接数不够:MaxOpenConns 太小,请求排队等待
- 空闲连接过期:数据库/防火墙杀死空闲连接,客户端不知道
- DNS 变更不生效:旧连接仍指向旧 IP
通用连接池设计
type Pool struct {
mu sync.Mutex
conns chan *Conn // 使用 Channel 做连接队列
factory func() (*Conn, error)
maxSize int
current int
}
func (p *Pool) Get(ctx context.Context) (*Conn, error) {
select {
case conn := <-p.conns:
if conn.IsAlive() {
return conn, nil
}
// 连接已失效,创建新的
default:
}
p.mu.Lock()
if p.current >= p.maxSize {
p.mu.Unlock()
// 等待归还
select {
case conn := <-p.conns:
return conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
p.current++
p.mu.Unlock()
return p.factory()
}
func (p *Pool) Put(conn *Conn) {
if !conn.IsAlive() {
p.mu.Lock()
p.current--
p.mu.Unlock()
return
}
select {
case p.conns <- conn:
default:
conn.Close() // 池满了,直接关闭
p.mu.Lock()
p.current--
p.mu.Unlock()
}
}
常见面试问题
Q1: 连接池为什么需要 ConnMaxLifetime?
答案:
- 数据库/负载均衡器可能有连接最大存活时间限制
- DNS 变更后需要新连接指向新 IP
- 防止连接状态异常积累
Q2: MaxIdleConns 设置多大合适?
答案:太大浪费内存和数据库连接,太小导致频繁创建连接。经验值是 MaxOpenConns 的 25%~50%。需要根据实际流量模式压测确定。