rabbitmq 开发过程中会出现的问题

目录

rabbitmq_坑

None of the specified endpoints were reachable

异常:

1、unable to connect to node rabbit@10: nodedown

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

3、Rabbitmq实际数据文件、日志文件、配置文件路径

RabbitMQ消息丢失以及解决策略

消息丢失

RabbitMQ流程

producer端

RabbitMQ Server端

Consumer端

消息乱序

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

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

启用ack机制后,没有及时ack导致的队列异常;

启用nack机制后,导致的死循环

启用Qos和ack机制后,没有及时ack导致的队列堵塞;

消费者串行处理,崩溃时导致未处理的预取数据丢失;

心跳时间设置太短导致的异常;


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:发生消息丢失的三种情况:

  1. producer端:发送消息过程中出现网络问题:producer以为发送成功,但RabbitMQ server没有收到;
  2. RabbitMQ server 端:接收到消息后由于服务器宕机或其他原因(消息默认存在内存中)导致消息丢失;
  3. Consumer端:Consumer端接收到消息后处理消息出错,没有完成消息的处理;

producer端

解决方式:

  1. 事务:在生产者发送数据之前开启rabbitmq事务(channel.txSelect),然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。
    • 缺点:耗费性能,效率低。
  2. 回执确认:开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
    区别:
    事务是同步的,会发生阻塞;
    confirm是异步的,效率更高;

RabbitMQ Server端

解决方式:

  • 消息持久化
    • RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。

要想做到消息持久化,必须满足以下三个条件,缺一不可。

  1. Exchange 设置持久化:durable:true
  2. Queue 设置持久化:durable:true
  3. Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息

Consumer端

解决方法:

  • ACK确认机制:
    • 多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?
      这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。
      这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。
      具体步骤:
  1. 交换机调用basicQos(1)方法:告诉RabbitMQ 不采用以前那种实现分配好的模式进行消息推送,而是每次只推送一条。
  2. 调用basicAck()方法手动回复ack消息
  3. 调用basicconsume()方法时将第二个参数写为false(开启手动ACK)
  4. mq收到ack消息会删除当前的消息。没收到会等待一段时间然后将重新发送该消息。

消息重复

原因

  1. 消费者端在消费完成后,手动ack发出,但是由于网络原因mqServer没有收到。so mq保留此条消息而后被重复消费。
  2. 生产者端发送消息,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

猜你喜欢

转载自blog.csdn.net/fangyu723/article/details/107911676