HangFire循环作业中作业因执行时间太长未完成新作业开启导致重复数据的问题
解决方法:在执行的任务方法前加上Mutex特性即可,如果作业未完成,新作业开启的话,新作业会放入计划中的作业队列中,直到前面的作业完成。
必须使用Hangfire.Pro.Redis 和 Hangfire.SqlServer 作为数据库。
参考:https://github.com/HangfireIO/Hangfire/issues/1053
[Mutex("DownloadVideo")]
public async Task DownloadVideo()
{
}
Mutex特性代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage; namespace Hangfire.Pro
{
/// <summary>
/// Represents a background job filter that helps to disable concurrent execution
/// without causing worker to wait as in <see cref="Hangfire.DisableConcurrentExecutionAttribute"/>.
/// </summary>
public class MutexAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter
{
private static readonly TimeSpan DistributedLockTimeout = TimeSpan.FromMinutes(); private readonly string _resource; public MutexAttribute(string resource)
{
_resource = resource;
RetryInSeconds = ;
} public int RetryInSeconds { get; set; }
public int MaxAttempts { get; set; } public void OnStateElection(ElectStateContext context)
{
// We are intercepting transitions to the Processed state, that is performed by
// a worker just before processing a job. During the state election phase we can
// change the target state to another one, causing a worker not to process the
// backgorund job.
if (context.CandidateState.Name != ProcessingState.StateName ||
context.BackgroundJob.Job == null)
{
return;
} // This filter requires an extended set of storage operations. It's supported
// by all the official storages, and many of the community-based ones.
var storageConnection = context.Connection as JobStorageConnection;
if (storageConnection == null)
{
throw new NotSupportedException("This version of storage doesn't support extended methods. Please try to update to the latest version.");
} string blockedBy; try
{
// Distributed lock is needed here only to prevent a race condition, when another
// worker picks up a background job with the same resource between GET and SET
// operations.
// There will be no race condition, when two or more workers pick up background job
// with the same id, because state transitions are protected with distributed lock
// themselves.
using (AcquireDistributedSetLock(context.Connection, context.BackgroundJob.Job.Args))
{
// Resource set contains a background job id that acquired a mutex for the resource.
// We are getting only one element to see what background job blocked the invocation.
var range = storageConnection.GetRangeFromSet(
GetResourceKey(context.BackgroundJob.Job.Args),
,
); blockedBy = range.Count > ? range[] : null; // We should permit an invocation only when the set is empty, or if current background
// job is already owns a resource. This may happen, when the localTransaction succeeded,
// but outer transaction was failed.
if (blockedBy == null || blockedBy == context.BackgroundJob.Id)
{
// We need to commit the changes inside a distributed lock, otherwise it's
// useless. So we create a local transaction instead of using the
// context.Transaction property.
var localTransaction = context.Connection.CreateWriteTransaction(); // Add the current background job identifier to a resource set. This means
// that resource is owned by the current background job. Identifier will be
// removed only on failed state, or in one of final states (succeeded or
// deleted).
localTransaction.AddToSet(GetResourceKey(context.BackgroundJob.Job.Args), context.BackgroundJob.Id);
localTransaction.Commit(); // Invocation is permitted, and we did all the required things.
return;
}
}
}
catch (DistributedLockTimeoutException)
{
// We weren't able to acquire a distributed lock within a specified window. This may
// be caused by network delays, storage outages or abandoned locks in some storages.
// Since it is required to expire abandoned locks after some time, we can simply
// postpone the invocation.
context.CandidateState = new ScheduledState(TimeSpan.FromSeconds(RetryInSeconds))
{
Reason = "Couldn't acquire a distributed lock for mutex: timeout exceeded"
}; return;
} // Background job execution is blocked. We should change the target state either to
// the Scheduled or to the Deleted one, depending on current retry attempt number.
var currentAttempt = context.GetJobParameter<int>("MutexAttempt") + ;
context.SetJobParameter("MutexAttempt", currentAttempt); context.CandidateState = MaxAttempts == || currentAttempt <= MaxAttempts
? CreateScheduledState(blockedBy, currentAttempt)
: CreateDeletedState(blockedBy);
} public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
if (context.BackgroundJob.Job == null) return; if (context.OldStateName == ProcessingState.StateName)
{
using (AcquireDistributedSetLock(context.Connection, context.BackgroundJob.Job.Args))
{
var localTransaction = context.Connection.CreateWriteTransaction();
localTransaction.RemoveFromSet(GetResourceKey(context.BackgroundJob.Job.Args), context.BackgroundJob.Id); localTransaction.Commit();
}
}
} public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
} private static DeletedState CreateDeletedState(string blockedBy)
{
return new DeletedState
{
Reason = $"Execution was blocked by background job {blockedBy}, all attempts exhausted"
};
} private IState CreateScheduledState(string blockedBy, int currentAttempt)
{
var reason = $"Execution is blocked by background job {blockedBy}, retry attempt: {currentAttempt}"; if (MaxAttempts > )
{
reason += $"/{MaxAttempts}";
} return new ScheduledState(TimeSpan.FromSeconds(RetryInSeconds))
{
Reason = reason
};
} private IDisposable AcquireDistributedSetLock(IStorageConnection connection, IEnumerable<object> args)
{
return connection.AcquireDistributedLock(GetDistributedLockKey(args), DistributedLockTimeout);
} private string GetDistributedLockKey(IEnumerable<object> args)
{
return $"extension:job-mutex:lock:{GetKeyFormat(args, _resource)}";
} private string GetResourceKey(IEnumerable<object> args)
{
return $"extension:job-mutex:set:{GetKeyFormat(args, _resource)}";
} private static string GetKeyFormat(IEnumerable<object> args, string keyFormat)
{
return String.Format(keyFormat, args.ToArray());
}
}
}
HangFire循环作业中作业因执行时间太长未完成新作业开启导致重复数据的问题的更多相关文章
- 解决 ffmpeg 在avformat_find_stream_info执行时间太长
用ffmpeg做demux,网上很多参考文章.对于网络流,avformt_find_stream_info()函数默认需要花费较长的时间进行流格式探测,那么,如何减少探测时间内? 可以通过设置AVFo ...
- SQLServer 删除表中的重复数据
create table Student( ID varchar(10) not null, Name varchar(10) not null, ); insert in ...
- Oracle、SQLServer 删除表中的重复数据,只保留一条记录
原文地址: https://blog.csdn.net/yangwenxue_admin/article/details/51742426 https://www.cnblogs.com/spring ...
- 使用T-SQL找出执行时间过长的作业
有些时候,有些作业遇到问题执行时间过长,因此我写了一个脚本可以根据历史记录,找出执行时间过长的作业,在监控中就可以及时发现这些作业并尽早解决,代码如下: SELECT sj.name , ...
- spark SQL读取ORC文件从Driver启动到开始执行Task(或stage)间隔时间太长(计算Partition时间太长)且产出orc单个文件中stripe个数太多问题解决方案
1.背景: 控制上游文件个数每天7000个,每个文件大小小于256M,50亿条+,orc格式.查看每个文件的stripe个数,500个左右,查询命令:hdfs fsck viewfs://hadoop ...
- 详解C#泛型(二) 获取C#中方法的执行时间及其代码注入 详解C#泛型(一) 详解C#委托和事件(二) 详解C#特性和反射(四) 记一次.net core调用SOAP接口遇到的问题 C# WebRequest.Create 锚点“#”字符问题 根据内容来产生一个二维码
详解C#泛型(二) 一.自定义泛型方法(Generic Method),将类型参数用作参数列表或返回值的类型: void MyFunc<T>() //声明具有一个类型参数的泛型方法 { ...
- 《发际线总是和我作队》第八次团队作业:Alpha冲刺 第五天
项目 内容 这个作业属于哪个课程 软件工程 这个作业的要求在哪里 实验十二 团队作业8:软件测试与Alpha冲刺实验十一 团队作业7:团队项目设计完善&编码 团队名称 发际线总和我作队 作业学 ...
- kettle作业中的js如何写日志文件
在kettle作业中JavaScript脚本有时候也扮演非常重要的角色,此时我们希望有一些日志记录.下面是job中JavaScript记录日志的方式. job的js写日志的方法. 得到日志输出实例 o ...
- 【转】【SQL SERVER】怎样处理作业中的远程服务器错误(42000)
(SQL SERVER)怎样处理作业中的远程服务器错误(42000) 问: 1.我创建了一个链接服务器. 2.在两台服务器之间创建了新的SQL用户. 3.编写了访问链接服务器的SQL语句,执行成功. ...
随机推荐
- Docker构建其它组件
构建mysql 运行centos7容器 docker run --privileged -dti --name=centos-container centos:7 /usr/sbin/init 查询c ...
- 剑指C++面试
传闻公司老总欠下巨款,带着小姨子跑路了~ 树倒猢狲散,接下来要好好准备面试,以期找到一份满意的工作. 面试准备分下面几个方面进行,形成面试系列文章,文章内容以问答的方式呈现. 1.C++语言基础 传 ...
- Java安全编码:糟糕的在线建议和令人困惑的APIs
对于程序员和软件开发人员来说,网络论坛提供了一个交流知识和寻找具体编码难题答案的好地方.遗憾的是,他们并不总是准确信息的来源. 弗吉尼亚理工大学的一组研究人员分析了数百篇关于Stack Overflo ...
- 如何推翻JAVA的统治地位?
“java越来越过份了.”php狠狠的说,他转头看着C:“C哥,您可是前辈,java最近砸了我不少场子,您老再不出来管管,我怕他眼里就没有您了啊.” C哥吸烟,慢慢的说:“年轻人不要着急,java的根 ...
- Javascript高级编程学习笔记(49)—— DOM2和DOM3(1)DOM变化
DOM变化 我们知道DOM有许多的版本,其中DOM0和DOM2这两个级别以对事件的纳入标准而为人所知 但是呢,这里不讲事件,在后面会有专门和事件有关的部分作为详细讲解 这里就只讲一下DOM2和DOM3 ...
- 吴恩达机器学习笔记6-梯度下降II(Gradient descent intuition)--梯度下降的直观理解
在之前的学习中,我们给出了一个数学上关于梯度下降的定义,本次视频我们更深入研究一下,更直观地感受一下这个算法是做什么的,以及梯度下降算法的更新过程有什么意义.梯度下降算法如下: 描述:对
- iframe简单框架
<iframe width='738' height='523' class='preview-iframe' scrolling='no' frameborder='0' src='http: ...
- centos6安装cas5
cas是Central Authentication Service的缩写,中文为中央认证服务,在这里我就不说理论了,在公司里项目研发需要cas平台,所以经过两天研究,搞了一个简化版的cas服务,有不 ...
- Python中函数和模块的体验与使用
函数基础 目标 函数的快速体验 函数的基本使用 函数的参数 函数的返回值 函数的嵌套调用 在模块中定义函数 01. 函数的快速体验 1.1 快速体验 所谓函数,就是把 具有独立功能的代码块 组织为一个 ...
- Redis 如何分析慢查询操作?
什么是慢查询 和mysql的慢SQL日志分析一样,redis也有类似的功能,来帮助定位一些慢查询操作. Redis slowlog是Redis用来记录查询执行时间的日志系统. 查询执行时间指的是不包括 ...