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

Intro

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

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

cron 表达式介绍

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

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

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

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

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

0 0 12 * * ? 每天中午12点

0 15 10 ? * * 每天 10:15

0 15 10 * * ? 每天 10:15

30 15 10 * * ? * 每天 10:15:30

0 15 10 * * ? 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

.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表达式使用方法

    Quartz中CronTrigger支持日历相关的反复时间间隔(比方每月第一个周一运行),而不是简单的周期时间间隔. 它的调度规则基于 Cron 表达式. 以下就来说一下Cron表达式的规则及使用方法 ...

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

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

  4. 使用Cron表达式创建定时任务

    CronTriggerCronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用.CroTr ...

  5. 基于Spring的最简单的定时任务实现与配置(三)--番外篇 cron表达式的相关内容

    本来这篇文章是会跟本系列的前两篇文章一起发布的.但是,昨天在找资料总结的时候遇到了一点意外,就延后了一些. 本篇的内容主要参考了 这篇博文:http://www.cnblogs.com/junrong ...

  6. 任务调度Cron表达式及Quartz代码详解

    在线Cron表达式生成器 http://cron.qqe2.com/ cron表达式详解 http://www.cnblogs.com/linjiqin/archive/2013/07/08/3178 ...

  7. Quartz.NET总结(二)CronTrigger和Cron表达式

    Quartz.NET的任务调度,主要就是依靠CronTrigger和Cron表达式.Cron是已经在UNIX存在了很长一段时间,它有着强大和可靠的调度能力.CronTrigger类也正是是基于Cron ...

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

    一个基于Spring boot的一个demo: Java配置中开户对Scheduled的支持 import org.springframework.context.annotation.Configu ...

  9. Quartz 用 cron 表达式存放执行计划

    Quartz 用 cron 表达式存放执行计划.引用了 cron 表达式的 CronTrigger 在计划的时间里会与 job 关联上. 1.Quartz cron 表达式支持七个域如下: 名称 是否 ...

随机推荐

  1. JS 数据类型分析及字符串的方法

    1.js数据类型分析 (1)基础类型:string.number.boolean.null.undefined (2)引用类型:object-->json.array... 2.点运算  xxx ...

  2. 向Rocket.Chat推送消息

    Rocket.Chat推送消息 Rocket.Chat是一个开源实时通讯平台, 支持Windows, Mac OS, Linux. 支持聊天, 文件上传, 视频通话, 语音通话功能. 向Rocket. ...

  3. 从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用ApplicationPart动态加载控制器和视图

    标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图 作者:Lamond Lu 地址:http://www.cnblogs ...

  4. 记一次基于Cloudflare服务的爬虫

    前言 前几天有个朋友托我帮忙爬一个网站,第一次打开那个网站时,我去,它竟然就要验证码,不过当时是想加几个header应该就能解决,不过事实证明我还是错了.接下来将记录下爬虫中遇到的一些坑以及解决办法. ...

  5. 趣解 ceph rgw multisite data sync 机制

    multisite是ceph rgw对象数据异地容灾备份的一个有效方案,笔者希望深入理解该技术,并应用于生产环境中,然而rgw的这部分代码晦涩难懂,笔者多次尝试阅读,仍云里雾里不解其意,最终流着泪咬着 ...

  6. 并发编程-concurrent指南-原子操作类-AtomicBoolean

    类AtomicBoolean

  7. WebSocket API 学习笔记

    WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在 W ...

  8. c++学习书籍推荐《C++ Primer Plus中文版(第6版)》下载

    百度云及其他网盘下载地址:点我 编辑推荐 一本经久不衰的C++畅销经典教程:一本支持C++11新标准的程序设计图书.  它被誉为“开发人员学习C++的教程,没有之一”! Amazon网站“Langua ...

  9. 图片去水印工具:Inpaint 7.2中文专业破解版下载及使用方法

    下载地址: 点我 Inpaint 是一款可以从图片上去除不必要的物体,让您轻松摆脱照片上的水印.划痕.污渍.标志等瑕疵的实用型软件:简单说来,Inpaint 就是一款强大实用的图片去水印软件,您的图片 ...

  10. 关系型数据库MySql简介

    什么是关系型数据库? 数据库就是用来存储数据的仓库,是一种特殊的文件. 根据存储的数据不同,划分为关系型数据库和非关系型数据库. 关系型数据库就是指 建立在关系模型基础上的数据库,通俗来讲这种数据库就 ...