重构消息队列连接管理,支持多数据源配置
主要变更: 1. 重构NATS、RabbitMQ和Redis连接管理模块,支持多数据源配置 2. 统一连接管理接口,增加数据源名称参数 3. 优化连接状态检查和错误处理 4. 增加连接池管理和资源清理机制 5. 改进日志输出格式和内容
This commit is contained in:
@@ -11,6 +11,12 @@ import (
|
||||
)
|
||||
|
||||
type RabbitMQPublishMsgConfig struct {
|
||||
QueueName string
|
||||
Durable bool
|
||||
Data any
|
||||
}
|
||||
|
||||
type RabbitMQPublishDelayMsgConfig struct {
|
||||
QueueName string
|
||||
Durable bool
|
||||
DelayTime int
|
||||
@@ -19,8 +25,6 @@ type RabbitMQPublishMsgConfig struct {
|
||||
|
||||
type RabbitMQSubscribeMsgConfig struct {
|
||||
QueueName string
|
||||
Durable bool
|
||||
DelayTime int
|
||||
ConsumerName string
|
||||
AutoAck bool
|
||||
PrefetchCount int
|
||||
@@ -31,32 +35,36 @@ func (*RabbitMQPublishMsgConfig) GetPublishMsgType() {
|
||||
|
||||
}
|
||||
|
||||
func (*RabbitMQPublishDelayMsgConfig) GetPublishDelayMsgType() {}
|
||||
|
||||
func (*RabbitMQSubscribeMsgConfig) GetSubscribeMsgType() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 注册 RabbitMQ 插件,必须使用 RegisterPlugin 确保连接检测
|
||||
//registerPlugin(MessageRabbitMQ, func() messageUtil {
|
||||
// return &rabbitMQ{}
|
||||
//})
|
||||
type rabbitMQ struct {
|
||||
name string // 数据源名称
|
||||
}
|
||||
|
||||
type rabbitMQ struct{}
|
||||
func init() {
|
||||
// 注册 RabbitMQ 插件(默认数据源)
|
||||
RegisterPlugin(context.Background(), "default", MessageRabbitMQ, func() messageUtil {
|
||||
return &rabbitMQ{name: "default"}
|
||||
})
|
||||
}
|
||||
|
||||
// Connect 连接 RabbitMQ
|
||||
func (c *rabbitMQ) Connect(ctx context.Context) error {
|
||||
return rabbitmqConnect(ctx, c.name)
|
||||
}
|
||||
|
||||
// Ping 检测 RabbitMQ 连接状态
|
||||
func (c *rabbitMQ) ping(ctx context.Context) bool {
|
||||
return rabbitmqPing()
|
||||
}
|
||||
|
||||
// Reconnect 重连 RabbitMQ
|
||||
func (c *rabbitMQ) reconnect(ctx context.Context) error {
|
||||
return rabbitmqReconnect(ctx)
|
||||
func (c *rabbitMQ) Ping(ctx context.Context) bool {
|
||||
return rabbitmqPing(ctx, c.name)
|
||||
}
|
||||
|
||||
// Close 关闭 RabbitMQ 连接
|
||||
func (c *rabbitMQ) close(ctx context.Context) error {
|
||||
return rabbitmqClose(ctx)
|
||||
func (c *rabbitMQ) Close(ctx context.Context) error {
|
||||
return rabbitmqClose(ctx, c.name)
|
||||
}
|
||||
|
||||
// Publish 发布消息
|
||||
@@ -71,11 +79,43 @@ func (c *rabbitMQ) Publish(ctx context.Context, msgConfig messagePublishConfig)
|
||||
if cfg.Data == nil {
|
||||
return fmt.Errorf("数据不能为空")
|
||||
}
|
||||
return c.publishMessageInternal(ctx, cfg.QueueName, cfg.Durable, 0, cfg.Data)
|
||||
}
|
||||
|
||||
// PublishDelay 发布延迟消息
|
||||
func (c *rabbitMQ) PublishDelay(ctx context.Context, msgConfig messagePublishDelayConfig) error {
|
||||
cfg, ok := msgConfig.(*RabbitMQPublishDelayMsgConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("无效的 RabbitMQ 配置类型")
|
||||
}
|
||||
if g.IsEmpty(cfg.QueueName) {
|
||||
return fmt.Errorf("队列名称不能为空")
|
||||
}
|
||||
if cfg.Data == nil {
|
||||
return fmt.Errorf("数据不能为空")
|
||||
}
|
||||
return c.publishMessageInternal(ctx, cfg.QueueName, cfg.Durable, cfg.DelayTime, cfg.Data)
|
||||
}
|
||||
|
||||
// publishMessage 发布消息内部实现
|
||||
func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string, durable bool, delayTime int, data interface{}) error {
|
||||
if !c.Ping(ctx) {
|
||||
if err := commonConnect(ctx, MessageRabbitMQ, c.name, func(ctx context.Context) error {
|
||||
return c.Connect(ctx)
|
||||
}, func(ctx context.Context) error {
|
||||
return c.Close(ctx)
|
||||
}); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ [%s][%s] 连接失败: %v", MessageRabbitMQ, c.name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
channel := getRabbitMQChannel(c.name)
|
||||
if channel == nil || channel.IsClosed() {
|
||||
g.Log().Errorf(ctx, "❌ RabbitMQ [%s] Channel 不存在或已关闭", c.name)
|
||||
return fmt.Errorf("RabbitMQ Channel 不存在或已关闭")
|
||||
}
|
||||
|
||||
delayMsg := delayTime > 0
|
||||
|
||||
// 1. 决定 Exchange 类型
|
||||
@@ -86,12 +126,12 @@ func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string,
|
||||
if delayMsg {
|
||||
exchangeType = "x-delayed-message"
|
||||
exchangeName = queueName + ".delayed"
|
||||
args["x-delayed-type"] = "fanout" // 底层用 topic
|
||||
args["x-delayed-type"] = "fanout"
|
||||
}
|
||||
|
||||
// 2. 声明 Exchange(只声明一次)
|
||||
// 2. 声明 Exchange(使用 exchangeName 而不是 queueName)
|
||||
if err := channel.ExchangeDeclare(
|
||||
queueName, // exchange 交换机名称
|
||||
exchangeName, // 修复:使用正确的交换机名称
|
||||
exchangeType,
|
||||
durable,
|
||||
false, // autoDelete
|
||||
@@ -99,7 +139,8 @@ func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string,
|
||||
false, // noWait
|
||||
args,
|
||||
); err != nil {
|
||||
return fmt.Errorf("声明 Exchange 失败: %w", err)
|
||||
g.Log().Errorf(ctx, "❌ 声明 Exchange 失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 声明队列
|
||||
@@ -111,7 +152,8 @@ func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string,
|
||||
false, // noWait
|
||||
nil, // args
|
||||
); err != nil {
|
||||
return fmt.Errorf("声明队列失败: %w", err)
|
||||
g.Log().Errorf(ctx, "❌ 声明队列失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 绑定队列
|
||||
@@ -122,13 +164,15 @@ func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string,
|
||||
false, // noWait
|
||||
nil, // args
|
||||
); err != nil {
|
||||
return fmt.Errorf("绑定队列失败: %w", err)
|
||||
g.Log().Errorf(ctx, "❌ 绑定队列失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. 序列化数据
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化数据失败: %w", err)
|
||||
g.Log().Errorf(ctx, "❌ 序列化数据失败: %v", err)
|
||||
return err
|
||||
}
|
||||
// 6. 发布消息
|
||||
deliveryMode := amqp.Transient
|
||||
@@ -142,9 +186,9 @@ func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
if delayMsg {
|
||||
duration := time.Duration(delayTime) * time.Minute
|
||||
duration := delayTime * 1000 // 延迟时间(毫秒)= 秒 * 1000
|
||||
publishing.Headers = amqp.Table{
|
||||
"x-delay": duration, // 延迟时间(毫秒)
|
||||
"x-delay": duration,
|
||||
}
|
||||
}
|
||||
err = channel.PublishWithContext(
|
||||
@@ -154,6 +198,11 @@ func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string,
|
||||
false, false,
|
||||
publishing,
|
||||
)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "❌ 发布消息失败: %v", err)
|
||||
return err
|
||||
}
|
||||
g.Log().Infof(ctx, "📨 发布消息成功: queueName=%s, data=%v", queueName, data)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -180,10 +229,28 @@ func (c *rabbitMQ) Subscribe(ctx context.Context, msgConfig messageSubscribeConf
|
||||
|
||||
// createSubscribe 内部订阅消息
|
||||
func (c *rabbitMQ) createSubscribeInternal(ctx context.Context, queueName, consumerName string, prefetchCount int, autoAck bool, handler func(ctx context.Context, message map[string]interface{}) error) error {
|
||||
g.Log().Infof(ctx, "🔔 RabbitMQ 开始订阅: queueName=%s, consumerName=%s", queueName, consumerName)
|
||||
g.Log().Infof(ctx, "🔔 RabbitMQ [%s] 开始订阅: queueName=%s, consumerName=%s", c.name, queueName, consumerName)
|
||||
|
||||
if !c.Ping(ctx) {
|
||||
if err := commonConnect(ctx, MessageRabbitMQ, c.name, func(ctx context.Context) error {
|
||||
return c.Connect(ctx)
|
||||
}, func(ctx context.Context) error {
|
||||
return c.Close(ctx)
|
||||
}); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ [%s][%s] 连接失败: %v", MessageRabbitMQ, c.name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
channel := getRabbitMQChannel(c.name)
|
||||
if channel == nil || channel.IsClosed() {
|
||||
g.Log().Errorf(ctx, "❌ RabbitMQ [%s] Channel 不存在或已关闭", c.name)
|
||||
return fmt.Errorf("RabbitMQ Channel 不存在或已关闭")
|
||||
}
|
||||
|
||||
if err := channel.Qos(prefetchCount, 0, false); err != nil {
|
||||
return fmt.Errorf("设置 Qos 失败: %w", err)
|
||||
g.Log().Errorf(ctx, "❌ 设置 Qos 失败: %v", err)
|
||||
return err
|
||||
}
|
||||
g.Log().Infof(ctx, "📊 设置 Prefetch Count: %d", prefetchCount)
|
||||
|
||||
@@ -197,97 +264,48 @@ func (c *rabbitMQ) createSubscribeInternal(ctx context.Context, queueName, consu
|
||||
nil, // args
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("注册消费者失败: %w", err)
|
||||
g.Log().Errorf(ctx, "❌ 消费消息失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
g.Log().Errorf(ctx, "❌ RabbitMQ 消费者 panic: %v", r)
|
||||
g.Log().Infof(ctx, "👀 开始监听消息")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Context 取消,退出
|
||||
g.Log().Infof(ctx, "context cancel 监听消息退出")
|
||||
return nil
|
||||
case m, ok := <-msg:
|
||||
if !ok {
|
||||
// Channel 关闭,退出
|
||||
g.Log().Infof(ctx, "channel close 监听消息退出")
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
g.Log().Infof(ctx, "📨 收到消息: %s", string(m.Body))
|
||||
|
||||
// 并发控制信号量
|
||||
semaphore := make(chan struct{}, 10) // 限制最大并发数为 10
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
g.Log().Infof(ctx, "🔕 RabbitMQ 消费者停止: queueName=%s, consumerName=%s", queueName, consumerName)
|
||||
return
|
||||
case msg, ok := <-msg:
|
||||
if !ok {
|
||||
g.Log().Warningf(ctx, "⚠️ RabbitMQ 消息通道关闭")
|
||||
return
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(m.Body, &data); err != nil {
|
||||
// 如果不是 JSON,直接使用原始内容
|
||||
data = map[string]interface{}{
|
||||
"data": string(m.Body),
|
||||
}
|
||||
|
||||
// 获取并发控制槽位
|
||||
semaphore <- struct{}{}
|
||||
|
||||
go func(m amqp.Delivery) {
|
||||
defer func() {
|
||||
<-semaphore // 释放槽位
|
||||
if r := recover(); r != nil {
|
||||
g.Log().Errorf(ctx, "❌ 消息处理 panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.handleMessageWithRetryInternal(ctx, m, handler, autoAck); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ 消息处理失败(重试次数耗尽): %v", err)
|
||||
|
||||
// 仅在手动 ACK 模式下拒绝消息
|
||||
if !autoAck {
|
||||
// 拒绝消息不再重新入队(避免死循环)
|
||||
m.Nack(false, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 仅在手动 ACK 模式下确认消息
|
||||
if autoAck {
|
||||
if err := m.Ack(false); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ ACK 消息失败: %v", err)
|
||||
}
|
||||
}
|
||||
}(msg)
|
||||
}
|
||||
err := handler(ctx, data)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "❌ 消息处理失败: %v", err)
|
||||
// 仅在手动 ACK 模式下拒绝消息
|
||||
if !autoAck {
|
||||
// 拒绝消息不再重新入队(避免死循环)
|
||||
m.Nack(false, false)
|
||||
continue
|
||||
}
|
||||
}
|
||||
g.Log().Infof(ctx, "✅ 消息处理成功: %v", err)
|
||||
// 仅在手动 ACK 模式下确认消息
|
||||
if err := m.Ack(false); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ AUTO ACK 消息失败: %v", err)
|
||||
} else {
|
||||
g.Log().Infof(ctx, "✅ AUTO ACK 消息成功")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleMessageWithRetry 处理消息(支持重试)
|
||||
func (c *rabbitMQ) handleMessageWithRetryInternal(ctx context.Context, msg amqp.Delivery, handler func(ctx context.Context, message map[string]interface{}) error, autoAck bool) error {
|
||||
var data map[string]interface{}
|
||||
|
||||
if err := json.Unmarshal(msg.Body, &data); err != nil {
|
||||
// 如果不是 JSON,直接使用原始内容
|
||||
data = map[string]interface{}{
|
||||
"data": string(msg.Body),
|
||||
}
|
||||
}
|
||||
|
||||
// 重试逻辑
|
||||
const maxRetry = 3
|
||||
for attempt := 0; attempt <= maxRetry; attempt++ {
|
||||
if attempt > 0 {
|
||||
g.Log().Infof(ctx, "🔄 消息处理重试 (第%d次)", attempt)
|
||||
// 指数退避
|
||||
time.Sleep(time.Duration(attempt) * time.Second)
|
||||
}
|
||||
|
||||
err := handler(ctx, data)
|
||||
if err == nil {
|
||||
return nil // 成功
|
||||
}
|
||||
|
||||
g.Log().Warningf(ctx, "⚠️ 消息处理失败 (第%d次): %v", attempt+1, err)
|
||||
|
||||
if attempt == maxRetry {
|
||||
return fmt.Errorf("达到最大重试次数 %d: %w", maxRetry, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user