分布式后端作业调度框架服务,我们只需要关心业务逻辑代码,而不用关心调度机制持。
官方原文:在.NET和.NET Core应用程序中执行后台处理的简单方法。无需Windows服务或单独的进程。免费开源且可用于商业应用。Easy to set up, easy to use。
Hangfire支持久存储支,存储方式可支持sqlserver、redis,mongodb等等。
Hangfire支持所有类型的后台任务 - 短时间运行和长时间运行,CPU密集型和I/O密集型,一次性和周期性。
官网:www.hangfire.io
简单 Simple
易于设置易于使用。没有windows服务,没有windows计划策划稿内需,不需要单独的应用程序。
后台作业是常规静态或带有常规参数的实例.NET方法-不需要基类或者接口实现。
PS:启动和结束依托于宿主项目。可以正常的调用项目内部的方法,无需特意实现某个类或接口。实施上,它会拿取到你的方法的引用来源并和参数一起保存下来,空间名方法名项目名等。
可靠 Reliable
一旦创建了后台作业而没有任何异常,Hangfire将负责至少处理它一次。
您可以自由的抛出未处理的异常或终止您的应用程序 - 后台作业将会自动重新尝试。
PS:只要可以正常放入计划中,就一定能执行。当然,它没提到能否精确执行。事实上很多情况下会导致重复执行(某一任务执行时间过长)或不执行(多任务时间要求过于紧凑)或未按时执行(如网站被回收后又重新激活,此时若错过了执行时间,但是依旧会在激活第一时间执行)。
高效 Efficient
虽然默认安装使用SQL Server和轮询技术来获取作业,但您可以利用MSMQ或Redis扩展将处理延迟降低至最低。
PS:很显然是相对而言。
持久化 Persistent
后台作业时在永久存储创建-SQL服务器,Redis,PostgreSQL,MongoDB或其它。
您可以安全的重新启动应用程序并在ASP.NET中使用Hangfire,而无需担心应用程序池的循环使用。
PS:与RbaitMQ的消息持久化存储类似,实际上只要想达到永久存储肯定要有硬盘参与。
分布式 Distributed
后台方法调用以及其参数被序列化可以克服过程边界。
您可以在不同的计算机上使用hangfire以获得更多处理能力而无需配置 - 自动执行同步。
PS:参数全部被序列化的。分布式可以用是可以用但是并不智能,也无法自己开发服务端平衡机制。简单易用但也死板。
自我维护 Self-maintainable
您无需执行手动存储清理 - hangfire会尽可能保持清洁并自动删除旧纪录。
PS:就是尽量少占用数据库空间。而且对于异常信息的记录也并不详细。
透明 Transparent
内置的web睫毛允许您查看后台处理的整个画面,以及观察每个后台作业的状态。
对流行的日志框架的开箱即用支持允许您在零配置的早期捕获异常。
PS:有个简单的可视窗口页面。
扩展 Extensible
作业筛选器允许您以类似于ASP.NET MVC操作筛选器的方式向后台处理添加自定义功能。
PS:应该很有用,但是目前尚未使用过。
开源 Open source
Hangfire是开源软件,完全免费用于商业用途,它是根据LGPLv3许可证授权的。
GitHub。
截止目前(2019-02-20)最新稳定版为1.6.22。
当前项目使用版本为1.6.17。
自从1.6+版本后开始支持NetCore。
1.6.17-1.6.22之间基本为对Hangfire.Core的升级。
官网:各版本列表
核心组件:客户端,服务端,持久化存储。这些组件既可以分开部署在不同项目中也可以都部署在一个项目中。
客户端: 创建任务–>1、配置HangFire数据库连接 2、创建任务(在创建任务的时候HangFire会自动将任务序列化并存储到数据)。
服务端: 执行任务–>1、配置HangFire数据库连接 2、从HangFire数据库系统表读取客户端创建的任务然后开线程并行执行,任务之间不冲突。(服务端可宿主在Windows服务、控制台程序、IIS中…)。
数据库(持久化存储): HangFire程序框架表–>创建任务的时候HangFire会自动生成无需关心,但要注意如果采用现有的数据库,必须保证数据库中没有重名的表(aggregatedcounter、counter、distributedlock、hash、job、jobparameter、jobqueue、jobstate、list、server、set、state),数据库可采用 MySQL ,MSSQL,Redis视需要而定。
仪表盘(管理面板): 展示作业列表执行状态和结果等相关信息–>在.Net WebForm或.Net MVC 或.NetCore MVC网站程序对接HangFire数据库
组件之间关系图:
部署上述组件的方案(来自参考1):
备注(来自参考1):
使用:
引用命名空间。
using Hangfire;
var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget!"));
var jobId = BackgroundJob.Schedule(() => Console.WriteLine("Delayed!"), TimeSpan.FromDays(7));
RecurringJob.AddOrUpdate(() => Console.WriteLine("Recurring!"), Cron.Daily);
BackgroundJob.ContinueWith(jobId , () => Console.WriteLine("Continuation!"));
var batchId = BatchJob.StartNew(x =>
{
x.Enqueue(() => Console.WriteLine("Job 1"));
x.Enqueue(() => Console.WriteLine("Job 2"));
})
BatchJob.ContinueWith(batchId, x =>
{
x.Enqueue(() => Console.WriteLine("Last Job"));
});
// 内部源码
public static void AddOrUpdate(Expression methodCall, string cronExpression, TimeZoneInfo timeZone = null, tring queue = EnqueuedState.DefaultQueue)
{
var job = Job.FromExpression(methodCall);
var id = GetRecurringJobId(job);
Instance.Value.AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue);
}
该方法用于定期作业在指定的CRON计划上触发多次。该方法具有16个重载。
Job.FromExpression(methodCall) 用于获取基于Job类的新实例给定的方法调用的表达式树。
GetRecurringJobId(job)方法根据Job对象获取对应的JobID。
我们常用这个方法来增加定时执行的任务,实际使用实例:
// 使用实例
RecurringJob.AddOrUpdate(() => remindService.PushNoSubmittedDailyReportMsg(), Cron.Daily(8), TimeZoneInfo.Local, bgOption.Queues[0]); // 每天早8点
RecurringJob.AddOrUpdate(() => remindService.PushNoSubmittedWeeklyReportMsg(), Cron.Weekly(DayOfWeek.Monday, 8), TimeZoneInfo.Local, bgOption.Queues[0]); // 每周周一早八点
RecurringJob.AddOrUpdate(() => remindService.PushNoSubmittedMonthlyReportMsg(), Cron.Monthly(1, 8), TimeZoneInfo.Local, bgOption.Queues[0]); // 每月1日早8点
请注意,该方法的名称为’Enqueue’,而不是’Call’,'Invoke’等。选择该方法的名称是为了强调给定方法的调用仅在当前执行上下文中排队,并且在排队之后立即将控制返回给调用线程。
它会在不同的执行上下文中调用。What does this mean?有些事情会脱离你对方法调用过程的通常期望。你应该知道他们。
(上面两段文字来自官网文档中的一个文章连接,也许是来着开发者的善意提醒。这篇文章也是一篇有助于理解hangfire的文章。See More)
// 内部源码
public static string Enqueue([NotNull, InstantHandle] Expression methodCall)
{
var client = ClientFactory();
return client.Enqueue(methodCall);
}
该方法基于给定的方法调用表达式创建一个新的fire-and-forget作业。该方法接受一个参数,表示将被编组到服务器的方法调用表达式。接下来我们看一下var client = ClientFactory();方法的具体实现
内部源码:
// 内部源码
internal static Func ClientFactory
{
get
{
lock (ClientFactoryLock)
{
return _clientFactory ?? DefaultFactory;
}
}
set
{
lock (ClientFactoryLock)
{
_clientFactory = value;
}
}
}
该属性定义了一个Func的泛型委托。该属性是一个可读可写的操作,对ClientFactoryLock加锁,确保不发生死锁情况。
我们常用这个方法来在后端服务中新增执行的任务,实际使用实例:
// 使用实例
var jobId = BackgroundJob.Enqueue(() => throwExTest(throwEx));
// 内部源码
public static string ContinueWith([NotNull] string parentId, [InstantHandle][NotNull] Expression methodCall);
该方法用于新增一个延续性作业,即当传入的作业id的作业执行完毕后,才会执行该作业。
使用该方法要注意的是,其等待执行完毕的上一个作业,如果未成功执行完毕,因为抛异常等原因导致仍在计划中、等待状态的话,该作业就会一直处于等待状态。只有当上一个作业成功执行完毕后,才会将该作业由等待状态转为计划中,等待被执行。
// 使用实例
var jobId1 = BackgroundJob.Enqueue(() => throwExTest(throwEx));
var jobId2= BackgroundJob.ContinueWith(jobId1, () => hangfireCheckTest(pushCircularJobId));
我新建了一个空白的,ASP.NET Web + MVC空白项目,在该项目上进行的步骤。
using Microsoft.Owin;
using Owin;
using System;
using Hangfire;
using Hangfire.MySql; // MySqlStorage
using Hangfire.MySql.Core;
using System.Data; // IsolationLevel
using System.Web.Configuration; // ConnectionStrings
using Hangfire.Dashboard;
[assembly: OwinStartup(typeof(Web.Startup))]
namespace Web
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 简洁版本一 无任何自定义配置,均采用默认配置
var dbHangfire = WebConfigurationManager.ConnectionStrings["dbHangfire"].ToString();
GlobalConfiguration.Configuration.UseStorage(new MySqlStorage(dbHangfire));
app.UseHangfireDashboard();
app.UseHangfireServer();
}
}
}
备注:
。// 配置 MySqlStorageOptions
var dbHangfire = WebConfigurationManager.ConnectionStrings["dbHangfire"].ToString();
GlobalConfiguration.Configuration.UseStorage(new MySqlStorage(dbHangfire, new MySqlStorageOptions
{
TransactionIsolationLevel = IsolationLevel.ReadCommitted, // 事务隔离级别。默认值为读提交。
QueuePollInterval = TimeSpan.FromSeconds(15), // 作业队列轮询间隔。默认值为15秒
JobExpirationCheckInterval = TimeSpan.FromHours(1), // 作业过期检查间隔(管理过期记录)。默认为1小时
CountersAggregateInterval = TimeSpan.FromMinutes(5), // 间隔到聚合计数器。默认为5分钟
PrepareSchemaIfNecessary = true, // 如果设置为true,则创建数据库表。默认值为true
DashboardJobListLimit = 50000, // 仪表板作业列表上限。默认值为50000
TransactionTimeout = TimeSpan.FromMinutes(1), // 事务超时。默认为1分钟
}));
app.UseHangfireDashboard();
app.UseHangfireServer();
// 配置 服务端配置项
var dbHangfire = WebConfigurationManager.ConnectionStrings["dbHangfire"].ToString();
GlobalConfiguration.Configuration.UseStorage(new MySqlStorage(dbHangfire));
app.UseHangfireDashboard();
var bgOption = new BackgroundJobServerOptions
{
ServerName = String.Format("{0}.{1}", Environment.MachineName, Guid.NewGuid().ToString()), //服务器唯一的标识符
Queues = new string[] { "defult" }, // 自定义队列
WorkerCount = 5, //最大并行数
SchedulePollingInterval = TimeSpan.FromMinutes(1),
};
app.UseHangfireServer(bgOption);
备注:
// 方法一 如bgOption.Queues[0] 这样指定队列
RecurringJob.AddOrUpdate(() => remindService.CreateReport(), Cron.Daily(0), TimeZoneInfo.Local, bgOption.Queues[0])
//方法二 使用QueueAttribute类
[Queue("critical")]
public void SomeMethod() { }
BackgroundJob.Enqueue(() => SomeMethod())
WorkerCount = Environment.ProcessorCount * 5
,我的主机为I5-8400,6核心,Environment.ProcessorCount为6,默认值为20。不过因为未在多台主机测试不确定是否与什么有关还是就是写死的默认值为20。测试环境的机器配置为2核心,但是部署后其默认值为10。所以如果有并发要求最好自己手动配置数量。概述:
Hangfire Dashboard为我们提供了可视化的对后台任务进行管理的界面,我们可以直接在这个页面上对定时任务进行删除、立即执行等操作。
默认情况下,这个页面只能在部署Hangfire的机器上进行访问,想要在其他地方进行访问,需要配置权限认证模块:Hangfire.Dashboard.Authorization。
引用安装:
应用:
在Startup.cs中的Configuration方法中添加以下代码:
在代码中的Login和Password后面写登录的用户名和密码,这样在下次打开Hangfire的Dashboard时,就会弹出需要输入用户名和密码的窗口了,输入之后就可了打开Dashboard了。
using Hangfire;
using Hangfire.MySql; // MySqlStorage
using Hangfire.MySql.Core;
using System.Data; // IsolationLevel
using System.Web.Configuration; // ConnectionStrings
using Hangfire.Dashboard;
// Startup.cs > Configuration
// 配置 登陆
var dbHangfire = WebConfigurationManager.ConnectionStrings["dbHangfire"].ToString();
GlobalConfiguration.Configuration.UseStorage(new MySqlStorage(dbHangfire));
var filter = new BasicAuthAuthorizationFilter(
new BasicAuthAuthorizationFilterOptions
{
SslRedirect = false, // 是否将所有非SSL请求重定向到SSL URL
RequireSsl = false, // 需要SSL连接才能访问HangFire Dahsboard。强烈建议在使用基本身份验证时使用SSL
LoginCaseSensitive = false, //登录检查是否区分大小写
Users = new[]
{
new BasicAuthAuthorizationUser
{
Login ="mzc",//用户名
PasswordClear="mzc"
// Password as SHA1 hash
//Password=new byte[]{ 0xf3,0xfa,,0xd1 }
}
}
});
var options = new DashboardOptions
{
//Authorization = new[] { new HangfireDashboardAuthorizationFilter() }
AuthorizationFilters = new[] {
filter
}
};
app.UseHangfireDashboard("/hf", options);
app.UseHangfireServer();
SHA1 hash密码生成方式(来自参考4):
string password = "";
using (var cryptoProvider = System.Security.Cryptography.SHA1.Create())
{
byte[] passwordHash = cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(password));
string result = "new byte[] { " +
String.Join(",", passwordHash.Select(x => "0x" + x.ToString("x2")).ToArray())
+ " } ";
}
hangfire总共会在数据库中创建十二张表,用以做数据永久化存储。
aggregatedcounter,counter,distributedlock,hash,job,jobparameter,jobqueue,jobstate,list,server,set,state。
一下内容为官网内容,只是官网为英文版,此处只是将翻译后的中文放在这里,结尾会附官网连接:
默认情况下,在第一个用户访问您的站点之前,不会启动Web应用程序中的Hangfire Server实例。更重要的是,有些事件会在一段时间后将您的Web应用程序关闭(我说的是Idle Timeout和不同的应用程序池回收事件)。在这些情况下,您的重复任务和延迟的作业将不会排队,并且不会处理排队的作业。
对于在您控制的服务器上运行的Web应用程序(物理或虚拟),您可以使用Windows®≥2002R2附带的IIS≥7.5的自动启动功能。完整设置需要执行以下步骤:
实现该IProcessHostPreloadClient接口的特殊类。它将在启动期间和每个应用程序池回收后由Windows进程激活服务自动调用。
public class ApplicationPreload : System.Web.Hosting.IProcessHostPreloadClient
{
public void Preload(string[] parameters)
{
HangfireBootstrapper.Instance.Start();
}
}
HangfireBootstrapper按如下方式创建类。由于这两个Application_Start和Preload方法将在启用了自动启动的环境中被调用,我们需要确保初始化逻辑将被调用一次。
public class HangfireBootstrapper : IRegisteredObject
{
public static readonly HangfireBootstrapper Instance = new HangfireBootstrapper();
private readonly object _lockObject = new object();
private bool _started;
private BackgroundJobServer _backgroundJobServer;
private HangfireBootstrapper()
{
}
public void Start()
{
lock (_lockObject)
{
if (_started) return;
_started = true;
HostingEnvironment.RegisterObject(this);
GlobalConfiguration.Configuration
.UseSqlServerStorage("connection string");
// Specify other options here
_backgroundJobServer = new BackgroundJobServer();
}
}
public void Stop()
{
lock (_lockObject)
{
if (_backgroundJobServer != null)
{
_backgroundJobServer.Dispose();
}
HostingEnvironment.UnregisterObject(this);
}
}
void IRegisteredObject.Stop(bool immediate)
{
Stop();
}
}
然后,global.asax.cs按如下所述更新您的文件。调用类实例的方法很重要,同样重要的是在没有启用自动启动功能的环境中启动Hangfire服务器(例如,在开发机器上)。
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
HangfireBootstrapper.Instance.Start();
}
protected void Application_End(object sender, EventArgs e)
{
HangfireBootstrapper.Instance.Stop();
}
}
请注意,对于最后一个条目,WebApplication1.ApplicationPreload是应用程序中实现的类的全名,IProcessHostPreloadClient并且WebApplication1是应用程序库的名称。你可以在这里阅读更多相关信息。无需将IdleTimeout设置为零 - 当应用程序池的启动模式设置为时AlwaysRunning,空闲超时不再起作用。
=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!=
官方文档中的applicationHost文件中配置serviceAutoStartProviders的介绍有坑!
实际应为:
其实此处不过是对于IIS网站自动启动的一种应用:
其原理为 [9] :当冷启动IIS 7.5服务器或回收单个应用程序池时,IIS 7.5使用applicationHost.config文件中的信息来确定需要自动启动哪些Web应用程序。对于标记为自动启动的每个应用程序,IIS7.5向ASP.NET 4发送请求,以在应用程序暂时不接受HTTP请求的状态下启动应用程序。当它处于此状态时,ASP.NET将实例化serviceAutoStartProvider属性定义的类型(如上例所示)并调用其公共入口点。
初始化代码在Preload方法中运行并且方法返回后,ASP.NET应用程序已准备好处理请求。
通过在IIS .5和ASP.NET 4中添加自动启动功能,您现在可以在处理第一个HTTP请求之前执行昂贵的应用程序初始化。例如,您可以使用新的自动启动功能初始化应用程序,然后向负载均衡器发出信号,表明应用程序已初始化并准备接受HTTP流量。
再一个问题,就是如果此处配置了这个,同时startup中也配置了实例化服务,那么你会发现这样一种情况,就是会出现两个serve同时存在的情况。因为Startup类和ApplicationPreload 类均被调用了一遍,里面各实例化了一个服务端。是这样的,Hangfire官网有一句话:By default, Hangfire Server instance in a web application will not be started until the first user hits your site. 也就是说,默认情况下,无人访问时,即便网站正在运行未回收,Hangfire是不会启动的(Startup)。然而ApplicationPreload会在每次网站准备回收准备实现always running时都会被调用一次。所以碰到这种情况,如果担心机器配置或数据库连接池不足以支撑两个服务端的最大工作数量的话,可以将startup中的服务实例化去掉。其余暂时尚未发现问题。
=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!=
最简单的方法 - 回收您的应用程序池,等待5分钟,然后转到Hangfire Dashboard UI并检查当前的Hangfire Server实例是否在5分钟前启动。
如果在进行这些更改后您的应用程序无法加载,请打开控制面板→管理工具→事件查看器,检查Windows事件日志。然后打开Windows日志→应用程序并查找最近的错误记录.
Always On
感谢以下参考资料:
[1] 云栖社区》HangFire分布式后端作业调度框架服务
[2] Hangfire项目实践分享
[3] 定时任务组件Hangfire解析
[4] 为DashBoard页面添加权限认证
[5] C#中Byte转换相关的函数
[6] wall-ee》hangfire使用笔记
[7] 简书》.NET Core开源组件:后台任务利器之Hangfire
[8]Are your methods ready to run in background?(官方文档中的连接)
[9]ASP.NET4和Visual Studio2010 Web开发概述》自动启动Web应用程序