RabbitMQ的ack或nack机制使用不当导致的队列堵塞或死循环问题

记录几个RabbitMQ使用过程中容易踩的那些坑:

1、自动ack机制会导致消息丢失的问题;

简要代码如下,设置消息自动ack,这种情况下,MQ只要确认消息发送成功,无须等待应答就会丢弃消息,
这会导致客户端还未处理完时,出异常或断电了,导致消息丢失的后果,
解决方法就是把代码里的true,改成false,并在消息处理完后发ack响应。

// 要监听队列,所以不能用using关闭channel通道
var channel = GetChannel();
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue, true, consumer); // 消息自动ack

注:自动ack还有个弊端,只要队列不空,RabbitMQ会源源不断的把消息推送给客户端,而不管客户端能否消费的完

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);
        // 无异常时,发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消息了

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.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。

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.BasicAck(e.DeliveryTag, false);
    }
    catch (Exception exp)
    {
        LogHelper.Info(exp);
    }
};

这段代码中,由于开启了QoS,当RabbitMQ的队列达到5条Unacked消息时,不会再推送消息给Consumer,
如果回调函数出异常了,就会导致消息无法ack,从而导致无法继续处理后续的消息。
你问解决办法?当然是参考问题3,全部消息都去做ack响应呗(异常里也用Ack,而不用Nack)。

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

  • 12
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
RabbitMQ 是一个流行的消息队列实现,它提供了一种异步通信机制,让不同的应用程序之间可以通过消息传递进行交互。在 RabbitMQ 中,生产者将消息发送到队列中,消费者则从队列中取出并处理这些消息。 消息确认机制RabbitMQ 中的一个重要特性,它确保了消息的可靠性传递和处理。在 RabbitMQ 中,有两种消息确认机制:基本确认(basic.ack)和基本拒绝(basic.nack)。 基本确认(basic.ack机制是指,当消费者从队列中取出一条消息并成功处理后,它可以向 RabbitMQ 发送一个确认消息,告诉 RabbitMQ 这条消息已经被成功处理了,可以从队列中删除。这个确认消息可以让 RabbitMQ 确保消息被正确地处理了。 基本拒绝(basic.nack机制是指,当消费者无法处理某条消息时,它可以向 RabbitMQ 发送一个拒绝消息,告诉 RabbitMQ 这条消息无法被处理,并希望 RabbitMQ 将其重新发送到队列中。这个拒绝消息可以让 RabbitMQ 重新将消息发送到队列中,等待其他消费者进行处理。 此外,还有一种中间状态:基本返回(basic.return)。当消费者从队列中取出一条消息,但无法将消息传递给目标消费者时,RabbitMQ 可以将消息返回给生产者。生产者可以选择忽略这些返回的消息,或者将其重新发送到队列中,等待下一轮传递。 通过这些机制RabbitMQ 可以确保消息的可靠性传递和处理。当消费者无法处理消息时,RabbitMQ 可以将其重新发送到队列中,等待其他消费者进行处理;当消息被成功处理时,RabbitMQ 可以将其从队列中删除,确保消息只被处理一次。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游北亮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值