MassTransit / RabbitMQ-为什么跳过这么多邮件?

时间:2019-05-08 18:14:55

标签: rabbitmq masstransit

我正在使用MassTransit / RabbitMQ在生产者/消费者场景中使用2个.NET Core控制台应用程序。我需要确保即使没有消费者在运行,来自生产者的消息仍然可以成功排队。这似乎不适用于Publish()-消息只是消失了,所以我改用Send()。消息至少要排队,但是没有任何使用者运行这些消息,所有消息最终都会进入“ _skipped”队列。

这是我的第一个问题[strong> :这是基于需求的正确方法(即使没有消费者运行,生产者的消息仍然成功排队)? / p>

使用Send(),我的使用者确实可以工作,但是仍然有很多邮件掉入裂缝并倾倒到“ _skipped”队列中。消费者的逻辑是最小的(此刻仅记录消息),因此它不是一个长期运行的过程。

这是我的第二个问题:为什么仍然有这么多邮件被转储到“ _skipped”队列中?

这又引出了我的第[strong>第三个问题:这是否意味着我的消费者也需要收听“ _skipped”队列?


我不确定该问题需要看什么代码,但这是RabbitMQ管理UI的屏幕截图:

RabbitMQ queues

生产者配置:

    static IHostBuilder CreateHostBuilder(string[] args)
    {
        return Host.CreateDefaultBuilder()
                      .ConfigureServices((hostContext, services) =>
                      {
                          services.Configure<ApplicationConfiguration>(hostContext.Configuration.GetSection(nameof(ApplicationConfiguration)));

                          services.AddMassTransit(cfg =>
                          {
                              cfg.AddBus(ConfigureBus);
                          });

                          services.AddHostedService<CardMessageProducer>();
                      })
                      .UseConsoleLifetime()
                      .UseSerilog();
    }

    static IBusControl ConfigureBus(IServiceProvider provider)
    {
        var options = provider.GetRequiredService<IOptions<ApplicationConfiguration>>().Value;

        return Bus.Factory.CreateUsingRabbitMq(cfg =>
        {
            var host = cfg.Host(new Uri(options.RabbitMQ_ConnectionString), h =>
            {
                h.Username(options.RabbitMQ_Username);
                h.Password(options.RabbitMQ_Password);
            });

            cfg.ReceiveEndpoint(host, typeof(CardMessage).FullName, e =>
            {
                EndpointConvention.Map<CardMessage>(e.InputAddress);
            });
        });
    }

生产者代码:

Bus.Send(message);

消费者配置:

    static IHostBuilder CreateHostBuilder(string[] args)
    {
        return Host.CreateDefaultBuilder()
                      .ConfigureServices((hostContext, services) =>
                      {
                          services.AddSingleton<CardMessageConsumer>();

                          services.Configure<ApplicationConfiguration>(hostContext.Configuration.GetSection(nameof(ApplicationConfiguration)));

                          services.AddMassTransit(cfg =>
                          {
                              cfg.AddBus(ConfigureBus);
                          });

                          services.AddHostedService<MassTransitHostedService>();
                      })
                      .UseConsoleLifetime()
                      .UseSerilog();
    }

    static IBusControl ConfigureBus(IServiceProvider provider)
    {
        var options = provider.GetRequiredService<IOptions<ApplicationConfiguration>>().Value;

        return Bus.Factory.CreateUsingRabbitMq(cfg =>
        {
            var host = cfg.Host(new Uri(options.RabbitMQ_ConnectionString), h =>
            {
                h.Username(options.RabbitMQ_Username);
                h.Password(options.RabbitMQ_Password);
            });

            cfg.ReceiveEndpoint(host, typeof(CardMessage).FullName, e =>
            {
                e.Consumer<CardMessageConsumer>(provider);
            });

            //cfg.ReceiveEndpoint(host, typeof(CardMessage).FullName + "_skipped", e =>
            //{
            //    e.Consumer<CardMessageConsumer>(provider);
            //});
        });
    }

消费者代码:

class CardMessageConsumer : IConsumer<CardMessage>
{
    private readonly ILogger<CardMessageConsumer> logger;
    private readonly ApplicationConfiguration configuration;
    private long counter;

    public CardMessageConsumer(ILogger<CardMessageConsumer> logger, IOptions<ApplicationConfiguration> options)
    {
        this.logger = logger;
        this.configuration = options.Value;
    }

    public async Task Consume(ConsumeContext<CardMessage> context)
    {
        this.counter++;

        this.logger.LogTrace($"Message #{this.counter} consumed: {context.Message}");
    }
}

2 个答案:

答案 0 :(得分:0)

当使用者启动总线bus.Start()时,它要做的一件事就是为传输创建所有交换和队列。如果您要求在使用者之前进行发布/发送,则唯一的选择是运行DeployTopologyOnly。不幸的是,此功能未在官方文档中记录,但单元测试在这里:https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit.RabbitMqTransport.Tests/BuildTopology_Specs.cs

当将消息发送给不知道如何处理的使用者时,将发生跳过的队列。

例如,如果您有一个可以处理IConsumer<MyMessageA>的使用者,该使用者正在接收端点名称“ my-queue-a”。但是您的消息生产者随后进行了Send<MyMessageB>(Uri("my-queue-a")...),这是一个问题。消费者仅了解A,不知道如何处理B。因此,它只是将其移至跳过的队列并继续。

答案 1 :(得分:0)

在MassTransit中,_skipped队列是dead letter queue概念的实现。邮件到达那里是因为它们不会被消耗。

带有RMQ的

MassTransit始终将消息传递到交换,而不是 queue 。默认情况下,每个MassTransit端点都会创建(如果没有现有队列)具有端点名称的队列,具有相同名称的交换并将它们绑定在一起。当应用程序具有配置的使用者(或处理程序)时,还将创建该消息类型的交换(使用消息类型作为交换名),并且端点交换将绑定到消息类型交换。因此,当您使用Publish时,该消息将发布到消息类型交换并使用端点绑定(或多个绑定)进行相应地传递。使用Send时,未使用消息类型交换,因此消息直接到达目标交换。而且,正如@maldworth正确指出的那样,每个 MassTransit端点仅希望获取它可以消费的消息。如果不知道如何使用该消息,则将消息移至死信队列。这以及有害消息队列都是消息传递的基本模式。

如果您需要消息排队等待以后使用,最好的方法是建立连接,但是端点本身(我的意思是应用程序)不应运行。应用程序启动后,将使用所有排队的消息。