Hangfire - 无法安排重复作业,详情请参阅内部异常

Hangfire - Recurring job can’t be scheduled, see inner exception for details

我有一个应用;它位于三个不同的服务器上,使用负载均衡器进行用户分配。 该应用程序使用自己的队列,我为作业添加了一个过滤器,以保留它们的 original 队列,以防它们在某个时候失败。但话又说回来,它继续表现得好像应用程序不是 运行。错误如下;

System.InvalidOperationException: Recurring job can't be scheduled, see inner exception for details.
 ---> Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
 ---> System.IO.FileNotFoundException: Could not resolve assembly 'My.Api'.
   at System.TypeNameParser.ResolveAssembly(String asmName, Func`2 assemblyResolver, Boolean throwOnError, StackCrawlMark& stackMark)
   at System.TypeNameParser.ConstructType(Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)
   at System.TypeNameParser.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)
   at System.Type.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError)
   at Hangfire.Common.TypeHelper.DefaultTypeResolver(String typeName)
   at Hangfire.Storage.InvocationData.DeserializeJob()
   --- End of inner exception stack trace ---
   at Hangfire.Storage.InvocationData.DeserializeJob()
   at Hangfire.RecurringJobEntity..ctor(String recurringJobId, IDictionary`2 recurringJob, ITimeZoneResolver timeZoneResolver, DateTime now)
   --- End of inner exception stack trace ---
   at Hangfire.Server.RecurringJobScheduler.ScheduleRecurringJob(BackgroundProcessContext context, IStorageConnection connection, String recurringJobId, RecurringJobEntity recurringJob, DateTime now)
What can be the issue here? The apps are running. And once I trigger the recurring jobs, they are good to go, until they show the above error.

这是我的 AppStart 文件;

private IEnumerable<IDisposable> GetHangfireServers()
{
    Hangfire.GlobalConfiguration.Configuration
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
        .UseSimpleAssemblyNameTypeSerializer()
        .UseRecommendedSerializerSettings()
        .UseSqlServerStorage(HangfireServer, new SqlServerStorageOptions
        {
            CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
            SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
            QueuePollInterval = TimeSpan.Zero,
            UseRecommendedIsolationLevel = true,
            DisableGlobalLocks = true
        });

    yield return new BackgroundJobServer(new BackgroundJobServerOptions {
        Queues = new[] { "myapp" + GetEnvironmentName() },
        ServerName = "MyApp" + ConfigurationHelper.GetAppSetting("Environment")
    });
}

public void Configuration(IAppBuilder app)
{
    var container = new Container();
    container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
    
    RegisterTaskDependencies(container);
    container.RegisterWebApiControllers(System.Web.Http.GlobalConfiguration.Configuration);
    container.Verify();
    
    var configuration = new HttpConfiguration();
    configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
    
    /* HANGFIRE CONFIGURATION */
    if (Environment == "Production")
    {
        GlobalJobFilters.Filters.Add(new PreserveOriginalQueueAttribute());
        Hangfire.GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(container));
        Hangfire.GlobalConfiguration.Configuration.UseLogProvider(new Api.HangfireArea.Helpers.CustomLogProvider(container.GetInstance<Core.Modules.LogModule>()));
        app.UseHangfireAspNet(GetHangfireServers);
        app.UseHangfireDashboard("/hangfire", new DashboardOptions
        {
            Authorization = new[] { new DashboardAuthorization() },
            AppPath = GetBackToSiteURL(),
            DisplayStorageConnectionString = false
        });
        AddOrUpdateJobs();
    }
    /* HANGFIRE CONFIGURATION */
    
    app.UseWebApi(configuration);
    
    WebApiConfig.Register(configuration);

}

public static void AddOrUpdateJobs()
{
    var queueName = "myapp" + GetEnvironmentName();
    RecurringJob.AddOrUpdate<HangfireArea.BackgroundJobs.AttachmentCreator>(
         "MyApp_MyTask",
         (service) => service.RunMyTask(), 
      "* * * * *", queue: queueName, timeZone: TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time"));
}

这里可能是什么问题?

事实证明,当多个应用程序使用相同的 sql schema 时,Hangfire 本身并不能正常工作。为了解决这个问题,我使用了 Hangfire.MAMQSqlExtension。它是一个第三方扩展,但 repo 说它被 Hangfire 官方认可。 如果您为多个应用程序使用相同的架构,您必须在您的所有应用程序中使用此扩展,否则您将遇到上述错误以上。

如果您的应用同时具有不同的版本(例如 productiontestdevelopment) 此应用程序本身不能完全处理 失败的作业 。如果作业失败,常规的 Hangfire 将不会尊重它的原始队列,因此会将其移至 default 队列。如果您的应用仅适用于您应用的队列或者 default 队列是共享的,这最终会产生问题。为了解决这个问题,强制 Hangfire 尊重原始队列属性,我使用了 this 解决方案。效果很好,您可以根据 web.configappsettings.json.

命名您的应用队列

我之前的回答不知为何被删除了?这解决了问题,没有其他办法。请勿删除答案,以防遇到此问题的人。

我发现的另一个选择是使用 Hangfire 的后台进程 https://www.hangfire.io/overview.html#background-process

public class CleanTempDirectoryProcess : IBackgroundProcess
{
    public void Execute(BackgroundProcessContext context)
    {
        Directory.CleanUp(Directory.GetTempDirectory());
        context.Wait(TimeSpan.FromHours(1));
    }
}

并设置延迟。这解决了我的问题,因为我需要重复 运行 的工作。我不确定这对仪表板可能有什么影响。