记录几个RabbitMQ使用过程中容易踩的那些坑:
1、启用ack机制后,没有及时ack导致的队列异常;
简要代码如下,设置消息自动ack,会导致消息未处理完,出异常了,结果消息丢失了,
解决方法就是把代码里的true,改成false,并在消息处理完后发ack响应。
// 要监听队列,所以不能用using关闭channel通道
var channel = GetChannel();
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue, true, consumer); // 消息自动ack
2、启用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);
((EventingBasicConsumer) sender).Model.BasicNack(e.DeliveryTag, false, true);
}
catch (Exception exp)
{
LogHelper.Info(exp);
}
};
这段代码中,先处理消息,完成后,再做ack响应,失败就不做ack响应,这样消息会储存在MQ的Unacked消息里,不会丢失,看起来没啥问题,
但是有一次,callback触发了一个bug,导致所有消息都抛出异常,然后队列的Unacked消息数暴涨,导致MQ响应越来越慢,甚至崩溃的问题。
原因是如果RabbitMQ没有得到ack响应,这些消息会堆积在Unacked消息里,不会抛弃,
而且如果Consumer不断开连接,这些Unacked消息,也不会变回ready的消息,重新推送,
消息多了,占用内存越来越大,就会异常了。
解决办法就是及时去ack消息了
3、启用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.BasicNack(e.DeliveryTag, false, true);
}
catch (Exception exp)
{
LogHelper.Info(exp);
// 出错了,自动把消息塞回RabbitMQ的队列头部(不是尾部)
((EventingBasicConsumer) sender).Model.BasicNack(e.DeliveryTag, false, true);
}
};
嗯,改成这模样总没问题了吧,正常就ack,不正常就nack,并等下一次重新消费。
果然,又出问题了,这回又是callback出异常了,但是故障现象是Ready的消息猛增,一直不见减少。
原因是出异常后,把消息塞回队列头部,下一步又消费这条会出异常的消息,又出错,塞回队列……
进入了死循环了,当然新的消息不会消费,导致堆积了……
这个咋办?只能不用nack,所有消息都ack,自己记录日志,后续走其它job恢复日志了。
4、启用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.BasicNack(e.DeliveryTag, false, true);
}
catch (Exception exp)
{
LogHelper.Info(exp);
}
};
这段代码中,由于开启了QoS,当RabbitMQ的队列达到5条Unacked消息时,不会再推送消息给Consumer,
如果回调函数出异常了,就会导致消息无法ack,从而导致无法继续处理后续的消息。
你问解决办法?当然是参考问题4,全部消息都去做ack响应呗。
5、消费者串行处理,崩溃时导致未处理的预取数据丢失;
在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);
};
6、心跳时间设置太短导致的异常;
RequestedHeartbeat要设置为5~20秒,我的项目中默认是设置为10秒
具体问题和解决,请参考:https://blog.csdn.net/youbl/article/details/79024061