为什么 Hangfire 在轮询 SQL Server 作业时每隔几秒等待 15 秒?

Ror*_*ory 0 .net asp.net hangfire

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

\n

在我的开发环境中运行时查看 SQL Profiler,针对 Hangfire 数据库运行的 SQL 如下所示 -

\n
exec sp_executesql N\'delete top (1) JQ\noutput DELETED.Id, DELETED.JobId, DELETED.Queue\nfrom [HangFire].JobQueue JQ with (readpast, updlock, rowlock, forceseek)\nwhere 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\n\n-- Exactly the same SQL as above is executed about 6 times/second for about 3-4 seconds,\n-- then nothing for about 2 seconds, then: \n\nexec sp_getapplock @Resource=N\'HangFire:recurring-jobs:lock\',@DbPrincipal=N\'public\',@LockMode=N\'Exclusive\',@LockOwner=N\'Session\',@LockTimeout=5000\nexec sp_getapplock @Resource=N\'HangFire:locks:schedulepoller\',@DbPrincipal=N\'public\',@LockMode=N\'Exclusive\',@LockOwner=N\'Session\',@LockTimeout=5000\nexec 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\nexec 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\nexec sp_releaseapplock @Resource=N\'HangFire:recurring-jobs:lock\',@LockOwner=N\'Session\'\nexec sp_releaseapplock @Resource=N\'HangFire:locks:schedulepoller\',@LockOwner=N\'Session\'\n\n-- Then nothing is executed for about 8-10 seconds, then: \n\nexec 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\'\n\n-- Then about 500ms-2s later I get \nexec sp_executesql N\'delete top (1) JQ ... -- i.e. Same as first query\nThe update LastHeartbeat query is only there every second time (from just a brief inspection, maybe that\xe2\x80\x99s not exactly right).\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

我的开发环境中只有一个队列,尽管在实际情况下我们\xe2\x80\x99d 有 20-50 个 I\xe2\x80\x99d 猜测。

\n

关于我应该在哪里查找导致\xe2\x80\x99s 的配置的任何建议:\na) 检查作业之间的 8-10 秒暂停\nb) 正在检查作业的线程数 - 似乎我也有许多

\n
\n

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

\n
        app.UseHangfireDashboard();\n\n        GlobalConfiguration.Configuration\n            .UseSqlServerStorage(connstring, new SqlServerStorageOptions\n            {\n                CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),\n                QueuePollInterval = TimeSpan.Zero,\n                SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),\n                UseRecommendedIsolationLevel = true,\n                PrepareSchemaIfNecessary = true, // Default value: true\n                EnableHeavyMigrations = true     // Default value: false\n            })\n            .UseAutofacActivator(_container);\n        JobActivator.Current = new AutofacJobActivator(_container);\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

(交叉发布到hangfire 论坛

\n

Ror*_*ory 5

如果您未设置,QueuePollInterval则带有 sql server 存储的 Hangfire 默认每 15 秒轮询一次。因此,如果遇到此问题,首先要做的就是设置QueuePollInterval较小的值,例如 1s。

但就我而言,即使我设置了它也没有任何效果。原因是在我打电话之前app.UseHangfireServer() 打电话了。GlobalConfiguration.Configuration.UseSqlServerStorage()SqlServerStorageOptions

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

    var storage = new SqlServerStorage(connstring);
    JobStorage.Current = storage;
Run Code Online (Sandbox Code Playgroud)

然后后来打电话

    app.UseHangfireServer()
Run Code Online (Sandbox Code Playgroud)

然后后来打电话

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

重新排序为SqlServerStorageOptions之前使用app.UseHangfireServer()SqlServerStorageOptions生效。