// ============================================================================= // Redis 数据源连接管理 // 使用 GoFrame 框架自带的 Redis 客户端,负责数据源的连接、重连、健康检查和优雅关闭 // ============================================================================= package message import ( "context" "fmt" "os" "os/signal" "sync" "sync/atomic" "syscall" "time" "github.com/gogf/gf/v2/database/gredis" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/util/gconv" ) // ============================================================================= // 数据源配置结构 // ============================================================================= type RedisDataSourceConfig struct { Name string `json:"name"` // 数据源名称 Address string `json:"address"` // Redis 地址,如: 127.0.0.1:6379 Db int `json:"db"` // 数据库编号 Pass string `json:"pass"` // 密码 Timeout time.Duration `json:"timeout"` // 连接超时 MaxIdle int `json:"maxIdle"` // 最大空闲连接数 MaxOpen int `json:"maxOpen"` // 最大活跃连接数 } // ============================================================================= // 单个数据源接口 // ============================================================================= type DataSource interface { Name() string Redis() *gredis.Redis IsConnected() bool Connect(ctx context.Context) error Reconnect(ctx context.Context) error Close(ctx context.Context) error } // ============================================================================= // 数据源实现 // ============================================================================= type BaseDataSource struct { config *RedisDataSourceConfig redis *gredis.Redis isConnected bool mu sync.RWMutex lastError error lastErrorTime time.Time metrics RedisMetrics } func NewBaseDataSource(config *RedisDataSourceConfig) *BaseDataSource { return &BaseDataSource{ config: config, isConnected: false, } } func (d *BaseDataSource) Name() string { return d.config.Name } func (d *BaseDataSource) Redis() *gredis.Redis { d.mu.RLock() defer d.mu.RUnlock() return d.redis } func (d *BaseDataSource) IsConnected() bool { d.mu.RLock() defer d.mu.RUnlock() return d.isConnected && d.redis != nil } func (d *BaseDataSource) Connect(ctx context.Context) error { d.mu.Lock() defer d.mu.Unlock() // 设置默认值 config := d.config if config.Timeout == 0 { config.Timeout = 10 * time.Second } if config.MaxIdle == 0 { config.MaxIdle = 10 } if config.MaxOpen == 0 { config.MaxOpen = 100 } // 构建 GoFrame Redis 配置 redisConfig := &gredis.Config{ Address: config.Address, Db: config.Db, Pass: config.Pass, } // 使用 GoFrame 的 Redis 连接 redisObj, err := gredis.New(redisConfig) if err != nil { d.isConnected = false d.lastError = err d.lastErrorTime = time.Now() d.metrics.PingError.Add(1) return fmt.Errorf("datasource [%s] connection failed: %w", d.config.Name, err) } d.redis = redisObj // 测试连接 if err := d.Ping(ctx); err != nil { d.isConnected = false d.lastError = err d.lastErrorTime = time.Now() return fmt.Errorf("datasource [%s] ping failed: %w", d.config.Name, err) } d.isConnected = true d.lastError = nil glog.Infof(ctx, "✅ datasource [%s] connected successfully", d.config.Name) return nil } func (d *BaseDataSource) Ping(ctx context.Context) error { defer func() { if r := recover(); r != nil { d.metrics.PingError.Add(1) glog.Errorf(ctx, "❌ datasource [%s] ping panic: %v", d.config.Name, r) } }() if d.redis == nil { d.metrics.PingError.Add(1) return fmt.Errorf("redis client is nil") } _, err := d.redis.Do(ctx, "PING") if err != nil { d.metrics.PingError.Add(1) return err } d.metrics.PingCount.Add(1) return nil } func (d *BaseDataSource) Reconnect(ctx context.Context) error { glog.Infof(ctx, "🔄 reconnecting datasource [%s]", d.config.Name) return d.Connect(ctx) } func (d *BaseDataSource) Close(ctx context.Context) error { d.mu.Lock() defer d.mu.Unlock() if d.redis != nil { if err := d.redis.Close(ctx); err != nil { return fmt.Errorf("datasource [%s] close failed: %w", d.config.Name, err) } } d.isConnected = false d.redis = nil glog.Infof(ctx, "datasource [%s] closed", d.config.Name) return nil } func (d *BaseDataSource) GetMetrics() RedisMetrics { return d.metrics } // ============================================================================= // 监控指标 // ============================================================================= type RedisMetrics struct { PingCount atomic.Int64 PingError atomic.Int64 CommandCount atomic.Int64 CommandError atomic.Int64 } // GetPingMetrics 获取 Ping 相关指标 func (m *RedisMetrics) GetPingMetrics() (int64, int64) { return m.PingCount.Load(), m.PingError.Load() } // GetCommandMetrics 获取命令相关指标 func (m *RedisMetrics) GetCommandMetrics() (int64, int64) { return m.CommandCount.Load(), m.CommandError.Load() } // ============================================================================= // 多数据源管理器 // ============================================================================= type DataSourceManager struct { sources map[string]DataSource mu sync.RWMutex ctx context.Context cancel context.CancelFunc started bool maxRetries int metrics RedisMetrics } var ( manager *DataSourceManager once sync.Once ) // GetManager 获取全局管理器 func GetManager() *DataSourceManager { once.Do(func() { ctx, cancel := context.WithCancel(context.Background()) manager = &DataSourceManager{ sources: make(map[string]DataSource), ctx: ctx, cancel: cancel, started: false, maxRetries: 3, } }) return manager } // RegisterDataSource 注册数据源 func (m *DataSourceManager) RegisterDataSource(config *RedisDataSourceConfig) error { m.mu.Lock() defer m.mu.Unlock() if _, exists := m.sources[config.Name]; exists { return fmt.Errorf("datasource [%s] already exists", config.Name) } source := NewBaseDataSource(config) m.sources[config.Name] = source return nil } // GetDataSource 获取数据源 func (m *DataSourceManager) GetDataSource(name string) (DataSource, error) { m.mu.RLock() defer m.mu.RUnlock() source, exists := m.sources[name] if !exists { return nil, fmt.Errorf("datasource [%s] not found", name) } return source, nil } // GetAllDataSourceNames 获取所有数据源名称 func (m *DataSourceManager) GetAllDataSourceNames() []string { m.mu.RLock() defer m.mu.RUnlock() names := make([]string, 0, len(m.sources)) for name := range m.sources { names = append(names, name) } return names } // GetDefaultDataSource 获取默认数据源(第一个注册的数据源) func (m *DataSourceManager) GetDefaultDataSource() (DataSource, error) { m.mu.RLock() defer m.mu.RUnlock() for _, source := range m.sources { return source, nil } return nil, fmt.Errorf("no datasource available") } // GetMetrics 获取全局监控指标 func (m *DataSourceManager) GetMetrics() RedisMetrics { return m.metrics } // init 初始化多数据源 func init() { ctx := context.Background() // 从配置初始化多数据源 if err := GetManager().InitializeFromConfig(ctx); err != nil { glog.Errorf(ctx, "❌ Failed to initialize Redis datasources: %v", err) } else { glog.Infof(ctx, "✅ Redis datasources initialized: %v", GetManager().GetAllDataSourceNames()) } // 启动健康检查 GetManager().StartHealthCheck() // 设置优雅关闭 setupGracefulShutdown() } // InitializeFromConfig 从配置初始化数据源 // 动态读取 config.yml 中 redis 下的所有配置项 func (m *DataSourceManager) InitializeFromConfig(ctx context.Context) error { var firstErr error // 获取 redis 配置下的所有子键 redisConfig := g.Cfg().MustGet(ctx, "redis") if redisConfig.IsNil() { glog.Warningf(ctx, "no redis configuration found in config.yml") return nil } // 将配置转换为 map configMap := redisConfig.Map() if configMap == nil { glog.Warningf(ctx, "redis configuration is not a map") return nil } // 遍历所有 redis 子配置 for name, subConfig := range configMap { // 跳过非对象类型的配置 subMap, ok := subConfig.(map[string]interface{}) if !ok { continue } // 检查是否有 address 配置 address, hasAddress := subMap["address"] if !hasAddress || gconv.String(address) == "" { continue } // 构建数据源配置 config := &RedisDataSourceConfig{ Name: name, Address: gconv.String(address), Db: gconv.Int(subMap["db"]), Pass: gconv.String(subMap["pass"]), } // 设置默认值 if config.Db == 0 { config.Db = 0 } if config.Timeout == 0 { config.Timeout = 10 * time.Second } if config.MaxIdle == 0 { config.MaxIdle = 10 } if config.MaxOpen == 0 { config.MaxOpen = 100 } // 注册数据源 if err := m.RegisterDataSource(config); err != nil { glog.Errorf(ctx, "failed to register datasource [%s]: %v", name, err) if firstErr == nil { firstErr = err } continue } // 连接数据源 source, _ := m.GetDataSource(name) if err := source.Connect(ctx); err != nil { glog.Errorf(ctx, "failed to initialize datasource [%s]: %v", name, err) if firstErr == nil { firstErr = err } } } return firstErr } // StartHealthCheck 启动健康检查 func (m *DataSourceManager) StartHealthCheck() { if m.started { return } m.started = true go m.healthCheckLoop() } // healthCheckLoop 健康检查循环 func (m *DataSourceManager) healthCheckLoop() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-m.ctx.Done(): return case <-ticker.C: m.checkAndReconnect() } } } // checkAndReconnect 检查并重新连接 func (m *DataSourceManager) checkAndReconnect() { m.mu.RLock() defer m.mu.RUnlock() for name, source := range m.sources { if !source.IsConnected() { glog.Warningf(context.Background(), "datasource [%s] disconnected, attempting reconnect", name) reconnectCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := source.Reconnect(reconnectCtx); err != nil { glog.Errorf(reconnectCtx, "datasource [%s] reconnect failed: %v", name, err) } else { glog.Infof(reconnectCtx, "✅ datasource [%s] reconnected successfully", name) } } } } // CloseAll 关闭所有数据源 func (m *DataSourceManager) CloseAll(ctx context.Context) error { m.cancel() m.mu.RLock() defer m.mu.RUnlock() var lastErr error for name, source := range m.sources { if err := source.Close(ctx); err != nil { glog.Errorf(ctx, "failed to close datasource [%s]: %v", name, err) lastErr = err } } return lastErr } // setupGracefulShutdown 设置优雅关闭 func setupGracefulShutdown() { go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() glog.Info(ctx, "🔄 Shutting down Redis connections...") if err := GetManager().CloseAll(ctx); err != nil { glog.Errorf(ctx, "❌ Failed to close Redis connections: %v", err) } else { glog.Info(ctx, "✅ Redis connections closed successfully") } }() }