【问题标题】:RabbitMQ. Best practice for waiting for next message兔MQ。等待下一条消息的最佳实践
【发布时间】:2018-12-10 13:28:58
【问题描述】:

我在 ASP.NET Core Web 应用程序中有一个接收器方法:

    public void ReceiveMessage()
    {
        using (var connection = CreateConnection())
        using (var channel = connection.CreateModel())
        {
            channel.QueueDeclare(queue: "QueueName",durable: false,exclusive: false,autoDelete: false,arguments: null);

            channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                //Do something

                channel.BasicAck(deliveryTag: ea.DeliveryTag,multiple: false);
            };

            channel.BasicConsume(queue: "QueueName",autoAck: false,consumer: consumer);

            //BAD!!!
            while (true)
            {

            }
        }
    }

你会注意到我正在使用 while(true),它闻起来很糟糕。我基本上需要保持这个方法活着,不知道其他人是怎么做到的?

此方法应始终保持活动状态并自动逐一处理消息

【问题讨论】:

  • 这段代码在什么上下文中运行?控制台应用、WinForms 应用、网站?
  • 这是一个 ASP.NET Core Web 应用
  • while(true) 没有任何问题。你需要在循环中计算一些表达式,但除此之外,你没有错。
  • 您可能会考虑将此接收器分离到它自己的应用程序中,该应用程序与 Web 服务器分开托管。这样您就不会通过监视队列来消耗 Web 服务器资源。这是一个可扩展性问题,对于何时需要发生这种情况有一个很好的规定。最终您决定何时需要这样做,并且可能永远不需要!

标签: c# .net rabbitmq


【解决方案1】:

您可以创建一个托管服务并在其中使用您的消息。此托管服务始终处于活动状态,并且可以接收所有消息。

public class ProductTopicConsumerService : ConsumerBase, IHostedService
{
    public ProductTopicConsumerService(ConnectionFactory connectionFactory)
     : base(connectionFactory, ExchangeTypes.topic)
    {
        try
        {
            Consume<ProductCreatedIntegrationEvent>();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error => {ex?.Message}");
        } 
    }

    protected override string Exchange => "ProductExchangeTopic5";
    protected override string Queue => "Product.Updated5";
    protected override string AppId => "ProductCreatedConsumer";
    protected override string QueueAndExchangeRoutingKey => "Product.*";


    public virtual Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;

    public virtual Task StopAsync(CancellationToken cancellationToken)
    {
        Dispose();
        return Task.CompletedTask;
    }
}

 

ConsumerBase 可能是这样的:

public abstract class ConsumerBase :  RabbitMqClientBase
{
    public ConsumerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
        : base(connectionFactory, exchangeType,false)
    {
    }
    

    protected void Consume<TMessage>()
    {
        var consumer = new AsyncEventingBasicConsumer(Channel);
        consumer.Received += OnEventReceived<TMessage>;
        Channel.BasicConsume(queue: RefinedQueueName, autoAck: false, consumer: consumer);
    }

    private Task OnEventReceived<TMessage>(object sender, BasicDeliverEventArgs @event)
    {
        try
        {
            var body = Encoding.UTF8.GetString(@event.Body.ToArray());
            var message = JsonConvert.DeserializeObject<TMessage>(body);

            ReceiveAction(message);

            Channel.BasicAck(@event.DeliveryTag, false);

            return Task.CompletedTask;

        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error occurred in OnEventReceived. Message: {ex?.Message}");
            Channel.BasicNack(@event.DeliveryTag, false, true);
            throw;
        }
        finally
        {
        }
    }

    protected virtual void ReceiveAction<TMessage>(TMessage message)
    {
         
    }

}

最后,rabbitMQBase 可能是这样的:

 public abstract class RabbitMqClientBase : IDisposable
{
    protected const string VirtualHost = "MQ";
    protected abstract string Exchange { get; }
    protected abstract string Queue { get; }
    protected abstract string AppId { get; }
    protected abstract string QueueAndExchangeRoutingKey { get; }

    protected IModel Channel { get; private set; }
    private IConnection _connection;
    private readonly ConnectionFactory _connectionFactory;
    private readonly ExchangeTypes _exchangeType;
    private readonly bool _isPublisher;

    protected RabbitMqClientBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType, bool isPublisher)
    {            
        _connectionFactory = connectionFactory;
        this._exchangeType = exchangeType;
        this._isPublisher = isPublisher;
        ConnectToRabbitMq();
    }

    protected internal string RefinedExchangeName => $"{VirtualHost}.{Exchange}";
    protected internal string RefinedQueueName => $"{VirtualHost}.{Queue}";
    protected internal string RefinedRoutingKey => $"{VirtualHost}.{QueueAndExchangeRoutingKey}";
    private void ConnectToRabbitMq()
    {
        if (_connection == null || _connection.IsOpen == false)
        {
            _connection = _connectionFactory.CreateConnection();
        }

        if (Channel == null || Channel.IsOpen == false)
        {
            Channel = _connection.CreateModel();
            Channel.ExchangeDeclare(exchange: RefinedExchangeName, type: _exchangeType.ToString(), durable: true, autoDelete: false);
            if (!_isPublisher)
            {
                Channel.QueueDeclare(queue: RefinedQueueName, durable: true, exclusive: false, autoDelete: false);
                Channel.QueueBind(queue: RefinedQueueName, exchange: RefinedExchangeName, routingKey: RefinedRoutingKey);
            }
        }
    }

    public void Dispose()
    {
        Channel?.Close();
        Channel?.Dispose();
        Channel = null;

        _connection?.Close();
        _connection?.Dispose();
        _connection = null;
    }
}

这是 RabbitMQ 的 Publisher|Prodcuer 类:

 public interface IRabbitMqProducer<in T>
{
    PublishResult Publish(T @event);
}

public abstract class ProducerBase<T> : RabbitMqClientBase, IRabbitMqProducer<T>
{

    protected ProducerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
        : base(connectionFactory, exchangeType, true) { }

    public PublishResult Publish(T @event)
    {
        var result = new PublishResult();
        try
        {
            var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(@event));

            var properties = Channel.CreateBasicProperties();
            properties.AppId = AppId;
            properties.ContentType = "application/json";
            properties.DeliveryMode = 2; //persist mode
            properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds());

            Channel.BasicPublish(exchange: RefinedExchangeName, routingKey: RefinedRoutingKey, basicProperties: properties, body: body);

            result.SetSuccess();
        }
        catch (Exception ex)
        {
            result.SetError(ex);
        }
        return result;
    }
}

为了发布你的 publisher 应该是这样的:

 public class ProductTopicProducerService : ProducerBase<ProductCreatedIntegrationEvent>
{

    public ProductTopicProducerService(ConnectionFactory connectionFactory)
        : base(connectionFactory,ExchangeTypes.topic)
    {

    }

    protected override string Exchange => "ProductExchangeTopic5";
    protected override string Queue => "";
    protected override string AppId => "ProductCreatedConsumer";
    protected override string QueueAndExchangeRoutingKey => "Product.*";
}

对于发布新消息,请遵循以下代码:

var result = _producer.Publish(@event);

            if (!result.IsSuccess)
            {
                Console.WriteLine($"Error => {result.Description}");
            }

外包您可以简化您的消费者。但这是我为 rabbitMQBase 创建基类的经验,现在发布者和消费者都可以使用它。

【讨论】:

    【解决方案2】:

    查看 OWIN 包 - 在 NuGet 中可用:Microsoft.Owin.Hosting

    当您使用它时,您将自行托管 Web 服务,并且您的应用程序将首先调用:

    WebApp.Start(queryServiceUrl, Startup);
    

    “启动”是一种进行所有初始化的方法。您的应用/服务会继续运行,并将接受对指定 URL 的查询。

    【讨论】:

    • 嗨拍卖。我已经用 appLifetime.ApplicationStarted.Register(() => {CallReceiveMessage} 做了类似的事情。问题是我必须继续在 ReceiveMessage 内旋转,就好像我不这样做一样,连接和通道将超出范围,我赢了'不再收到任何消息
    • 很难看出这个答案与问题有什么关系。
    【解决方案3】:

    如果没有循环,在您调用 channel.BasicConsume 之后的那一刻,整个事物(连接/通道)将超出范围并通过 using 语句立即处理/销毁。因此,如果没有循环,您的消费者实际上不会消费任何东西。

    为确保消费者运行,您需要有一个无限循环,并在您关闭程序时退出适当的逻辑。这是 RabbitMQ .NET 库的不幸设计,但它就是这样。

    while (_isRunning & channel.IsOpen) {
        Thread.Sleep(1);
        // Other application logic here; e.g. periodically break out of the
        // loop to prevent unacknowledged messages from accumulating in the system
        // (if you don't, random effects will guarantee that they eventually build up)
    }
    

    【讨论】:

    • 感谢您的建议。你有一些关于这个的示例代码吗?或者一篇关于“随机影响”的文章或关于何时“定期爆发……”的最佳实践。我真的很感激任何其他可能有帮助的东西
    • 嗯,定期跳出循环并不是那么难,我想你只想每隔几个小时做一次。我只是注意到,未确认的消息会在一段时间后排队,这对它们有所帮助——有点像自动清除。消息将返回队列并重新处理(或最初处理,视情况而定)。
    猜你喜欢
    • 2013-02-02
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-07
    • 2021-01-31
    相关资源
    最近更新 更多