Hangfire 分布式后端作业调度框架服务

  • 概述
  • 特点
  • 版本
  • 基本结构
  • 基本功能及使用
  • 基本功能的部分对象解析
  • 项目引入
  • 拓展
    • MySqlStorageOptions 数据库配置项
    • BackgroundJobServerOptions 服务端配置项
    • DashBoard 页面配置权限认证(登陆)
    • Hangfire的数据库分析(MySql)
    • 使ASP.NET应用程序始终运行
  • 开发过程需要注意的点
  • 体验得到的一些缺点/注意点
  • 留疑/思索

概述

分布式后端作业调度框架服务,我们只需要关心业务逻辑代码,而不用关心调度机制持。

官方原文:在.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):

  • 方案一(不推荐):客户端A、服务端B、仪表盘C 分别运行在3个独立的项目中。
  • 优缺点:没什么优点,而且AB分开会导致服务端B从数据库提取任务列表,准备反向加载程序集执行任务的时候,在自己的程序代码中找不到对应的业务逻辑代码或者引用类,因为业务逻辑代码是在客户端A项目中创建,自然业务逻辑类及功能代码都在A项目的程序集中,自然找不到。
  • 让服务端B也引用YourOwnJobLibrary.dll即可,但是这并不是我们想要的结果,以后更新服务什么的还得两头更新很麻烦。
  • 方案二(推荐):客户端A、服务端B在同一个项目中,仪表盘C独立网站项目中。
  • 优缺点:这样比较合理,更新服务方便,即便没有仪表盘C,作业正常调度执行;适合后台相对固定的作业。
  • 可以将AB放在Windows服务项目中(VS中可以创建),这样系统重启什么的毫无顾虑,更新服务程序的时候只需停止服务->替换程序->开启服务即可。
  • 方案三(中等):客户端A、服务端B、仪表盘C在同一网站项目只能是网站项目,因为仪表盘只能在Web项目(API、WebForm、MVC )中。
  • 优缺点:视情况也,如果宿主在IIS中,IIS默认20分钟没有人访问会停止,HangFire作业服务也会定制,可将此时间在IIS配置中延长,比较适合任务经常需要灵活变动处理的场景。

备注(来自参考1):

  1. 创建任务可以是在控制台程序Main方法中执行一次把任务Load到数据库,或则在网站上用户点击某个按钮执行后端方法创建。
  2. 服务端比较灵活,可宿主在Windows服务、控制台程序、IIS中…
  3. 首次启动连接数据库时,会自动生成12张系统表,请确保现有库中不存在以下表名:aggregatedcounter、counter、distributedlock、hash、job、jobparameter、jobqueue、jobstate、list、server、set、state
  4. 原则上同一台电脑上只需要1个服务端存在,测试中发现这么一种情况:服务端B宿主在D控制台程序中,因误操作在仪表盘C网站项目中也启动一个HangeFire宿主服务E,最后的结果是服务端B的程序失效,服务端E正常运作,但是仪表盘上显示找不到任务作业对应的程序集,因为程序集在服务端B程序中,如方案1的System.IO.FileNotFoundException…
  5. HangFire执行的任务里面如果涉及到C盘创建文件夹 可能会因为权限问题 创建失败,采用管理员权限运行程序即可创建

基本功能及使用

使用:
引用命名空间。

using Hangfire;
  • Fire-and-forget jobs(基于队列的任务处理)
var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget!"));
  • Delayed jobs(延迟任务执行)
var jobId = BackgroundJob.Schedule(() => Console.WriteLine("Delayed!"), TimeSpan.FromDays(7));
  • Recurring jobs(定时任务执行)
RecurringJob.AddOrUpdate(() => Console.WriteLine("Recurring!"), Cron.Daily);
  • Continuations(延续性任务执行)
BackgroundJob.ContinueWith(jobId , () => Console.WriteLine("Continuation!"));
  • Batches(批处理) | Pro 版
var batchId = BatchJob.StartNew(x =>
{x.Enqueue(() => Console.WriteLine("Job 1"));x.Enqueue(() => Console.WriteLine("Job 2"));
})
  • Batch Continuations(延续性批处理) | Pro 版
BatchJob.ContinueWith(batchId, x =>
{x.Enqueue(() => Console.WriteLine("Last Job"));
});

基本功能的部分对象解析

  • RecurringJob.AddOrUpdate()
// 内部源码
public static void AddOrUpdate(Expression<Action> 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点
  • BackgroundJob.Enqueue()

请注意,该方法的名称为’Enqueue’,而不是’Call’,'Invoke’等。选择该方法的名称是为了强调给定方法的调用仅在当前执行上下文中排队,并且在排队之后立即将控制返回给调用线程。
它会在不同的执行上下文中调用。What does this mean?有些事情会脱离你对方法调用过程的通常期望。你应该知道他们。
(上面两段文字来自官网文档中的一个文章连接,也许是来着开发者的善意提醒。这篇文章也是一篇有助于理解hangfire的文章。See More)

// 内部源码
public static string Enqueue([NotNull, InstantHandle] Expression<Action> methodCall)
{var client = ClientFactory();return client.Enqueue(methodCall);
}

该方法基于给定的方法调用表达式创建一个新的fire-and-forget作业。该方法接受一个参数,表示将被编组到服务器的方法调用表达式。接下来我们看一下var client = ClientFactory();方法的具体实现
内部源码:

// 内部源码
internal static Func<IBackgroundJobClient> ClientFactory
{get{lock (ClientFactoryLock){return _clientFactory ?? DefaultFactory;}}set{lock (ClientFactoryLock){_clientFactory = value;}}
}

该属性定义了一个Func的泛型委托。该属性是一个可读可写的操作,对ClientFactoryLock加锁,确保不发生死锁情况。

我们常用这个方法来在后端服务中新增执行的任务,实际使用实例

// 使用实例
var jobId = BackgroundJob.Enqueue(() => throwExTest(throwEx));
  • BackgroundJob.ContinueWith()
// 内部源码
public static string ContinueWith([NotNull] string parentId, [InstantHandle][NotNull] Expression<Action> methodCall);

该方法用于新增一个延续性作业,即当传入的作业id的作业执行完毕后,才会执行该作业。
使用该方法要注意的是,其等待执行完毕的上一个作业,如果未成功执行完毕,因为抛异常等原因导致仍在计划中、等待状态的话,该作业就会一直处于等待状态。只有当上一个作业成功执行完毕后,才会将该作业由等待状态转为计划中,等待被执行。

// 使用实例
var jobId1 = BackgroundJob.Enqueue(() => throwExTest(throwEx));
var jobId2= BackgroundJob.ContinueWith(jobId1, () => hangfireCheckTest(pushCircularJobId));

项目引入

我新建了一个空白的,ASP.NET Web + MVC空白项目,在该项目上进行的步骤。

  1. NuGet下载安装Hangfire;NuGet下载安装Hangfire.MySql.Core;
  2. 在项目的App_Start文件夹中的Startup.cs的Configuration中添加代码
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();}}
}
  1. Web.config中添加数据库连接串。
<connectionStrings>    <add name="dbHangfire" connectionString="server=;initial catalog=;persist security info=True;user id=;password=;Character Set=utf8;Allow User Variables=True" providerName="MySql.Data.MySqlClient" /></connectionStrings>
  1. 然后运行项目就可以跑起来了。默认的面板连接为/hangfire。

演示结果如下:

备注:

  1. Startup.cs类如果是手动创建的,未执行,请检查Microsoft.Owin.Host.SystemWeb包是否安装,Web.config中是否有
    <add key="owin:AutomaticAppStartup" value="true" />
  2. 根据使用的是Hangfire.MySql还是hangfire.MySql.Core,或则高版本或低版本,在自定义配置项时会有所区别。比如MySqlStorage的初始化入参传递数据库连接串信息时,根据情况会需要传递ConnectionStrings的name或必须得是ConnectionStrings。

拓展

MySqlStorageOptions 数据库配置项

// 配置 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,则创建数据库表。默认值为trueDashboardJobListLimit = 50000,                            // 仪表板作业列表上限。默认值为50000 TransactionTimeout = TimeSpan.FromMinutes(1),             // 事务超时。默认为1分钟
}));
app.UseHangfireDashboard();
app.UseHangfireServer();

BackgroundJobServerOptions 服务端配置项

// 配置 服务端配置项
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);

备注:

  1. 每个Hangfire服务器都有一个唯一的标识符,由两部分组成,后一部分是处理同一台机器上多个服务器的进程ID。前一部分是服务器名称,默认为计算机名称,用于处理不同计算机的唯一性。由于默认值仅在进程级别上提供唯一性,因此如果要在同一进程中运行不同的服务器实例,那么你就需要如上所示进行手动处理,上面实例就是手动new NewGuid。
  2. 对于Queues 如果开发者不自行配置的话,hangfire默认是只增加一个名为‘defult’的队列,既意为若未进行配置,就一个队列可选Queues[0]。
  3. 队列名称参数必须仅包含小写字母,数字和下划线字符。
  4. 要将作业放入不同的队列,方法一是在创建作业时选择队列,二是在方法上使用QueueAttribute类。
// 方法一  如bgOption.Queues[0] 这样指定队列
RecurringJob.AddOrUpdate(() => remindService.CreateReport(), Cron.Daily(0), TimeZoneInfo.Local, bgOption.Queues[0])
//方法二  使用QueueAttribute类
[Queue("critical")]
public void SomeMethod() { }BackgroundJob.Enqueue(() => SomeMethod())
  1. hangfire将首先从关键队列中获取作业,然后从默认队列中获取作业
  2. 关于WorkerCount ,有文章称其默认值是CPU核心数*5,但可能是被官网资料的代码示例误导WorkerCount = Environment.ProcessorCount * 5,我的主机为I5-8400,6核心,Environment.ProcessorCount为6,默认值为20。不过因为未在多台主机测试不确定是否与什么有关还是就是写死的默认值为20。测试环境的机器配置为2核心,但是部署后其默认值为10。所以如果有并发要求最好自己手动配置数量。

DashBoard 页面配置权限认证(登陆)

概述:
Hangfire Dashboard为我们提供了可视化的对后台任务进行管理的界面,我们可以直接在这个页面上对定时任务进行删除、立即执行等操作。
默认情况下,这个页面只能在部署Hangfire的机器上进行访问,想要在其他地方进行访问,需要配置权限认证模块:Hangfire.Dashboard.Authorization。
引用安装:

  1. 项目地址:https://github.com/HangfireIO/Hangfire.Dashboard.Authorization
  2. 在已经安装Hangfire基本组件的项目中,通过Nuget程序包管理器添加Hangfire.Dashboard.Authorizaiton或Nuget控制台添加。
    通过Nuget程序包管理控制台安装的命令:通过Nuget程序包管理控制台安装的命令:
    Install-Package 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 URLRequireSsl = false,           // 需要SSL连接才能访问HangFire Dahsboard。强烈建议在使用基本身份验证时使用SSLLoginCaseSensitive = 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 = "<your password here>";
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())+ " } ";
}

实际应用结果:

备注:

  1. 注意的是,使用AuthorizationFilters ,会有过时将来被移除的提示。提示推荐使用Authorization = new[] { new HangfireDashboardAuthorizationFilter() }
  2. 根据使用版本不同,其应用方法和对象均有所改动。此处我是用的是Hangfire.MySql.Core最新版本nugert包。使用Hangfire.MySql的1.0.0.0包我未能成功实现,虽然编译未报错,但是跳转login页面时报404,尚待研究。而且Hangfire.MySql的0.0.0.7版本的包甚至在配置数据库配置项那一步就出了问题。
  3. 这种应用是适用于本地打开,即hangfire默认获取当前本地权限,在此基础上添加了登陆验证。若是后端服务器,想要被远程登陆访问,应该是另一种思路去实现。

Hangfire的数据库分析(MySql)

hangfire总共会在数据库中创建十二张表,用以做数据永久化存储。
aggregatedcounter,counter,distributedlock,hash,job,jobparameter,jobqueue,jobstate,list,server,set,state。

使ASP.NET应用程序始终运行

一下内容为官网内容,只是官网为英文版,此处只是将翻译后的中文放在这里,结尾会附官网连接:

默认情况下,在第一个用户访问您的站点之前,不会启动Web应用程序中的Hangfire Server实例。更重要的是,有些事件会在一段时间后将您的Web应用程序关闭(我说的是Idle Timeout和不同的应用程序池回收事件)。在这些情况下,您的重复任务和延迟的作业将不会排队,并且不会处理排队的作业。

  • 步骤一:内部部署应用程序

对于在您控制的服务器上运行的Web应用程序(物理或虚拟),您可以使用Windows®≥2002R2附带的IIS≥7.5的自动启动功能。完整设置需要执行以下步骤:

  1. 启用Windows进程激活(WAS)和万维网发布(W3SVC)服务的自动启动(默认情况下启用)
  2. 配置应用程序池的自动启动(默认情况下启用)
  3. 启用应用程序池的始终运行模式并配置自动启动功能
  • 步骤二:创建类

实现该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();}
}
  • 步骤三:启用服务自动启动
    创建上面的类后,您应该编辑全局applicationHost.config文件(%WINDIR%\System32\inetsrv\config\applicationHost.config)。首先,您需要将应用程序池的启动模式更改为AlwaysRunning,然后启用Service AutoStart Providers。
    进行这些更改后,将自动重新启动相应的应用程序池。确保仅在修改所有元素后保存更改
<applicationPools><add name="MyAppWorkerProcess" managedRuntimeVersion="v4.0" startMode="AlwaysRunning" />
</applicationPools><!-- ... --><sites><site name="MySite" id="1"><application path="/" serviceAutoStartEnabled="true"serviceAutoStartProvider="ApplicationPreload" /></site>
</sites><!-- Just AFTER closing the `sites` element AND AFTER `webLimits` tag -->
<serviceAutoStartProviders><add name="ApplicationPreload" type="WebApplication1.ApplicationPreload, WebApplication1" />
</serviceAutoStartProviders>

请注意,对于最后一个条目,WebApplication1.ApplicationPreload是应用程序中实现的类的全名,IProcessHostPreloadClient并且WebApplication1是应用程序库的名称。你可以在这里相关信息。无需将IdleTimeout设置为零 - 当应用程序池的启动模式设置为时AlwaysRunning,空闲超时不再起作用。

=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!=
官方文档中的applicationHost文件中配置serviceAutoStartProviders的介绍有坑!

<add name="ApplicationPreload" type="WebApplication1.ApplicationPreload, WebApplication1" />

实际应为:

 <add name="ApplicationPreload"  type="MyNamespace.ApplicationPreload, MyLibrary" />

其实此处不过是对于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日志→应用程序并查找最近的错误记录.

  • Azure Web应用程序
    为Microsoft Azure中托管的应用程序启用始终运行功能更简单:只需打开“配置”页面上的开关并保存设置即可。Always On
    This setting does not work for free sites

    官网文档:Making ASP.NET application always running

开发过程需要注意的点

  • 要保证压入队列的方法是幂等的。因为方法可以被手动重试,也会在失败时自动重试。虽然可以通过[Retry(0)]过滤器应用于精确方法或全局来禁用自动重试,但作为一般规则请记住,你的job会至少执行一次。[8]
  • 关于队列之间的执行优先级顺序,优先级是基于字母顺序的。是的,使用hangfire时,优先级是基于字母顺序的,因为在table variable or temporary table中更复杂的查询会降低吞吐量。所以如果想要队列之间有优先级顺序就按照首字母排序吧。官方文档对队列这块只说了一句顺序是很重要的,以至于误导了一部分人一开始就认为是按照索引顺序。该点获取来源

体验得到的一些缺点/注意点

  • 多个服务器时,服务器与服务器之间是互不关心的。你即无法指定由哪个服务器去执行任务,也无法设置服务器优先级。服务器之间的负载均衡等就由hangfire自己调控。
  • 任务执行当出现分钟级别的时间差时,要多注意了。尽管hangfire能够精确到分级别,但是根据论坛讨论和反馈,在分钟级别的时间差上,可能因为延迟、性能、队列优先级的问题等,导致一些问题无法精确执行或不执行作业,如设置两个任务均每分钟执行一次,这种情况。
  • 当执行压力过大,如超出数据库连接池等情况,会出现作业堆积或随机锁死作业的情况。这是从论坛中获取来的情况。
  • 当碰到异常需要重试时,“未找到目标方法”这个错误的重试时间间隔似乎是不固定的,第一次是5分钟(3:55创建,4:00重试),第二次是19分钟(4:19重试),第三次是24分钟(4:43重试),请注意,此时该作业被放置在计划中,被放在重试中,而非被打上‘失败’标签,也未放入失败队列中。
  • 根据多次验证,如果不指定队列,那么就是会使用default队列。所以不指定队列和指定队列‘default’是一样的。它会在有default的服务器中随机一个服务器去执行(下一篇细说,同时一种偏门解决本地调试不要与其它服务端混淆的方法)。

留疑/思索

  1. 若一个作业正在进行中,此时hangfire宿主服务被关闭,该作业会被如何处理?hangfire再次开启时又会如何?中间若产生了不完全数据会被如何处理?
  2. 一个作业需要执行时间过长(循环作业之类的)尚未结束,出现了新的作业(可能会导致重复数据)的情况,该如何处理?【思路1.在执行的任务方法前加上mutex特性即可,如果作业未完成,新作业开启的话,新作业会放入计划中的作业队列中,直到前面的作业完成。思路2.在作业上设置分布式锁,直到它正确完成为止。思路可用么?】

感谢以下参考资料:
[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应用程序

C#-初识Hangfire相关推荐

  1. day3----编码-集合-深浅copy-文件操作-函数初识

    day3----编码-集合-深浅copy-文件操作-函数初识 本文档主要内容: 一 编码 二 集合 三 深浅copy 四 文件操作 五 函数初识 首先,我们来看看两个字符串的比较 打开cmd,进入do ...

  2. ⑥python模块初识、pyc和PyCodeObject

    一.模块初识(一) 模块,也叫库.库有标准库第三方库. 注意事项:文件名不能和导入的模块名相同 1. sys模块 import sys print(sys.path) #打印环境变量 print(sy ...

  3. 初识java类的接口实现

    初识java类的接口实现 如果两个类之间不存在继承关系,且两个类都想实现同一个接口,两个类都必须实现接口中全部方法,否则报语法错误 如果两个类之间存在继承关系也想实现同一个接口,父类如果实现了某个接口 ...

  4. vba 编辑combobox内容_初识Visual Basic编辑器并建立一段简单的代码

    大家好,从今日开始我正式推出"VBA之EXCEL应用"教程,这个教程是面向初学人员的教程,教程一共三册,十七个章节,从简单的录制宏实现一直讲到窗体的搭建,都是我们在利用EXCEL工 ...

  5. 16.1、python初识面向对象(1)

    初识面向对象 楔子 你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战,那至少需要2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人 ...

  6. 精通Python网络爬虫:核心技术、框架与项目实战.1.1 初识网络爬虫

    摘要 网络爬虫也叫做网络机器人,可以代替人们自动地在互联网中进行数据信息的采集与整理.在大数据时代,信息的采集是一项重要的工作,如果单纯靠人力进行信息采集,不仅低效繁琐,搜集的成本也会提高.此时,我们 ...

  7. 初识mysql数据字段属性_MySQL数据库~~~~初识、基础数据类型

    一 数据库初识 1.1 什么是数据库 数据库(DataBase,简称DB),简而言之可视为电子化的文件柜----存储电子文件的处所,用户可以对文件中的数据运行新增,截取,更新,删除等操作. 所谓数据库 ...

  8. Nancy in .Net Core学习笔记 - 初识Nancy

    原文:Nancy in .Net Core学习笔记 - 初识Nancy 前言 去年11月份参加了青岛MVP线下活动,会上老MVP衣明志介绍了Nancy, 一直没有系统的学习一下,最近正好有空,就结合. ...

  9. Python 函数初识 (1)

    一.今日主要内容 认识函数 函数:对功能或者动作的封装(定义) 语法: def 函数名字(形参) 函数体 函数的调用格式:函数名(实参) 函数的返回值 关键字:return 终止函数的运行 1.函数内 ...

最新文章

  1. 爬虫scrapy框架中间件的使用
  2. 一个好玩的 屏蔽别人审查元素F12 右键及其他复制粘贴等
  3. Android3个页面跳转代码,从零开始Android组件化改造(三) - 页面跳转与路由组件...
  4. Swagger生成的接口需要权限验证的处理方法
  5. 分布式通信协议RPC协议简介
  6. 苹果iPhone XI新爆料:用了被小米当噱头的TOF技术
  7. hdu4616_Game_树形DP
  8. 实现文字左右滚动 javascript
  9. 郑州大学远程教育c语言程序设计答案,郑州大学远程教育C语言考试试卷.doc
  10. HyperLedger Fabric 1.4 kafka生产环境部署(11.1)
  11. linux下安装与部署redis
  12. 地址解析:使用Google API将地址文本转换为经纬度
  13. 【摄像头】Global Shutter(全局快门)与Rolling Shutter(卷帘快门)的区别与比较...
  14. 9月30日skype事件
  15. 私活,永远解救不了自己屌丝的人生!
  16. 你要知道的N个Android适配问题
  17. idea项目配置jsp模板
  18. 7-4 出圈游戏 (c 语言)PTA
  19. SEO优化收徒蜘蛛池是什么
  20. 【Python随笔】python进程池ProcessPoolExecutor的用法与实现分析

热门文章

  1. 安装fcitx五笔拼音
  2. 牛!程序媛一口气拿下BAT、美团、vivo、爱奇艺等公司Offer面经总结
  3. 每当图片传过来时进行对比_每当应用开始使用Mac的网络摄像头时如何获取通知...
  4. 不打开Excel文件读取工作表名(ADOX)
  5. Python无法打开excel文档解决办法
  6. 小米8绑定账号和设备验证失败_不要浪费小米10的双扬声器!杜比全景音刷入教程分享...
  7. Sencha Touch(Extjs)
  8. enable 华为交换机ntdp_华为交换机常用命令
  9. 《阿尔比恩的种子》pdf、mobi、epub
  10. C语音:输入两个整数,要求输出其中值较大者。要求用函数来找到大数。