使用Brighter V10和Hangfire进行消息调度

在之前的文章中,我介绍了Brighter V10 RC1以及使用PostgreSQL作为消息网关。Brighter的一个关键特性是通过SendPublishPost方法进行消息调度,可以使用DateTimeOffsetTimeSpan参数。虽然Brighter的默认调度器使用内存中的Timer,但它也支持HangfireQuartz.NET、AWS EventBridge SchedulerAzure Service Bus等外部调度器。

为什么在Brighter中使用Hangfire?

Hangfire提供了一个强大且可用于生产的调度解决方案,具有持久化存储、仪表板监控和重试功能——使其成为消息传递可靠性至关重要的企业应用程序的理想选择。与内存调度器不同,Hangfire能在应用程序重启后继续维护已调度的任务。

要求

.NET 8或更高版本(Brighter V10支持net8.0、net9.0和netstandard2.0)
包含以下NuGet包的.NET项目

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

对于开发环境,可以使用内存存储,但在生产环境中,始终应使用PostgreSQLSQL ServerRedis等持久化存储解决方案:

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提供了企业级应用程序所需的持久性和监控能力。

在GitHub上查看完整实现

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);
    }
}

### 使用萤火虫算法解决作业车间调度问题 #### 1. 萤火虫算法简介 萤火虫算法是一种基于生物群体行为的智能优化算法,灵感来源于萤火虫的闪光行为。通过萤火虫之间的相互吸引移动,该算法能够在复杂的搜索空间中找到全局最优解[^1]。 #### 2. 作业车间调度问题描述 作业车间调度问题(Job Shop Scheduling Problem, JSSP)是一个经典的组合优化问题,旨在最小化完成所有任务所需的时间(即最大完工时间)。每个工件由一系列操作组成,每项操作必须按照特定顺序执行,并且只能在一个机器上处理一次。 #### 3. 应用萤火虫算法到JSSP中的基本流程 ##### 编码方式 为了适应JSSP的特点,通常采用排列编码来表示萤火虫的位置。例如: ```python import numpy as np def encode_firefly(num_jobs, num_machines): """生成初始种群""" population_size = 50 fireflies = [] for _ in range(population_size): job_sequence = list(range(1, num_jobs * num_machines + 1)) np.random.shuffle(job_sequence) fireflies.append(job_sequence) return fireflies ``` ##### 初始种群初始化 创建一定数量随机分布的个体作为起始点: ```python num_jobs = 6 num_machines = 4 fireflies = encode_firefly(num_jobs, num_machines) ``` ##### 计算亮度/吸引力 根据目标函数评估各个解的好坏程度,这里的目标是最小化总加工周期: ```python def calculate_brightness(firefly_position, problem_instance): makespan = evaluate_makespan(problem_instance, firefly_position) brightness = 1 / (makespan + 1e-9) # 避免除零错误 return brightness ``` ##### 移动规则 当较暗的萤火虫向更亮的伙伴靠近时,会调整自己的位置;如果两个萤火虫具有相同的亮度,则保持不变: ```python alpha = 0.2 # 吸引力衰减系数 beta_min = 0.2 # 最小吸引力参数 def move_towards_brighter(i, j, positions, attractivenesses): distance = euclidean_distance(positions[i], positions[j]) beta = max(beta_min, alpha ** distance) new_pos_i = update_position(positions[i], positions[j], beta) return new_pos_i ``` ##### 更新过程 重复迭代直到满足终止条件为止,在每次循环内更新所有萤火虫的位置并重新计算它们各自的亮度值: ```python max_iterations = 1000 for iteration in range(max_iterations): for i in range(len(fireflies)): current_brightness = calculate_brightness(fireflies[i], instance_data) for j in range(len(fireflies)): other_brightness = calculate_brightness(fireflies[j], instance_data) if other_brightness > current_brightness: fireflies[i] = move_towards_brighter(i, j, fireflies, ...) best_solution = find_best_solution(fireflies) print("Best solution found:", best_solution) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值