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

Azure多实例App Service中Quartz Job部署后重复执行问题

问题:Azure App Service多实例下Quartz部署后作业重复执行

我在.NET Core 6 Web API中使用Quartz.Extensions.Hosting 3.8.1调度包,部署环境是Azure App Service(2个实例)。应用运行基本正常,但每次部署后,作业会快速连续执行2-5次,执行间隔从几秒到几分钟不等,首次触发完成后后续都能按设定间隔正常运行。

例如:作业设定为1小时执行一次,部署后立即执行了4次(绿色标记),之后按1小时间隔正常执行(黄色标记)。

使用版本

  • Quartz.Extensions.Hosting 3.8.1

代码实现

配置Quartz的扩展方法

public static void ConfigureQuartzJobs(this WebApplicationBuilder builder)
{
    var services = builder.Services;
    services.AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true);
    services.AddQuartz(q =>
    {
        q.SchedulerId = "SomeId";
        q.SchedulerName = "SomeName";
        q.UsePersistentStore(s =>
                {
                  s.UseProperties = true;
                  s.UseClustering();
                  s.UseSqlServer(connectionString);
                  s.UseNewtonsoftJsonSerializer();
                });
                q.AddJob<job1>(jobkey1)
                 .AddTrigger(trigger => trigger
                           .ForJob(jobkey1)
                           .WithIdentity(tr1)
                           .StartAt(DateTimeOffset.UtcNow.AddSeconds(5))
                           .WithSimpleSchedule(schedule => schedule.WithIntervalInMinutes(5).RepeatForever()));
                });
    }
}

Job1实现类

[DisallowConcurrentExecution]
public class Job1 : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        try
        {
            // 业务逻辑调用
        }
        catch (Exception ex)
        {
            await Console.Out.WriteLineAsync($"{context.FireTimeUtc}, {ex}. FireInstanceId={context.FireInstanceId}");
        }
    }
}

已尝试的配置(未解决问题)

q.UseDefaultThreadPool(x => x.MaxConcurrency = 5);
q.MisfireThreshold = TimeSpan.FromSeconds(60);

// 触发器启动时间的不同设置
trigger.StartAt(DateTimeOffset.UtcNow.AddSeconds(5))
trigger.StartNow()
// 不设置任何启动时间

期望行为

部署后,作业应仅执行一次或不执行,确保后续严格按触发器设定时间触发。


解决方案

1. 修正集群节点标识配置

硬编码的SchedulerId会导致多实例集群识别异常,改为自动生成唯一节点ID:

q.UsePersistentStore(s =>
{
    s.UseProperties = true;
    s.UseClustering(c =>
    {
        // 让Quartz自动生成每个实例的唯一节点ID
        c.InstanceId = "AUTO";
    });
    s.UseSqlServer(connectionString);
    s.UseNewtonsoftJsonSerializer();
});

2. 调整触发器启动策略与错过触发处理

避免用当前时间计算启动点,改为基于固定时间偏移,同时明确错过触发的处理逻辑:

.AddTrigger(trigger => trigger
    .ForJob(jobkey1)
    .WithIdentity(tr1)
    // 部署后第一次触发在当前整点加5分钟,避免多实例同时计算"现在+5秒"导致重叠
    .StartAt(DateTimeOffset.UtcNow.Round(TimeSpan.FromMinutes(1)).AddMinutes(5))
    .WithSimpleSchedule(schedule => schedule
        .WithIntervalInMinutes(5)
        .RepeatForever()
        // 忽略错过的触发,避免部署重启后重复执行
        .WithMisfireHandlingInstructionIgnoreMisfires()))

3. 调整MisfireThreshold阈值

将阈值设为更大的值,避免部署重启被误判为错过触发:

q.MisfireThreshold = TimeSpan.FromMinutes(2);

4. 检查实例时钟同步

在作业中输出实例UTC时间,确认多实例时钟是否一致:

await Console.Out.WriteLineAsync($"Instance UTC Time: {DateTime.UtcNow}, FireTimeUtc: {context.FireTimeUtc}");

若存在偏差,联系Azure支持调整实例时钟同步配置。

5. 启用Azure部署槽预热

利用部署槽的预热机制,让新实例完成Quartz初始化后再切换流量,避免新旧实例同时调度作业。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 12:15:31