我发现了几个听起来相似但从未真正解决的问题。
我有两台 Hangfire 服务器。我们称它们为 A 和 B。服务器 A 配置为侦听 QueueA(并且只有 QueueA,没有“默认”队列!),服务器 B 侦听 QueueB(也没有“默认”队列)。我有一项安排在 QueueA 上的作业(无论我将其安排为后台作业还是重复作业都没有关系)。但该工作(有时)会由服务器 B 接手。
services.AddHangfireServer((sp, options) =>
{
options.Queues = new[] { "queue_a" };
options.WorkerCount = 1;
options.ServerName = "server-a";
});
services.AddHangfireServer((sp, options) =>
{
options.Queues = new[] { "queue_b" };
options.WorkerCount = 1;
options.ServerName = "server-b";
});
recurringJobManager.AddOrUpdate<JobA>(
nameof(JobA),
"queue_a",
job => job.Execute(CancellationToken.None),
"<some cron expression>");
问题:服务器 B 没有引用同一组程序集,因此甚至无法从数据库加载作业。服务器 B 不会让其他服务器接收它,而是继续尝试,直到重试次数用尽并且作业失败。
Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
---> System.IO.FileNotFoundException: Could not resolve assembly 'JobA'.
at System.Reflection.TypeNameParser.ResolveAssembly(String assemblyName)
at System.Reflection.TypeNameParser.GetType(String typeName, ReadOnlySpan`1 nestedTypeNames, String assemblyNameIfAny)
at System.Reflection.TypeNameParser.NamespaceTypeName.ResolveType(TypeNameParser& parser, String containingAssemblyIfAny)
at System.Reflection.TypeNameParser.Parse()
at System.Reflection.TypeNameParser.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Assembly requestingAssembly, Boolean throwOnError, Boolean ignoreCase, Boolean extensibleParser)
at System.Type.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError)
at Hangfire.Common.TypeHelper.DefaultTypeResolver(String typeName) in C:\projects\hangfire-525\src\Hangfire.Core\Common\TypeHelper.cs:line 78
at Hangfire.Storage.InvocationData.DeserializeJob() in C:\projects\hangfire-525\src\Hangfire.Core\Storage\InvocationData.cs:line 96
--- End of inner exception stack trace ---
at Hangfire.Storage.InvocationData.DeserializeJob() in C:\projects\hangfire-525\src\Hangfire.Core\Storage\InvocationData.cs:line 120
at Hangfire.RecurringJobExtensions.TriggerRecurringJob(IBackgroundJobFactory factory, JobStorage storage, IStorageConnection connection, IProfiler profiler, RecurringJobEntity recurringJob, DateTime now) in C:\projects\hangfire-525\src\Hangfire.Core\RecurringJobExtensions.cs:line 115
at Hangfire.Server.RecurringJobScheduler.ScheduleRecurringJob(BackgroundProcessContext context, IStorageConnection connection, String recurringJobId, RecurringJobEntity recurringJob, DateTime now) in C:\projects\hangfire-525\src\Hangfire.Core\Server\RecurringJobScheduler.cs:line 333
我有一个作业过滤器,可以确保使用原始队列,即使在重试时也是如此。因此,该问题与使用错误队列重新排队的作业无关。
为什么服务器会从队列中获取作业,而它不应该监听,我该如何解决这个问题?
顺便说一句:此问题与使用的存储无关。我在 SqlServer、Redis 和 PostgreSql 中看到了它。
我联系了 Hangfire 支持人员,他们出色地解决了我的问题!
如果您有像我这样的类似设置(多个重复作业和具有不同引用程序集集的多个服务器),则该问题是已知的,并且 Hangfire 提供了解决方案:DynamicJobs
基本上,您所要做的就是在所有服务器中引用 NuGet 包(扩展方法
.UseDynamicJobs()
)并将安排作业的代码行从 AddOrUpdate
更改为 AddOrUpdateDynamic
。有了这个,我必须稍微扩展我的自定义作业过滤器,因为对于重复作业,队列还不是作业调用数据的一部分。
这解决了重复作业被错误的服务器接收的问题。
对于常规的即发即忘后台作业,一旦我从直接设置状态(通过
new EnqueuedState("queueName")
)切换到使用已经接受Enqueue<TJob>(...)
作为参数的queue
重载,问题就消失了。