关于c#:Hangfire计划不同服务器的后台任务

Hangfire Schedule Background tasks for different server

我有一个利用多个Hangfire服务器的.Net应用程序。

我希望能够使一个Hangfire RecurringJob触发多个可由任何可用服务器拾取的BackgroundJob。当前,每当我从Hangfire作业中调度后台作业时,只有调度了后台作业的服务器才会对其进行处理。

例如,我有5台Hangfire服务器和10项任务。
我希望每个Hangfire服务器上有2个任务,相反,我看到1个服务器有10个任务,而4个服务器有0。

所以我又有5台Hangfire服务器,所有服务器都使用相同的数据库,还有1台RecurringJob,这个RecurringJob只是读取一些文件并排队几个后台作业。

1
2
3
4
5
6
 foreach (var file in reportSourceSetFileList)
 {
      _logger.LogInformation($"Queuing Background job for: {file}");

      var backgroundJobId = BackgroundJob.Enqueue<IJobHandler>(job => job.ProcessFile(file, files, null));
 }

但是,只有运行RecurringJob的Hangfire服务器会处理入队作业。

我怎样才能让我的5台Hangfire服务器中的任何一个处理入队列的工作,而不仅仅是排队的服务器?


Hangfire中没有内置功能,无法在多个hangfire服务器之间使用循环类型的负载均衡器。

我的解决方案是使用排队系统。当每个Hangfire服务器启动时,会为它们提供一个任务标识符,即GUID,我还向该服务器添加了一个唯一队列,该队列使用与它的名称相同的GUID。

因此,每个服务器将查看2个队列,即默认队列和GUID。

然后,我使用以下代码来查找哪个服务器当前正在处理的作业最少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private string GetNextAvailableServer()
{
    var serverJobCounts = new Dictionary<string, int>();

    //get active servers
    var serverList = JobStorage.Current.GetMonitoringApi().Servers();
    foreach (var server in serverList)
    {
        if (server.Heartbeat.Value > DateTime.Now.AddMinutes(-1))
        {
            serverJobCounts.Add(server.Name, 0);
            foreach (var queue in server.Queues)
            {
                var currentQueues = JobStorage.Current.GetMonitoringApi().Queues();
                serverJobCounts[server.Name] += (int?)currentQueues.FirstOrDefault(e => e.Name == queue)?.Length ?? 0;
            }
        }
    }

    var jobs = JobStorage.Current.GetMonitoringApi().ProcessingJobs(0, int.MaxValue);
    foreach (var job in jobs)
    {
        if (serverJobCounts.ContainsKey(job.Value.ServerId))
        {
            serverJobCounts[job.Value.ServerId] += 1;
        }
    }

    var nextServer = serverJobCounts.OrderBy(e => e.Value).FirstOrDefault().Key;
    return nextServer.Split(':')[0].Replace("-", string.Empty, StringComparison.InvariantCulture);
}

这将返回作业最少的服务器的GUID,这也是队列的名称。因此,您可以将下一个作业调度到当前正在处理的作业最少的特定队列中。

1
2
3
4
5
var nextServer = GetNextAvailableServer();

var client = new BackgroundJobClient();
var state = new EnqueuedState(nextServer);
var enqueueJob = client.Create<IJobHandler>(job => job.ProcessFile(file, files, null), state);

此外,当我编写此Hangfire时,队列名称中不允许使用连字符,因此我进行了字符串操作以使GUID正常工作。我认为最新版本的hangfire可让您在名称中使用连字符。

要注意的一件事,当您的服务器之一死机时,此解决方案就会中断。由于为作业分配了唯一的队列,如果监视该队列的服务器在处理该作业之前死亡,则该队列将永远不会被拾取。