Quartz.Net添加DI后集群失效,任务多节点重复执行问题排查
问题背景
测试环境采用SQL Server作为Quartz集群存储,共调度15个任务:3个父任务(执行完成触发2个子任务)、6个独立任务。父任务通过Cron每10秒触发,并发数限制为3。未添加依赖注入(DI)前集群正常,任务可在两节点间合理分配;添加DI重构初始化逻辑后,出现同一任务在两个节点同时执行的问题,日志显示调度器实例ID为NON_CLUSTERED,集群协调失效。所有任务已添加[DisallowConcurrentExecution]属性,数据库配置未变更且验证通过,清空数据库表无法解决。
核心问题分析
调度器实例ID显示NON_CLUSTERED,说明集群模式未被正确启用,本质是Quartz配置结构错误导致集群配置未加载,节点间无法通过数据库共享状态,进而引发任务重复执行。
排查与解决步骤
1. 修正配置结构错误
原配置中存在两处关键结构问题:
- 线程池配置被嵌套在
UsePersistentStore内部,属于顶级配置的线程池无法被正确识别 AddQuartzHostedService被嵌套在AddQuartz的配置lambda中,导致Hosted Service注册上下文错误
修正后的配置代码:
services.AddQuartz(q => { // 配置Microsoft DI Job Factory q.UseMicrosoftDependencyInjectionJobFactory(options => { // 按需配置,比如是否允许自动激活依赖等 }); // 每个节点必须设置唯一的调度器名称,确保集群节点可区分 q.SchedulerName = node; // 线程池配置:顶级配置项,移出UsePersistentStore q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 3; }); q.UsePersistentStore(s => { s.PerformSchemaValidation = true; s.UseProperties = true; s.UseSqlServer(sqlServer => { sqlServer.ConnectionString = "Server=localhost;Database=quartz;Trusted_Connection=True;TrustServerCertificate=true"; sqlServer.TablePrefix = "QRTZ_"; }); s.UseJsonSerializer(); // 集群配置:确保在UsePersistentStore内正确配置 s.UseClustering(c => { c.CheckinMisfireThreshold = TimeSpan.FromSeconds(30); c.CheckinInterval = TimeSpan.FromSeconds(10); }); }); }); // 独立注册Quartz Hosted Service,避免嵌套在AddQuartz配置块中 services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; }); .Build() .RunAsync();
2. 验证节点唯一性
确保每个集群节点的SchedulerName(即代码中的node变量)拥有唯一值,Quartz集群通过该名称区分不同节点,若多个节点名称相同,会导致状态混乱。
3. 检查数据库集群状态表
查看SQL Server中的QRTZ_SCHEDULER_STATE表,确认存在与每个节点SchedulerName对应的记录,且INSTANCE_NAME不为NON_CLUSTERED。若只有NON_CLUSTERED记录,说明集群配置仍未生效,需重新检查配置加载逻辑。
4. 版本兼容性验证
确认使用的Quartz版本与Microsoft.Extensions.DependencyInjection集成包版本兼容,避免因版本不匹配导致集群配置失效。
5. 确认任务属性生效
虽然已添加[DisallowConcurrentExecution],但该属性仅限制单个节点内同一任务的并发执行,集群级别的任务互斥依赖于Quartz的集群协调机制,因此必须确保集群模式正常启用才能解决跨节点重复执行问题。
内容的提问来源于stack exchange,提问作者Mike

