You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quartz.NET无法加载SQL Server数据库中定义的作业与触发器求助

Quartz.NET无法加载数据库中定义的作业和触发器问题

我已在SQL Server数据库中定义了作业和触发器,但Quartz.NET无法加载作业也无法触发触发器。确认存在操作失误但无法定位,查阅官方文档仍未解决,寻求帮助。

启动日志

Using job store 'Quartz.Impl.AdoJobStore.JobStoreTX', supports persistence: True, clustered: True
{"@t":"2024-07-08T14:10:24.3246282Z","@mt":"Using job store 'Quartz.Impl.AdoJobStore.JobStoreTX', supports persistence: True, clustered: True","SourceContext":"Quartz.Impl.StdSchedulerFactory","SFTPService":"SFTPService"}
{"@t":"2024-07-08T14:10:24.3328792Z","@mt":"Adding 0 jobs, 0 triggers.","SourceContext":"Quartz.ContainerConfigurationProcessor","SFTPService":"SFTPService"}

C#控制台应用Quartz初始化代码

//-------------------------------------------------------------------------
// Setup Quartz the timer scheduling library
//-------------------------------------------------------------------------

services.AddQuartzHostedService(opt =>
        {
            opt.WaitForJobsToComplete = true;
        });
services.AddQuartz(q=>
        {
            q.UsePersistentStore(s =>
            {
                s.UseProperties = true;
                s.UseNewtonsoftJsonSerializer();
                s.UseSqlServer(sqlServer =>
                {
                    sqlServer.ConnectionString = configuration.GetConnectionString("Quartz") ?? throw new ArgumentNullException("Quartz connection string");
                    sqlServer.TablePrefix = "QRTZ_";

                });
                s.UseClustering(c =>
                {
                    c.CheckinInterval = TimeSpan.FromSeconds(10);
                    c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
                });
            });
        });

数据库作业创建脚本

INSERT INTO QRTZ_JOB_DETAILS 
    (SCHED_NAME, JOB_NAME, JOB_GROUP, JOB_CLASS_NAME, IS_DURABLE, IS_NONCONCURRENT, IS_UPDATE_DATA, REQUESTS_RECOVERY, JOB_DATA)
VALUES 
    ('SFTPService', 'SFTPDownloadJobName', 'SFTPJobGroup', 'MyCompany.SFTP.WorkerLibrary.SFTPDownloadJob, MyCompany.SFTP.WorkerLibrary', 1, 0, 0, 0, NULL);


INSERT INTO QRTZ_TRIGGERS 
    (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, JOB_NAME, JOB_GROUP, DESCRIPTION, NEXT_FIRE_TIME, PREV_FIRE_TIME, PRIORITY, TRIGGER_STATE, TRIGGER_TYPE, START_TIME, END_TIME, CALENDAR_NAME, MISFIRE_INSTR, JOB_DATA)
VALUES 
    ('SFTPService', 'FrequentTriggerName', 'FrequentTriggerGroup', 'SFTPDownloadJobName', 'SFTPJobGroup', 'Fires every 20 seconds', NULL, NULL, 5, 'WAITING', 'CRON', DATEDIFF(SECOND, '1970-01-01', GETDATE()), NULL, NULL, 0, NULL);


INSERT INTO QRTZ_CRON_TRIGGERS 
    (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, CRON_EXPRESSION, TIME_ZONE_ID)
VALUES 
    ('SFTPService', 'FrequentTriggerName', 'FrequentTriggerGroup', '0/20 * * * * ?', NULL);

DECLARE @jsonData NVARCHAR(MAX) = '{
    "SftpHostName": "eu-central-1.sftpcloud.io",
    "SftpPortNumber": "22",
    "SftpUserName": "*********************",
    "SftpPassword": "***********************",
    "SftpRemoteDirectory": "/",
    "SftpLocalDirectory": "",
    "SftpFileNameOrPattern": "test.txt"
}';
DECLARE @jobData VARBINARY(MAX) = CONVERT(VARBINARY(MAX), @jsonData);

UPDATE QRTZ_TRIGGERS
SET JOB_DATA = @jobData
WHERE SCHED_NAME = 'SFTPService' 
  AND TRIGGER_NAME = 'FrequentTriggerName' 
  AND TRIGGER_GROUP = 'FrequentTriggerGroup';

问题排查与解决方案

1. 调度器名称不匹配(核心问题)

Quartz默认调度器名称为QuartzScheduler,但数据库中所有作业/触发器的SCHED_NAME是SFTPService,两者不匹配导致Quartz无法识别数据库中的记录。

修复方法:在Quartz初始化时显式指定调度器名称:

services.AddQuartz(q=>
{
    // 添加以下两行
    q.SchedulerName = "SFTPService";
    q.SchedulerId = "SFTPService-Node1"; // 集群模式下每个节点ID需唯一

    q.UsePersistentStore(s =>
    {
        // 原有配置...
    });
});

2. JobData序列化格式不兼容

手动将JSON转成VARBINARY存储到JOB_DATA字段,但Quartz使用Newtonsoft.Json序列化时会包含特定类型信息,直接转换的JSON无法被正确解析。

修复方法:

  • 避免手动修改JOB_DATA,改用Quartz API添加作业时传入JobDataMap,让框架自动处理序列化。
  • 若必须手动插入,需确保序列化格式与Quartz一致,可通过以下代码生成正确的JOB_DATA:
    var jobDataMap = new JobDataMap
    {
        ["SftpHostName"] = "eu-central-1.sftpcloud.io",
        ["SftpPortNumber"] = "22",
        // 其他参数...
    };
    var serializer = new NewtonsoftJsonObjectSerializer();
    byte[] serializedData = serializer.Serialize(jobDataMap);
    // 将serializedData存入数据库JOB_DATA字段
    

3. 触发器启动时间与状态验证

  • 检查START_TIME是否为UTC时间戳:Quartz内部使用UTC时间,需将DATEDIFF(SECOND, '1970-01-01', GETDATE())改为DATEDIFF(SECOND, '1970-01-01', GETUTCDATE())。
  • 集群模式下,触发器状态WAITING需要Quartz节点正常注册后才能激活,确保数据库连接正常,QRTZ_SCHEDULER_STATE表中有当前节点的记录。

4. 作业类程序集名称验证

确认JOB_CLASS_NAME中的程序集名称完全匹配,包括版本、文化和公钥标记(如果程序集是强签名的)。可通过以下代码获取正确的程序集限定名:

typeof(SFTPDownloadJob).AssemblyQualifiedName

内容的提问来源于stack exchange,提问作者Bigtoe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:35:56