您是否有任何指示如何确定订阅问题何时发生,以便重新连接?
我的服务使用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操作被中断)足够长,所以也许这不是一个网络问题。现在我不知道它会是什么但是在运行几个小时之后就失败了。
答案 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.Dequeue
。 EventingBasicConsumer
什么都不做,因为它只是在等待消息。根据我的尝试,您正在使用的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;
}
}