我正在使用MassTransit / RabbitMQ在生产者/消费者场景中使用2个.NET Core控制台应用程序。我需要确保即使没有消费者在运行,来自生产者的消息仍然可以成功排队。这似乎不适用于Publish()-消息只是消失了,所以我改用Send()。消息至少要排队,但是没有任何使用者运行这些消息,所有消息最终都会进入“ _skipped”队列。
这是我的第一个问题[strong> :这是基于需求的正确方法(即使没有消费者运行,生产者的消息仍然成功排队)? / p>
使用Send(),我的使用者确实可以工作,但是仍然有很多邮件掉入裂缝并倾倒到“ _skipped”队列中。消费者的逻辑是最小的(此刻仅记录消息),因此它不是一个长期运行的过程。
这是我的第二个问题:为什么仍然有这么多邮件被转储到“ _skipped”队列中?
这又引出了我的第[strong>第三个问题:这是否意味着我的消费者也需要收听“ _skipped”队列?
我不确定该问题需要看什么代码,但这是RabbitMQ管理UI的屏幕截图:
生产者配置:
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}");
}
}
答案 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概念的实现。邮件到达那里是因为它们不会被消耗。
MassTransit始终将消息传递到交换,而不是 queue 。默认情况下,每个MassTransit端点都会创建(如果没有现有队列)具有端点名称的队列,具有相同名称的交换并将它们绑定在一起。当应用程序具有配置的使用者(或处理程序)时,还将创建该消息类型的交换(使用消息类型作为交换名),并且端点交换将绑定到消息类型交换。因此,当您使用Publish
时,该消息将发布到消息类型交换并使用端点绑定(或多个绑定)进行相应地传递。使用Send
时,未使用消息类型交换,因此消息直接到达目标交换。而且,正如@maldworth正确指出的那样,每个 MassTransit端点仅希望获取它可以消费的消息。如果不知道如何使用该消息,则将消息移至死信队列。这以及有害消息队列都是消息传递的基本模式。
如果您需要消息排队等待以后使用,最好的方法是建立连接,但是端点本身(我的意思是应用程序)不应运行。应用程序启动后,将使用所有排队的消息。