MQ架构设计

源代码1:
// ************************************************************************
//  
// Copyright © 2010-2018 wuhan wangyou tec.Corporation,All Rights Reserved.
// 版权所有:     武汉网友科技有限公司
//
// 作者(Author):                    毛善丽
//
// 数据表(Table):
//
// 创建日期(Create Time):
//
// 说明(Description):
//
// 修改记录(Revision History):
//    R1: 
//           
// ************************************************************************
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text;
using SysEngine;
using RabbitMQ.Client.Events;
using System.Threading.Tasks;
using BaseDll;

namespace DealPornImage
{
    public class RabbitMQMan
    {
        public string szHostName = "";
        public int iPort = 0;
        public string szUserName = "";
        public string szPassWord = "";
        public string szReceiveKey = "";
        public string szEnvirment = "";
        public Dele_Void_1Param Callback;
        public IConnectionFactory MQFactory;
        private IConnection MQConnection = null;
        private IModel MQQueue = null;

        public RabbitMQMan(string szHostName, int iPort, string szUserName, string szPassWord, string queKey)
        {
            this.szHostName = szHostName;
            this.iPort = iPort;
            this.szUserName = szUserName;
            this.szPassWord = szPassWord;
            this.szReceiveKey = "relay.integrate" + queKey;
            MQFactory = new ConnectionFactory
            {
                HostName = szHostName,
                Port = iPort,
                UserName = szUserName,
                Password = szPassWord,
                AutomaticRecoveryEnabled = true,
            };

        }
        public void Start()
        {
            //szReceiveKey = "relay.integrate"+szEnvirment;
            MQReceive();
        }
        public void Stop()
        {
            MQConnection.Dispose();
            MQConnection = null;
        }
        private void CreateConnection(string queuekey)
        {
            try
            {
                if (MQConnection == null)
                    MQConnection = MQFactory.CreateConnection();
                if (MQQueue == null)
                {
                    MQQueue = MQConnection.CreateModel();

                    MQQueue.QueueDeclare(
                   queue: queuekey,//消息队列名称
                   durable: false,//是否缓存
                   exclusive: false,
                   autoDelete: false,
                   arguments: null
                   );
                }

            }
            catch (Exception ex)
            {
                LogManager.debug.Info(ex.ToString());
            }

            return;
        }
        public void MQReceive()
        {
            CreateConnection(szReceiveKey);
            if (MQQueue == null)
                return;
            while (true)
            {
                BasicGetResult bgr = MQQueue.BasicGet(szReceiveKey, true);
                //LogManager.info.Info("获取到MQ数据包");
                if (bgr == null)
                    break;
                byte[] message =bgr.Body;
                LiaoPacket packet = new LiaoPacket();
                packet.SetBufferPtr(message);
                ushort cmd = 0;
                packet.GetShortInt(10, ref cmd);
                //LogManager.debug.Info(cmd.ToString());
               //获取聊号,语音类型    
                if (InfoBase.manager.m_infoBase.ContainsKey(cmd))
                    InfoBase.manager.m_infoBase[cmd].RQCallBack(packet);
              

            }
        }
           
     
        public void SendPacket(LiaoPacket packet, string queuekey)
        {
            CreateConnection(queuekey);
            if (MQQueue == null)
                return;
            // using (IModel channel = MQConnection.CreateModel())
            {
                byte[] body = new byte[packet.DataLen];
                Buffer.BlockCopy(packet.DataBuffer, 0, body, 0, packet.DataLen);
                MQQueue.BasicPublish("", queuekey, null, body);
            }

        }


    }
}

image

 

 

参考E:\RabbitMQ事件驱动分布式中转架构

 

源代码2:存在的问题与MQ连接断开无法重连,没有数据发送断开连接且数据来后不会自动恢复连接。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BaseDll;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using SysEngine;

namespace RelaySvrAssist
{
    public class MQOuterData
    {
        public int roomid { get; set; } = 0;
        public string lid { get; set; } = "";
    }
    class ServiceForMQ
    {
        #region 单件模式
        private static ServiceForMQ m_instance;
        public static ServiceForMQ Inst
        {
            get
            {
                if (m_instance == null)
                    m_instance = new ServiceForMQ();
                return m_instance;
            }
        }
        private ServiceForMQ()
        {}
        #endregion

        #region 变量属性
        private ServiceBase SvcQueue = new ServiceBase();
        private ConnectionFactory MQFactory = new ConnectionFactory();
        private IConnection MQConnection = null;
        private IModel MQChannel = null;
        private IBasicConsumer MQConsumer = null;
        private DateTime m_connTime = DateTime.MinValue;
       
        public string MQHostName { get; set; } = "192.168.9.199";
        public string MQUserName { get; set; } = "admin";
        public string MQUserPswd { get; set; } = "1";
        public int MQHostPort { get; set; } = 5672;
        public string MQEnvironment { get; set; } = "";
        public string MQQueueReceive_Svr { get; set; } = "";
        //中心
        public string MQQueueSend_Svr = "relay.integrate";
        //中转
       
        #endregion

        #region 公共函数
        public void Start(uint iSvrId)
        {
            if (MQConnection != null)
                return;
            MQFactory.HostName = MQHostName;
            MQFactory.UserName = MQUserName;
            MQFactory.Password = MQUserPswd;
            MQFactory.Port = MQHostPort;
            MQFactory.VirtualHost = "/";
            MQFactory.Protocol = Protocols.DefaultProtocol;
            MQQueueReceive_Svr = "relay.svr." + iSvrId;
            LogManager.debug.Info($"[MQ连接信息][ip地址{MQHostName}][端口号{MQHostPort}][密码{MQUserPswd}][用户名{MQUserName}][服务器id{iSvrId}]");
            LogManager.debug.Info($"[MQ发包队列][{ServiceForMQ.Inst.MQQueueSend_Svr}][收包队列][{ServiceForMQ.Inst.MQQueueReceive_Svr}]");
            CreateConnection(MQQueueReceive_Svr);
          
        }
        public void Stop()
        {
            if (MQConnection == null)
                return;
            MQConnection.Dispose();
            MQConnection = null;
            MQChannel.Dispose();
            MQChannel = null;
        }
        public async Task MQSend(string strQueueKey, LiaoPacket varpacket)
        {
            await SvcQueue.Call(MQSend_Internal, strQueueKey, varpacket).ConfigureAwait(false);
        }
        public async Task MQReceive(NetSession session)
        {
            await SvcQueue.Call(MQReceive_Internal, MQQueueReceive_Svr, session).ConfigureAwait(false);
        }
        #endregion

        #region 私有函数 
        private void CreateConnection(string strQueueKey)
        {
            //若连接失败,隔60s重连
            try
            {
                if (MQConnection == null || !MQConnection.IsOpen)
                {
                    MQConnection?.Dispose();
                    MQConnection = MQFactory.CreateConnection();
                    LogManager.debug.Info("[MQ启动成功]");
                }
                if (MQChannel == null || !MQChannel.IsOpen)
                {
                    MQChannel?.Dispose();
                    MQChannel = MQConnection.CreateModel();
                    MQChannel.QueueDeclare(strQueueKey, false, false, false, null);
                    MQConsumer = new RabbitMQ.Client.DefaultBasicConsumer(MQChannel);
                }
                if (MQChannel == null)
                    return;
            
            }
            catch(Exception ex)
            {
                LogManager.debug.Info($"[MQ启动失败][{ex.ToString()}]");
                return;
            }
            //QueueDeclare参数
            //queue - 队列唯一名称
            //durable - 消息队列是否持久化(重启MQ, 消息队列也不丢失)
            //          另外: 消息持久化方法
            //          var properties = channel.CreateBasicProperties();
            //          properties.SetPersistent(true);
            //exclusive
            //autoDelete
            //arguments - 
            

            //MQChannel.BasicConsume(
            //    queue: MQQueueName_Svr,
            //    autoAck: false,
            //    consumer: MQConsumer);
        }
        private async Task MQReceive_Internal(string strQueueKey, NetSession session)
        {
            try
            {
                CreateConnection(strQueueKey);

                if (MQChannel == null)
                    return;

                while (true)
                {
                    BasicGetResult bgr = null;
                    try
                    {
                        bgr = MQChannel.BasicGet(strQueueKey, true);

                    }
                    catch (Exception ex)
                    {
                        LogManager.debug.Info($"[MQ BasicGet 失败,准备重试]{ex}");
                        await Task.Delay(500);  // 避免疯狂重试
                        break; // 让外层逻辑重新进入 MQReceive 再重建连接
                    }

                    if (bgr == null)
                        break;
                    // 1. 先把 byte[] 转成字符串
                    string json = Encoding.UTF8.GetString(bgr.Body.ToArray());
                    MQOuterData data = new MQOuterData();
                    try
                    {
                        if (!string.IsNullOrWhiteSpace(json))
                        {
                            // 尝试解析 JSON,如果失败不会抛异常,data 保持默认值
                            data = JsonConvert.DeserializeObject<MQOuterData>(json) ?? data;
                        }
                    }catch(JsonException ex)
                    {
                        LogManager.debug.Info($"JSON解析失败: {ex.Message}, 原始数据: {json.Replace('{', '|').Replace('}', '|')}");
                    }

                    LogManager.info.Info(json.Replace('{','|').Replace('}','|'));
                    LiaoPacket sendpacket = new LiaoPacket();
                    sendpacket.SetShortInt(10, LiaoCmd.CMD_RE_MQ_GET_MSG);
                    sendpacket.SetString(20, json);
                    sendpacket.SetInt(30, data.roomid);
                    sendpacket.SetString(40, data.lid);
                    session.SendPacket(sendpacket);
                   
                }
            }
            catch (Exception ex)
            {
                LogManager.fatal.Info($"[未知异常]{ex.ToString()}");
            }
        }
        private async Task MQSend_Internal(string strQueueKey, LiaoPacket varpacket)
        {
            try
            {
                CreateConnection(strQueueKey);

                if (MQChannel == null)
                    return;

                byte[] btsend = new byte[varpacket.DataLen];
                Buffer.BlockCopy(varpacket.DataBuffer, 0, btsend, 0, varpacket.DataLen);
               // LogManager.test.Info($"发送数据包大小{varpacket.DataLen}");
                MQChannel.BasicPublish("", strQueueKey, null, btsend);
            }
            catch (Exception ex)
            {
                LogManager.fatal.Info($"[未知异常]{ex.ToString()}");
            }
        }
        #endregion
    }
}

⚠️ 核心问题 1:你在用 BasicGet 轮询(这是大坑)

你现在是:

 
while (true)
{
bgr = MQChannel.BasicGet(strQueueKey, true);

if (bgr == null)
break;
}
 

👉 这会导致:

  • 没数据 → 立刻 break
  • 上层再调 MQReceive → 再进来 → 再 break
  • 形成“伪轮询 + 空转 + 连接频繁空闲”

❗副作用

  • RabbitMQ 认为这个连接长时间 idle
  • TCP KeepAlive / Heartbeat 没触发
  • 服务器主动断开连接

⚠️ 核心问题 2:你没有开启 Heartbeat(致命)

你现在:

 
MQFactory.Protocol = Protocols.DefaultProtocol;
 

👉 但你没有设置:

 
MQFactory.RequestedHeartbeat = TimeSpan.FromSeconds(30);
 

❗结果

  • 默认 heartbeat 可能是 0(禁用)
  • 一旦连接空闲:
    👉 服务端会直接断开

⚠️ 核心问题 3:你没有自动恢复连接

RabbitMQ 官方推荐必须开:

 
MQFactory.AutomaticRecoveryEnabled = true;
MQFactory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);
 

否则:

👉 一旦断线 = 永久死亡连接


⚠️ 核心问题 4:你用的是 BasicGet + autoAck=true

 
bgr = MQChannel.BasicGet(strQueueKey, true);
 

问题:

  • autoAck=true → 消息直接确认
  • 如果你处理中断 → 消息丢失
  • 更重要:不会保持消费状态

⚠️ 核心问题 5:你其实根本没“消费”,只是“拉取”

RabbitMQ 正确姿势是:

👉 用 BasicConsume(事件驱动)

而不是:

👉 BasicGet(轮询)

 

 基于源代码2做优化:保留轮询获取数据

✔ 1. 必须开启 Heartbeat(最关键)

Start() 里加:

 
MQFactory.RequestedHeartbeat = TimeSpan.FromSeconds(30);
MQFactory.KeepAlive = true;
 

👉 否则:

  • 只要一段时间没数据
  • TCP idle
  • 服务端直接断连接(你现在就是这个情况)

✔ 2. 开启自动重连(必须)

 
MQFactory.AutomaticRecoveryEnabled = true;
MQFactory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);
 

👉 否则断了之后你这句:

 
if (MQConnection == null || !MQConnection.IsOpen)
 

很多时候感知不到真实断开状态(RabbitMQ 客户端不会立刻标记)


✔ 3. 你的轮询逻辑要“降速”,否则连接会被打死

你现在:

 
if (bgr == null)
break;
 

👉 问题:

    • 外层不断调 MQReceive
    • 实际是高频空轮询
    • RabbitMQ + 网络层会认为你是异常连接

第3点,因为是低频轮询,暂且可以保留,如果轮询高频,需要修改。

✔ 4. 定期“保活”(防止空闲断开)

👉 即使你开了 heartbeat,有些环境(云 / NAT / 防火墙)还是会断

简单粗暴方案(推荐)

加一个定时任务,每 20~30 秒:

 
public void KeepAlive()
{
try
{
if (MQChannel != null && MQChannel.IsOpen)
{
MQChannel.BasicQos(0, 1, false); // 任意轻量操作即可
}
}
catch {}
}
 

👉 或者更简单:

 
var _ = MQConnection.IsOpen;
 

✔ 5. 修复你的连接重建逻辑(隐藏大坑)

你现在:

 
if (MQConnection == null || !MQConnection.IsOpen)
 

👉 问题:

RabbitMQ 有个坑:

连接已经死了,但 IsOpen 还是 true(特别是网络断开)

🔴 问题1:IsOpen 判断仍然不可靠(核心隐患)

你现在:

 
if (MQConnection == null || !MQConnection.IsOpen)
 

👉 这个在 RabbitMQ 里是经典坑

❗ TCP 断了,但 IsOpen == true

💥 结果

  • 你以为连接是好的
  • 实际已经死了
  • 然后:
    • BasicGet 抛异常 ✔(你 catch 了)
    • 但有时候不会立刻抛 ❗ → 直接卡死

✅ 正确修复(必须改)

CreateConnection 改成这样👇(关键点)

 
private void CreateConnection(string strQueueKey)
{
try
{
bool needReconnect = false;

if (MQConnection == null)
{
needReconnect = true;
}
else
{
try
{
// ✅ 关键:触发底层检查
var test = MQConnection.IsOpen;
}
catch
{
needReconnect = true;
}
}

if (needReconnect)
{
MQConnection?.Dispose();
MQConnection = MQFactory.CreateConnection();
LogManager.debug.Info("[MQ连接创建成功]");
}

if (MQChannel == null || !MQChannel.IsOpen)
{
MQChannel?.Dispose();
MQChannel = MQConnection.CreateModel();
MQChannel.QueueDeclare(strQueueKey, false, false, false, null);
}
}
catch (Exception ex)
{
LogManager.debug.Info($"[MQ启动失败][{ex}]");
}
}
 

🔴 问题2:你现在是“被动重连”,缺少主动保活

你现在完全依赖:

👉 BasicGet 出异常 → 才重连

但问题是:

❗ 有一种情况:连接死了,但一直不抛异常

(网络半断、NAT回收、云环境特别常见)


✅ 加一个“轻量保活”(非常重要)

加个函数:

 
public void TickAlive()
{
try
{
if (MQConnection == null)
return;

// ✅ 触发底层 socket 活动
var _ = MQConnection.IsOpen;
}
catch
{
ErrorDispose();
}
}
 

👉 在你的主循环 / 定时器里调用

比如每 10~20 秒:

 
ServiceForMQ.Inst.TickAlive();
 

🔴 问题3:缺少“连接创建限速”(防止雪崩)

你现在:

 
MQConnection = MQFactory.CreateConnection();
 

👉 如果 MQ 挂了:

  • 每次调用都会尝试连接
  • 高并发下 → 疯狂重连风暴

✅ 建议加一个简单限流

 
private DateTime _lastConnTime = DateTime.MinValue;
 

然后:

 
if (needReconnect)
{
if ((DateTime.Now - _lastConnTime).TotalSeconds < 5)
return;

_lastConnTime = DateTime.Now;

MQConnection?.Dispose();
MQConnection = MQFactory.CreateConnection();

LogManager.debug.Info("[MQ连接创建成功]");
}
 
修复后的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BaseDll;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using SysEngine;

namespace RelaySvrAssist
{
    public class MQOuterData
    {
        public int roomid { get; set; } = 0;
        public string lid { get; set; } = "";
    }
    class ServiceForMQ
    {
        #region 单件模式
        private static ServiceForMQ m_instance;
        public static ServiceForMQ Inst
        {
            get
            {
                if (m_instance == null)
                    m_instance = new ServiceForMQ();
                return m_instance;
            }
        }
        private ServiceForMQ()
        {}
        #endregion

        #region 变量属性
        private ServiceBase SvcQueue = new ServiceBase();
        private ConnectionFactory MQFactory = new ConnectionFactory();
        private IConnection MQConnection = null;
        private IModel MQChannel = null;
        private IBasicConsumer MQConsumer = null;
        private DateTime m_connTime = DateTime.MinValue;
        private DateTime _lastConnTime = DateTime.MinValue;

        public string MQHostName { get; set; } = "192.168.9.199";
        public string MQUserName { get; set; } = "admin";
        public string MQUserPswd { get; set; } = "1";
        public int MQHostPort { get; set; } = 5672;
        public string MQEnvironment { get; set; } = "";
        public string MQQueueReceive_Svr { get; set; } = "";
        //中心
        public string MQQueueSend_Svr = "relay.integrate";
        //中转
       
        #endregion

        #region 公共函数
        public void Start(uint iSvrId)
        {
            if (MQConnection != null)
                return;
            MQFactory.HostName = MQHostName;
            MQFactory.UserName = MQUserName;
            MQFactory.Password = MQUserPswd;
            MQFactory.Port = MQHostPort;
            MQFactory.VirtualHost = "/";
            MQFactory.Protocol = Protocols.DefaultProtocol;
            MQFactory.RequestedHeartbeat =30;
            MQFactory.AutomaticRecoveryEnabled = true;
            try
            {
                MQFactory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);
            }
            catch { }
            MQQueueReceive_Svr = "relay.svr." + iSvrId;
            LogManager.debug.Info($"[MQ连接信息][ip地址{MQHostName}][端口号{MQHostPort}][密码{MQUserPswd}][用户名{MQUserName}][服务器id{iSvrId}]");
            LogManager.debug.Info($"[MQ发包队列][{ServiceForMQ.Inst.MQQueueSend_Svr}][收包队列][{ServiceForMQ.Inst.MQQueueReceive_Svr}]");
            CreateConnection(MQQueueReceive_Svr);
          
        }
        public void Stop()
        {
            if (MQConnection == null)
                return;
            MQConnection.Dispose();
            MQConnection = null;
            MQChannel.Dispose();
            MQChannel = null;
        }
        public async Task MQSend(string strQueueKey, LiaoPacket varpacket)
        {
            await SvcQueue.Call(MQSend_Internal, strQueueKey, varpacket).ConfigureAwait(false);
        }
        public async Task MQReceive(NetSession session)
        {
            await SvcQueue.Call(MQReceive_Internal, MQQueueReceive_Svr, session).ConfigureAwait(false);
        }
        #endregion

        #region 私有函数 
        private void CreateConnection(string strQueueKey)
        {
            try
            {
                bool needReconnect = false;

                if (MQConnection == null)
                {
                    needReconnect = true;
                }
                else
                {
                    try
                    {
                        // ✅ 关键:触发底层检查
                        var test = MQConnection.IsOpen;
                    }
                    catch
                    {
                        needReconnect = true;
                    }
                }

                if (needReconnect)
                {
                    if ((DateTime.Now - _lastConnTime).TotalSeconds < 5)
                        return;

                    _lastConnTime = DateTime.Now;
                    MQConnection?.Dispose();
                    MQConnection = MQFactory.CreateConnection();
                    LogManager.debug.Info("[MQ连接创建成功]");
                }

                if (MQChannel == null || !MQChannel.IsOpen)
                {
                    MQChannel?.Dispose();
                    MQChannel = MQConnection.CreateModel();
                    MQChannel.QueueDeclare(strQueueKey, false, false, false, null);
                }
            }
            catch (Exception ex)
            {
                LogManager.debug.Info($"[MQ启动失败][{ex}]");
            }
        }
        private async Task MQReceive_Internal(string strQueueKey, NetSession session)
        {
            try
            {
                CreateConnection(strQueueKey);

                var channel = MQChannel;
                if (channel == null)
                    return;

                while (true)
                {
                    BasicGetResult bgr = null;
                    try
                    {
                        bgr = channel.BasicGet(strQueueKey, true);

                    }
                    catch (Exception ex)
                    {
                        LogManager.debug.Info($"[MQ BasicGet 失败,准备重试]{ex}");
                        await Task.Delay(500);  // 避免疯狂重试
                        break; // 让外层逻辑重新进入 MQReceive 再重建连接
                    }

                    if (bgr == null)
                    {
                        break;
                    }
                    // 1. 先把 byte[] 转成字符串
                    string json = Encoding.UTF8.GetString(bgr.Body.ToArray());
                    MQOuterData data = new MQOuterData();
                
                    if (!string.IsNullOrWhiteSpace(json))
                    {
                        // 尝试解析 JSON,如果失败不会抛异常,data 保持默认值
                        data = JsonConvert.DeserializeObject<MQOuterData>(json) ?? data;
                        LogManager.info.Info(json.Replace('{', '|').Replace('}', '|'));
                        LiaoPacket sendpacket = new LiaoPacket();
                        sendpacket.SetShortInt(10, LiaoCmd.CMD_RE_MQ_GET_MSG);
                        sendpacket.SetString(20, json);
                        sendpacket.SetInt(30, data.roomid);
                        sendpacket.SetString(40, data.lid);
                        session.SendPacket(sendpacket);
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.fatal.Info($"[未知异常]{ex.ToString()}");
                ErrorDispose();
            }
        }
        private async Task MQSend_Internal(string strQueueKey, LiaoPacket varpacket)
        {
            try
            {
                CreateConnection(strQueueKey);

                var channel = MQChannel;
                if (channel == null)
                    return;


                byte[] btsend = new byte[varpacket.DataLen];
                Buffer.BlockCopy(varpacket.DataBuffer, 0, btsend, 0, varpacket.DataLen);
               // LogManager.test.Info($"发送数据包大小{varpacket.DataLen}");
                channel.BasicPublish("", strQueueKey, null, btsend);
            }
            catch (Exception ex)
            {
                LogManager.fatal.Info($"[未知异常]{ex.ToString()}");
                ErrorDispose();
            }
        }
        
        private void ErrorDispose()
        {
            // ✅ 出错强制断开,触发重连
            MQChannel?.Dispose();
            MQChannel = null;

            MQConnection?.Dispose();
            MQConnection = null;
        }
        public void TickAlive()
        {
            try
            {
                if (MQConnection == null)
                    return;

                // ✅ 触发底层 socket 活动
                var _ = MQConnection.IsOpen;
            }
            catch
            {
                ErrorDispose();
            }
        }
        #endregion
    }
}

  

posted @ 2026-04-17 11:41  头号程序媛  阅读(14)  评论(0)    收藏  举报