我有一个利用多个Hangfire服务器的.Net应用程序。
我希望能够让一个Hangfire RecurringJob触发多个BackgroundJob,这些BackgroundJob可以被任何可用的服务器接收。目前,每当我从一个Hangfire Job中安排BackgroundJob时,只有安排它们的服务器会处理它们。
例如,我有5个Hangfire服务器和10个任务.我希望每个Hangfire服务器上有2个任务,而不是我看到1个服务器有10个任务,4个有0个。
所以我又有5个Hangfire服务器,都使用同一个数据库,还有1个RecurringJob,这个RecurringJob只是读取一些文件,并报出几个后台任务。
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服务器会处理Enqueued作业。
我怎样才能让这些Enqueued作业由我的5个Hangfire服务器中的任何一个来处理,而不仅仅是那个排队的服务器?
在Hangfire中没有内置的功能来在多个Hangfire服务器之间使用循环型负载平衡器。
我的解决方案是使用排队系统。当每个Hangfire服务器启动时,他们被赋予一个任务标识符,这是一个GUID,我还添加了一个独特的队列到该服务器,它使用相同的GUID作为它的名字。
所以每台服务器都会查看2个队列,Default和GUID。
然后,我使用下面的代码来查找哪个服务器当前处理的作业最少。
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,这也是队列的名称。因此你可以将下一个作业安排到当前处理作业最少的特定队列中。
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不允许在队列名称中使用连字符,因此我对字符串进行了操作,使GUIDs工作。我认为最新版本的Hangfire允许你在名字中使用连字符。
有一点要注意,当你们其中一个服务器死亡时,这个解决方案就会失效。由于一个作业被赋予了一个唯一的队列,如果看管该队列的服务器在处理作业前死亡,它将永远不会被接收。