.net core 实现基于 cron 表达式的任务调度
.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)));
}
}
}
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 表达式的任务调度的更多相关文章
- (转)Java任务调度框架Quartz入门教程指南(四)Quartz任务调度框架之触发器精讲SimpleTrigger和CronTrigger、最详细的Cron表达式范例
http://blog.csdn.net/zixiao217/article/details/53075009 Quartz的主要接口类是Schedule.Job.Trigger,而触发器Trigge ...
- Quartz中Cron表达式使用方法
Quartz中CronTrigger支持日历相关的反复时间间隔(比方每月第一个周一运行),而不是简单的周期时间间隔. 它的调度规则基于 Cron 表达式. 以下就来说一下Cron表达式的规则及使用方法 ...
- 作业调度框架 Quartz 学习笔记(三) -- Cron表达式 (转载)
前面两篇说的是简单的触发器(SimpleTrigger) , SimpleTrigger 只能处理简单的事件出发,如果想灵活的进行任务的触发,就要请出 CronTrigger 这个重要人物了. Cro ...
- 使用Cron表达式创建定时任务
CronTriggerCronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用.CroTr ...
- 基于Spring的最简单的定时任务实现与配置(三)--番外篇 cron表达式的相关内容
本来这篇文章是会跟本系列的前两篇文章一起发布的.但是,昨天在找资料总结的时候遇到了一点意外,就延后了一些. 本篇的内容主要参考了 这篇博文:http://www.cnblogs.com/junrong ...
- 任务调度Cron表达式及Quartz代码详解
在线Cron表达式生成器 http://cron.qqe2.com/ cron表达式详解 http://www.cnblogs.com/linjiqin/archive/2013/07/08/3178 ...
- Quartz.NET总结(二)CronTrigger和Cron表达式
Quartz.NET的任务调度,主要就是依靠CronTrigger和Cron表达式.Cron是已经在UNIX存在了很长一段时间,它有着强大和可靠的调度能力.CronTrigger类也正是是基于Cron ...
- Spring 定时任务之 @Scheduled cron表达式
一个基于Spring boot的一个demo: Java配置中开户对Scheduled的支持 import org.springframework.context.annotation.Configu ...
- Quartz 用 cron 表达式存放执行计划
Quartz 用 cron 表达式存放执行计划.引用了 cron 表达式的 CronTrigger 在计划的时间里会与 job 关联上. 1.Quartz cron 表达式支持七个域如下: 名称 是否 ...
随机推荐
- JS 数据类型分析及字符串的方法
1.js数据类型分析 (1)基础类型:string.number.boolean.null.undefined (2)引用类型:object-->json.array... 2.点运算 xxx ...
- 向Rocket.Chat推送消息
Rocket.Chat推送消息 Rocket.Chat是一个开源实时通讯平台, 支持Windows, Mac OS, Linux. 支持聊天, 文件上传, 视频通话, 语音通话功能. 向Rocket. ...
- 从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用ApplicationPart动态加载控制器和视图
标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图 作者:Lamond Lu 地址:http://www.cnblogs ...
- 记一次基于Cloudflare服务的爬虫
前言 前几天有个朋友托我帮忙爬一个网站,第一次打开那个网站时,我去,它竟然就要验证码,不过当时是想加几个header应该就能解决,不过事实证明我还是错了.接下来将记录下爬虫中遇到的一些坑以及解决办法. ...
- 趣解 ceph rgw multisite data sync 机制
multisite是ceph rgw对象数据异地容灾备份的一个有效方案,笔者希望深入理解该技术,并应用于生产环境中,然而rgw的这部分代码晦涩难懂,笔者多次尝试阅读,仍云里雾里不解其意,最终流着泪咬着 ...
- 并发编程-concurrent指南-原子操作类-AtomicBoolean
类AtomicBoolean
- WebSocket API 学习笔记
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在 W ...
- c++学习书籍推荐《C++ Primer Plus中文版(第6版)》下载
百度云及其他网盘下载地址:点我 编辑推荐 一本经久不衰的C++畅销经典教程:一本支持C++11新标准的程序设计图书. 它被誉为“开发人员学习C++的教程,没有之一”! Amazon网站“Langua ...
- 图片去水印工具:Inpaint 7.2中文专业破解版下载及使用方法
下载地址: 点我 Inpaint 是一款可以从图片上去除不必要的物体,让您轻松摆脱照片上的水印.划痕.污渍.标志等瑕疵的实用型软件:简单说来,Inpaint 就是一款强大实用的图片去水印软件,您的图片 ...
- 关系型数据库MySql简介
什么是关系型数据库? 数据库就是用来存储数据的仓库,是一种特殊的文件. 根据存储的数据不同,划分为关系型数据库和非关系型数据库. 关系型数据库就是指 建立在关系模型基础上的数据库,通俗来讲这种数据库就 ...