• Quartz.Net 主要概念介绍和吐槽


    我们经常遇到需要定时执行某些任务的情况,比如清理缓存、异步结果轮询等,如果不打算造轮子,那么选择一款合适的定时任务组件就很关键了。所幸,.Net 世界中的选项并不多:)

    选型#

    主要有以下四款:

    • Quartz.Net:移植自 Java 生态的 Quartz,久经考验、成熟稳重,只是个人感觉有点过度设计,初次接触容易让人困惑;
    • Coravel:提供任务调度,缓存,排队,邮件,事件广播等功能,全面但不专一;
    • Hangfire:最大的特点是内置控制面板。分为社区版和商用版。github 上,这四款组件它的点赞数最多,可见其收欢迎程度。不过它更像一个定时任务管理解决方案,所涵盖的功能除任务本身,还涉及到账户管理、图表管理、告警系统等;
    • FluentScheduler:似乎是这四款中最易上手的,但是已经有段时间没更新了。

    稳妥起见,项目前期建议选择坑少、专注、轻量、社区较为活跃的组件 Quartz.Net,再择机使用 Hangfire 打造一个任务管理中心。

    如前所述,Quartz.Net 有过度设计的嫌疑,2.x 版本时期配置方式的杂乱可见一斑。虽然现在已经迭代到 3.x,一些概念还是需要花心思理解下。

    主要概念#

    我们围绕下面这个代码片段展开:

    Copy
    var schedulerFactory = builder.Services.GetRequiredService(); var scheduler = await schedulerFactory.GetScheduler(); // define the job and tie it to our HelloJob class var job = JobBuilder.Create() .WithIdentity("myJob", "group1") .UsingJobData("way", "email") .Build(); // Trigger the job to run now, and then every 40 seconds var trigger = TriggerBuilder.Create() .WithIdentity("myTrigger", "group1") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(40) .RepeatForever()) .Build(); await scheduler.ScheduleJob(job, trigger);

    Jobs and Triggers#

    Quartz.Net 将任务业务逻辑(Jobs)和任务执行时间计划(Triggers)做了分离。官方的解释是两者可以独立管理,还可以多个 Trigger 触发同一个 Job;有没有必要见仁见智

    Job 和 Trigger 还有group的概念,比如多个模块都有各自清理缓存的任务,可以将这些任务划分为同一个组。但是在实操过程中,group 除了语义上的指示,似乎并没有直接的其它作用;也许它可以用于业务端的扩展,不过私以为组件不应将可能的业务需求作为自己的设计点,且业务扩展不应依赖某个具体组件

    JobDetail#

    上述代码中变量 job 的类型并非IJob,而是IJobDetail,也就是说,Quartz.Net 调度维持的是 IJobDetail 实例,而 Job 实例,是在每次任务执行的时间点实例化的,执行完就销毁,实例状态并不能延续,需要借助 IJobDetail 实例存取每次执行后更新的状态。

    JobDataMap#

    Job 实例状态保存在 JobDataMap 中,构造 JobDetail 对象时使用 .UsingJobData(key, value) 定义键值对。Trigger 也有 JobDataMap,是为上面提到的———多个 Trigger 触发同一个 Job——这种情况设计的。

    使用方式如下:

    Copy
    public class HelloJob : IJob { public async Task Execute(IJobExecutionContext context) { JobKey key = context.JobDetail.Key; // 可使用 context.MergedJobDataMap 同时获取 Trigger 提供的键值 JobDataMap dataMap = context.JobDetail.JobDataMap; string way = dataMap.GetString("way"); await Console.Error.WriteLineAsync("Instance " + key + " of HelloJob is send by: " + way); } }

    另外要注意的是,用来修饰 Job 类的特性DisallowConcurrentExecution,其实约束的是 JobDetail,即对于 JobDetail-A,同一时间,只能有一个 Job 使用它。也就是说,可以同时激活多个 HelloJob 对象,只要它们关联的 JobDetail 不同即可(当然了,实际我们也只能操作多个不同的 JobDetail 去激活对应的 Job)。

    关于 JobDetail 的解释,官方文档写得过于复杂,其实它的目的就是为了解决 Job 实例并非单例的问题,那么作者为什么不干脆将 Job 实例单例化呢?根本没必要创造一个多余的 JobDetail 的概念,令人费解。如果是因为并发考量,那么应该从 Job 定义入手,加入多线程支持。

    如果项目中使用了 IOC,那么我们可以选择不使用 JobDataMap,而是将实例状态保存在自定义对象中,在每次实例化 Job 对象时注入。

    Misfire 处理方案#

    当一个 Job 在规定时间点没有被触发执行(比如线程池里面没有可用的线程、Job 被暂停等),且超时时间超过 misfireThreshold 配置的值(默认为60秒),则作业会被调度程序认为Misfire

    当系统恢复后(有空闲线程、Job 被恢复等),调度程序会根据配置的 Misfire 策略处理已错过的那些触发点。

    参考资料#

    Quartz misfire详解

  • 相关阅读:
    (附源码)springboot家庭装修管理系统 毕业设计 613205
    [交互]前端展示服务端获取的图片
    听GPT 讲Rust源代码--library/std(7)
    磁场设备—螺线管
    Qt生成PDF报告
    基于android的个性闹铃的设计与开发(闹铃,日历,计时器,备忘录)
    Leetcoder Day29| 贪心算法part03
    C++类的成员函数作为回调函数
    探索人工智能领域——每日30个名词详解【day3】
    Docker:入门
  • 原文地址:https://www.cnblogs.com/newton/p/17073896.html