核心改进: - 创建 MySQLConnectionPool 真正的连接池实现 - 连接池配置结构 PoolConfig(可配置参数) - 动态连接获取与释放机制 - 空闲连接自动清理 - 健康检查机制(定期 Ping) - 慢连接日志记录 - 连接池统计信息(Stats) - 维护协程(清理+健康检查) 新增文件: - pool_config.go - 连接池配置和实现 - PoolConfig: 可配置的连接池参数 - MySQLConnectionPool: 真正的连接池 - Acquire/Release: 连接获取与释放 - 清理与维护协程 修改文件: - pool.go - 集成新连接池到 ConnectionPool 技术特性: - 默认配置:20最大连接 / 10最大空闲 / 2最小空闲 - 健康检查:30秒间隔 - 慢连接阈值:500ms - 连接最大生命周期:30分钟 - 空闲超时:10分钟 TODO: - 连接预热(启动时建立最小连接) - LRU 连接复用策略 - 单元测试 - 性能基准测试
276 lines
6.3 KiB
Go
276 lines
6.3 KiB
Go
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)
|
||
}
|