最佳实践
JobDataMap
-
建议只存储基本数据(含String),避免序列化问题
-
作业执行期间,JobDetail和Trgger的底层共用一个JobDataMap 实例,因此Trigger的数据会覆盖Job中相同key的值。
-
每个独立触发器的JobDataMap 是独立的
-
在作业执行期间,建议使用MergedJobDataMap去检索key
// 不建议 var badMethod = context.JobDetail.JobDataMap.GetString("a-value"); var alsoBadMethod = context.Trigger.JobDataMap.GetString("a-value"); // 建议 var goodMethod = context.MergedJobDataMap.GetString("a-value");
Job
为统一作业的名称和群组,建议在IJob实现时就声明一个静态JobKey,便于触发器赋值使用
public class SomeJob : IJob
{
// 定义当前作业的名称和群组
public static readonly JobKey Key = new JobKey("job-name", "group-name");
public Task Execute(IJobExecutionContext context) { /* elided */ }
}
// 创建触发器
public async Task DoSomething(IScheduler schedule, CancellationToken ct)
{
var trigger = TriggerBuilder.Create()
.WithIdentity("a-trigger", "a-group")
.ForJob(SomeJob.Key)
.StartNow()
.Build();
await schedule.ScheduleJob(trigger, ct)
}
// 设置当前作业的触发器时
public async Task DoSomething(IScheduler schedule, CancellationToken ct)
{
await schedule.TriggerJob(SomeJob.Key, ct)
}
Trigger
使用TriggerUtils进行触发器时间上的操作
-
根据触发器获得执行时间列表
static IReadOnlyList
ComputeFireTimes(IOperableTrigger trigg, ICalendar? cal, int numTimes) // 设置时间 DateTimeOffset startCalendar = DateBuilder.DateOf(9, 30, 17, 1, 1, 2023); // 设置日期的触发器,间隔90天执行一次,从2023-01-01 17:30:09开始 var dailyTrigger = new CalendarIntervalTriggerImpl { StartTimeUtc = startCalendar, RepeatIntervalUnit = IntervalUnit.Day, RepeatInterval = 90 // every ninety days }; // 2023年1月1日加上360天 4*90 四个90天的循环 DateTimeOffset targetCalendar = startCalendar.AddDays(360); // 获得从0开始的6次循环(0-5) var fireTimes = TriggerUtils.ComputeFireTimes(dailyTrigger, null, 6); // 获得第五次的循环日期,2023-01-01 17:30:09当天也会执行一次 DateTimeOffset fifthTime = fireTimes[4]; // get the fifth fire time // 两次获得的日期相同 Assert.AreEqual(targetCalendar, fifthTime, "Day increment result not as expected.");
-
获得触发器最后一次执行的时间
static DateTimeOffset? ComputeEndTimeToAllowParticularNumberOfFirings(IOperableTrigger trigger, ICalendar? calendar, int numberOfTimes)
// 设置时间 DateTimeOffset startCalendar = DateBuilder.DateOf(9, 30, 17, 1, 1, 2023); // 设置日期的触发器,间隔90天执行一次,从2023-01-01 17:30:09开始 var dailyTrigger = new CalendarIntervalTriggerImpl { StartTimeUtc = startCalendar, RepeatIntervalUnit = IntervalUnit.Day, RepeatInterval = 90 // every ninety days }; // 2023年1月1日加上360天 4*90 四个90天的循环 DateTimeOffset targetCalendar = startCalendar.AddDays(360); // 获得从0开始的4次循环(0-3),然后返回第四次执行的时间 var endFireTimes = TriggerUtils.ComputeFireTimes(dailyTrigger, null, 4); // 两次获得的日期相同 Assert.AreEqual(targetCalendar, fifthTime, "Day increment result not as expected.");
-
在两个日期之间触发器执行的时间列表
static IReadOnlyList
ComputeFireTimesBetween(IOperableTrigger trigg, ICalendar? cal, DateTimeOffset from, DateTimeOffset to) // 设置触发器 var trigger = (IOperableTrigger) TriggerBuilder.Create() .WithDailyTimeIntervalSchedule(x => x .InTimeZone(TZConvert.GetTimeZoneInfo("GTB Standard Time")) // 每天从0点开始 .StartingDailyAt(new TimeOfDay(0, 0, 0)) // 跳过21点,第二次循环跳过了20和21点 .EndingDailyAt(new TimeOfDay(22, 0, 0)) // 15分钟循环一次 .WithInterval(15, IntervalUnit.Minute) // 触发失效什么也不做 .WithMisfireHandlingInstructionDoNothing() ) .Build(); var from = new DateTimeOffset(2023, 1, 31, 0, 0, 0, TimeSpan.Zero); var to = new DateTimeOffset(2023, 2, 2, 0, 0, 0, TimeSpan.Zero); //理论上会执行 ((23个小时+22个小时)*60分钟)/(15分钟/次) - 1次 = 179次 var times = TriggerUtils.ComputeFireTimesBetween(trigger, null, from, to); // 小于200次 Assert.That(times.Count, Is.LessThan(200));
ADO.NET JobStore
- 使用Quartz.NET的API写入数据库,不要直接操作数据库表
- 死锁
- 触发器中的作业未被正确执行
- 集群之间的问题
- 非集群与集群不能共用一个数据库
- 数据源连接大小
- 建议将数据源最大连接大小配置为至少为线程池中的工作线程数加上三
- 如果使用Api则需要额外的连接
Daylight Savings Time
夏令时-中国在执行了6年就取消了,因此该最佳实现目前不适用于无夏令时的地区。
因为SimpleTriggers为精确到毫秒,因此不受夏令时的影响;CronTriggers在转换的时候会受到夏令时的影响,可能会多触发一次;CalenderIntervalTrigger也会受影响,会导致偏移一小时的误差。
Jobs
- 长时间运行的作业会阻止其他线程运行(例如已运行作业数量与线程池数量相等)
- 不建议在作业中使用Thread.Sleep(),因为他会让线程一直被占用,建议在使用Sleep的位置重新安排自身或者其他作业来替代等待(通常等待是因为条件不为true,因此可以将该判断作为作业执行,周期短)
- 因为作业是周期性的,因此当作业异常时会一直触发异常,此时异常过多会导致应用程序不稳定。建议在catch处解决异常或重新开启新的作业来消解异常的发生。
Listener
- 不建议监听中执行大量工作,因为作业更合适做这些工作,监听只是监听。
- 同Jobs的异常处理,异常多了容易崩溃。
对外调用
- 不建议对外暴露,对于本机过于危险(例如NativeJob和SendEmailJob都可以用于恶意的目的)