日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2023/12/4 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .net core 实现基于 cron 表达式的任务调度 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

.net?core 實(shí)現(xiàn)基于 cron 表達(dá)式的任務(wù)調(diào)度

Intro

上次我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的基于 Timer 的定時(shí)任務(wù),詳細(xì)信息可以看這篇文章 。

但是使用過程中慢慢發(fā)現(xiàn)這種方式可能并不太合適,有些任務(wù)可能只希望在某個(gè)時(shí)間段內(nèi)執(zhí)行,只使用 timer 就顯得不是那么靈活了,希望可以像 quartz 那樣指定一個(gè) cron 表達(dá)式來指定任務(wù)的執(zhí)行時(shí)間。

cron 表達(dá)式介紹

cron 常見于Unix和類Unix的操作系統(tǒng)之中,用于設(shè)置周期性被執(zhí)行的指令。該命令從標(biāo)準(zhǔn)輸入設(shè)備讀取指令,并將其存放于“crontab”文件中,以供之后讀取和執(zhí)行。該詞來源于希臘語 chronos(χρ?νο?),原意是時(shí)間。

通常, crontab儲(chǔ)存的指令被守護(hù)進(jìn)程激活, crond 常常在后臺(tái)運(yùn)行,每一分鐘檢查是否有預(yù)定的作業(yè)需要執(zhí)行。這類作業(yè)一般稱為cron jobs。

cron 可以比較準(zhǔn)確的描述周期性執(zhí)行任務(wù)的執(zhí)行時(shí)間,標(biāo)準(zhǔn)的 cron 表達(dá)式是五位:

304**? 五個(gè)位置上的值分別對(duì)應(yīng) 分鐘/小時(shí)/日期/月份/周(day of week)

現(xiàn)在有一些擴(kuò)展,有6位的,也有7位的,6位的表達(dá)式第一個(gè)對(duì)應(yīng)的是秒,7個(gè)的第一個(gè)對(duì)應(yīng)是秒,最后一個(gè)對(duì)應(yīng)的是年份

0012**? 每天中午12點(diǎn) 01510?** 每天 10:15 01510**? 每天 10:15 301510**?* 每天 10:15:30 01510**?2005 2005年每天 10:15

詳細(xì)信息可以參考: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 定時(shí)服務(wù),實(shí)現(xiàn)如下:

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()) { // 執(zhí)行 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); } }

因?yàn)榫W(wǎng)站部署在多臺(tái)機(jī)器上,所以為了防止并發(fā)執(zhí)行,使用 redis 做了一些事情,Job執(zhí)行的時(shí)候嘗試獲取 redis 中 job 對(duì)應(yīng)的 master 的 hostname,沒有的話就設(shè)置為當(dāng)前機(jī)器的 hostname,在 job 停止的時(shí)候也就是應(yīng)用停止的時(shí)候,刪除 redis 中當(dāng)前 job 對(duì)應(yīng)的 master,job執(zhí)行的時(shí)候判斷是否是 master 節(jié)點(diǎn),是 master 才執(zhí)行job,不是 master 則不執(zhí)行。完整實(shí)現(xiàn)代碼:https://github.com/WeihanLi/ActivityReservation/blob/dev/ActivityReservation.Helper/Services/CronScheduleServiceBase.cs#L11

定時(shí) 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))); } } }

完整實(shí)現(xiàn)代碼:https://github.com/WeihanLi/ActivityReservation/blob/dev/ActivityReservation.Helper/Services/RemoveOverdueReservationService.cs

Memo

使用 redis 這種方式來決定 master 并不是特別可靠,正常結(jié)束的沒有什么問題,最好還是用比較成熟的服務(wù)注冊(cè)發(fā)現(xiàn)框架比較好

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


總結(jié)

以上是生活随笔為你收集整理的.net core 实现基于 cron 表达式的任务调度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。