Quartz.NET 定时任务
2024/5/15大约 4 分钟
Quartz.NET 定时任务
概述
Quartz.NET 是一个功能强大的开源作业调度框架,用于在 .NET 应用程序中实现定时任务调度。
1. 安装与配置
1.1 安装依赖
dotnet add package Quartz
dotnet add package Quartz.Extensions.DependencyInjection1.2 基本配置
// Program.cs
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
var builder = WebApplication.CreateBuilder(args);
// 添加 Quartz 服务
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// 添加作业
q.AddJob<HelloWorldJob>(j => j.WithIdentity("HelloWorldJob"));
// 添加触发器
q.AddTrigger(t => t
.WithIdentity("HelloWorldTrigger")
.ForJob("HelloWorldJob")
.WithCronSchedule("0/5 * * * * ?")); // 每5秒执行一次
});
// 添加 Quartz 主机服务
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
var app = builder.Build();
app.Run();2. 作业实现
2.1 创建作业类
using Quartz;
public class HelloWorldJob : IJob
{
private readonly ILogger<HelloWorldJob> _logger;
public HelloWorldJob(ILogger<HelloWorldJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Hello World! 任务执行时间: {Time}", DateTime.Now);
return Task.CompletedTask;
}
}2.2 带数据的作业
public class DataJob : IJob
{
private readonly ILogger<DataJob> _logger;
public DataJob(ILogger<DataJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
// 获取作业数据
var jobData = context.JobDetail.JobDataMap;
var message = jobData.GetString("Message");
var count = jobData.GetInt("Count");
_logger.LogInformation("收到消息: {Message}, 计数: {Count}", message, count);
return Task.CompletedTask;
}
}3. 触发器类型
3.1 SimpleTrigger
// 简单触发器 - 固定间隔执行
var trigger = TriggerBuilder.Create()
.WithIdentity("SimpleTrigger")
.StartNow()
.WithSimpleSchedule(s => s
.WithIntervalInSeconds(10) // 每10秒执行
.RepeatForever()) // 无限重复
.Build();3.2 CronTrigger
// Cron 触发器 - 基于 Cron 表达式
var trigger = TriggerBuilder.Create()
.WithIdentity("CronTrigger")
.WithCronSchedule("0 0 2 * * ?") // 每天凌晨2点执行
.Build();3.3 Cron 表达式示例
| 表达式 | 说明 |
|---|---|
0 0 2 * * ? | 每天凌晨2点 |
0 30 10 * * ? | 每天上午10:30 |
0 0/5 * * * ? | 每5分钟 |
0 0 12 * * MON-FRI | 工作日中午12点 |
0 0 1 1 * ? | 每月1号凌晨1点 |
0 0 0 L * ? | 每月最后一天 |
4. 作业存储
4.1 内存存储(默认)
// 内存存储 - 数据存储在内存中,重启后丢失
builder.Services.AddQuartz(q =>
{
q.UseInMemoryStore();
});4.2 SQL Server 存储
dotnet add package Quartz.Plugins.RecentHistory
dotnet add package Microsoft.Data.SqlClientbuilder.Services.AddQuartz(q =>
{
q.UsePersistentStore(s =>
{
s.UseSqlServer("Server=localhost;Database=Quartz;Trusted_Connection=True;");
s.UseJsonSerializer();
s.UseClusteredStore(); // 集群模式
});
});4.3 创建数据库表
Quartz.NET 提供了数据库初始化脚本,可以从 GitHub 获取:
5. 作业管理
5.1 调度器 API
public class JobScheduler
{
private readonly IScheduler _scheduler;
public JobScheduler(ISchedulerFactory schedulerFactory)
{
_scheduler = schedulerFactory.GetScheduler().Result;
}
// 添加作业
public async Task ScheduleJob<T>(string jobName, string cronExpression) where T : IJob
{
var job = JobBuilder.Create<T>()
.WithIdentity(jobName)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{jobName}Trigger")
.ForJob(job)
.WithCronSchedule(cronExpression)
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
// 暂停作业
public async Task PauseJob(string jobName)
{
await _scheduler.PauseJob(new JobKey(jobName));
}
// 恢复作业
public async Task ResumeJob(string jobName)
{
await _scheduler.ResumeJob(new JobKey(jobName));
}
// 删除作业
public async Task DeleteJob(string jobName)
{
await _scheduler.DeleteJob(new JobKey(jobName));
}
}5.2 动态添加作业
// 在运行时动态添加作业
var scheduler = await _schedulerFactory.GetScheduler();
var jobData = new JobDataMap();
jobData.Put("Message", "动态添加的消息");
var job = JobBuilder.Create<DataJob>()
.WithIdentity("DynamicJob")
.UsingJobData(jobData)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("DynamicTrigger")
.ForJob(job)
.StartNow()
.WithSimpleSchedule(s => s
.WithIntervalInMinutes(5)
.RepeatForever())
.Build();
await scheduler.ScheduleJob(job, trigger);6. 高级特性
6.1 作业监听器
public class JobListener : IJobListener
{
private readonly ILogger<JobListener> _logger;
public JobListener(ILogger<JobListener> logger)
{
_logger = logger;
}
public string Name => "JobListener";
public Task JobToBeExecuted(IJobExecutionContext context)
{
_logger.LogInformation("作业即将执行: {JobName}", context.JobDetail.Key.Name);
return Task.CompletedTask;
}
public Task JobExecutionVetoed(IJobExecutionContext context)
{
_logger.LogInformation("作业执行被否决: {JobName}", context.JobDetail.Key.Name);
return Task.CompletedTask;
}
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
if (jobException != null)
{
_logger.LogError(jobException, "作业执行失败: {JobName}", context.JobDetail.Key.Name);
}
else
{
_logger.LogInformation("作业执行完成: {JobName}", context.JobDetail.Key.Name);
}
return Task.CompletedTask;
}
}6.2 触发器监听器
public class TriggerListener : ITriggerListener
{
private readonly ILogger<TriggerListener> _logger;
public TriggerListener(ILogger<TriggerListener> logger)
{
_logger = logger;
}
public string Name => "TriggerListener";
public Task TriggerFired(ITrigger trigger, IJobExecutionContext context)
{
_logger.LogInformation("触发器触发: {TriggerName}", trigger.Key.Name);
return Task.CompletedTask;
}
public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context)
{
return Task.FromResult(false); // 返回 true 表示否决执行
}
public Task TriggerMisfired(ITrigger trigger)
{
_logger.LogWarning("触发器错过执行: {TriggerName}", trigger.Key.Name);
return Task.CompletedTask;
}
public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode)
{
_logger.LogInformation("触发器完成: {TriggerName}", trigger.Key.Name);
return Task.CompletedTask;
}
}6.3 配置监听器
builder.Services.AddQuartz(q =>
{
// 添加作业监听器
q.AddJobListener<JobListener>();
// 添加触发器监听器
q.AddTriggerListener<TriggerListener>();
});7. 集群部署
7.1 配置集群
builder.Services.AddQuartz(q =>
{
q.UsePersistentStore(s =>
{
s.UseSqlServer(connectionString);
s.UseJsonSerializer();
s.UseClusteredStore(c =>
{
c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
c.CheckinInterval = TimeSpan.FromSeconds(10);
});
});
});7.2 集群特点
| 特性 | 说明 |
|---|---|
| 负载均衡 | 作业自动分配到集群中的节点执行 |
| 故障转移 | 节点故障时,作业自动转移到其他节点 |
| 数据一致性 | 使用数据库保证作业状态一致性 |
| 单点执行 | 同一作业在集群中只执行一次 |
8. 实践建议
8.1 最佳实践
- 作业粒度:每个作业只做一件事,保持职责单一
- 错误处理:在作业中捕获异常,避免任务中断
- 日志记录:记录作业执行的开始、结束和异常信息
- 幂等性:确保作业可以安全地重复执行
- 资源释放:及时释放数据库连接、文件句柄等资源
8.2 性能优化
- 避免长作业:将长时间运行的作业拆分为多个短作业
- 合理调度:避免大量作业同时执行
- 使用异步:作业执行应使用异步方式
- 监控告警:监控作业执行状态,设置告警机制
总结
Quartz.NET 是一个功能强大的定时任务调度框架,支持多种触发器类型和集群部署。合理使用可以实现复杂的定时任务需求。
作者:Blogger
日期:2024年5月15日