Azure WebJob部署Hangfire多实例重复执行同一作业求助
Hangfire 1.6.21 Azure WebJob多实例重复执行作业的临时修复方案
问题根源
Hangfire 1.6.x版本基于SQL Server存储时,分布式锁机制存在局限性:当Azure实例缩容、异常宕机或多实例并发拾取作业时,锁的释放和竞争处理逻辑不够健壮,导致同一作业被多个实例标记为In Progress并执行。
临时修复方案
1. 调整Hangfire SQL存储的锁与超时参数
修改Hangfire初始化代码,延长分布式锁超时、作业不可见性超时,降低队列轮询频率,减少并发冲突概率:
var connectionString = "Your_Azure_SQL_Connection_String"; var storage = new SqlServerStorage(connectionString, new SqlServerStorageOptions { // 延长分布式锁超时,避免实例短暂不可用时锁提前释放 DistributedLockTimeout = TimeSpan.FromSeconds(30), // 延长作业不可见性超时,减少其他实例拾取同一作业的机会 SlidingInvisibilityTimeout = TimeSpan.FromSeconds(15), // 降低队列轮询间隔,减少并发查询冲突 QueuePollInterval = TimeSpan.FromSeconds(5), CommandBatchMaxTimeout = TimeSpan.FromSeconds(10) }); GlobalConfiguration.Configuration.UseStorage(storage);
2. 自定义过滤器拦截重复执行的作业
实现作业执行前的状态检查,当同一JobId存在多条In Progress记录时,取消当前执行:
public class PreventDuplicateExecutionFilter : IServerFilter { private readonly string _connectionString; public PreventDuplicateExecutionFilter(string connectionString) { _connectionString = connectionString; } public void OnPerforming(PerformingContext context) { var jobId = context.BackgroundJob.Id; using (var conn = new SqlConnection(_connectionString)) { conn.Open(); // 注意不要用NOLOCK,避免读取未提交的脏数据 var inProgressCount = conn.QueryFirstOrDefault<int>( @"SELECT COUNT(*) FROM [HangFire].[State] WHERE JobId = @JobId AND Name = 'InProgress'", new { JobId = jobId }); if (inProgressCount > 1) { context.Canceled = true; // 可添加日志记录,便于排查 // Log.Warn($"Job {jobId} is already in progress, execution canceled."); } } } public void OnPerformed(PerformedContext context) { } }
注册过滤器到Hangfire全局配置:
GlobalJobFilters.Filters.Add(new PreventDuplicateExecutionFilter(connectionString));
3. 优化Azure WebJob实例配置
- 优雅关闭配置:在Azure Portal的WebApp配置中,将"优雅关闭时间"设置为最大值120秒,确保实例缩容时Hangfire有足够时间完成当前作业或释放锁。
- 启用实例预热:开启WebApp的"实例预热"功能,避免新实例启动后立即批量拾取作业,减少并发冲突。
- 始终开启:确保WebApp设置为"始终开启",避免实例休眠导致锁异常释放。
4. 定期清理异常的In Progress状态
创建定时任务(可用Hangfire定时作业或Azure WebJob定时触发),定期清理超时的In Progress状态记录,避免残留状态导致重复执行:
-- 清理超过30分钟的In Progress状态(根据实际作业时长调整) DELETE FROM [HangFire].[State] WHERE Name = 'InProgress' AND CreatedAt < DATEADD(MINUTE, -30, GETUTCDATE())
总结
以上方案无需升级Hangfire版本,可快速部署实施,有效降低作业重复执行的概率。长期来看,升级到Hangfire最新版本(2.x及以上)能从根本上解决分布式锁和并发处理的缺陷。
内容的提问来源于stack exchange,提问作者Sagar
相关产品推荐
相关产品推荐

