Estoy ejecutando hangfire en una sola aplicación web, mi aplicación se ejecuta en 2 servidores físicos pero hangfire está en 1 base de datos.
Por el momento, estoy generando un servidor para cada cola, porque cada cola necesito ejecutar 1 trabajador a la vez y deben estar en orden. los configuro asi
// core services.AddHangfire(options => { options.SetDataCompatibilityLevel(CompatibilityLevel.Version_170); options.UseSimpleAssemblyNameTypeSerializer(); options.UseRecommendedSerializerSettings(); options.UseSqlServerStorage(appSettings.Data.DefaultConnection.ConnectionString, storageOptions); }); // add multiple servers, this way we get to control how many workers are in each queue services.AddHangfireServer(options => { options.ServerName = "workflow-queue"; options.WorkerCount = 1; options.Queues = new string[] { "workflow-queue" }; options.SchedulePollingInterval = TimeSpan.FromSeconds(10); }); services.AddHangfireServer(options => { options.ServerName = "alert-schedule"; options.WorkerCount = 1; options.Queues = new string[] { "alert-schedule" }; options.SchedulePollingInterval = TimeSpan.FromMinutes(1); }); services.AddHangfireServer(options => { options.ServerName = string.Format("trigger-schedule"); options.WorkerCount = 1; options.Queues = new string[] { "trigger-schedule" }; options.SchedulePollingInterval = TimeSpan.FromMinutes(1); }); services.AddHangfireServer(options => { options.ServerName = "report-schedule"; options.WorkerCount = 1; options.Queues = new string[] { "report-schedule" }; options.SchedulePollingInterval = TimeSpan.FromMinutes(1); }); services.AddHangfireServer(options => { options.ServerName = "maintenance"; options.WorkerCount = 5; options.Queues = new string[] { "maintenance" }; options.SchedulePollingInterval = TimeSpan.FromMinutes(10); }); Mi problema es que está generando múltiples colas en los servidores, con diferentes puertos. 
En mi código, estoy tratando de detener la ejecución de trabajos si están en cola/reintentando, pero si el trabajo se está ejecutando en un servidor físico diferente, no se encuentra y se vuelve a poner en cola.
Aquí está el código para verificar si ya se está ejecutando
public async Task<bool> IsAlreadyQueuedAsync(PerformContext context) { var disableJob = false; var monitoringApi = JobStorage.Current.GetMonitoringApi(); // get the jobId, method and queue using performContext var jobId = context.BackgroundJob.Id; var methodInfo = context.BackgroundJob.Job.Method; var queueAttribute = (QueueAttribute)Attribute.GetCustomAttribute(context.BackgroundJob.Job.Method, typeof(QueueAttribute)); // enqueuedJobs var enqueuedjobStatesToCheck = new[] { "Processing" }; var enqueuedJobs = monitoringApi.EnqueuedJobs(queueAttribute.Queue, 0, 1000); var enqueuedJobsAlready = enqueuedJobs.Count(e => e.Key != jobId && e.Value != null && e.Value.Job != null && e.Value.Job.Method.Equals(methodInfo) && enqueuedjobStatesToCheck.Contains(e.Value.State)); if (enqueuedJobsAlready > 0) disableJob = true; // scheduledJobs if (!disableJob) { // check if there are any scheduledJobs that are processing var scheduledJobs = monitoringApi.ScheduledJobs(0, 1000); var scheduledJobsAlready = scheduledJobs.Count(e => e.Key != jobId && e.Value != null && e.Value.Job != null && e.Value.Job.Method.Equals(methodInfo)); if (scheduledJobsAlready > 0) disableJob = true; } // failedJobs if (!disableJob) { var failedJobs = monitoringApi.FailedJobs(0, 1000); var failedJobsAlready = failedJobs.Count(e => e.Key != jobId && e.Value != null && e.Value.Job != null && e.Value.Job.Method.Equals(methodInfo)); if (failedJobsAlready > 0) disableJob = true; } // if runBefore is true, then lets remove the current job running, else it will write a "successful" message in the logs if (disableJob) { // use hangfire delete, for cleanup BackgroundJob.Delete(jobId); // create our sqlBuilder to remove the entries altogether including the count var sqlBuilder = new SqlBuilder() .DELETE_FROM("Hangfire.[Job]") .WHERE("[Id] = {0};", jobId); sqlBuilder.Append("DELETE TOP(1) FROM Hangfire.[Counter] WHERE [Key] = 'stats:deleted' AND [Value] = 1;"); using (var cmd = _context.CreateCommand(sqlBuilder)) await cmd.ExecuteNonQueryAsync(); return true; } return false; }Cada método tiene algo así como los siguientes atributos también
public interface IAlertScheduleService { [Hangfire.Queue("alert-schedule")] [Hangfire.DisableConcurrentExecution(60 * 60 * 5)] Task RunAllAsync(PerformContext context); }Implementación simple de la interfaz.
public class AlertScheduleService : IAlertScheduleService { public Task RunAllAsync(PerformContext context) { if (IsAlreadyQueuedAsync(context)) return; // guess it isnt queued, so run it here.... } }Así es como agrego mis trabajos programados
//// our recurring jobs //// set these to run hourly, so they can play "catch-up" if needed RecurringJob.AddOrUpdate<IAlertScheduleService>(e => e.RunAllAsync(null), Cron.Hourly(0), queue: "alert-schedule");¿Por qué pasó esto? ¿Cómo puedo evitar que suceda?
Algo así como un tiro ciego, que evita que un trabajo se ponga en cola si un trabajo ya está en cola en la misma cola. La lógica de intento y captura es bastante fea, pero no tengo una mejor idea en este momento... Además, no estoy seguro de que la lógica de bloqueo siempre impida tener dos trabajos en EnqueudState, pero debería ayudar de todos modos. Tal vez mezclar con un IApplyStateFilter.
public class DoNotQueueIfAlreadyQueued : IElectStateFilter { public void OnStateElection(ElectStateContext context) { if (context.CandidateState is EnqueuedState) { EnqueuedState es = context.CandidateState as EnqueuedState; IDisposable distributedLock = null; try { while (distributedLock == null) { try { distributedLock = context.Connection.AcquireDistributedLock($"{nameof(DoNotQueueIfAlreadyQueued)}-{es.Queue}", TimeSpan.FromSeconds(1)); } catch { } } var m = context.Storage.GetMonitoringApi(); if (m.EnqueuedCount(es.Queue) > 0) { context.CandidateState = new DeletedState(); } } finally { distributedLock.Dispose(); } } } }El filtro se puede declarar como en esta respuesta.