重构消息队列连接管理,支持多数据源配置

主要变更:
1. 重构NATS、RabbitMQ和Redis连接管理模块,支持多数据源配置
2. 统一连接管理接口,增加数据源名称参数
3. 优化连接状态检查和错误处理
4. 增加连接池管理和资源清理机制
5. 改进日志输出格式和内容
This commit is contained in:
2026-02-04 13:49:17 +08:00
committed by 张斌
parent 69d2ace17f
commit 55a6ec0374
12 changed files with 1339 additions and 1114 deletions

View File

@@ -1,7 +1,6 @@
// =============================================================================
// Redis 数据源连接管理
// 负责 Redis 数据源的连接、重连、健康检查和优雅关闭
// 支持多数据源和无限重连
// Redis 连接管理
// 负责 Redis 的连接、重连、健康检查和优雅关闭
// =============================================================================
package message
@@ -9,477 +8,191 @@ package message
import (
"context"
"fmt"
"os"
"os/signal"
"sync"
"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"
)
// =============================================================================
// Redis 数据源配置结构
// =============================================================================
var (
muRedis sync.RWMutex
redisConns map[string]*gredis.Redis
redisConfigs map[string]*gredis.Config
)
type redisDataSourceConfig struct {
name string // 数据源名称
address string // Redis 地址,如: 127.0.0.1:6379
db int // 数据库编号
pass string // 密码
maxRetries int // 最大重试次数,-1 表示无限重试
retryInterval time.Duration // 重试间隔
func init() {
redisConns = make(map[string]*gredis.Redis)
redisConfigs = make(map[string]*gredis.Config)
}
// =============================================================================
// Redis 数据源接口
// =============================================================================
type redisDataSource interface {
name() string
getClient() *gredis.Redis
getIsConnected() bool
redisConnect(ctx context.Context) error
redisReconnect(ctx context.Context) error
redisClose(ctx context.Context) error
redisPing(ctx context.Context) bool
}
// =============================================================================
// Redis 数据源实现
// =============================================================================
type baseRedisDataSource struct {
config *redisDataSourceConfig
client *gredis.Redis
isConnected bool
mu sync.RWMutex
lastError error
lastErrorTime time.Time
reconnectMu sync.Mutex
}
func newBaseRedisDataSource(config *redisDataSourceConfig) *baseRedisDataSource {
return &baseRedisDataSource{
config: config,
isConnected: false,
// redisConnect 建立 Redis 连接
// name: 数据源名称,如果为空则使用默认数据源
func redisConnect(ctx context.Context, name string) error {
if g.Cfg().MustGet(ctx, "redis").IsEmpty() {
g.Log().Errorf(ctx, "❌ Redis 配置不存在")
return fmt.Errorf("redis Configuration does not exist")
}
}
func (d *baseRedisDataSource) name() string {
return d.config.name
}
func (d *baseRedisDataSource) getClient() *gredis.Redis {
d.mu.RLock()
defer d.mu.RUnlock()
return d.client
}
func (d *baseRedisDataSource) getIsConnected() bool {
d.mu.RLock()
defer d.mu.RUnlock()
return d.isConnected && d.client != nil
}
func (d *baseRedisDataSource) redisConnect(ctx context.Context) error {
// 使用互斥锁防止并发重连
d.reconnectMu.Lock()
defer d.reconnectMu.Unlock()
d.mu.Lock()
if d.client != nil {
d.client.Close(ctx)
// 确定数据源名称
dsName := "default"
if !g.IsEmpty(name) {
dsName = name
}
d.mu.Unlock()
g.Log().Infof(ctx, "🔔 Redis [%s] 开始创建连接", dsName)
muRedis.Lock()
defer muRedis.Unlock()
// 安全地关闭旧连接(仅针对该数据源)
if oldRedis, exists := redisConns[dsName]; exists && oldRedis != nil {
oldRedis.Close(ctx)
delete(redisConns, dsName)
}
// 从配置文件读取 Redis 配置
redisAddr := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.address", dsName)).String()
if g.IsEmpty(redisAddr) {
g.Log().Errorf(ctx, "❌ Redis 配置错误: address 不能为空 (数据源: %s)", dsName)
return fmt.Errorf("❌ Redis 配置错误: address 不能为空 (数据源: %s)", dsName)
}
redisDB := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.db", dsName)).Int()
if redisDB < 0 || redisDB > 15 {
g.Log().Errorf(ctx, "❌ Redis 配置错误: db 必须在 0-15 之间 (当前值: %d)", redisDB)
return fmt.Errorf("❌ Redis 配置错误: db 必须在 0-15 之间 (当前值: %d)", redisDB)
}
idleTimeout := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.idleTimeout", dsName)).String()
redisIdleTimeout, err := time.ParseDuration(idleTimeout)
if err != nil {
g.Log().Errorf(ctx, "❌ Redis idleTimeout 格式错误: %v", err)
return err
}
maxConnLifetime := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.maxConnLifetime", dsName)).String()
redisMaxConnLifetime, err := time.ParseDuration(maxConnLifetime)
if err != nil {
g.Log().Errorf(ctx, "❌ Redis maxConnLifetime 格式错误: %v", err)
return err
}
waitTimeout := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.waitTimeout", dsName)).String()
redisWaitTimeout, err := time.ParseDuration(waitTimeout)
if err != nil {
g.Log().Errorf(ctx, "❌ Redis waitTimeout 格式错误: %v", err)
return err
}
dialTimeout := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.dialTimeout", dsName)).String()
redisDialTimeout, err := time.ParseDuration(dialTimeout)
if err != nil {
g.Log().Errorf(ctx, "❌ Redis dialTimeout 格式错误: %v", err)
return err
}
readTimeout := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.readTimeout", dsName)).String()
redisReadTimeout, err := time.ParseDuration(readTimeout)
if err != nil {
g.Log().Errorf(ctx, "❌ Redis readTimeout 格式错误: %v", err)
return err
}
writeTimeout := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.writeTimeout", dsName)).String()
redisWriteTimeout, err := time.ParseDuration(writeTimeout)
if err != nil {
g.Log().Errorf(ctx, "❌ Redis writeTimeout 格式错误: %v", err)
return err
}
maxActive := g.Cfg().MustGet(ctx, fmt.Sprintf("redis.%s.maxActive", dsName)).Int()
if g.IsEmpty(maxActive) {
g.Log().Errorf(ctx, "❌ Redis maxActive 配置错误: %v", maxActive)
return fmt.Errorf("❌ Redis maxActive 配置错误")
}
// 构建 GoFrame Redis 配置
redisConfig := &gredis.Config{
Address: d.config.address,
Db: d.config.db,
Pass: d.config.pass,
Address: redisAddr,
Db: redisDB,
IdleTimeout: redisIdleTimeout,
MaxConnLifetime: redisMaxConnLifetime,
WaitTimeout: redisWaitTimeout,
DialTimeout: redisDialTimeout,
ReadTimeout: redisReadTimeout,
WriteTimeout: redisWriteTimeout,
MaxActive: maxActive,
}
redisConfigs[dsName] = redisConfig
// 使用 GoFrame 的 Redis 连接
redisObj, err := gredis.New(redisConfig)
newRedis, err := gredis.New(redisConfig)
if err != nil {
d.mu.Lock()
d.isConnected = false
d.lastError = err
d.lastErrorTime = time.Now()
d.mu.Unlock()
return fmt.Errorf("datasource [%s] connection failed: %w", d.config.name, err)
g.Log().Errorf(ctx, "❌ Redis [%s] 连接失败: %v", dsName, err)
return err
}
// 测试连接(直接调用避免死锁)
_, err = newRedis.Do(ctx, "PING")
if err != nil {
g.Log().Errorf(ctx, "❌ Redis [%s] 连接失败: ping 失败 - %v", dsName, err)
_ = newRedis.Close(ctx)
return err
}
d.mu.Lock()
d.client = redisObj
d.mu.Unlock()
// 测试连接
if !d.redisPing(ctx) {
d.mu.Lock()
d.isConnected = false
d.lastError = err
d.lastErrorTime = time.Now()
d.mu.Unlock()
return fmt.Errorf("datasource [%s] ping failed: %w", d.config.name, err)
}
d.mu.Lock()
d.isConnected = true
d.lastError = nil
d.mu.Unlock()
glog.Infof(ctx, "✅ datasource [%s] connected successfully", d.config.name)
redisConns[dsName] = newRedis
g.Log().Infof(ctx, "✅ Redis [%s] 连接成功: %s (DB: %d)", dsName, redisAddr, redisDB)
return nil
}
func (d *baseRedisDataSource) redisReconnect(ctx context.Context) error {
glog.Infof(ctx, "🔄 reconnecting datasource [%s]", d.config.name)
return d.redisConnect(ctx)
}
func (d *baseRedisDataSource) redisClose(ctx context.Context) error {
d.mu.Lock()
defer d.mu.Unlock()
if d.client != nil {
if err := d.client.Close(ctx); err != nil {
return fmt.Errorf("datasource [%s] close failed: %w", d.config.name, err)
}
// redisPing 检测 Redis 连接状态(带超时保护)
func redisPing(ctx context.Context, name string) bool {
// 确定数据源名称
dsName := "default"
if !g.IsEmpty(name) {
dsName = name
}
d.isConnected = false
glog.Infof(ctx, "datasource [%s] closed", d.config.name)
return nil
}
muRedis.RLock()
defer muRedis.RUnlock()
func (d *baseRedisDataSource) redisPing(ctx context.Context) bool {
d.mu.RLock()
client := d.client
d.mu.RUnlock()
if client == nil {
rc, exists := redisConns[dsName]
if !exists || rc == nil {
g.Log().Errorf(ctx, "❌ Redis [%s] 连接未建立", dsName)
return false
}
_, err := client.Do(ctx, "PING")
// 创建带超时的子上下文,避免死锁
timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
_, err := rc.Do(timeoutCtx, "PING")
if err != nil {
g.Log().Errorf(ctx, "❌ Redis [%s] ping 失败: %v", dsName, err)
return false
}
g.Log().Infof(ctx, "📊 Redis [%s] 连接正常", dsName)
return true
}
// =============================================================================
// Redis 多数据源管理器
// =============================================================================
type redisDataSourceManager struct {
sources map[string]redisDataSource
mu sync.RWMutex
ctx context.Context
cancel context.CancelFunc
started bool
maxRetries int
reconnectCh chan string
}
var (
globalRedisManager *redisDataSourceManager
redisManagerOnce sync.Once
)
// getRedisManager 获取全局 Redis 管理器
func getRedisManager() *redisDataSourceManager {
redisManagerOnce.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
globalRedisManager = &redisDataSourceManager{
sources: make(map[string]redisDataSource),
ctx: ctx,
cancel: cancel,
started: false,
maxRetries: -1, // 默认无限重试
reconnectCh: make(chan string, 100),
}
})
return globalRedisManager
}
// registerDataSource 注册 Redis 数据源
func (m *redisDataSourceManager) 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)
// redisClose 关闭 Redis 连接
func redisClose(ctx context.Context, name string) error {
// 确定数据源名称
dsName := "default"
if !g.IsEmpty(name) {
dsName = name
}
source := newBaseRedisDataSource(config)
m.sources[config.name] = source
muRedis.Lock()
defer muRedis.Unlock()
if rc, exists := redisConns[dsName]; exists && rc != nil {
if err := rc.Close(ctx); err != nil {
g.Log().Errorf(ctx, "❌ Redis [%s] 关闭失败: %v", dsName, err)
return err
}
delete(redisConns, dsName)
}
g.Log().Infof(ctx, "✅ Redis [%s] 连接已关闭", dsName)
return nil
}
// getDataSource 获取 Redis 数据源
func (m *redisDataSourceManager) getDataSource(name string) (redisDataSource, error) {
m.mu.RLock()
defer m.mu.RUnlock()
source, exists := m.sources[name]
if !exists {
return nil, fmt.Errorf("datasource [%s] not found", name)
// getRedisConn 获取 Redis 连接(内部使用)
func getRedisConn(name string) *gredis.Redis {
dsName := "default"
if !g.IsEmpty(name) {
dsName = name
}
return source, nil
}
// getAllDataSourceNames 获取所有 Redis 数据源名称
func (m *redisDataSourceManager) 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
}
// initializeFromConfig 从配置初始化 Redis 数据源
func (m *redisDataSourceManager) 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"]),
maxRetries: gconv.Int(subMap["maxRetries"]),
retryInterval: gconv.Duration(subMap["retryInterval"]),
}
// 设置默认值
if config.maxRetries == 0 {
config.maxRetries = -1 // 默认无限重试
}
if config.retryInterval == 0 {
config.retryInterval = 5 * time.Second
}
// 注册数据源
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.redisConnect(ctx); err != nil {
glog.Errorf(ctx, "failed to initialize datasource [%s]: %v", name, err)
if firstErr == nil {
firstErr = err
}
}
}
return firstErr
}
// startHealthCheck 启动健康检查
func (m *redisDataSourceManager) startHealthCheck() {
if m.started {
return
}
m.started = true
// 启动健康检查循环
go m.healthCheckLoop()
// 启动重连处理循环
go m.reconnectLoop()
}
// healthCheckLoop 健康检查循环
func (m *redisDataSourceManager) healthCheckLoop() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-m.ctx.Done():
return
case <-ticker.C:
m.checkConnections()
}
}
}
// reconnectLoop 重连处理循环
func (m *redisDataSourceManager) reconnectLoop() {
reconnectCounts := make(map[string]int)
for {
select {
case <-m.ctx.Done():
return
case name := <-m.reconnectCh:
go func(dsName string) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
source, err := m.getDataSource(dsName)
if err != nil {
glog.Errorf(ctx, "datasource [%s] not found for reconnect", dsName)
return
}
if err := source.redisReconnect(ctx); err != nil {
glog.Errorf(ctx, "datasource [%s] reconnect failed: %v", dsName, err)
// 记录重连次数
reconnectCounts[dsName]++
// 检查重连次数限制
if m.maxRetries > 0 && reconnectCounts[dsName] > m.maxRetries {
glog.Errorf(ctx, "datasource [%s] reconnect count %d exceeds limit %d, stopping auto-reconnect",
dsName, reconnectCounts[dsName], m.maxRetries)
return
}
// 延迟后重新放入重连队列
time.Sleep(5 * time.Second)
select {
case m.reconnectCh <- dsName:
default:
// 通道已满,丢弃通知
}
} else {
// 重连成功,重置计数器
reconnectCounts[dsName] = 0
}
}(name)
}
}
}
// checkConnections 检查连接状态
func (m *redisDataSourceManager) checkConnections() {
m.mu.RLock()
defer m.mu.RUnlock()
for name, source := range m.sources {
if !source.getIsConnected() {
glog.Warningf(context.Background(), "datasource [%s] disconnected, queued for reconnect", name)
// 发送到重连队列
select {
case m.reconnectCh <- name:
default:
// 通道已满,丢弃通知
}
}
}
}
// closeAll 关闭所有 Redis 数据源
func (m *redisDataSourceManager) 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.redisClose(ctx); err != nil {
glog.Errorf(ctx, "failed to close datasource [%s]: %v", name, err)
lastErr = err
}
}
return lastErr
}
// =============================================================================
// 全局初始化
// =============================================================================
var (
redisManager = getRedisManager()
)
// init 初始化 Redis 数据源
func init() {
ctx := context.Background()
// 从配置初始化多数据源
if err := redisManager.initializeFromConfig(ctx); err != nil {
glog.Errorf(ctx, "❌ Failed to initialize Redis datasources: %v", err)
} else {
glog.Infof(ctx, "✅ Redis datasources initialized: %v", redisManager.getAllDataSourceNames())
}
// 启动健康检查
redisManager.startHealthCheck()
// 设置优雅关闭
setupGracefulShutdown()
}
// 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 := redisManager.closeAll(ctx); err != nil {
glog.Errorf(ctx, "❌ Failed to close Redis connections: %v", err)
} else {
glog.Info(ctx, "✅ Redis connections closed successfully")
}
}()
}
// =============================================================================
// 私有辅助函数
// =============================================================================
// getDefaultDataSource 获取默认数据源
func getDefaultDataSource() (redisDataSource, error) {
return redisManager.getDataSource("default")
return redisConns[dsName]
}