Hangfire 使用 MongoDB 执行长时间运行的后台作业,为什么会一直重新启动?

3

我目前使用Hangfire版本1.6.19与MongoDB作为存储,遇到了问题。我们有一个按以下方式调度的方法:

BackgroundJob.Schedule(() => DoAsyncTask(parameters, JobCancellationToken.Null), TimeSpan.FromMinutes(X))

这个任务将运行一个多小时,其中包含一个循环来验证作业是否已完成。在循环内部,会调用cancellationToken.ThrowIfCancellationRequested()来验证是否已请求取消,但是此调用在执行约30分钟后不断被触发,并在作业完成之前终止了作业。
我一直在寻找有关此问题的信息,但大部分与旧版本或InvisibilityTimeout的使用有关,根据这个答案,它已经被弃用,因此我想知道是否有其他人遇到过这个问题以及任何可能的解决方案。
谢谢
编辑:经过进一步调查,我发现取消问题只是HangFire再次调用任务的副作用,而这次调用发生在运行30分钟后。由于我在方法中设置了验证以避免在进程仍在运行时重新进入(以避免数据重复),因此该进程将被视为已完成并因此被取消。
所以我面临的真正问题是我无法确定为什么HangFire在执行约30分钟后会再次调用进程,我按照这里描述的步骤在IIS上设置应用程序始终运行并防止池被回收,但行为仍然存在。
3个回答

0
我解决问题的方法是使用this filter来在作业完成之前对其设置分布式锁。我对实现进行了一些小的修改,包括添加作业ID并更新对HangFire这个版本中新对象的调用。详细的实现如下:
public class SkipConcurrentExecutionAttribute : JobFilterAttribute, IServerFilter
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger();

    private readonly int _timeoutInSeconds;

    public SkipConcurrentExecutionAttribute(int timeoutInSeconds)
    {
        if (timeoutInSeconds < 0) throw new ArgumentException("Timeout argument value should be greater that zero.");

        _timeoutInSeconds = timeoutInSeconds;
    }


    public void OnPerforming(PerformingContext filterContext)
    {
        var resource = $"{filterContext.BackgroundJob.Job.Type.FullName}.{filterContext.BackgroundJob.Job.Method.Name}.{filterContext.BackgroundJob.Id}";

        var timeout = TimeSpan.FromSeconds(_timeoutInSeconds);

        try
        {
            var distributedLock = filterContext.Connection.AcquireDistributedLock(resource, timeout);
            filterContext.Items["DistributedLock"] = distributedLock;
        }
        catch (Exception)
        {
            filterContext.Canceled = true;
            logger.Warn("Cancelling run for {0} job, id: {1} ", resource, filterContext.BackgroundJob.Id);
        }
    }

    public void OnPerformed(PerformedContext filterContext)
    {
        if (!filterContext.Items.ContainsKey("DistributedLock"))
        {
            throw new InvalidOperationException("Can not release a distributed lock: it was not acquired.");
        }

        var distributedLock = (IDisposable)filterContext.Items["DistributedLock"];
        distributedLock.Dispose();
    }
}

所以现在调用后台进程的方式是:

[SkipConcurrentExecution(300)]
public async Task DoAsyncTask(parameters, IJobCancellationToken cancellationToken){
    //code execution here
}

希望这能帮到你,重新进入的原因仍然未知,所以请随时根据你找到的任何信息来扩展这个答案。


0

在ServiceFabric集群中,我遇到了与Hangfire.Core 1.7.6和Hangfire.Mongo 0.5.6相同的问题。我使用this guide将PerformContext添加到我的作业中。

这允许获取当前作业的作业ID:var jobId = performContext.BackgroundJob.Id;

被安排在30分钟后重新启动的作业具有相同的作业ID。因此,可以检查是否存在具有相同ID的成功作业:

var backgroundJob = performContext.BackgroundJob;
var monitoringApi = JobStorage.Current.GetMonitoringApi();
var succeededCount = (int)monitoringApi.SucceededListCount();
if (succeededCount > 0) 
{
    var queryCount = Math.Min(succeededCount, 1000);

    // read up to 1000 latest succeeded jobs:
    var succeededJobs = monitoringApi.SucceededJobs(succeededCount - queryCount, queryCount);

    // check if job with the same ID already finished:
    if (succeededJobs.Any(succeededKp => backgroundJob.Id == succeededKp.Key)) 
    {
        // The job was already started and succeeded, skip this execution
        return;
    }
}

注意:工作方法也必须进行注释,以便不会同时启动。超时应该有合理的限制,例如6小时:[DisableConcurrentExecution(6 * 60 * 60)]。否则,第二个任务可能会在30分钟后开始,而不是在第一个任务完成后。


-1

我曾经遇到过同样的问题,花了很多时间在Hangfire的话题中寻找解决方案。但后来我注意到取消事件只在控制台事件之后触发。

所以问题不在于Hangfire本身,而是项目中的Hangfire.Console插件。你使用这个插件吗?切换到另一种日志记录方法解决了我的所有问题。


这不是一个答案!请考虑在hangfire.console中添加更多关于问题的细节。 - helcode
这正是我在浪费大量时间寻找解决方案之前所需要的答案。Hangfire.Console是一个可选的扩展,可以很容易地替换掉。在Hangfire的github上有几个未解决的问题,以及在StackOverflow上相同的问题。我在那里的截图中看到了Console。而且这个bug看起来确实像是旧版Hangfire中的一个bug。了解这种差异非常重要。 - Nickolay Klestov
你好,@NickolayKlestov,感谢你的建议。不幸的是,在我的情况下,我没有使用hangfire.Console扩展。问题出现后,我尝试了一段时间,希望能从中获得一些额外的见解,但无论是否使用该扩展,问题的行为都没有改变,所以我知道这不是问题的原因。你切换到了哪种日志记录方法来解决你的问题? - Armando Bracho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接