.net core 实现基于 cron 表达式的任务调度

Intro

上次我们实现了一个简单的基于 Timer 的定时任务,详细信息可以看这篇文章 。

但是使用过程中慢慢发现这种方式可能并不太合适,有些任务可能只希望在某个时间段内执行,只使用 timer 就显得不是那么灵活了,希望可以像 quartz 那样指定一个 cron 表达式来指定任务的执行时间。

cron 表达式介绍

cron 常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令。该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行。该词来源于希腊语 chronos(χρόνος),原意是时间。

通常, crontab储存的指令被守护进程激活, crond 常常在后台运行,每一分钟检查是否有预定的作业需要执行。这类作业一般称为cron jobs

cron 可以比较准确的描述周期性执行任务的执行时间,标准的 cron 表达式是五位:

304**? 五个位置上的值分别对应 分钟/小时/日期/月份/周(day of week)

现在有一些扩展,有6位的,也有7位的,6位的表达式第一个对应的是秒,7个的第一个对应是秒,最后一个对应的是年份

0012**? 每天中午12点 01510?** 每天 10:15 01510**? 每天 10:15 301510**?* 每天 10:15:30 01510**?2005 2005年每天 10:15

详细信息可以参考:http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

.NET Core CRON service

CRON 解析库 使用的是 https://github.com/HangfireIO/Cronos,支持五位/六位,暂不支持年份的解析(7位)

基于 BackgroundService 的 CRON 定时服务,实现如下:

public abstract class CronScheduleServiceBase : BackgroundService
{   /// <summary> /// job cron trigger expression /// refer to: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html /// </summary>    public abstract string CronExpression { get; }  protected abstract bool ConcurrentAllowed { get; }  protected readonly ILogger Logger;  private readonly string JobClientsCache = "JobClientsHash";  protected CronScheduleServiceBase(ILogger logger)   {   Logger = logger;   }   protected abstract Task ProcessAsync(CancellationToken cancellationToken);  protected override async Task ExecuteAsync(CancellationToken stoppingToken) {   {   var next = CronHelper.GetNextOccurrence(CronExpression);   while (!stoppingToken.IsCancellationRequested && next.HasValue) {   var now = DateTimeOffset.UtcNow;   if (now >= next)    {   if (ConcurrentAllowed)  {   _ = ProcessAsync(stoppingToken);   next = CronHelper.GetNextOccurrence(CronExpression);   if (next.HasValue)  {   Logger.LogInformation("Next at {next}", next);    }   }   else    {   var machineName = RedisManager.HashClient.GetOrSet(JobClientsCache, GetType().FullName, () => Environment.MachineName); // try get job master  if (machineName == Environment.MachineName) // IsMaster   {   using (var locker = RedisManager.GetRedLockClient($"{GetType().FullName}_cronService"))  {   // redis 互斥锁    if (await locker.TryLockAsync())    {   // 执行 job   await ProcessAsync(stoppingToken);  next = CronHelper.GetNextOccurrence(CronExpression);   if (next.HasValue)  {   Logger.LogInformation("Next at {next}", next);    await Task.Delay(next.Value - DateTimeOffset.UtcNow, stoppingToken);    }   }   else    {   Logger.LogInformation($"failed to acquire lock"); }   }   }   }   }   else    {   // needed for graceful shutdown for some reason.    // 1000ms so it doesn't affect calculating the next    // cron occurence (lowest possible: every second)   await Task.Delay(1000, stoppingToken);  }   }   }   }   public override Task StopAsync(CancellationToken cancellationToken) {   RedisManager.HashClient.Remove(JobClientsCache, GetType().FullName); // unregister from jobClients  return base.StopAsync(cancellationToken);   }   }

因为网站部署在多台机器上,所以为了防止并发执行,使用 redis 做了一些事情,Job执行的时候尝试获取 redis 中 job 对应的 master 的 hostname,没有的话就设置为当前机器的 hostname,在 job 停止的时候也就是应用停止的时候,删除 redis 中当前 job 对应的 master,job执行的时候判断是否是 master 节点,是 master 才执行job,不是 master 则不执行。完整实现代码:https://github.com/WeihanLi/ActivityReservation/blob/dev/ActivityReservation.Helper/Services/CronScheduleServiceBase.cs#L11

定时 Job 示例:

public class RemoveOverdueReservationService : CronScheduleServiceBase
{   private readonly IServiceProvider _serviceProvider; private readonly IConfiguration _configuration; public RemoveOverdueReservationService(ILogger<RemoveOverdueReservationService> logger,   IServiceProvider serviceProvider, IConfiguration configuration) : base(logger)  {   _serviceProvider = serviceProvider;    _configuration = configuration;    }   public override string CronExpression => _configuration.GetAppSetting("RemoveOverdueReservationCron") ?? "0 0 18 * * ?";    protected override bool ConcurrentAllowed => false; protected override async Task ProcessAsync(CancellationToken cancellationToken) {   using (var scope = _serviceProvider.CreateScope()) {   var reservationRepo = scope.ServiceProvider.GetRequiredService<IEFRepository<ReservationDbContext, Reservation>>();    await reservationRepo.DeleteAsync(reservation => reservation.ReservationStatus == 0 && (reservation.ReservationForDate < DateTime.Today.AddDays(-3))); }   }
}

完整实现代码:https://github.com/WeihanLi/ActivityReservation/blob/dev/ActivityReservation.Helper/Services/RemoveOverdueReservationService.cs

Memo

使用 redis 这种方式来决定 master 并不是特别可靠,正常结束的没有什么问题,最好还是用比较成熟的服务注册发现框架比较好

Reference

  • http://crontab.org/

  • https://en.wikipedia.org/wiki/Cron

  • http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

  • https://github.com/WeihanLi/ActivityReservation

.net core 实现基于 cron 表达式的任务调度相关推荐

  1. (转)Java任务调度框架Quartz入门教程指南(四)Quartz任务调度框架之触发器精讲SimpleTrigger和CronTrigger、最详细的Cron表达式范例...

    http://blog.csdn.net/zixiao217/article/details/53075009 Quartz的主要接口类是Schedule.Job.Trigger,而触发器Trigge ...

  2. 作业调度框架 Quartz 学习笔记(三) -- Cron表达式

    2019独角兽企业重金招聘Python工程师标准>>> 前面两篇说的是简单的触发器(SimpleTrigger) , SimpleTrigger 只能处理简单的事件出发,如果想灵活的 ...

  3. quartz 每30秒执行一次_作业调度框架 Quartz 学习笔记(三) -- Cron表达式

    前面两篇说的是简单的触发器(SimpleTrigger) , SimpleTrigger 只能处理简单的事件出发,如果想灵活的进行任务的触发,就要请出 CronTrigger 这个重要人物了. Cro ...

  4. Java基于Quartz的定时任务调度服务(一)

    Quartz的基本用法 一 Quartz的简单介绍 Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现,一个优秀的开源调度框架,其特点是:强大的 ...

  5. Spring 定时任务之 @Scheduled cron表达式

    首先在配置文件头部的必须要有: xmlns:task="http://www.springframework.org/schema/task" 1 其次xsi:schemaLoca ...

  6. 摆脱困境:将环境特定的Cron表达式与@Scheduled批注一起使用

    @Scheduled注释提供了一种在Spring驱动的应用程序中创建计划任务的简便方法. 我们可以使用它通过定期调度或cron表达式来调度我们的任务. 尽管时段调度也可能有用,但是cron表达式使我们 ...

  7. Quartz使用总结、Cron表达式

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. Quartz可以用来做什么? Quartz是一个任务调度框架.比如你遇到这样的问题 想每月25号,信 ...

  8. cron表达式入门_Sourcehunt:Cron管理,Hackathon入门,PHP-GUI…

    cron表达式入门 Ready for a new edition of sourcehunt? Get those starring-fingers ready! 准备使用新版本的sourcehun ...

  9. Mybatis逆向工程、Quartz框架的定时任务管理详解、Cron表达式

    Mybatis逆向工程   mybatis是目前很流行的持久层框架,很多企业都在采用.但是其复杂繁琐的配置,重复性的实体类创建等等,消耗了程序员大量的精力,同时有些地方如果一个细小的疏忽,可能导致最终 ...

最新文章

  1. [javascript] 看知乎学习js闭包
  2. tensorflow使用tf.placeholder会报错
  3. android的动态注册,Android应用开发之BroadcastReceiver(广播)的静态注册和动态注册 --Android开发...
  4. [BOI2019][第K大问题][暴力剪枝]D2T1 Olympiads
  5. android通讯录管理(获取联系人,通话记录,短信消息),Android通讯录管理(获取联系人、通话记录、短信消息)(二)...
  6. No ExecutorFactory found to execute the application.
  7. 10自带sftp服务器_WinSCP v5.15.3 免费的 开源图形化 SFTP 客户端
  8. import cv2时ImportError: libjasper.so.1: cannot open shared object file: No such file or directory
  9. 手动实现一个迷你版的AOP(实战增强版)
  10. .net平台借助第三方推送服务在推送Android消息(极光推送) 转
  11. opencv python3树莓派_树莓派4B日志七:Python3上的OpenCV安装
  12. 安装Windows Server 2008 R2 Cluster
  13. 897-递增顺序查找树
  14. 怎么写加密邮件,企业邮箱支持吗?【企业邮箱注册】
  15. 工业互联网+危化安全生产综合管理平台怎样建
  16. C语言----文件存储
  17. win7 关闭计算机休眠,技术编辑教您win7下怎么关闭休眠
  18. ON_NOTIFY用法
  19. 关于IDEA不生成out文件无法执行程序的问题
  20. 三款破解PHP加密程序工具软件

热门文章

  1. airdrop 是 蓝牙吗_您可以在Windows PC或Android手机上使用AirDrop吗?
  2. dmidecode常用的查询
  3. javascript事件之:jQuery事件中实例对象和拓展对象之间的通信
  4. Nginx server之Nginx作为反向代理服务器
  5. Java程序员从笨鸟到菜鸟之(一百零四)java操作office和pdf文件(二)利用POI实现数据导出excel报表...
  6. view 背景透明
  7. MYSQL技术连环斩-MYSQL简述
  8. microdot - 一个开源 .NET 微服务框架。
  9. AspNetCoreRateLimit - ASP.NET Core 速率限制中间件。
  10. Avalonia跨平台入门第十五篇之ListBox聊天窗口