本人在rabbitmq 创建连接时都将Connection创建为永久连接将modle 创建为瞬时,或者将两者都创建为瞬时的使用完直接关闭,当再次使用时在进行创建,类似于这样的写法
string queueName = "normal";
using (var connection = RabbitMQHelper.GetConnection()) { // 创建信道 using(var channel = connection.CreateModel()) { // 创建队列 channel.QueueDeclare(queue:queueName,durable: false, false, false, null); // 没有绑定交换机,怎么找到路由队列的呢? while (true) { string message = "Hello RabbitMQ Message"; var body = Encoding.UTF8.GetBytes(message); // 发送消息到rabbitmq,使用rabbitmq中默认提供交换机路由,默认的路由Key 和队列名称完全一致 channel.BasicPublish(exchange: "", routingKey: queueName, null, body); Thread.Sleep(1000); Console.WriteLine("Send Normal message"); }
} } |
用using 将需要回收的对象包含起来这样运行完代码就会自动关闭连接,但这样做对于服务器的消耗是巨大的因为每一次发送消息或者接受消息都需要重新连接一次,同时如果消息过多会导致连接数用尽程序爆炸,所以在mq 官方文档里有这样一句话
连接应该是长期存在的。底层协议的设计和优化是针对 长时间运行的连接。这意味着每个操作打开一个新连接, 例如,发布的消息是不必要的,强烈建议不要这样做,因为它会引入很多 网络往返和开销。
|
那么好当我看到这句话时我就讲conntion连接改为全局的一个程序改为一个连接这样就用每次发送消息就重新连接了 改完后类似于如下代码
/// <summary> /// 根据hostName获取单个RabbitMQ连接 /// </summary> /// <returns></returns> public static IConnection GetConnection(string hostName,int port) { var factory = new ConnectionFactory { HostName = hostName, //ip Port = port, // 端口 UserName = "guest", // 账户 Password = "guest", // 密码 VirtualHost = "/" // 虚拟主机 };
return factory.CreateConnection(); }
public static void SendMessage() { var exchangeA = "changeA"; var routeA = "routeA"; var queueA = "queueA";
var exchangeD = "changeD"; var routeD = "routeD"; var queueD = "queueD";
using (var connection = RabbitMQHelper.GetConnection()) {
///注意这里的写法 using(var channel = connection.CreateModel()) { channel.ExchangeDeclare(exchangeD, type: "fanout", durable: true, autoDelete: false); channel.QueueDeclare(queueD, durable: true, exclusive: false, autoDelete: false); channel.QueueBind(queueD, exchangeD, routeD);
channel.ExchangeDeclare(exchangeA, type: "fanout", durable: true, autoDelete: false); channel.QueueDeclare(queueA, durable: true, exclusive: false, autoDelete: false, arguments: new Dictionary<string, object> { { "x-dead-letter-exchange",exchangeD}, //设置当前队列的DLX { "x-dead-letter-routing-key",routeD}, //设置DLX的路由key,DLX会根据该值去找到死信消息存放的队列 { "x-message-ttl",10000} //设置消息的存活时间,即过期时间 }); channel.QueueBind(queueA, exchangeA, routeA);
var properties = channel.CreateBasicProperties(); properties.Persistent = true; //发布消息 channel.BasicPublish(exchange: exchangeA, routingKey: routeA, basicProperties: properties, body: Encoding.UTF8.GetBytes("message")); } }
}
|
在改为上述方式之后,连接数不会暴涨了,程序也运行正常了,我以为也就这样了,其实当消息过多,同时消费者处理能力不足的时候,弊端也就显现出来了,这也是我们当前线上出现的一个非常隐晦的问题,我的服务器是当收到一条消息之后就会给客户端回执一条消息,当消息队列消息过多,我想提高消费者处理消息的速度,于是我给消费这增加了处理消息的线程大概在十个线程那增加线程消费的处理能力是高了,于此同时回执消息也增加了,但是紧接着程序就报错了显示 mq连接失败
Error on AMQP connection,或者
.CreateModel() 上抛出 TimeOut 异常
也就con是连接失败
//using (model = connection.CreateModel()) 这一句开始报错 |
|
问题分析
网上大多数都是说用户名密码或者权限链接地址不对,其实不是,如果要不对那程序刚开始运行的时候就应该直接报错连接不上的,所以问题不在这,仔细看这句报错的代码,那我之前说过当我消息增加并且增加了处理消息的线程之后才出现了这个问题,那我增加了线程我这个 Createmodel也是会随着线程增加的,如果再加上using代码块内的代码运行速度不快那这个创建的modle是不会回收太快的也就是说,这个model创建的速度比销毁的速度要快很多,所以这是不是导致报错的原因呢(因为我查看了mq的日志没有任何有用的信息,这个猜想也是我解决完问题才明白的)然后我去看了mq的文档里边有这样一句至关重要的话
通道也意味着长期存在,但由于许多可恢复的协议错误将 导致通道关闭,通道寿命可能短于其连接寿命。 每个操作关闭和打开新通道通常是不必要的,但可以 适当。如有疑问,请考虑先重复使用频道。 通道级异常,例如尝试从 不存在的队列将导致通道关闭。封闭的通道不能 更长的使用时间,并且不 会再从服务器接收任何事件(例如 作为消息传递)。通道级异常将由 RabbitMQ 记录 并将启动通道的关断序列
|
也就是说创建mq的连接并不是在得到conntion全局连接之后每次使用modle都需要销毁,只有通道出现问题时才需要销毁,而且官方建议通道要长期存在
所以将连接改成如下样子
public static ConnectionFactory factory; public static IConnection createconnection; public static IModel model; static RabbitFactoryPwd() { factory = new ConnectionFactory {
HostName = "localhost", Port = 5672, // 端口 UserName = "guest", // 账户 Password = "guest", // 密码 VirtualHost = "/", // 虚拟主机 RequestedConnectionTimeout = TimeSpan.FromSeconds(10000),
}; createconnection = factory.CreateConnection(); model = createconnection.CreateModel(); } |
这样在多线程处理多个消息并且回执的时候mq就不会报错了速度也非常快乐,当然这样还是不够的,如果mq服务突然崩了,那我们还需要一个重连机制