Desacoplando a execução de jobs “pesados” ou recorrentes com Hangfire em .NET

Muitas aplicações precisam executar jobs pesados (com alto consumo de recursos ou tempo de processamento) rotineiramente. É  boa estratégia “desacoplar” a execução desses jobs de nossas aplicações para que eles não interfiram na performance geral.

Entendemos, por exemplo, que a atribuição de uma aplicação web é prover boa experiência para seus usuários. Se esta aplicação também for responsável por executar algum job pesado, provavelmente acabará consumindo recursos que irão comprometer sua finalidade principal.

Além disso, muitas aplicações executam jobs de maneira recorrente. Ou seja, programas que são executados rotineiramente, geralmente em segudo plano. Para citar alguns exemplos comuns, temos: rotinas de integração entre aplicações, consolidação de dados, tarefas administrativas, disparo de emails, etc.

Entendemos que a execução de jobs recorrentes não deve ser responsabilidade de uma aplicação web, por exemplo.

Temos visto, em nossos clientes, esse tipo de job sendo desenvolvido em aplicações independentes, geralmente instaladas como serviços em servidores. Infelizmente, a maioria das implementações não está preparada para se recuperar de falhas e são monitoradas de maneira precária.

O Hangfire ajuda muito na execução de jobs pesados e recorrentes.

Nesse post, apresentamos um pequeno overview do que é e para que serve o Hangfire. Para isso descrevemos como acreditamos que ele deve ser usado. Atente, porém, para o fato de que essa não será uma descrição exaustiva (há outras formas de usar o Hangfire além da que estamos mostrando aqui).

O que é Hangfire?

Hangfire é uma biblioteca para gerenciamento de jobs que são executados em segundo plano, de forma recorrente ou não. Ele é simples de usar, open source (código-fonte no Github) e funciona para qualquer tipo de aplicação .NET.

A licença free permite uso comercial. As versões pagas tem opções extendidas de suporte e pacotes adicionais de recursos.

O Hangfire conta com uma interface gráfica com dashboards que permitem pausar, retomar e reexecutar jobs.

Com Hangfire é possível explicitar políticas de retentativas de execução, distribuir a execução dos jobs para escalar horizontalmente e permitir alta disponibilidade.

Dashboard do Hangfire

 

Um cenário de uso (necessidade)

Considere o seguinte cenário:

Você possui uma aplicação web que, rotineiramente, precisa enviar atualizações para sistemas de backoffice. Essa atualização poderia ocorrer de forma agendada ou através de intervenção do usuário.

Para não onerar o  servidor web com o volume de processamento para a execução dessa tarefa, você, provavelmente, consideraria implementá-la em um processo distinto que seria executado em outro servidor.

A aplicação web conteria apenas uma interface para permitir ao usuário disparar o processo, ou ainda, mecanismos para configurar o agendamento para execução.

Como o Hangfire funciona?

No cenário acima, usando a terminologia do Hangfire, a aplicação web seria um hangfire client. A aplicação que, efetivamente, executa o trabalho seria um hangfire server. A comunicação entre o client e o server ocorreria de forma assíncrona, através de um job storage.

hangfire oferece implementações prontas que aceleram muito o desenvolvimento de um server. Essas implementações incluem, por exemplo, um dashboard para acompanhar a execução dos jobs.

Retomando:

  • No Hangfire Server, escrevemos todos os jobs que devem ser executados. Nele, também, disponibilizamos uma  interface de monitoramento.
  • No Hangfire Client definimos as estratégias de execução dos jobs definidos no server (como desejamos executar).
  • O Job Storage é o componente responsável por efetivar a comunicação entre o Server e o client.

Arquitetura do Hanfire

Anatomia de um Job

A implementação de um Job consiste, simplesmente, de uma classe POCO, com um método contendo o código que precisa ser executado.

public class SampleJob : ISample
{
  public void Start()
  {
    // code to run
  }
}

O Hangfire não estabelece nenhuma regra sobre que nome utilizar para o método que conterá o código que deverá ser executado, afinal, ele será especificado durante a configuração. Entretanto, recomendamos utilizar um padrão consistente (gostamos do nome “Start”).

Em nossos projetos, adotamos como padrão sempre definir uma interface, que deverá estar contida em um assembly de contratos e poderá ser “compartilhada” entre o hangire server e o hangfire client.

public interface ISample
{
  void Start();
}

Essa abordagem nos permitirá desacoplar a configuração do job (que ocorre no hangfire client) de sua implementação (que precisa estar disponível no hangfire server).

Implementando um Hangfire Server

Como já dissemos, o Hangfire Server será o componente responsável por executar, efetivamente, o job.

Recomendamos implementar o Hangfire Server como uma aplicação como um Windows Service, usando TopShelf. Dessa forma, garantimos que ela se manterá em execução ininterruptamente no servidor, iniciando no boot e se recuperando automaticamente em caso de falhas.

Para hospedar o Hangfire Server recomendamos utilizar o Owin ou ASP.net core.

O código que segue mostra as rotinas que iniciam e param o serviço, implementado usando TopShelf, que irá operar como Hangfire Server, com host em Owin.

public class Bootstrap
{
    private IDisposable _host;
               
    public void Start()
    {
        var options = new StartOptions { Port = 8999 };
        _host = WebApp.Start<Startup>(options);
        Console.WriteLine();
        Console.WriteLine("HangFire has started");
        Console.WriteLine("Dashboard is available at http://localhost:8999/hangfire");
        Console.WriteLine();
    }
    public void Stop()
    {
        _host.Dispose();
    }
}

Podemos especificar como o Hangfire Server deve funcionar através de extension methods para sua classe GlobalConfiguration. No exemplo abaixo, indicamos  a string de conexão para o banco de dados que será utilizado como JobStorage.

public void Configuration(IAppBuilder app)
{
    //Configure database
    GlobalConfiguration.Configuration.UseSqlServerStorage(@"Data Source=servername;Initial Catalog=HangfireDB; User ID=username;Password=mypassword;");
    //Configure Dependency Injection solver 
    GlobalConfiguration.Configuration.UseAutofacActivator(BuildContainer()); 
    //Start a new instance of the BackgroundServer
    app.UseHangfireServer();

}

É tarefa do hangfire server instanciar os jobs que serão configurados pelo hangfire client. Para que tenhamos mais controle sobre esse processo, podemos sobrescrever a classe JobActivator (que é a responsável por instanciar os Jobs) ou utilizar um dos pacotes de integração já disponíveis para os principais IoC containers. Recomendamos a segunda abordagem.

Utilizamos, em nossos projetos, o Hangfire.Autofac. A beleza dessa abordagem é que podemos modificar a implementação de um job no hangfire server sem impactar em nada a aplicação hangfire client.

public static IContainer BuildContainer()
{
    var builder = new ContainerBuilder();
    builder.RegisterInstance(new SampleJob()).As<ISample>();
    return builder.Build();
}

No exemplo acima, associamos a implementação SampleJob a definição de ISample.

Job Storage

A comunicação entre hangfire client e hangfire server é realizada, de maneira assíncrona, através do job storage.

O job storage pode ser uma base de dados no SQL Server, por exemplo. Também podem ser utilizados mecanismos de mensageria como o MSMQ ou RabbitMQ.

Uma das desvantagens de se utilizar o SQL Server como implementação do JobStorage é que a comunicação é feita através da técnica de pooling. Ou seja, de tempos em tempos, o servidor vai até o banco de dados para saber o que precisa ser processado, causando delay na execução dos jobs. Por padrão, esse intervalo é de 15 segundos, mas pode ser configurado.

NOTA: A partir da versão 1.7.0 do Hangfire é possível definir o intervalo de consultas para 0s, fazendo com que o Hangfire passe adotar estratégias de long pooling.

Implementando um Hangfire Client

Recordando, o Hangfire client é a aplicação que irá “disparar” a execução do Job ou definir uma estratégia de agendamento. Pode ser, por exemplo, uma aplicação web que recebe uma demanda do usuário que inicia a execução de um job pesado.

A configuração da hangfire client é similar a do server.

O hangfire client especifica como um job será executado no server usando o método AddOrUpdate da classe RecurringJobManager.

No exemplo abaixo, vemos a configuração de um job (identificado pela interface ISample).

var jobManager = new RecurringJobManager();
jobManager.AddOrUpdate<ISample>("XPTO", x => x.Start(), Cron.Dayly, TimeZoneInfo.Local);

Determinamos um identificador para o job (útil para futuras interações), o método implementado pelo Job que deverá ser executado, uma expressão Cron (que configura o agendamento de execução) e um fuso horário especificando qual horário deverá ser respeitado.

Podemos tanto configurar jobs para que sejam executados recorrentemente, quanto para que sejam executados apenas uma vez. Basta modificar a configuração.

Cenário de uso (soluçã0)

Retomando, para atender o cenário que descrevemos acima, recomendamos:

  • Desenvolver um projeto de um serviço usando TopShelf, com Hangfire, onde implementamos a tarefa recorrente que precisa ser executada – hangfire server;
  • Utilizar um mecanismo de job storage aderente ao ambiente de produção (SQL Server, MSMQ ou RabbitMQ)
  • Implementar a configuração do job, na aplicação web (nossa hangfire client).
  • Criar um projeto que compartilha as contratos (interfaces) dos jobs entre hangfire client e hangfire server.

Concluindo

Há diversos cenários onde precisamos implementar programas (jobs) pesados que são executados de forma recorrente ou não, geralmente em segundo plano.

Tão importante quanto os jobs em si, é importante que consideremos estratégias de monitoramento e recuperação.

Hangfire é uma solução fantástica para que possamos executar tarefas em segundo plano (jobs), inclusive de maneira recorrente, com monitoramento e estratégias de recuperação, nos preocupando apenas com nosso negócio.

Como você tem resolvido essa necessidade em suas aplicações?

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
5 Comentários
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Rodolfo
Rodolfo
4 anos atrás

Realmente é uma excelente ferramenta.

Luis Augusto Moreira Barbosa
Luis Augusto Moreira Barbosa
4 anos atrás

Bom dia!. Em boa parte da empresas este processamento é feito sobre um BD relacional e nestes casos, a utilização padrão é utilizar um “job” do próprio BD que execute geralmente uma procedure.
Vejo vantagens de teste e desacoplamento nesta solução, já que monitoramento e recuperação podem ser feitas no BD, já processamento o “job” tenderia a ser mais rápido.
Ambos, pelo que entendi, funcionam de forma reativa configurando seu tempo de processamento. Vê outras vantanges / desvantagens entre estas abordagens? Obrigado.

Luis Augusto Moreira Barbosa
Luis Augusto Moreira Barbosa
4 anos atrás

“Vejo vantagens de teste e desacoplamento nesta solução”, quis dizer a do Hangfire.

Fernando Neiva Paiva
Fernando Neiva Paiva
4 anos atrás

Como você citou, o desacoplamento da solução melhoraria a testabilidade dela. Também vejo como ponto positivo da abordagem com Hangfire a possibilidade de escalar horizontalmente executando vários servers ao mesmo tempo e a facilidade de implementar paralelismo sem precisar programar as threads diretamente. Talvez também possa ser um diferencial o fato da solução não rodar diretamente no banco de dados, dessa forma esse processamento não oneraria seu banco de produção e tornaria sua aplicação menos dependente do BD. Sem contar que evitaria que vazasse regras do domínio da aplicação.
Mas, é sempre importante levar em consideração que complexidade é custo. Eventualmente, devido a características da equipe, tipo de job e estratégia da empresa, possa fazer sentido a implementação no próprio banco de dados.

Eric Hosokawa
Eric Hosokawa
4 anos atrás

Olá, boa noite! Achei muito interessante esse framework pra gerenciamento de jobs, estive estudando uma forma para criar rotinas/jobs no docker e me deparei com esse framework.
Estou trabalhando para implementar o Hangfire + .Net Core Worker Services + Docker, onde trabalho, só utilizo para realizar um bulk copy entre tabelas Sql Server, mas pelo protótipo que criei funcionou muito bem.

AUTOR

Fernando Paiva
Larga experiência como CTO, especialista em execução tecnológica e estruturação de times ágeis de desenvolvimento de software.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Arquivo

Pós-pandemia, trabalho remoto e a retenção dos profissionais de TI

CTO Consulting e Especialista em Execução em TI
5
0
Queremos saber a sua opinião, deixe seu comentáriox
Oferta de pré-venda!

Mentoria em
Arquitetura de Software

Práticas, padrões & técnicas para Arquitetura de Software, de maneira efetiva, com base em cenários reais para profissionais envolvidos no projeto e implantação de software.

Muito obrigado!

Deu tudo certo com seu envio!
Logo entraremos em contato

Desacoplando a execução de jobs “pesados” ou recorrentes com Hangfire em .NET

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Desacoplando a execução de jobs “pesados” ou recorrentes com Hangfire em .NET

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?