Hangfire在SQL Server中导致锁定

时间:2019-05-27 07:46:37

标签: c# sql-server backgroundworker hangfire

我们在带有SQL Server 2016的ASP.NET Web项目中使用Hangfire 1.7.2。服务器上有大约150个站点,每个站点都使用Hangfire 1.7.2。我们注意到,当我们升级这些站点以使用Hangfire时,数据库服务器崩溃了。查看数据库日志,我们发现有多个锁定查询。我们在所有阻止会话中都标识了一个RPC事件“ sys.sp_getapplock; 1”。看来Hangfire锁定了我们的数据库,使整个数据库无法使用。由于Hangfire,我们注意到将近670多个锁定查询。

这可能是由于我们设置的这些属性导致的:

   SlidingInvisibilityTimeout = TimeSpan.FromMinutes(30),
   QueuePollInterval = TimeSpan.FromHours(5)

每个站点都有大约20个后台作业,其中一些每分钟运行一次,而另一些每小时,每6小时运行一次,每天运行一次。

我已经搜索了文档,但是找不到任何可以解释这两个属性或如何设置它们以避免数据库锁定的信息。

在此方面寻求帮助。

编辑:每秒执行以下查询:

exec sp_executesql N'select count(*) from [HangFire].[Set] with (readcommittedlock, forceseek) where [Key] = @key',N'@key nvarchar(4000)',@key=N'retries'

select distinct(Queue) from [HangFire].JobQueue with (nolock)

exec sp_executesql N'select count(*) from [HangFire].[Set] with (readcommittedlock, forceseek) where [Key] = @key',N'@key nvarchar(4000)',@key=N'retries'

无论我们设置的时间跨度值如何组合。这是我们正在使用的GetHangfirServers的代码:

  public static IEnumerable<IDisposable> GetHangfireServers()
    {
        // Reference for GlobalConfiguration.Configuration: http://docs.hangfire.io/en/latest/getting-started/index.html
        // Reference for UseSqlServerStorage: http://docs.hangfire.io/en/latest/configuration/using-sql-server.html#configuring-the-polling-interval
        GlobalConfiguration.Configuration
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)

            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()
            .UseSqlServerStorage(ConfigurationManager.ConnectionStrings["abc"]
                .ConnectionString, new SqlServerStorageOptions
            {
                CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
                SlidingInvisibilityTimeout = TimeSpan.FromMinutes(30),
                QueuePollInterval = TimeSpan.FromHours(5), // Hangfire will poll after 5 hrs to check failed jobs.
                UseRecommendedIsolationLevel = true,
                UsePageLocksOnDequeue = true,
                DisableGlobalLocks = true
            });

        // Reference: https://docs.hangfire.io/en/latest/background-processing/configuring-degree-of-parallelism.html
        var options = new BackgroundJobServerOptions
        {
            WorkerCount = 5
        };

        var server = new BackgroundJobServer(options);

        yield return server;
    }

工人数仅设置为5。

只有4个工作,甚至已经完成(SELECT * FROM [HangFire]。[State]): enter image description here

您知道为什么Hangfire每秒会遇到这么多查询吗?

3 个答案:

答案 0 :(得分:1)

我们在我们的一个项目中遇到了这个问题。 hangfire仪表板读起来很繁琐,它非常频繁地轮询hangfire数据库,以刷新作业状态。

最适合我们的解决方案是拥有一个专用的hangfire数据库。 这样,您可以将应用程序查询与hangfire查询隔离开,并且您的应用程序查询将不受hangfire服务器和仪表板查询的影响。

答案 1 :(得分:1)

在配置SqlServerStorage时,有一个名为SlidingInvisibilityTimeout的较新配置选项,该选项导致这些数据库锁作为较新的获取非事务性消息获取算法的一部分。它适用于可能导致事务日志备份出错的长时间运行的作业(因为长期运行的作业中有一个仍处于活动状态的数据库事务)。

.UseSqlServerStorage(
    "connection_string", 
    new SqlServerStorageOptions { SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5) });

我们的DBA不喜欢数据库锁,所以我删除了这个SlidingInvisibilityTimeout选项,以使用旧的基于事务的消息获取算法,因为我的队列中没有长时间运行的工作。

是否启用此选项取决于您的情况。您可能需要考虑将队列数据库移到应用程序数据库之外(如果尚未移动),并启用SlidingInvisibilityTimeout选项。如果即使队列是一个单独的数据库,DBA也无法使用这些锁,那么也许您可以将任务重构为寿命更短的更多小任务。只是一些想法。

https://www.hangfire.io/blog/2017/06/16/hangfire-1.6.14.html

答案 2 :(得分:0)

SqlServerStorage运行Install.sql,该文件在Hangfire模式上获得排他的模式锁定。

DECLARE @SchemaLockResult INT;
EXEC @SchemaLockResult = sp_getapplock @Resource = '$(HangFireSchema):SchemaLock', 
@LockMode = 'Exclusive'

从Hangfire文档中:

“ SQL Server对象通过执行语句从SqlServerStorage构造函数自动安装 Install.sql文件(位于NuGet软件包中的tools文件夹下)中描述的内容。其中包含 迁移脚本,因此可以无缝安装具有模式更改的新版Hangfire,而无需安装 干预。”

如果您不想每次都运行此脚本,则可以将SqlServerStorageOptions.PrepareSchemaIfNecessary设置为false。

var options = new SqlServerStorageOptions
{
    PrepareSchemaIfNecessary = false
};

var sqlServerStorage = new SqlServerStorage(connectionstring, options);

相反,使用以下命令行手动运行Install.sql:

SqlServerObjectsInstaller.Install(connection);