Private
Public Access
1
0
Files
u-desk/internal/dbclient/pool.go
绝尘 bfe5226bfe 新增:MySQL 真连接池重构基础架构
核心改进:
- 创建 MySQLConnectionPool 真正的连接池实现
- 连接池配置结构 PoolConfig(可配置参数)
- 动态连接获取与释放机制
- 空闲连接自动清理
- 健康检查机制(定期 Ping)
- 慢连接日志记录
- 连接池统计信息(Stats)
- 维护协程(清理+健康检查)

新增文件:
- pool_config.go - 连接池配置和实现
  - PoolConfig: 可配置的连接池参数
  - MySQLConnectionPool: 真正的连接池
  - Acquire/Release: 连接获取与释放
  - 清理与维护协程

修改文件:
- pool.go - 集成新连接池到 ConnectionPool

技术特性:
- 默认配置:20最大连接 / 10最大空闲 / 2最小空闲
- 健康检查:30秒间隔
- 慢连接阈值:500ms
- 连接最大生命周期:30分钟
- 空闲超时:10分钟

TODO:
- 连接预热(启动时建立最小连接)
- LRU 连接复用策略
- 单元测试
- 性能基准测试
2026-03-31 11:49:25 +08:00

276 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dbclient
import (
"context"
"encoding/json"
"fmt"
"sync"
"u-desk/internal/common"
"u-desk/internal/crypto"
"u-desk/internal/storage/models"
)
// ConnectionPool 连接池管理器
type ConnectionPool struct {
mysqlClients map[uint]*MySQLClient
redisClients map[uint]*RedisClient
mongoClients map[uint]*MongoClient
// 新增MySQL 真连接池
mysqlPool *MySQLConnectionPool
mu sync.RWMutex
}
var (
globalPool *ConnectionPool
poolOnce sync.Once
)
// GetPool 获取全局连接池实例
func GetPool() *ConnectionPool {
poolOnce.Do(func() {
// 创建 MySQL 连接池
poolConfig := DefaultPoolConfig()
mysqlPool := NewMySQLConnectionPool(poolConfig)
// 启动维护协程
mysqlPool.StartMaintenance()
globalPool = &ConnectionPool{
mysqlClients: make(map[uint]*MySQLClient),
redisClients: make(map[uint]*RedisClient),
mongoClients: make(map[uint]*MongoClient),
mysqlPool: mysqlPool,
}
})
return globalPool
}
// GetMySQLClient 获取或创建 MySQL 客户端(使用连接池)
func (p *ConnectionPool) GetMySQLClient(conn *models.DbConnection) (*MySQLClient, error) {
p.mu.Lock()
defer p.mu.Unlock()
// 尝试从连接池获取连接
if p.mysqlPool != nil {
entry, err := p.mysqlPool.Acquire(conn)
if err == nil {
// 成功从池中获取连接
return entry.Client, nil
}
// 连接池错误,返回
return nil, err
}
// 降级到原有逻辑(如果连接池未初始化)
return p.getMySQLClientLegacy(conn)
}
// getMySQLClientLegacy 原有的 MySQL 客户端获取逻辑(向后兼容)
func (p *ConnectionPool) getMySQLClientLegacy(conn *models.DbConnection) (*MySQLClient, error) {
// 检查是否已存在
if client, ok := p.mysqlClients[conn.ID]; ok {
// 测试连接是否有效
if err := client.sqlDB.Ping(); err == nil {
return client, nil
}
// 连接已断开,移除并重新创建
client.Close()
delete(p.mysqlClients, conn.ID)
}
// 解密密码
password, err := crypto.DecryptPassword(conn.Password)
if err != nil {
return nil, fmt.Errorf("密码解密失败: %v", err)
}
// 创建新客户端
config := &MySQLConfig{
Host: conn.Host,
Port: conn.Port,
Username: conn.Username,
Password: password, // 如果密码为空MySQL会尝试无密码连接
Database: conn.Database,
}
client, err := NewMySQLClient(config)
if err != nil {
return nil, err
}
p.mysqlClients[conn.ID] = client
return client, nil
}
// GetMySQLPoolStats 获取 MySQL 连接池统计信息
func (p *ConnectionPool) GetMySQLPoolStats() *PoolStats {
if p.mysqlPool != nil {
stats := p.mysqlPool.Stats()
return &stats
}
return nil
}
// GetRedisClient 获取或创建 Redis 客户端
func (p *ConnectionPool) GetRedisClient(conn *models.DbConnection) (*RedisClient, error) {
p.mu.Lock()
defer p.mu.Unlock()
// 检查是否已存在
if client, ok := p.redisClients[conn.ID]; ok {
// 测试连接是否有效
ctx, cancel := context.WithTimeout(context.Background(), common.TimeoutPing)
defer cancel()
if err := client.client.Ping(ctx).Err(); err == nil {
return client, nil
}
// 连接已断开,移除并重新创建
client.Close()
delete(p.redisClients, conn.ID)
}
// 解密密码
password, err := crypto.DecryptPassword(conn.Password)
if err != nil {
return nil, fmt.Errorf("密码解密失败: %v", err)
}
// 解析 Redis DB 编号(从 Database 字段,默认为 0
dbNum := 0
if conn.Database != "" {
// 尝试解析 Database 字段为数字
_, err := fmt.Sscanf(conn.Database, "%d", &dbNum)
if err != nil {
// 如果解析失败,使用默认值 0
dbNum = 0
}
// 限制 DB 编号在 0-15 之间
if dbNum < 0 || dbNum > 15 {
dbNum = 0
}
}
// 创建新客户端
config := &RedisConfig{
Host: conn.Host,
Port: conn.Port,
Password: password,
DB: dbNum,
}
client, err := NewRedisClient(config)
if err != nil {
return nil, err
}
p.redisClients[conn.ID] = client
return client, nil
}
// GetMongoClient 获取或创建 MongoDB 客户端
func (p *ConnectionPool) GetMongoClient(conn *models.DbConnection) (*MongoClient, error) {
p.mu.Lock()
defer p.mu.Unlock()
// 检查是否已存在
if client, ok := p.mongoClients[conn.ID]; ok {
// 测试连接是否有效
ctx, cancel := context.WithTimeout(context.Background(), common.TimeoutPing)
defer cancel()
if err := client.client.Ping(ctx, nil); err == nil {
return client, nil
}
// 连接已断开,移除并重新创建
client.Close()
delete(p.mongoClients, conn.ID)
}
// 解密密码
password, err := crypto.DecryptPassword(conn.Password)
if err != nil {
return nil, fmt.Errorf("密码解密失败: %v", err)
}
// 解析 Options 获取 MongoDB 连接参数
authSource := ""
authMechanism := ""
if conn.Options != "" {
var opts map[string]interface{}
if err := json.Unmarshal([]byte(conn.Options), &opts); err == nil {
if as, ok := opts["authSource"].(string); ok && as != "" {
authSource = as
}
if am, ok := opts["authMechanism"].(string); ok && am != "" {
authMechanism = am
}
}
}
// 创建新客户端
config := &MongoConfig{
Host: conn.Host,
Port: conn.Port,
Username: conn.Username,
Password: password,
Database: conn.Database,
AuthSource: authSource,
AuthMechanism: authMechanism,
}
client, err := NewMongoClient(config)
if err != nil {
return nil, err
}
p.mongoClients[conn.ID] = client
return client, nil
}
// CloseConnection 关闭指定连接
func (p *ConnectionPool) CloseConnection(connID uint, dbType string) {
p.mu.Lock()
defer p.mu.Unlock()
switch dbType {
case "mysql":
if client, ok := p.mysqlClients[connID]; ok {
client.Close()
delete(p.mysqlClients, connID)
}
case "redis":
if client, ok := p.redisClients[connID]; ok {
client.Close()
delete(p.redisClients, connID)
}
case "mongo":
if client, ok := p.mongoClients[connID]; ok {
client.Close()
delete(p.mongoClients, connID)
}
}
}
// CloseAll 关闭所有连接
func (p *ConnectionPool) CloseAll() {
p.mu.Lock()
defer p.mu.Unlock()
for _, client := range p.mysqlClients {
client.Close()
}
for _, client := range p.redisClients {
client.Close()
}
for _, client := range p.mongoClients {
client.Close()
}
p.mysqlClients = make(map[uint]*MySQLClient)
p.redisClients = make(map[uint]*RedisClient)
p.mongoClients = make(map[uint]*MongoClient)
}