.NET Core 3.1 + Hangfire 配置以及踩坑
起初在基于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();