目录
None of the specified endpoints were reachable
1、unable to connect to node rabbit@10: nodedown
RabbitMQ的ack或nack机制使用不当导致的队列堵塞或死循环问题
rabbitmq_坑
None of the specified endpoints were reachable
这个异常在创建连接时抛出(CreateConnection()),原因一般是ConnectionFactory参数设置不对,比如HostName、UserName、Password
标准设置:
var factory = new ConnectionFactory();
factory.UserName = QueueSetttiong.UserName; //用户名
factory.Password = QueueSetttiong.Password; //密码
factory.HostName = QueueSetttiong.HostName; //Rabbitmq服务IP,不包含端口
factory.Port = AmqpTcpEndpoint.UseDefaultPort;
factory.VirtualHost = QueueSetttiong.VirtualHost; //默认为"/"
factory.Protocol = Protocols.DefaultProtocol;
部署生产后,factory配置都ok,但是还是抛异常None of the specified endpoints were reachable,最后发现原因是生产机器防火墙未打开RabbitMQ的端口,RabbitMQ的默认端口是:5672
另外一个可能的原因:未设置VirtualHost的权限
设置方法:RabbitmqWeb管理网站-->Admin
未设置权限时:
设置权限:(点击admin进入设置页面)
异常:
1、unable to connect to node rabbit@10: nodedown
重新安装Rabbitmq服务端即可,有时候需要重启
2、Message:Already closed: The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=320, text="CONNECTION_FORCED - broker forced connection closure with reason 'shutdown'", classId=0, methodId=0, cause= StackTrace
原因:队列服务端重启后,队列发布端断开了接口,无法再次发送消息
解决:发送端设置断开连接后自动启动属性,默认为断开后每隔五秒钟重试连接
var factory = new ConnectionFactory();
factory.AutomaticRecoveryEnabled = true; //设置端口后自动恢复连接属性即可
3、Rabbitmq实际数据文件、日志文件、配置文件路径
地址栏输入:%APPDATA%\RabbitMQ\
C:\Users\Administrator\AppData\Roaming\RabbitMQ
信息来源:C:\Program Files (x86)\RabbitMQ Server\rabbitmq_server-3.5.1\etc 中的README.txt
RabbitMQ消息丢失以及解决策略
消息丢失
RabbitMQ流程
producer——》RabbitMQ——》consumer
SO:发生消息丢失的三种情况:
- producer端:发送消息过程中出现网络问题:producer以为发送成功,但RabbitMQ server没有收到;
- RabbitMQ server 端:接收到消息后由于服务器宕机或其他原因(消息默认存在内存中)导致消息丢失;
- Consumer端:Consumer端接收到消息后处理消息出错,没有完成消息的处理;
producer端
解决方式:
- 事务:在生产者发送数据之前开启rabbitmq事务(channel.txSelect),然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。
- 缺点:耗费性能,效率低。
- 回执确认:开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
区别:
事务是同步的,会发生阻塞;
confirm是异步的,效率更高;
RabbitMQ Server端
解决方式:
- 消息持久化
- RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。
要想做到消息持久化,必须满足以下三个条件,缺一不可。
- Exchange 设置持久化:durable:true
- Queue 设置持久化:durable:true
- Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
Consumer端
解决方法:
- ACK确认机制:
- 多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?
这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。
这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。
具体步骤:
- 多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?
- 交换机调用basicQos(1)方法:告诉RabbitMQ 不采用以前那种实现分配好的模式进行消息推送,而是每次只推送一条。
- 调用basicAck()方法手动回复ack消息
- 调用basicconsume()方法时将第二个参数写为false(开启手动ACK)
- mq收到ack消息会删除当前的消息。没收到会等待一段时间然后将重新发送该消息。
- 消费者端在消费完成后,手动ack发出,但是由于网络原因mqServer没有收到。so mq保留此条消息而后被重复消费。
- 生产者端发送消息,mqServer收到消息返回回执确认同样因为网络等原因生产者端并没有收到。再次发送同一条消息。
消息乱序
比如说生产者发送了两条消息:消息一:添加一条数据;消息二:修改这条数据;然而mq收到消息后发给消费者的顺序是不能保证的。有可能消费者先收到消息二;
- so 如何解决呢?
- 很简单:将这种带有先后顺序的消息发送到同一个队列中,这样它的顺序是肯定的。
RabbitMQ的ack或nack机制使用不当导致的队列堵塞或死循环问题
自动ack机制会导致消息丢失的问题;
简要代码如下,设置消息自动ack,这种情况下,MQ只要确认消息发送成功,无须等待应答就会丢弃消息,
这会导致客户端还未处理完时,出异常或断电了,导致消息丢失的后果,
解决方法就是把代码里的true,改成false,并在消息处理完后发ack响应。
// 要监听队列,所以不能用using关闭channel通道
var channel = GetChannel();
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue, true, consumer); // 消息自动ack
注:自动ack还有个弊端,只要队列不空,RabbitMQ会源源不断的把消息推送给客户端,而不管客户端能否消费的完
启用ack机制后,没有及时ack导致的队列异常;
为了解决问题1,做了改进,简要代码如下:
var channel = GetChannel();
var consumer = new EventingBasicConsumer(channel);
// 开启acknowledge机制,在接收事件里ack
channel.BasicConsume(queue, false, consumer);
consumer.Received += (sender, e) =>
{
try
{
callback(e.Body, e.BasicProperties.Headers);
// 无异常时,发ack通知mq丢弃消息
((EventingBasicConsumer)sender).Model.BasicAck(e.DeliveryTag, false);
}
catch (Exception exp)
{
LogHelper.Info(exp);
}
};
这段代码中,先处理消息,完成后,再做ack响应,失败就不做ack响应,这样消息会储存在MQ的Unacked消息里,不会丢失,看起来没啥问题,
但是有一次,callback触发了一个bug,导致所有消息都抛出异常,然后队列的Unacked消息数暴涨,导致MQ响应越来越慢,甚至崩溃的问题。
原因是如果MQ没得到ack响应,这些消息会堆积在Unacked消息里,不会抛弃,直至客户端断开重连时,才变回ready;
如果Consumer客户端不断开连接,这些Unacked消息,永远不会变回ready状态,Unacked消息多了,占用内存越来越大,就会异常了。
解决办法就是及时去ack消息了
启用nack机制后,导致的死循环
为了解决问题2,再调整一下代码,简要代码如下:
var channel = GetChannel();
var consumer = new EventingBasicConsumer(channel);
// 开启acknowledge机制,在接收事件里ack
channel.BasicConsume(queue, false, consumer);
consumer.Received += (sender, e) =>
{
try
{
callback(e.Body, e.BasicProperties.Headers);
((EventingBasicConsumer) sender).Model.BasicAck(e.DeliveryTag, false);
}
catch (Exception exp)
{
LogHelper.Info(exp);
// 出错了,发nack,并通知MQ把消息塞回的队列头部(不是尾部)
((EventingBasicConsumer) sender).Model.BasicNack(e.DeliveryTag, false, true);
}
};
嗯,改成这模样总没问题了吧,正常就ack,不正常就nack,并等下一次重新消费。
果然,又出问题了,这回又是callback出异常了,但是故障现象是Ready的消息猛增,一直不见减少。
原因是出异常后,把消息塞回队列头部,下一步又消费这条会出异常的消息,又出错,塞回队列……
进入了死循环了,当然新的消息不会消费,导致堆积了……
这个咋办?只能不用nack,所有消息都ack,自己记录日志,后续走其它job恢复日志了。
就是把catch里的BasicNack改成BasicAck。
启用Qos和ack机制后,没有及时ack导致的队列堵塞;
这个问题跟前面的3个没啥联系,简要代码如下:
var channel = GetChannel();
// 启用QoS,每次预取5条消息,避免消息处理不过来,全部堆积在本地缓存里
channel.BasicQos(0, 5, false);
var consumer = new EventingBasicConsumer(channel);
// 开启acknowledge机制,在接收事件里ack,配合qos进行流控
channel.BasicConsume(queue, false, consumer);
consumer.Received += (sender, e) =>
{
try
{
callback(e.Body, e.BasicProperties.Headers);
((EventingBasicConsumer) sender).Model.BasicAck(e.DeliveryTag, false);
}
catch (Exception exp)
{
LogHelper.Info(exp);
}
};
这段代码中,由于开启了QoS,当RabbitMQ的队列达到5条Unacked消息时,不会再推送消息给Consumer,
如果回调函数出异常了,就会导致消息无法ack,从而导致无法继续处理后续的消息。
你问解决办法?当然是参考问题3,全部消息都去做ack响应呗(异常里也用Ack,而不用Nack)。
消费者串行处理,崩溃时导致未处理的预取数据丢失;
在RabbitMQ的.Net 3.6.9版本驱动里,不支持异步处理消息的方法,如果预取了10条消息,这10条消息会在本地缓存里,一条一条串行处理,效率比较低下,
在5.0以后的驱动里添加了AsyncEventingBasicConsumer类的支持,
但是我们还在用3.6.9,只能自己去用多线程搞了,简要代码如下:
var channel = GetChannel();
// 启用QoS,每次预取5条消息,避免消息处理不过来,全部堆积在本地缓存里
channel.BasicQos(0, 5, false);
var consumer = new EventingBasicConsumer(channel);
// 开启acknowledge机制,在接收事件里ack,配合qos进行流控
channel.BasicConsume(queue, false, consumer);
consumer.Received += (sender, e) =>
{
ThreadPool.UnsafeQueueUserWorkItem(state => {
try
{
callback(e.Body, e.BasicProperties.Headers);
}
catch (Exception exp)
{
LogHelper.Info(exp);
}
finally
{
((EventingBasicConsumer) sender).Model.BasicAck(e.DeliveryTag, false);
}
}, null);
};
心跳时间设置太短导致的异常;
RequestedHeartbeat要设置为5~20秒,我的项目中默认是设置为10秒
具体问题和解决,请参考:https://blog.csdn.net/youbl/article/details/79024061