起初在基於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();