首页 > 技术文章 > .NET Core 3.1 + Hangfire 配置以及踩坑

memoyu 2020-11-10 17:43 原文

起初在基于ABP开发的个人博客中尝试过使用Hangfire构建后台任务服务,期间配置相对简单,毕竟ABP做了相应的拓展。现在常规的.NET Core 3.1框架下进行集成使用,并且是基于MySql 5.6,并对遇到的问题进行一个汇总。

集成Hangfire

构建完成后整个系统的结构:

添加后台任务层

1、在后台任务层中添加Hangfire Nuget 包

1、Hangfire.AspNetCore
2、Hangfire.Core
3、Hangfire.Dashboard.BasicAuthorization
4、Hangfire.MySql.Core

如上图所示,添加一个以BackgroundJobs结尾的程序集,进行对后台任务接口、实现的分离。

其中主要有任务接口、对应实现以及常用的Cron定义

2、任务接口:IBackgroundJob

 public interface IBackgroundJob
    {
        /// <summary>
        /// 执行任务
        /// </summary>
        /// <returns></returns>
        Task ExecuteAsync();
    }

3、任务实现:HangfireTestJob

 public class HangfireTestJob : IBackgroundJob
    {
        public async Task ExecuteAsync()
        {
            Console.WriteLine("定时任务测试");
            await Task.CompletedTask;
        }
    }

4、常用的Cron:JobCronType

主要定义一些常用的Cron,静态方法返回Cron 字符串

5、将任务实现注入到Autofac容器中

 public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterAssemblyTypes(Assembly.LoadFrom(backgroundJob))//后台任务
                   .Where(a=>a.IsClass )
                   .InstancePerDependency();
        }

服务注册与添加中间件

1、配置Hang服务、中间件拓展

这块采用添加拓展服务的形式添加Hangfire服务,让Startup更简洁,代码如下:

public static void AddHangfireService(this IServiceCollection services )
        {
            services.AddHangfire(options =>
            {
                options.UseStorage(
                    new MySqlStorage(AppSettings.ConnectionString,//配置连接字符串,连接字符串需要加入Allow User Variables=true;配置
                    new MySqlStorageOptions
                    {
                        TablePrefix = "ps_hangfire"//配置表名前缀
                        }));
            });
        }

采用扩展的形式配置Hangfire中间件:

 public static void UseHangfireMiddleware(this IApplicationBuilder app, ILifetimeScope lifetimeScope)
        {
            app.UseHangfireServer();//添加hangfire服务中间件
            app.UseHangfireDashboard(options: new DashboardOptions
            {
                Authorization = new[] {new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
                {
                    RequireSsl = false,//需要SSL连接才能访问HangFire Dahsboard。
                    SslRedirect = false,//是否将所有非SSL请求重定向到SSL URL
                    LoginCaseSensitive = true,//区分大小写
                    Users = new []//用户
                    {
                        new BasicAuthAuthorizationUser
                        {
                            Login = AppSettings.Hangfire.Login,
                            PasswordClear = AppSettings.Hangfire.Password
                        },
                    }
                }), },
                DashboardTitle = "任务调度中心"
            });//添加hangfire仪表盘中间件,添加登陆认证

            HangfireService(lifetimeScope);//配置各个任务
        }

        private static void HangfireService(ILifetimeScope lifetimeScope)
        {
            var job = lifetimeScope.Resolve<HangfireTestJob>();//获取容器实例(记得的要注入任务实现)
            RecurringJob.AddOrUpdate("定时任务测试", () => job.ExecuteAsync(), JobCronType.Minute());
        }

2、添加服务、中间件

 public void ConfigureServices(IServiceCollection services)
        {
             #region Hangfire注册

            services.AddHangfireService();

            #endregion
        }

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env ,ILifetimeScope lifetimeScope)
        {
     		app.UseHangfireMiddleware(lifetimeScope);//添加Hangfire中间件
 		}

出现的问题

问题有2个,一个是对于连接数据库MySql5.6引发的问题,另一个是问题是个人对Autofac依赖注入的不了解导致的。

问题(一):在配合MySql5.6使用时,运行报错

问题分析:在配置完成后,启动项目,Hangfire在创建所需要的表时失败,提示“Index column size too large. The maximum column size is 767 bytes.”,再次启动时,会发现再次报错为缺少名为“前缀_set ”的表。从错误信息中可看出,是INNODB 引擎,UTF-8,主键字符串 默认最大 767,所以导致报错,进而导致生成的表不完全。

解决方案:

首先需要对数据库进行如下设置:

SET GLOBAL INNODB_LARGE_PREFIX = ON;
SET GLOBAL innodb_file_format = BARRACUDA;

并进行查看是否生效:

SHOW variables like 'innodb_large_prefix';
SHOW variables like 'innodb_file_format';

最后,需要手动的创建者几个缺少的表([前缀]_set、[前缀]_State、[前缀]_Job):

注意修改为自己的前缀

CREATE TABLE `ps_hangfire_Set` (
 `Id` int(11) NOT NULL AUTO_INCREMENT,
 `Key` varchar(100) NOT NULL,
 `Value` varchar(256) NOT NULL,
 `Score` float NOT NULL,
 `ExpireAt` datetime DEFAULT NULL,
 PRIMARY KEY (`Id`),
 UNIQUE KEY `IX_Set_Key_Value` (`Key`,`Value`)
) ENGINE=InnoDB  CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

CREATE TABLE `ps_hangfire_State`
(
	Id int(11) NOT NULL AUTO_INCREMENT,
	JobId int(11) NOT NULL,
	Name varchar(20) NOT NULL,
	Reason varchar(100) NULL,
	CreatedAt datetime NOT NULL,
	Data longtext NULL,
	PRIMARY KEY (`Id`),
	KEY `FK_HangFire_State_Job` (`JobId`)
) ENGINE=InnoDB  CHARSET=utf8mb4;

CREATE TABLE `ps_hangfire_List`
(
	`Id` int(11) NOT NULL AUTO_INCREMENT,
	`Key` varchar(100) NOT NULL,
	`Value` longtext NULL,
	`ExpireAt` datetime NULL,
	PRIMARY KEY (`Id`)
) ENGINE=InnoDB  CHARSET=utf8mb4;

问题(二):Autofac注册任务后无法获取到实例

问题分析:在Autofac注入时,我采用了程序集注入的形式注入,如下:

var backgroundJob = Path.Combine(basePath,"PaymentStatistics.BackgroundJobs.dll");
builder.RegisterAssemblyTypes(Assembly.LoadFrom(backgroundJob))//后台任务
                  .AsImplementedInterfaces()
                  .InstancePerDependency();

结果通过以下方式愣是拿不到实例:

var job = lifetimeScope.Resolve<HangfireTestJob>();

最后才发现,自己采用的是接口注册的方式进行注册,用实现类根本拿不到,只用用对应接口才能够拿到:

var job = lifetimeScope.Resolve<IBackgroundJob>();

但是,这么拿也不对,毕竟我是一个接口,对应多个实例,这样只能拿一个。

解决方案:

采用实例的注册方式,这样就能通过实现类去获取实例:

var backgroundJob = Path.Combine(basePath,"PaymentStatistics.BackgroundJobs.dll");
builder.RegisterAssemblyTypes(Assembly.LoadFrom(backgroundJob))//后台任务
                  .Where(a=>a.IsClass )
                  .InstancePerDependency();

推荐阅读