Hangfire定时触发作业,好像很简单?
【导读】本节我们继续稍微详细讲讲在我没有详细了解源码的前提下来探讨通过Hangfire定时触发作业有哪些需要注意的事项
间隔时间内执行作业
举个栗子,每隔10秒监控系统CPU,若CPU飙高(根据实际业务定义百分比)则在控制台打印输出,第一次执行作业若CPU飙高则打印输出,但在接下来一分钟内CPU连续飙高则不再打印,若中间有中断(CPU正常)则恢复正常打印,如此反复循环。
一般来讲定时作业都会执行业务,但上述栗子却根据作业内部逻辑判断是否执行打印,所以二者还是有所区别
接下来我们一步步来进行大致模拟实现,首先我们利用内存来存储作业相关操作,然后每隔10秒执行打印方法
_colorify = new Format(Theme.Dark);
GlobalConfiguration.Configuration.UseMemoryStorage();
using var server = new BackgroundJobServer();
RecurringJob.AddOrUpdate(() => Print(), "*/10 * * * * *", TimeZoneInfo.Local);
Console.ReadLine();
接下来则是执行上述打印方法
public static void Print()
{
_colorify.WriteLine($"{DateTime.Now}:CPU飙高啦~~~", Colors.txtSuccess);
}
待执行作业方法一定要为公共(public)方法,否则会抛出如下异常
最后我们每隔10秒执行一次作业看看打印输出时间是否如我们所预期那样
虽然Hangfire从1.7+开始支持秒级,但对于作业默认的最小间隔时间是15秒,貌似是无法改变。所以上述我们看到的作业间隔时间差是15秒而非10秒
即使针对只触发一次作业设置为10秒也无济于事,不知道是否可改变,未深入研究
var options = new BackgroundJobServerOptions
{
SchedulePollingInterval = TimeSpan.FromMilliseconds(10)
};
BackgroundJob.Schedule(() => Print(), TimeSpan.FromSeconds(10));
之前我们讲过若是利用SQLite存储作业那么将会出现重复并发执行的情况,比如我们如下将其修改为SQLite存储
GlobalConfiguration.Configuration.UseSQLiteStorage("Data Source=./hangfire.db;");
此时毫无疑问会出现连续打印情况(在内存中也会偶尔出现,概率没有SQLit高)
若是必须限制在间隔时间内只能执行一次作业且在内存或SQLite中存储作业,那么我们可以尝试使用限流算法(漏桶算法),在指定时间内只允许几个请求进入(算法参考地址:https://github.com/robertmircea/RateLimiters)
private static readonly FixedTokenBucket bucket = new FixedTokenBucket(1, 1, 10000);
实例化对应漏桶算法且在10秒内只能透传1个请求执行作业
public static void Print()
{
if (bucket.ShouldThrottle(1))
{
return;
}
_colorify.WriteLine($"{DateTime.Now}:CPU飙高啦~~~", Colors.txtSuccess);
}
若在10秒超过1个请求进入则立即返回
接下来我们实现在1分钟内禁止连续打印CPU飙高的情况,首先我们将1分钟内时间控制利用内存存储来实现
var provider = new ServiceCollection()
.AddMemoryCache()
.BuildServiceProvider();
cache = provider.GetService();
然后我们继续改造打印方法,在内存中记录第1次打印的时间,然后对比接下来1分钟的时间差,若小于则返回,否则打印再次存储打印的时间
public static void Print()
{
double totalMinutes = 0;
if (cache.TryGetValue("sys_alarm_time", out DateTime time))
{
var subtract = DateTime.Now.Subtract(time);
totalMinutes = subtract.TotalMinutes;
_colorify.WriteLine($"subtract:{totalMinutes}", Colors.txtInfo);
}
if ((int)totalMinutes < 1 && totalMinutes != 0)
{
return;
}
cache.Set("sys_alarm_time", DateTime.Now);
_colorify.WriteLine($"{DateTime.Now}:CPU飙高啦~~~", Colors.txtSuccess);
}
这里唯一需要注意的是在比较时间差1分钟,不能用Convert.ToInt32来进行强制转换
if (Convert.ToInt32(totalMinutes) < 1 && totalMinutes != 0)
{
return;
}
利用上述强制转换不能精确到接近于1分钟,因为它是银行家算法四舍五入,更贴切的说是四舍六入,比如为时间差为0.6时,经过强制转换后结果就为1,所以利用第一种强制转换则是只取整数部分
虽说作业执行时间长短会略有差异,但利用第1种强制转换会控制时间差不会和1分钟相差太多
我们看到上述时间间隔刚好是1分钟加上默认的时间间隔15秒,能做到这样基本上差不多了
本节我们借助一个栗子主要讲述在控制台中执行存储在内存或SQLite中的作业,在实际项目中,使用Hangfire时或多或少都会存在一些问题
比如我们是否考虑将作业存储在内存中,那么对于间隔时间很短的定时作业,是否会带来的很大的存储开销呢?理论上Hangfire会对其进行处理,又比如如果作业有几百个间隔时间很短的定时作业,那么Hangfire是否会存在性能问题呢?还有其他等在使用过程中可能遇到的问题。