在之前的文章中,我介绍了Brighter V10 RC1以及使用PostgreSQL作为消息网关。Brighter的一个关键特性是通过Send、Publish和Post方法进行消息调度,可以使用DateTimeOffset或TimeSpan参数。虽然Brighter的默认调度器使用内存中的Timer,但它也支持Hangfire、Quartz.NET、AWS EventBridge Scheduler和Azure Service Bus等外部调度器。
为什么在Brighter中使用Hangfire?
Hangfire提供了一个强大且可用于生产的调度解决方案,具有持久化存储、仪表板监控和重试功能——使其成为消息传递可靠性至关重要的企业应用程序的理想选择。与内存调度器不同,Hangfire能在应用程序重启后继续维护已调度的任务。
要求
.NET 8或更高版本(Brighter V10支持net8.0、net9.0和netstandard2.0)
包含以下NuGet包的.NET项目
- Hangfire.AspNetCore: 使Hangfire与ASP.NET Core集成
- Hangfire.InMemory: 为开发提供内存存储选项(生产环境中应使用基于数据库的存储)
- Paramore.Brighter.MessageScheduler.Hangfire: 将Hangfire与Brighter的调度系统集成
- Paramore.Brighter.MessagingGateway.Postgres: 启用Postgres集成以进行消息队列
- Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection: 通过Microsoft DI注册Brighter
- Paramore.Brighter.ServiceActivator.Extensions.Hosting: 将Brighter作为后台服务托管
- Serilog.AspNetCore: 用于结构化日志记录(可选但推荐)
Brighter概念回顾
在深入Hangfire集成之前,让我们简要回顾一下Brighter的关键概念:
请求(命令/事件)
使用`IRequest`定义消息:
public class Greeting() : Event(Guid.NewGuid())
{
public string Name { get; set; } = string.Empty;
}
- 命令:单接收者操作(例如,SendEmail)
- 事件:广播通知(例如,OrderShipped)
消息映射器(可选)
在Brighter消息和应用程序对象之间进行转换,默认情况下Brighter将使用JSON序列化
请求处理器
处理传入的消息:
public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
public override Greeting Handle(Greeting command)
{
logger.LogInformation("Hello {Name}", command.Name);
return base.Handle(command);
}
}
使用Hangfire调度器配置Brighter
1. 将Hangfire注册到Microsoft DI
对于开发环境,可以使用内存存储,但在生产环境中,始终应使用PostgreSQL、SQL Server或Redis等持久化存储解决方案:
services
.AddHangfire(opt => opt
.UseDefaultActivator()
.UseRecommendedSerializerSettings()
.UseSimpleAssemblyNameTypeSerializer()
.UseInMemoryStorage())
.AddHangfireServer();
2. 在Brighter中注册Hangfire
使用工厂模式将Hangfire调度器工厂注册到Brighter。由于Hangfire需要在Brighter使用之前完全初始化,因此我们使用工厂模式:
services
.AddHostedService<ServiceActivatorHostedService>()
.AddServiceActivator(opt => ... )
.UseScheduler(_ => new HangfireMessageSchedulerFactory())
3. 使用调度器
使用相对时间(TimeSpan)或绝对时间(DateTimeOffset)进行精确的时间控制来调度消息:
// 安排1秒后执行
await process.PostAsync(TimeSpan.FromSeconds(1), new SchedulerCommand { Name = name, Type = "Post"});
// 安排2秒后执行
await process.SendAsync(TimeSpan.FromSeconds(2), new SchedulerCommand { Name = name, Type = "Send"});
// 安排正好3秒后执行
await process.PublishAsync(DateTimeOffset.UtcNow + TimeSpan.FromSeconds(3), new SchedulerCommand { Name = name, Type = "Publish"});
重要注意事项
1. 生产部署:切勿在生产环境中使用Hangfire.InMemory。应配置Hangfire使用与您的基础设施匹配的持久化存储后端。
2. 错误处理:Hangfire会根据其配置自动重试失败的任务。请查看Hangfire的重试策略,确保它们符合您的消息传递要求。
3. 监控:利用Hangfire的仪表板来监控已调度的任务、处理历史记录和潜在故障。
结论
将Hangfire与Brighter V10集成创建了一个强大的调度解决方案,它结合了Brighter稳健的消息模式和Hangfire可靠的任务处理能力。这种组合对于需要延迟消息处理或在系统重启后保证消息传递的场景特别有价值。
内存调度器非常适合开发和测试,但对于生产环境,Hangfire提供了企业级应用程序所需的持久性和监控能力。
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessageScheduler.Hangfire;
using Paramore.Brighter.MessagingGateway.Postgres;
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(static (_, services) =>
{
var config = new RelationalDatabaseConfiguration ("Host=localhost;Username=postgres;Password=password;Database=brightertests;", queueStoreTable: "QueueData");
services
.AddHangfire(opt => opt
.UseDefaultActivator()
.UseRecommendedSerializerSettings()
.UseSimpleAssemblyNameTypeSerializer()
.UseInMemoryStorage())
.AddHangfireServer();
services
.AddHostedService<ServiceActivatorHostedService>()
.AddServiceActivator(opt =>
{
opt.Subscriptions = [
new PostgresSubscription<SchedulerCommand>(
subscriptionName: new SubscriptionName("greeting.subscription"),
channelName: new ChannelName("greeting.topic"),
makeChannels: OnMissingChannel.Create,
messagePumpType: MessagePumpType.Proactor,
timeOut: TimeSpan.FromSeconds(10)
)
];
opt.DefaultChannelFactory= new PostgresChannelFactory(new PostgresMessagingGatewayConnection(config));
})
.UseScheduler(_ => new HangfireMessageSchedulerFactory())
.UseExternalBus(opt =>
{
opt.ProducerRegistry = new PostgresProducerRegistryFactory(new PostgresMessagingGatewayConnection(config), [
new PostgresPublication<SchedulerCommand>
{
Topic = new RoutingKey("greeting.topic"),
MakeChannels = OnMissingChannel.Create
}
]).Create();
})
.AutoFromAssemblies();
})
.Build();
await host.StartAsync();
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(2));
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>();
await process.PostAsync(TimeSpan.FromSeconds(1), new SchedulerCommand { Name = name, Type = "Post"});
await process.SendAsync(TimeSpan.FromSeconds(2), new SchedulerCommand { Name = name, Type = "Send"});
await process.PublishAsync(DateTimeOffset.UtcNow + TimeSpan.FromSeconds(3), new SchedulerCommand { Name = name, Type = "Publish"});
}
await host.StopAsync();
public class SchedulerCommand() : Event(Guid.NewGuid())
{
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
}
public class SchedulerCommandHandler(ILogger<SchedulerCommandHandler> logger) : RequestHandlerAsync<SchedulerCommand>
{
public override Task<SchedulerCommand> HandleAsync(SchedulerCommand command, CancellationToken cancellationToken = new CancellationToken())
{
logger.LogInformation("Hello {Name} (with Type: {Type})", command.Name, command.Type);
return base.HandleAsync(command, cancellationToken);
}
}
1592

被折叠的 条评论
为什么被折叠?



