RabbitMQ C#驱动程序停止接收消息

时间:2012-09-19 16:42:49

标签: c# rabbitmq

您是否有任何指示如何确定订阅问题何时发生,以便重新连接?

我的服务使用RabbitMQ.Client.MessagePatterns.Subscription进行订阅。一段时间后,我的客户端默默地停止接收消息。我怀疑网络问题,因为我的VPN连接不是最可靠的。

我已阅读文档一段时间寻找密钥,以了解此订阅何时可能因网络问题而无法运气而被破坏。我已经尝试检查连接和频道是否仍然打开,但似乎总是报告它仍处于打开状态。

它确实处理的消息工作得很好并且被确认回队列,因此我认为这不是“ack”的问题。

我确信我一定只是错过了一些简单的东西,但我还没有找到它。

public void Run(string brokerUri, Action<byte[]> handler)
{
    log.Debug("Connecting to broker: {0}".Fill(brokerUri));
    ConnectionFactory factory = new ConnectionFactory { Uri = brokerUri };

    using (IConnection connection = factory.CreateConnection())
    {
        using (IModel channel = connection.CreateModel())
        {
            channel.QueueDeclare(queueName, true, false, false, null);

            using (Subscription subscription = new Subscription(channel, queueName, false))
            {
                while (!Cancelled)
                {
                    BasicDeliverEventArgs args;

                    if (!channel.IsOpen)
                    {
                        log.Error("The channel is no longer open, but we are still trying to process messages.");
                        throw new InvalidOperationException("Channel is closed.");
                    }
                    else if (!connection.IsOpen)
                    {
                        log.Error("The connection is no longer open, but we are still trying to process message.");
                        throw new InvalidOperationException("Connection is closed.");
                    }

                    bool gotMessage = subscription.Next(250, out args);

                    if (gotMessage)
                    {
                        log.Debug("Received message");
                        try
                        {
                            handler(args.Body);
                        }
                        catch (Exception e)
                        {
                            log.Debug("Exception caught while processing message. Will be bubbled up.", e);
                            throw;
                        }

                        log.Debug("Acknowledging message completion");
                        subscription.Ack(args);
                    }
                }
            }
        }
    }
}

更新:

我通过在虚拟机中运行服务器来模拟网络故障,当我断开连接时,我获得异常(RabbitMQ.Client.Exceptions.OperationInterruptedException:AMQP操作被中断)足够长,所以也许这不是一个网络问题。现在我不知道它会是什么但是在运行几个小时之后就失败了。

1 个答案:

答案 0 :(得分:54)

编辑:由于我已经开始投票了,我应该指出.NET RabbitMQ客户端现在内置了这个功能:https://www.rabbitmq.com/dotnet-api-guide.html#connection-recovery

理想情况下,您应该能够使用它并避免手动实现重新连接逻辑。


我最近不得不实施几乎相同的事情。据我所知,RabbitMQ上的大多数可用信息都假定您的网络非常可靠,或者您在与任何发送或接收消息的客户端相同的计算机上运行RabbitMQ代理,从而允许Rabbit处理任何连接问题。 / p>

设置Rabbit客户端以防止断开连接并不是那么难,但是您需要处理一些特性。

你需要做的第一件事就是打开心跳:

ConnectionFactory factory = new ConnectionFactory() 
{
  Uri = brokerUri,
  RequestedHeartbeat = 30,
}; 

如果连接仍然存在,则将“RequestedHeartbeat”设置为30将使客户端每30秒检查一次。如果没有打开它,消息订阅者将愉快地坐在那里等待另一条消息进来,而不知道它的连接已经坏了。

打开心跳也会使服务器检查连接是否仍然正常,这可能非常重要。如果在订阅者收到消息之后但在确认消息之前连接变坏,则服务器只是假定客户端花费很长时间,并且消息在死连接上“卡住”直到它被关闭。在心跳开启的情况下,服务器将识别连接变坏并关闭它,将消息放回队列中,以便其他用户可以处理它。如果没有心跳,我必须手动进入并关闭Rabbit管理UI中的连接,以便将卡住的消息传递给订阅者。

其次,您需要处理OperationInterruptedException。正如您所注意到的,这通常是Rabbit客户端在注意到连接被中断时将抛出的异常。如果在连接中断时调用IModel.QueueDeclare(),则这是您将获得的异常。处理此例外处理您的订阅,渠道和连接并创建新的例外。

最后,您必须处理消费者在尝试使用已关闭连接中的消息时所执行的操作。不幸的是,从Rabbit客户端队列中消费消息的每种不同方式似乎都有不同的反应。如果您在已关闭的连接上致电QueueingBasicConsumer,则EndOfStreamException会抛出QueueingBasicConsumer.Queue.DequeueEventingBasicConsumer什么都不做,因为它只是在等待消息。根据我的尝试,您正在使用的Subscription类似乎从调用Subscription.Next返回true,但args的值为null。再一次,通过处理您的连接,通道和订阅并重新创建它们来处理它。

如果连接因心跳开启而失败,connection.IsOpen的值将更新为False,因此您可以根据需要进行检查。但是,由于心跳在一个单独的线程上运行,您仍需要处理检查时连接打开的情况,但在调用subscription.Next()之前关闭。

最后要注意的是IConnection.Dispose()。如果在连接关闭后调用dispose,此调用将抛出EndOfStreamException。这对我来说似乎是一个错误,我不喜欢不在IDisposable对象上调用dispose,所以我调用它并吞下异常。

将这一切放在一个快速而肮脏的例子中:

public bool Cancelled { get; set; }

IConnection _connection = null;
IModel _channel = null;
Subscription _subscription = null;

public void Run(string brokerUri, string queueName, Action<byte[]> handler)
{
    ConnectionFactory factory = new ConnectionFactory() 
    {
        Uri = brokerUri,
        RequestedHeartbeat = 30,
    };

    while (!Cancelled)
    {               
        try
        {
            if(_subscription == null)
            {
                try
                {
                    _connection = factory.CreateConnection();
                }
                catch(BrokerUnreachableException)
                {
                    //You probably want to log the error and cancel after N tries, 
                    //otherwise start the loop over to try to connect again after a second or so.
                    continue;
                }

                _channel = _connection.CreateModel();
                _channel.QueueDeclare(queueName, true, false, false, null);
                _subscription = new Subscription(_channel, queueName, false);
            }

            BasicDeliverEventArgs args;
            bool gotMessage = _subscription.Next(250, out args);
            if (gotMessage)
            {
                if(args == null)
                {
                    //This means the connection is closed.
                    DisposeAllConnectionObjects();
                    continue;
                }

                handler(args.Body);
                _subscription.Ack(args);
            }
        }
        catch(OperationInterruptedException ex)
        {
            DisposeAllConnectionObjects();
        }
    }
    DisposeAllConnectionObjects();
}

private void DisposeAllConnectionObjects()
{
    if(_subscription != null)
    {
        //IDisposable is implemented explicitly for some reason.
        ((IDisposable)_subscription).Dispose();
        _subscription = null;
    }

    if(_channel != null)
    {
        _channel.Dispose();
        _channel = null;
    }

    if(_connection != null)
    {
        try
        {
            _connection.Dispose();
        }
        catch(EndOfStreamException) 
        {
        }
        _connection = null;
    }
}