重构消息队列模块,新增NATS和RabbitMQ连接实现,移除旧版消息队列代码

This commit is contained in:
2026-01-31 07:35:41 +08:00
committed by 张斌
parent ee6d3c9033
commit 69d2ace17f

485
message/connection_redis.go Normal file
View File

@@ -0,0 +1,485 @@
// =============================================================================
// Redis 数据源连接管理
// 负责 Redis 数据源的连接、重连、健康检查和优雅关闭
// 支持多数据源和无限重连
// =============================================================================
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 数据源配置结构
// =============================================================================
type redisDataSourceConfig struct {
name string // 数据源名称
address string // Redis 地址,如: 127.0.0.1:6379
db int // 数据库编号
pass string // 密码
maxRetries int // 最大重试次数,-1 表示无限重试
retryInterval time.Duration // 重试间隔
}
// =============================================================================
// 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,
}
}
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)
}
d.mu.Unlock()
// 构建 GoFrame Redis 配置
redisConfig := &gredis.Config{
Address: d.config.address,
Db: d.config.db,
Pass: d.config.pass,
}
// 使用 GoFrame 的 Redis 连接
redisObj, 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)
}
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)
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)
}
}
d.isConnected = false
glog.Infof(ctx, "datasource [%s] closed", d.config.name)
return nil
}
func (d *baseRedisDataSource) redisPing(ctx context.Context) bool {
d.mu.RLock()
client := d.client
d.mu.RUnlock()
if client == nil {
return false
}
_, err := client.Do(ctx, "PING")
if err != nil {
return false
}
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)
}
source := newBaseRedisDataSource(config)
m.sources[config.name] = source
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)
}
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")
}