3分钟掌握Quartz.net分布式定时任务的姿势

简介: 长话短说,今天聊一聊分布式定时任务,我的流水账笔记:ASP.NET Core+Quartz.Net实现web定时任务AspNetCore结合Redis实践消息队列

引言


长话短说,今天聊一聊分布式定时任务,我的流水账笔记:



细心朋友稍一分析,就知道还有问题:


水平扩展后的WebApp的Quartz.net定时任务会多次触发, 因为webapp实例使用的是默认的RAMJobStore, 多实例在内存中都维护了Job和Trigger的副本.


我的定时任务是同步任务,多次执行倒是没有太大问题,但对于特定业务的定时任务, 多次执行可能是致命问题。


基于此,来看看Quartz.net 分布式定时任务的姿势


AdoJobStore


很明显,水平扩展的多实例需要一个 独立于web实例的机制来存储Job和Trigger.

Quartz.NET提供ADO.NET JobStore来存储任务数据。


  1. 先使用SQL脚本在数据库中生成指定的表结构


执行脚本之后,会看到数据库中多出几个以 QRTZ_开头的表


  1. 配置Quartz.net使用AdoJobStore


可采用编码形式或者 quartz.config形式添加配置


快速实践


1. 预先生成Job、Trigger表


https://github.com/quartznet/quartznet/tree/master/database/tables 下载合适的数据库表脚本, 生成指定的表结构


2. 添加AdoJobStore


本次使用编码方式添加AdoJobStore配置。


首次启动会将代码中Job和Trigger持久化到sqlite,后面就直接从sqlite中加载Job和Trigger


using System;
using System.Collections.Specialized;
using System.Data;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Quartz;
using Quartz.Impl;
using Quartz.Impl.AdoJobStore.Common;
using Quartz.Spi;
namespace EqidManager
{
    using IOCContainer = IServiceProvider;
    public class QuartzStartup
    {
        public IScheduler Scheduler { get; set; }
        private readonly ILogger _logger;
        private readonly IJobFactory iocJobfactory;
        public QuartzStartup(IOCContainer IocContainer, ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<QuartzStartup>();
            iocJobfactory = new IOCJobFactory(IocContainer);
            DbProvider.RegisterDbMetadata("sqlite-custom", new DbMetadata()
            {
                AssemblyName = typeof(SqliteConnection).Assembly.GetName().Name,
                ConnectionType = typeof(SqliteConnection),
                CommandType = typeof(SqliteCommand),
                ParameterType = typeof(SqliteParameter),
                ParameterDbType = typeof(DbType),
                ParameterDbTypePropertyName = "DbType",
                ParameterNamePrefix = "@",
                ExceptionType = typeof(SqliteException),
                BindByName = true
            });
            var properties = new NameValueCollection
            {
                ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
                ["quartz.jobStore.useProperties"] = "true",
                ["quartz.jobStore.dataSource"] = "default",
                ["quartz.jobStore.tablePrefix"] = "QRTZ_",
                ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SQLiteDelegate, Quartz",
                ["quartz.dataSource.default.provider"] = "sqlite-custom",
                ["quartz.dataSource.default.connectionString"] = "Data Source=EqidManager.db",
                ["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz",
                ["quartz.serializer.type"] = "binary"
            };
            var schedulerFactory = new StdSchedulerFactory(properties);
            Scheduler = schedulerFactory.GetScheduler().Result;
            Scheduler.JobFactory = iocJobfactory;
        }
        public async Task<IScheduler> ScheduleJob()
        {
            var _eqidCounterResetJob = JobBuilder.Create<EqidCounterResetJob>()
              .WithIdentity("EqidCounterResetJob")
              .Build();
            var _eqidCounterResetJobTrigger = TriggerBuilder.Create()
                .WithIdentity("EqidCounterResetCron")
                .StartNow()
                //每天凌晨0s
                .WithCronSchedule("0 0 0 * * ?")      Seconds,Minutes,Hours,Day-of-Month,Month,Day-of-Week,Year(optional field)
                .Build();
           // 这里一定要先判断是否已经从SQlite中加载了Job和Trigger
            if (!await Scheduler.CheckExists(new JobKey("EqidCounterResetJob")) &&
                !await Scheduler.CheckExists(new TriggerKey("EqidCounterResetCron")))
            {
                await Scheduler.ScheduleJob(_eqidCounterResetJob, _eqidCounterResetJobTrigger);
            }
            await Scheduler.Start();
            return Scheduler;
        }
        public void EndScheduler()
        {
            if (Scheduler == null)
            {
                return;
            }
            if (Scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000))
                Scheduler = null;
            else
            {
            }
            _logger.LogError("Schedule job upload as application stopped");
        }
    }
}


上面是Quartz.NET 从sqlite中加载Job和Trigger的核心代码


这里要提示两点:


①  IOCJobFactory 是自定义JobFactory,目的是与ASP.NET Core原生依赖注入结合

② 在调度任务的时候,先判断是否已经从sqlite加载了Job和Trigger


3.添加Quartz.Net UI轮子


附赠Quartz.NET的调度UI: CrystalQuartz, 方便在界面管理和调度任务


① Install-Package CrystalQuartz.AspNetCore -IncludePrerelease

② Startup启用CrystalQuartz


using CrystalQuartz.AspNetCore;
/*
 * app is IAppBuilder
 * scheduler is your IScheduler (local or remote)
 */
var quartz = app.ApplicationServices.GetRequiredService<QuartzStartup>();
var _schedule = await  quartz.ScheduleJob();
app.UseCrystalQuartz(() => scheduler);


③ 在localhost:YOUR_PORT/quartz地址查看调度


abdc51d5d8d4b1966f1964f4b0eefe2a.png


总结输出


  1. Quartz.net以AdoJobStore支撑分布式定时任务,解决多实例多次触发的问题


  1. 快速抛出轮子:Quartz.Net UI库
相关文章
|
3月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
129 1
|
2月前
|
开发框架 NoSQL .NET
利用分布式锁在ASP.NET Core中实现防抖
【9月更文挑战第5天】在 ASP.NET Core 中,可通过分布式锁实现防抖功能,仅处理连续相同请求中的首个请求,其余请求返回 204 No Content,直至锁释放。具体步骤包括:安装分布式锁库如 `StackExchange.Redis`;创建分布式锁服务接口及其实现;构建防抖中间件;并在 `Startup.cs` 中注册相关服务和中间件。这一机制有效避免了短时间内重复操作的问题。
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
14981 29
|
3月前
|
存储 消息中间件 移动开发
还在用crontab? 分布式定时任务了解一下
还在用crontab? 分布式定时任务了解一下
|
3月前
|
消息中间件 存储 Kafka
微服务实践之分布式定时任务
微服务实践之分布式定时任务
|
3月前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
70 0
|
3月前
|
消息中间件 开发框架 .NET
Asp.net core Webapi 如何执行定时任务?
Asp.net core Webapi 如何执行定时任务?
|
3月前
|
存储 缓存 开发框架
看看 Asp.net core Webapi 项目如何优雅地使用分布式缓存
看看 Asp.net core Webapi 项目如何优雅地使用分布式缓存
|
5月前
|
SQL JavaScript NoSQL
.NET有哪些好用的定时任务调度框架
.NET有哪些好用的定时任务调度框架
104 1
|
开发框架 缓存 .NET
.NET Core 项目启动时运行定时任务
.NET Core 项目启动时运行定时任务
88 0
下一篇
无影云桌面