跳到主要内容

连接池设计

问题

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数据库并发上限防止压垮数据库
MaxIdleConnsMaxOpenConns 的 25%减少连接创建开销
ConnMaxLifetime3~5 分钟防止使用过期连接
ConnMaxIdleTime1~3 分钟释放长期空闲连接

HTTP 连接池

http.Transport 自带 TCP 连接池:

transport := &http.Transport{
MaxIdleConns: 100, // 全局最大空闲
MaxIdleConnsPerHost: 10, // 每 host 最大空闲
MaxConnsPerHost: 100, // 每 host 最大连接
IdleConnTimeout: 90 * time.Second,
}
连接池常见问题
  1. 连接泄漏:忘记 Close(resp.Body、rows、stmt)
  2. 连接数不够:MaxOpenConns 太小,请求排队等待
  3. 空闲连接过期:数据库/防火墙杀死空闲连接,客户端不知道
  4. 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?

答案

  1. 数据库/负载均衡器可能有连接最大存活时间限制
  2. DNS 变更后需要新连接指向新 IP
  3. 防止连接状态异常积累

Q2: MaxIdleConns 设置多大合适?

答案:太大浪费内存和数据库连接,太小导致频繁创建连接。经验值是 MaxOpenConns 的 25%~50%。需要根据实际流量模式压测确定。

相关链接