【问题标题】:Why does Hangfire wait for 15s every few seconds when polling sql server for jobs?为什么在轮询 sql server 的作业时,Hangfire 每隔几秒等待 15 秒?
【发布时间】:2020-07-29 23:26:10
【问题描述】:

我继承了一个使用 Hangfire 和 sql server 作业存储的系统。通常,当一个作业计划立即运行时,我们会注意到它需要几秒钟才能被触发。

在我的开发环境中运行时查看 SQL Profiler,针对 Hangfire db 运行的 SQL 看起来像这样 -

exec sp_executesql N'delete top (1) JQ
output DELETED.Id, DELETED.JobId, DELETED.Queue
from [HangFire].JobQueue JQ with (readpast, updlock, rowlock, forceseek)
where Queue in (@queues1) and (FetchedAt is null or FetchedAt < DATEADD(second, @timeout, GETUTCDATE()))',N'@queues1 nvarchar(4000),@timeout float',@queues1=N'MYQUEUENAME_master',@timeout=-1800

-- Exactly the same SQL as above is executed about 6 times/second for about 3-4 seconds,
-- then nothing for about 2 seconds, then: 

exec sp_getapplock @Resource=N'HangFire:recurring-jobs:lock',@DbPrincipal=N'public',@LockMode=N'Exclusive',@LockOwner=N'Session',@LockTimeout=5000
exec sp_getapplock @Resource=N'HangFire:locks:schedulepoller',@DbPrincipal=N'public',@LockMode=N'Exclusive',@LockOwner=N'Session',@LockTimeout=5000
exec sp_executesql N'select top (@count) Value from [HangFire].[Set] with (readcommittedlock, forceseek) where [Key] = @key and Score between @from and @to order by Score',N'@count int,@key nvarchar(4000),@from float,@to float',@count=1000,@key=N'recurring-jobs',@from=0,@to=1596053348
exec sp_executesql N'select top (@count) Value from [HangFire].[Set] with (readcommittedlock, forceseek) where [Key] = @key and Score between @from and @to order by Score',N'@count int,@key nvarchar(4000),@from float,@to float',@count=1000,@key=N'schedule',@from=0,@to=1596053348
exec sp_releaseapplock @Resource=N'HangFire:recurring-jobs:lock',@LockOwner=N'Session'
exec sp_releaseapplock @Resource=N'HangFire:locks:schedulepoller',@LockOwner=N'Session'

-- Then nothing is executed for about 8-10 seconds, then: 

exec sp_executesql N'update [HangFire].Server set LastHeartbeat = @now where Id = @id',N'@now datetime,@id nvarchar(4000)',@now='2020-07-29 20:09:19.097',@id=N'ps12345:19764:fe362d1a-5ee4-4d97-b70d-134fdfab2b87'

-- Then about 500ms-2s later I get 
exec sp_executesql N'delete top (1) JQ ... -- i.e. Same as first query
The update LastHeartbeat query is only there every second time (from just a brief inspection, maybe that’s not exactly right).

看起来至少有 3 个线程在对 JQ 运行 DELETE 查询,因为我可以看到几个 RPC:Starting 在 RPC:Completed 之前,这表明它们是并行执行的,而不是顺序执行的。 我不知道这是否正常,但看起来很奇怪,因为我认为我们只有一个“消费者”的工作。

我的开发环境中只有一个队列,虽然我猜在现场我们会有 20-50 个。

关于我应该在哪里寻找导致的配置的任何建议: a) 检查作业之间的 8-10 秒停顿 b)正在检查作业的线程数 - 似乎我有太多


写完后,我意识到我们使用的是旧版本,所以我从 1.5.x 升级到 1.7.12,升级了数据库,并将启动配置更改为:

        app.UseHangfireDashboard();

        GlobalConfiguration.Configuration
            .UseSqlServerStorage(connstring, new SqlServerStorageOptions
            {
                CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
                QueuePollInterval = TimeSpan.Zero,
                SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
                UseRecommendedIsolationLevel = true,
                PrepareSchemaIfNecessary = true, // Default value: true
                EnableHeavyMigrations = true     // Default value: false
            })
            .UseAutofacActivator(_container);
        JobActivator.Current = new AutofacJobActivator(_container);

但如果有的话,问题现在更糟了。或者相同但更快:现在大约 1 秒内发生了对 delete top (1) JQ... 的 20 次调用,然后是其他查询,然后等待 15 秒,然后重新开始。

需要明确的是,主要问题是,如果在 15 秒延迟期间添加了任何作业,那么在执行我的作业之前将需要 15 秒的剩余时间。我认为第二个问题是它对 SQL Server 的影响超出了需要:至少对我的需要而言,每秒 20 次有点多。

(交叉发布到hangfire forums

【问题讨论】:

  • 根据来源中的这一行,将 QueuePollInterval 设置为 0 会使系统切换到长轮询。我建议选择一个严格高于 1s 的值,比如 2s,来更改等待策略,看看它是否有所作为(除了删除转换为更新)github.com/HangfireIO/Hangfire/blob/…
  • 谢谢,我试试。仍然没有解释为什么它会经常等待 10 秒,显然无论它使用什么策略都是不好的。但也许战略的改变将意味着它无论如何都会停止这样做。

标签: asp.net .net hangfire


【解决方案1】:

如果您没有设置QueuePollInterval,那么带有 sql server 存储的 Hangfire 默认为每 15 秒轮询一次。因此,如果您遇到此问题,首先要做的是将 QueuePollInterval 设置为更小的值,例如1秒。

但在我的情况下,即使我设置它也没有任何效果。原因是打电话给app.UseHangfireServer()之前我用SqlServerStorageOptions打电话给GlobalConfiguration.Configuration.UseSqlServerStorage()

当您调用app.UseHangfireServer() 时,它使用JobStorage.Current 的当前值。我的代码设置了:

    var storage = new SqlServerStorage(connstring);
    JobStorage.Current = storage;

后来调用

    app.UseHangfireServer()

后来调用

        GlobalConfiguration.Configuration
            .UseSqlServerStorage(connstring, new SqlServerStorageOptions
        {
            CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
            QueuePollInterval = TimeSpan.Zero,
            SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
            UseRecommendedIsolationLevel = true,
            PrepareSchemaIfNecessary = true, 
            EnableHeavyMigrations = true     
        })

app.UseHangfireServer() 之前重新排序以使用SqlServerStorageOptions 意味着SqlServerStorageOptions 生效。

【讨论】:

    【解决方案2】:

    我建议检查 Hangfire BackgroundJobServerOptions 以查看您在那里设置的轮询间隔。这将定义hangfire服务器检查队列中是否有任何作业要执行之前的时间。

    来自文档

    Hangfire Docs

    Hangfire 服务器会定期检查计划以将计划的作业排入其队列,从而允许工作人员 执行它们。默认情况下,检查间隔等于 15 秒,但您可以通过在传递给 BackgroundJobServer 构造函数的选项上设置 SchedulePollingInterval 属性来更改它:

    var options = new BackgroundJobServerOptions
    {
        SchedulePollingInterval = TimeSpan.FromMinutes(1)
    };
    var server = new BackgroundJobServer(options);
    

    【讨论】:

    • 感谢您的指点,但我的BackgroundJobServerOptions 只设置了Queues 属性。我不太明白轮询间隔的用途,所以我会继续阅读。
    • 好的,现在我明白了。不,在这种情况下,我们通常会触发立即运行的作业,因此您所说的 15 秒轮询不应该是相关的,因为那是为了将作业添加到应该很快运行的队列中,例如如果它们是像BackgroundJob.Schedule(() =&gt; Console.WriteLine("Hello, world"), TimeSpan.FromDays(1)); 这样创建的
    • 您可以运行 BackgroundJob.Schedule 或 BackgroundJob.Enqueue... 两者都使用相同的轮询间隔。一定要测试一下,看看这是否对你的情况有帮助。 BackgroundJobServeroptions 中的大多数值都设置为默认值,但您可以将这些值覆盖为默认值以外的值(new TimeSpan(0, 0, 5) 在 Hangfire 服务器获取下一个作业之前的 5 秒间隔)
    • 谢谢,我会调查的。但总的来说,您不会期望 Hangfire 不断轮询 sqlserver 以获取新作业吗?我的意思是,为什么它会停止投票?这是在没有作业要执行的情况下......所以当我们添加一个作业时,在它开始之前会有一个很大的停顿,因为 Hangfire 没有连续轮询。
    • 我授予赏金是因为您的回答为我指明了正确的方向,尽管并没有直接解决问题。我会发布一个答案来解释更多。
    猜你喜欢
    • 2012-11-04
    • 2014-04-12
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 2021-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多