消费者(Consumer)
消费者是一个广泛使用的名词,用于表示消费某些东西的东西。在 MassTransit 中,消费者在配置或连接到接收端点时消费一个或多个消息类型。MassTransit 包含许多消费者类型,包括消费者、saga、saga状态机、路由单活动(routing slip activities)、处理程序(handlers)和job消费者(job consumers)。
消息消费者(Message Consumers)
消息消费者是最常见的消费者类型,是一个消费一个或多个消息类型的类。对于每种消息类型,类实现 IConsumer<TMessage>
并实现 Consume
方法。
public interface IConsumer<in TMessage> :
IConsumer
where TMessage : class
{
Task Consume(ConsumeContext<TMessage> context);
}
消息必须是引用类型,可以是记录(record)、接口(interface)或类(class)。有关更多详细信息,请参阅消息概念页面。
下面展示了一个消费 SubmitOrder
消息类型的消息消费者示例。
class SubmitOrderConsumer :
IConsumer<SubmitOrder>
{
public async Task Consume(ConsumeContext<SubmitOrder> context)
{
await context.Publish<OrderSubmitted>(new
{
context.Message.OrderId
});
}
}
要添加消费者并为消费者自动配置接收端点,请调用 AddConsumer
方法之一,并调用 ConfigureEndpoints
,如下所示。
services.AddMassTransit(x =>
{
x.AddConsumer<SubmitOrderConsumer>();
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
通过调用 ConfigureEndpoints
进行自动接收端点配置是非常推荐的。可以使用几个可选的配置选项来更改默认约定并自定义端点,这些选项在配置部分中进行了介绍。
MassTransit 秉承好莱坞原则,“不要调用我们,我们会调用你。” 控制流从 MassTransit 流向开发者的代码以响应事件,在这种情况下是传输的消息传递。这种行为类似于 ASP.NET,它在接收到 HTTP 请求时创建控制器并调用操作方法。当消息从传输传递到接收端点并且消息类型被消费者消费时,MassTransit 创建一个容器作用域,解析消费者实例,并执行 Consume
方法,传递包含消息的 ConsumeContext
。
Consume
方法返回一个 Task
,由 MassTransit 等待。在消费者方法执行期间,消息对其他接收端点不可用。如果 Task
成功完成,消息将被确认并从队列中移除。
如果 Task
因异常而失败,或者被取消(显式地,或通过 OperationCanceledException
),消费者实例将被释放,异常将传播回管道。如果异常不会触发重试,默认管道会将消息移动到错误队列。
当消费者在接收端点上配置时,消费者消息类型(每个 IConsumer<T>
一个)用于配置接收端点的消费拓扑。消费拓扑随后用于配置代理,以便将发布的消息传递到队列。代理拓扑因传输而异。例如,上面的 RabbitMQ 示例将导致为 SubmitOrder
消息类型创建一个交换机,并从该交换机绑定到与队列同名的交换机(后者交换机然后直接绑定到队列)。
如果队列是持久的(AutoDelete
为 false
,这是默认值),拓扑即使在总线停止后仍然存在。当总线重新创建并启动时,代理实体将重新配置以确保它们正确配置。任何在队列中等待的消息将在总线启动后继续传递到接收端点。
批量消费者(Batch Consumers)
在某些情况下,高消息量可能导致消费者资源瓶颈。如果一个系统每秒发布数千条消息,并且有一个消费者将这些消息的内容写入某种存储,存储系统可能无法优化每秒数千次单独写入。然而,如果以批量方式执行写入,例如接收一百条消息,然后使用单个存储操作写入这些消息的内容,可能会显著提高效率(和速度)。
MassTransit 支持接收多条消息并将这些消息以批量方式传递给消费者。
要创建批量消费者,请消费 Batch<T>
接口,其中 T
是消息类型。然后可以使用容器集成配置该消费者,并在消费者定义中指定批量选项。下面的示例消费一批 OrderAudit
事件,每次最多 100 条,最多 10 个并发批量。
class BatchMessageConsumer :
IConsumer<Batch<Message>>
{
public async Task Consume(ConsumeContext<Batch<Message>> context)
{
for(int i = 0; i < context.Message.Length; i++)
{
ConsumeContext<Message> message = context.Message[i];
}
}
}
设置
PrefetchCount
和ConcurrentMessageLimit
在使用批量消费者时,正确配置PrefetchCount
和ConcurrentMessageLimit
非常重要。当使用ConfigureEndpoints
自动配置批量消费者时,传输将配置为匹配批量消息限制。如果接收端点是手动配置的,请确保这些值设置得足够高以达到指定的批量消息限制。如果PrefetchCount
低于批量限制,性能将受限于时间限制,因为批量大小永远不会达到。
每个传输都有其自身的限制,可能会限制批量大小。例如,Amazon SQS 一次获取十条消息,这使其成为最佳批量大小。最好进行实验,看看在你的环境中什么效果最好。
定义(Definitions)
消费者定义用于指定消费者的行为,以便它们可以自动配置。定义可以通过 AddConsumer
显式添加,或者使用任何 AddConsumers
方法自动发现。
下面展示了一个消费者定义的示例。有关完整的配置参考,请参阅配置部分。
public class SubmitOrderConsumerDefinition :
ConsumerDefinition<SubmitOrderConsumer>
{
public SubmitOrderConsumerDefinition()
{
// override the default endpoint name, for whatever reason
EndpointName = "ha-submit-order";
// limit the number of messages consumed concurrently
// this applies to the consumer only, not the endpoint
ConcurrentMessageLimit = 4;
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<DiscoveryPingConsumer> consumerConfigurator)
{
endpointConfigurator.UseMessageRetry(r => r.Interval(5, 1000));
endpointConfigurator.UseInMemoryOutbox();
}
}
跳过的消息(Skipped Messages)
当消费者从接收端点移除(或断开连接)、从消费者移除消息类型,或者如果消息被错误地发送到接收端点时,可能会将消息传递到没有消费者的接收端点。
如果发生这种情况,未消费的消息将被移动到 _skipped 队列(以原始队列名称为前缀)。原始消息内容被保留,并添加额外的头信息以标识跳过消息的主机。
可能需要使用代理管理工具为不再被接收端点消费的消息类型移除交换机绑定或主题订阅,以防止进一步跳过消息。