Brighter与AWS:如何设置并使用Brighter与AWS SNS/SQS

本文将重点讲解如何将 Brighter 与 AWS SNS/SQS 集成。

AWS SNS/SQS 简介

AWS SNS(Simple Notification Service)和 SQS(Simple Queue Service)构成了分布式系统中强大的消息传递基础架构。SNS 支持发布/订阅模式的消息分发(支持多消费者),而 SQS 提供基于队列的消息缓冲,确保可靠的消息处理。通过将这些服务与 Brighter(一个用于命令/查询处理和消息传递的 .NET 库)结合使用,可以构建具备高弹性和解耦特性的大规模系统。

本指南将逐步演示如何配置 Brighter 使用 SNS 发布事件并通过 SQS 消费事件,并附带实用代码示例及当前限制的解决方法。

环境要求


1. .NET 8 或更高版本
2. .NET 项目需引用以下 NuGet 包:  

Brighter 快速回顾 


在继续 AWS SNS/SQS 配置前,先回顾一下 Brighter 的核心概念。

请求(Command/Event)

使用 IRequest 定义消息:  

public class Greeting : Event(Guid.NewGuid())
{
    public string Name { get; set; } = string.Empty;
}
  • 命令(Commands):单接收者操作(如 `SendEmail`)。  
  • 事件(Events):广播通知(如 `OrderShipped`)。  

消息映射器(Message Mapper) 


在 Brighter 消息与应用对象之间进行转换:

public class GreetingMapper : IAmAMessageMapper<Greeting>
{
    public Message MapToMessage(Greeting request)
    {
        var header = new MessageHeader();
        header.Id = request.Id; 
        header.TimeStamp = DateTime.UtcNow;
        header.Topic = "greeting.topic"; // 目标主题名称
        header.MessageType = MessageType.MT_EVENT;

        var body = new MessageBody(JsonSerializer.Serialize(request));
        return new Message(header, body);
    }

    public Greeting MapToRequest(Message message)
    {
        return JsonSerializer.Deserialize<Greeting>(message.Body.Bytes)!;
    }
}

请求处理器(Request Handler)

 
处理传入的消息:  

public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
    public override Greeting Handle(Greeting command)
    {
        logger.LogInformation("Hello {Name}", command.Name);
        return base.Handle(command);
    }
}

配置 Brighter 与 AWS SNS/SQS  

连接设置


定义连接参数:  

var connection = new AWSMessagingGatewayConnection(
    FallbackCredentialsFactory.GetCredentials(), // AWS 凭证
    AWSConfigs.RegionEndpoint,  // 如:RegionEndpoint.USWest2
    opt => {  // 自定义 AWS 客户端设置 });

SQS 消费者配置  


订阅 SQS 队列并将其映射到 SNS 主题:  

.AddServiceActivator(opt =>
{
    opt.Subscriptions = [
        new SqsSubscription<Greeting>(
            subscriptionName: new SubscriptionName("greeting-subscription"), // 可选:用于日志记录
            channelName: new ChannelName("greeting-queue"), // SQS 队列名称
            routingKey: new RoutingKey("greeting.topic").ToValidSNSTopicName(), // SNS 主题名称
            bufferSize: 2 // 并发处理的消息数量
        )
    ];
    opt.ChannelFactory = new ChannelFactory(connection);
});

SNS 生产者配置  


向 SNS 发布事件:  

.UseExternalBus(new SnsProducerRegistryFactory(connection, new []
{
    new SnsPublication
    {
        Topic = new RoutingKey("greeting.topic".ToValidSNSTopicName()),
        MakeChannels = OnMissingChannel.Create
    }
}).Create());

已知限制(Brighter v9)

  1. 强制依赖 SNS:所有消息必须通过 SNS 路由,即使仅使用 SQS。  
  2.  不支持 FIFO:v9 不支持 FIFO 队列或主题(用于有序处理)。  
  3.  LocalStack 兼容性问题:自定义 AWS 客户端配置(如 LocalStack 端点)可能无法传播到 Brighter 的所有内部组件。  

好消息:Brighter v10(正在开发中)将解决这些问题。

最佳实践  

验证 SNS 主题名称

  
指定 SNS 主题时始终使用 `.ToValidSNSTopicName()` 方法。该方法确保主题名称符合 AWS 命名规则(仅允许字母数字字符、连字符和句点)。  

new RoutingKey("greeting.topic".ToValidSNSTopicName()) // 转换为有效的 SNS 主题名称

配置死信队列(DLQ)*

  
通过设置死信队列(DLQ)提高容错能力。在 `SqsSubscription` 中使用 `redrivePolicy` 参数指定:  

  • DLQ 名称:用于存储失败消息的 SQS 队列名称。  
  • 最大重试次数:消息被移至 DLQ 前的重试次数。  
new SqsSubscription<Greeting>(
    subscriptionName: new SubscriptionName("greeting-subscription"),
    channelName: new ChannelName("greeting-queue"),
    routingKey: new RoutingKey("greeting.topic").ToValidSNSTopicName(),
    bufferSize: 2,
    redrivePolicy: new RedrivePolicy(
        deadLetterQueueName: "greeting-dead", // DLQ 名称
        maxReceiveCount: 3 // 重试阈值
    )
)

结论

 
将 Brighter 与 AWS SNS/SQS 集成,可以将 Brighter 库的简单性与 AWS 弹性消息传递基础设施相结合。尽管 v9 存在一些限制,但其核心功能仍然能够实现:  

  • 解耦架构:生产者和消费者独立演化。  
  • 弹性:SQS 在流量高峰期间缓冲消息。  
  • 可扩展性:SNS/SQS 自动扩展以应对工作负载。  

参考资料


完整代码:GitHub 仓库]

using System.Text.Json;
using Amazon;
using Amazon.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessagingGateway.AWSSQS;
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.ServiceActivator.Extensions.Hosting;
using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateLogger();


var host = new HostBuilder()
    .UseSerilog()
    .ConfigureServices((_, services) =>
    {
        var connection = new AWSMessagingGatewayConnection(FallbackCredentialsFactory.GetCredentials(), AWSConfigs.RegionEndpoint);

        services
            .AddHostedService<ServiceActivatorHostedService>()
            .AddServiceActivator(opt =>
            {
                opt.Subscriptions = [
                    new SqsSubscription<Greeting>(
                        new SubscriptionName("greeting-subscription"), // Optional
                        new ChannelName("greeting-queue"), // SQS queue name
                        new RoutingKey("greeting.topic".ToValidSNSTopicName()), // SNS Topic Name
                        bufferSize: 2)
                ];
                opt.ChannelFactory = new ChannelFactory(connection);
            })
            .AutoFromAssemblies()
            .UseExternalBus(new SnsProducerRegistryFactory(connection, new []
            {
                new SnsPublication
                {
                    Topic = new RoutingKey("greeting.topic".ToValidSNSTopicName()),
                    MakeChannels = OnMissingChannel.Create
                }
            }).Create());
    })
    .Build();

_ = host.RunAsync();

while (true)
{
    await Task.Delay(TimeSpan.FromSeconds(10));
    Console.Write("Say your name (or q to quit): ");
    var name = Console.ReadLine();

    if (string.IsNullOrEmpty(name))
    {
        continue;
    }

    if (name == "q")
    {
        break;
    }

    var process = host.Services.GetRequiredService<IAmACommandProcessor>();
    process.Post(new Greeting { Name = name });
}

host.WaitForShutdown();


public class Greeting() : Event(Guid.NewGuid())
{
    public string Name { get; set; } = string.Empty;
}

public class GreetingMapper : IAmAMessageMapper<Greeting>
{
    public Message MapToMessage(Greeting request)
    {
        var header = new MessageHeader();
        header.Id = request.Id;
        header.TimeStamp = DateTime.UtcNow;
        header.Topic = "greeting.topic";
        header.MessageType = MessageType.MT_EVENT;

        var body = new MessageBody(JsonSerializer.Serialize(request));
        return new Message(header, body);
    }

    public Greeting MapToRequest(Message message)
    {
        return JsonSerializer.Deserialize<Greeting>(message.Body.Bytes)!;
    }
}

public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
    public override Greeting Handle(Greeting command)
    {
        logger.LogInformation("Hello {Name}", command.Name);
        return base.Handle(command);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值