Skip to content

Hangfire简单分布式

今晚研究了下,发现这个Hangfire实在是比Quartz.Net灵活好多。赶紧笔记下~~

开发环境

VS 2019 / .NET Framework 4.6.2

架构图

可以有很多Client,设置不同的Queue后来分配任务。

Queue队列概念

Hangfire中的队列是用于区分任务的管道,在程序入口的 BackgroundJobServerOptions 中可以配置客户端能接受的队列。
这样可以方便的区分开任务,每个服务器可以独立处理指定任务。这里简单理解为类似MQ的感觉吧。

Queue的名称需要注意:小写字母,数字,下划线和破折号。

Demo构建

分别创建了两个项目:
- ConsoleApp Job的具体执行方法和BackgroundJobServer放在这里
- ConsoleWeb Hangfire的Web控制台放在这里,项目是MVC框架

ConsoleApp 需要如下Nuget包:

Install-Package Hangfire
Install-Package Hangfire.Redis.StackExchange
Install-Package Hangfire.Dashboard.Management
Install-Package Hangfire.Console

ConsoleWeb 需要如下Nuget包:

Install-Package Hangfire
Install-Package Hangfire.Redis.StackExchange
Install-Package Hangfire.Dashboard.Management
Install-Package Hangfire.Console

ConsoleApp

Program.cs

// Redis 配置
var redisOption = new RedisStorageOptions()
{
    Db = 12,
    Prefix = "hangfire:"
};

// 使用Redis存储
GlobalConfiguration.Configuration
    .UseRedisStorage("127.0.0.1", redisOption)
    .UseConsole();

var option = new BackgroundJobServerOptions() {ServerName = "666", Queues = new[] {"wb"}};

using (var server = new BackgroundJobServer(option))
{
    Console.WriteLine("Hangfire Server started. Press any key to exit...");
    Console.ReadKey();
}
// 阻止控制台关闭
Thread.Sleep(Timeout.Infinite);

另外创建一个测试的调用方法

[ManagementPage("演示")]
public class JobCenter
{
    [DisplayName("主方法")]
    [Queue("wb")]
    [Hangfire.Dashboard.Management.Support.Job]
    public static void JobA(PerformContext context = null)
    {
        Console.WriteLine(DateTime.Now);
        context.WriteLine("嗯??");
        Thread.Sleep(1000);
        context.WriteLine("睡醒了");
    }
}

如果需要在控制台中看得见方法,需要在方法的Class上增加修饰器,并且使用"DisplayName"来定义名称。
JobA中的 PerformContext 是 Hangfire.Console 的拓展功能,可以方便的输出信息到web中。

ConsoleWeb

ConsoleWeb需要引用客户端项目,或者直接将Dll放到目录下读取也可以。

Startup.cs 代码

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHangfire(config => config
                .UseSimpleAssemblyNameTypeSerializer()
                .UseRecommendedSerializerSettings()
                .UseColouredConsoleLogProvider()
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.ServerCount)//服务器数量
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.RecurringJobCount)//定时任务
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.RetriesCount)//重试次数
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.EnqueuedAndQueueCount)//队列数量
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.ScheduledCount)//计划
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.ProcessingCount)//执行中
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.SucceededCount)//成功的作业
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.FailedCount)//失败
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.DeletedCount)//已删除的任务
                .UseDashboardMetric(Hangfire.Dashboard.DashboardMetrics.AwaitingCount)//等待中
                .UseConsole()// Hangfire.Console 需要web输出消息则要引用他
                .UseRedisStorage("127.0.0.1", new Hangfire.Redis.RedisStorageOptions { Db = 12, Prefix = "hangfire:" })
                .UseManagementPages(cc => cc.AddJobs(() => { return GetModuleTypes(); }))//任务管理工具
                        );
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseHangfireDashboard();
        }

        /// <summary>
        /// 取出所有的  ManagementPage
        /// </summary>
        /// <returns></returns>
        private static Type[] GetModuleTypes()
        {
            var assemblyList = new List<Assembly>()
            {
                Assembly.Load("ConsoleApp1")// 获取对象
            };

            var moduleTypes = assemblyList.SelectMany(f =>
            {
                try
                {
                    return f.GetTypes();
                }
                catch (Exception)
                {
                    return new Type[] { };
                }
            }).ToArray();

            return moduleTypes;
        }
    }

其他的无用代码暂时删除了

执行项目后可以在这里看见方法了

这里可以设定任务怎么执行

  • 列队执行 => BackgroundJob.Enqueue();
  • 定时执行 => BackgroundJob.Schedule();
  • 延时执行 => BackgroundJob.ContinueWith();
  • 重复执行 => RecurringJob.AddOrUpdate();

重复执行还内置了Cron的生成工具,简直不要太爽,但是Hangfire并没支持到秒级别。
如果手工写秒级计划任务,会发现并不会按照间隔来执行。

创建作业

通过向导创建重复执行任务后就能在“周期性作业”中看见这个任务了

在这里可以手工执行,删除和暂停任务等。

在“作业”Tab中可以查看到历来执行的任务记录。