引言

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

  • 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.StdAdoDelegate, 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地址查看调度

总结输出

  1. Quartz.net以AdoJobStore支撑分布式定时任务,解决多实例多次触发的问题
  2. 快速抛出轮子:Quartz.Net UI库

20200419 更新

以上配置只是完成从DB加载Job和Trigger, 从实际看没有解决Quartz.net在集群环境下执行重复任务的事情,

需要添加 cluster= true 属性支持负载均衡和故障转移, 抱歉,以上代码只是完成了从DB分离加载Quartz配置的步骤。

["quartz.jobStore.clustered"] = "true",
["quartz.scheduler.instanceId"] = "AUTO"

https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/advanced-enterprise-features.html

https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/crontriggers.html

3分钟掌握Quartz.net分布式定时任务的姿势的更多相关文章

  1. 基于spring+quartz的分布式定时任务框架

    问题背景 我公司是一个快速发展的创业公司,目前有200人,主要业务是旅游和酒店相关的,应用迭代更新周期比较快,因此,开发人员花费了更多的时间去更=跟上迭代的步伐,而缺乏了对整个系统的把控 没有集群之前 ...

  2. Quartz实现分布式可动态配置的定时任务

    关键词: 1. 定时任务 2. 分布式 3. 可动态配置触发时间 一般通过Quartz实现定时任务很简单.如果实现分布式定时任务需要结合分布式框架选择master节点触发也可以实现.但我们有个实际需求 ...

  3. 使用Spring整合Quartz轻松完成定时任务

    一.背景 上次我们介绍了如何使用Spring Task进行完成定时任务的编写,这次我们使用Spring整合Quartz的方式来再一次实现定时任务的开发,以下奉上开发步骤及注意事项等. 二.开发环境及必 ...

  4. spring Quartz多个定时任务的配置

    Quartz多个定时任务的配置 1,配置文件与spring整合,需要在spring 的总配置中一入或者在web.xml中spring监听中加上 ztc_cp-spring-quartz.xml 注:定 ...

  5. Spring 3整合Quartz 2实现定时任务--转

    常规整合 http://www.meiriyouke.net/?p=82 最近工作中需要用到定时任务的功能,虽然Spring3也自带了一个轻量级的定时任务实现,但感觉不够灵活,功能也不够强大.在考虑之 ...

  6. Spring 整合 Quartz 实现动态定时任务

    复制自:https://www.2cto.com/kf/201605/504659.html 最近项目中需要用到定时任务的功能,虽然Spring 也自带了一个轻量级的定时任务实现,但感觉不够灵活,功能 ...

  7. Elastic-Job - 分布式定时任务框架

    Elastic-Job - 分布式定时任务框架 摘要 Elastic-Job是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架.去掉了和dd-job中的监控和ddframe接入规范 ...

  8. 原!总结 quartz集群 定时任务 测试运行ok

    由于项目优化重构,想将定时任务从quartz单机模式变成集群或分布式的方式.于是,百度了一圈....修修改改...用集群的方式部署定时任务,测试可以... 集群?分布式?什么区别? 集群:同一个业务, ...

  9. 【转】Spring 整合 Quartz 实现动态定时任务

    http://blog.csdn.net/u014723529/article/details/51291289 最近项目中需要用到定时任务的功能,虽然spring 也自带了一个轻量级的定时任务实现, ...

随机推荐

  1. Natas31 Writeup(Perl 远程命令执行)

    Natas31: 源码如下: my $cgi = CGI->new; if ($cgi->upload('file')) { my $file = $cgi->param('file ...

  2. 2020年Java基础高频面试题汇总(1.4W字详细解析)

    1. Java语言有哪些特点 (1)简单易学.有丰富的类库 (2)面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高) (3)与平台无关性(JVM是Java跨平台使用的根本) (4)可靠安全 ...

  3. loadrunner-事务

    自从安装了loadrunner之后,就没怎么用过它了,项目之前也没做过性能测试,所以学习起来比较困难,而且性能测试远远不止使用工具这么简单.下面介绍一下最近学习的loadrunner添加事务. 事务是 ...

  4. 树莓派4B踩坑指南 - (13)用samba建立家庭局域网共享中心

    树莓派在家中至少三个作用:家庭资源共享中心.无线打印服务器.下载服务器. 家庭资源共享中心用samba实现家庭局域网共享,树莓派4B的话可以接2个3.0的移动硬盘. 实测速度不快,Win读2Mb/s写 ...

  5. 动态规划/MinMax-Stone Game

    2019-09-07 16:34:48 877. Stone Game 问题描述: 问题求解: 典型的博弈问题,也是一个典型的min-max问题.通常使用算diff的方法把min-max转为求max. ...

  6. WordPress 迁移站点更换域名为新域名

    使用 wp-cli 工具搜索替换域名的方式更换 WordPress 域名 wp-cli 是一个命令行工具,可以让我们通过命令行安装.更新 WordPress,对 WordPress 执行一些批量操作, ...

  7. spring-cloud-gateway过滤器实践

    概述 这里是 SpringCloud Gateway 实践的第一篇,主要讲过滤器的相关实现.Spring-Cloud-Gateway 是以 WebFlux 为基础的响应式架构设计, 是异步非阻塞式的, ...

  8. 走近源码:Redis如何清除过期key

    "叮--",美好的周六就这么被一阵钉钉消息吵醒了. 业务组的同学告诉我说很多用户的帐号今天被强制下线.我们的帐号系统正常的逻辑是用户登录一次后,token的有效期可以维持一天的时间 ...

  9. inux上安装mysql

    目录 1.先验证是否安装了mysql 2.先下载mysql的repo源 3.安装mysql rpm包,执行命令: 4.安装mysql,执行命令: 5.登录然后重置密码,执行: 6.执行命令赋权,重启m ...

  10. Python python 五种数据类型--元组

    # 定义一个元组 var1 = ('Hello','Python') var2 = tuple() print(type(var1)) #<class 'tuple'> print(typ ...