RabbitMQ。等待下一条消息的最佳做法

时间:2018-12-10 13:28:58

标签: c# .net rabbitmq

我在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),它闻起来很不好。我基本上需要保持这种方法的生命,并想知道其他人是怎么做到的?

此方法应始终保持活动状态,并自动按1对1的顺序处理消息

3 个答案:

答案 0 :(得分:0)

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

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;
    }
}

发布您的发布商应该是这样的:

 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 创建基类的经验,现在发布者和消费者都可以使用它。

答案 1 :(得分:-1)

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

使用它时,您将自托管Web服务,并且您的应用程序将以以下调用开头:

WebApp.Start(queryServiceUrl, Startup);

“启动”是执行所有初始化的一种方法。您的应用/服务保持运行状态,并且将接受对指定URL的查询。

答案 2 :(得分:-1)

在没有循环的情况下,调用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)
}