Compartilhar via


padrão de trabalho Queue-Centric (criando aplicativos de nuvem Real-World com o Azure)

por Rick Anderson, Tom Dykstra

Baixar Corrigir Projeto ou Baixar Livro Eletrônico

O livro eletrônico Building Real World Cloud Apps with Azure é baseado em uma apresentação desenvolvida por Scott Guthrie. Ele explica 13 padrões e práticas que podem ajudá-lo a desenvolver aplicativos Web para a nuvem com êxito. Para obter informações sobre o livro eletrônico, consulte o primeiro capítulo.

Anteriormente, vimos que o uso de vários serviços pode resultar em um SLA "composto", em que o SLA efetivo do aplicativo é o produto dos SLAs individuais. Por exemplo, o aplicativo Corrigir usa Sites, Armazenamento e Banco de Dados SQL. Se qualquer um desses serviços falhar, o aplicativo retornará um erro ao usuário.

O cache é uma boa maneira de lidar com falhas transitórias para conteúdo somente leitura. Mas e se seu aplicativo precisar trabalhar? Por exemplo, quando o usuário envia uma nova tarefa Corrigir, o aplicativo não pode simplesmente colocar a tarefa no cache. O aplicativo precisa gravar a tarefa Corrigir em um armazenamento de dados persistente, para que possa ser processada.

É aí que entra o padrão de trabalho centrado na fila. Esse padrão permite o acoplamento flexível entre uma camada da Web e um serviço de back-end.

Veja como o padrão funciona. Quando o aplicativo recebe uma solicitação, ele coloca um item de trabalho em uma fila e retorna imediatamente a resposta. Em seguida, um processo de back-end separado efetua pull de itens de trabalho da fila e faz o trabalho.

O padrão de trabalho centrado na fila é útil para:

  • Trabalho demorado (alta latência).
  • Trabalho que requer um serviço externo que pode nem sempre estar disponível.
  • Trabalho com uso intensivo de recursos (alta CPU).
  • Trabalho que se beneficiaria do nivelamento de taxa (sujeito a intermitências repentinas de carga).

Latência reduzida

As filas são úteis sempre que você está fazendo um trabalho demorado. Se uma tarefa levar alguns segundos ou mais, em vez de bloquear o usuário final, coloque o item de trabalho em uma fila. Diga ao usuário "Estamos trabalhando nele" e use um ouvinte de fila para processar a tarefa em segundo plano.

Por exemplo, quando você compra algo em um varejista online, o site confirma seu pedido imediatamente. Mas isso não significa que suas coisas já estão em um caminhão sendo entregue. Eles colocam uma tarefa em uma fila e, em segundo plano, estão fazendo o crédito marcar, preparando seus itens para envio e assim por diante.

Para cenários com latência curta, o tempo total de ponta a ponta pode ser mais longo usando uma fila, em comparação com a execução da tarefa de forma síncrona. Mas mesmo assim, os outros benefícios podem superar essa desvantagem.

Maior confiabilidade

Na versão do Fix It que estamos examinando até agora, o front-end da Web está firmemente acoplado ao back-end Banco de Dados SQL. Se o serviço de banco de dados SQL não estiver disponível, o usuário receberá um erro. Se as novas tentativas não funcionarem (ou seja, a falha é mais do que transitória), a única coisa que você pode fazer é mostrar um erro e pedir ao usuário para tentar novamente mais tarde.

Diagrama mostrando falha no front-end da Web quando Banco de Dados SQL back-end falha

Usando filas, quando um usuário envia uma tarefa Corrigir, o aplicativo grava uma mensagem na fila. O conteúdo da mensagem é uma representação JSON da tarefa. Assim que a mensagem é gravada na fila, o aplicativo retorna e mostra imediatamente uma mensagem de êxito para o usuário.

Se qualquer um dos serviços de back-end, como o banco de dados SQL ou o ouvinte de fila, ficar offline, os usuários ainda poderão enviar novas tarefas Corrigir. As mensagens serão enfileiradas até que os serviços de back-end estejam disponíveis novamente. Nesse ponto, os serviços de back-end serão atualizados na lista de pendências.

Diagrama mostrando o front-end da Web continuando a funcionar quando há um erro de Banco de Dados SQL

Além disso, agora você pode adicionar mais lógica de back-end sem se preocupar com a resiliência do front-end. Por exemplo, talvez você queira enviar um email ou mensagem SMS para o proprietário sempre que uma nova Correção for atribuída. Se o serviço de email ou SMS ficar indisponível, você poderá processar todo o resto e, em seguida, colocar uma mensagem em uma fila separada para enviar mensagens de email/SMS.

Anteriormente, nosso SLA efetivo era Aplicativos Web × armazenamento × Banco de Dados SQL = 99,7%. (Consulte Design para sobreviver a falhas.)

Quando alteramos o aplicativo para usar uma fila, o front-end da Web depende apenas de Aplicativos Web e armazenamento, para um SLA composto de 99,8%. (Observe que as filas fazem parte do serviço de armazenamento do Azure, portanto, elas são incluídas no mesmo SLA que o armazenamento de blobs.)

Se você precisar de ainda mais de 99,8%, poderá criar duas filas em duas regiões diferentes. Designe um como o primário e o outro como o secundário. Em seu aplicativo, faça failover para a fila secundária se a fila primária não estiver disponível. A chance de ambos ficarem indisponíveis ao mesmo tempo é muito pequena.

Nivelamento de taxa e dimensionamento independente

As filas também são úteis para algo chamado nivelamento de taxa ou nivelamento de carga.

Os aplicativos Web geralmente são suscetíveis a intermitências repentinas no tráfego. Embora você possa usar o dimensionamento automático para adicionar automaticamente servidores Web para lidar com o aumento do tráfego da Web, o dimensionamento automático pode não ser capaz de reagir rapidamente o suficiente para lidar com picos abruptos na carga. Se os servidores Web puderem descarregar parte do trabalho que precisam fazer escrevendo uma mensagem em uma fila, eles poderão lidar com mais tráfego. Um serviço de back-end pode ler mensagens da fila e processá-las. A profundidade da fila aumentará ou diminuirá conforme a carga de entrada variar.

Com grande parte de seu trabalho demorado desempedido para um serviço de back-end, a camada da Web pode responder mais facilmente a picos repentinos no tráfego. E você economiza dinheiro porque qualquer quantidade determinada de tráfego pode ser tratada por menos servidores Web.

Você pode dimensionar a camada da Web e o serviço de back-end de forma independente. Por exemplo, você pode precisar de três servidores Web, mas apenas uma mensagem de fila de processamento de servidor. Ou, se você estiver executando uma tarefa com uso intensivo de computação em segundo plano, talvez precise de mais servidores de back-end.

Diagrama que mostra uma representação das Camadas de Escala enquanto processam as tarefas na fila

O dimensionamento automático funciona com serviços de back-end, bem como com a camada da Web. Você pode escalar ou reduzir verticalmente o número de VMs que estão processando as tarefas na fila, com base no uso da CPU das VMs de back-end. Ou você pode dimensionar automaticamente com base em quantos itens estão em uma fila. Por exemplo, você pode dizer ao dimensionamento automático para tentar manter no máximo 10 itens na fila. Se a fila tiver mais de 10 itens, o dimensionamento automático adicionará VMs. Quando eles alcançarem, o dimensionamento automático derrubará as VMs extras.

Adicionando filas ao aplicativo Fix It

Para implementar o padrão de fila, precisamos fazer duas alterações no aplicativo Corrigir.

  • Quando um usuário envia uma nova tarefa Corrigir, coloque a tarefa na fila, em vez de escrevê-la no banco de dados.
  • Crie um serviço de back-end que processe mensagens na fila.

Para a fila, usaremos o Serviço de Armazenamento de Filas do Azure. Outra opção é usar Barramento de Serviço do Azure.

Para decidir qual serviço de fila usar, considere como seu aplicativo precisa enviar e receber as mensagens na fila:

  • Se você tiver produtores cooperados e consumidores concorrentes, considere usar o Serviço de Armazenamento de Filas do Azure. "Produtores de cooperação" significa que vários processos estão adicionando mensagens a uma fila. "Consumidores concorrentes" significa que vários processos estão retirando mensagens da fila para processá-las, mas qualquer mensagem determinada só pode ser processada por um "consumidor". Se você precisar de mais taxa de transferência do que pode obter com uma única fila, use filas adicionais e/ou contas de armazenamento adicionais.
  • Se você precisar de um modelo de publicação/assinatura, considere usar Barramento de Serviço do Azure Filas.

O aplicativo Fix It se encaixa no modelo de produtores cooperadores e consumidores concorrentes.

Outra consideração é a disponibilidade do aplicativo. O Serviço de Armazenamento de Filas faz parte do mesmo serviço que estamos usando para o armazenamento de blobs, portanto, usá-lo não tem nenhum efeito em nosso SLA. Barramento de Serviço do Azure é um serviço separado com seu próprio SLA. Se usarmos filas do Barramento de Serviço, teríamos que considerar um percentual de SLA adicional e nosso SLA composto seria menor. Ao escolher um serviço de fila, certifique-se de entender o impacto de sua escolha na disponibilidade do aplicativo. Para obter mais informações, consulte a seção Recursos .

Criando mensagens de fila

Para colocar uma tarefa Corrigir na fila, o front-end da Web executa as seguintes etapas:

  1. Crie uma instância do CloudQueueClient. A CloudQueueClient instância é usada para executar solicitações no Serviço de Fila.
  2. Crie a fila, se ela ainda não existir.
  3. Serialize a tarefa Corrigir.
  4. Chame CloudQueue.AddMessageAsync para colocar a mensagem na fila.

Faremos esse trabalho no construtor e SendMessageAsync no método de uma nova FixItQueueManager classe.

public class FixItQueueManager : IFixItQueueManager
{
    private CloudQueueClient _queueClient;
    private IFixItTaskRepository _repository;

    private static readonly string fixitQueueName = "fixits";

    public FixItQueueManager(IFixItTaskRepository repository)
    {
        _repository = repository;
        CloudStorageAccount storageAccount = StorageUtils.StorageAccount;
        _queueClient = storageAccount.CreateCloudQueueClient();
    }

    // Puts a serialized fixit onto the queue.
    public async Task SendMessageAsync(FixItTask fixIt)
    {
        CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
        await queue.CreateIfNotExistsAsync();

        var fixitJson = JsonConvert.SerializeObject(fixIt);
        CloudQueueMessage message = new CloudQueueMessage(fixitJson);

        await queue.AddMessageAsync(message);
    }

    // Processes any messages on the queue.
    public async Task ProcessMessagesAsync()
    {
        CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
        await queue.CreateIfNotExistsAsync();

        while (true)
        {
            CloudQueueMessage message = await queue.GetMessageAsync();
            if (message == null)
            {
                break;
            }
            FixItTask fixit = JsonConvert.DeserializeObject<FixItTask>(message.AsString);
            await _repository.CreateAsync(fixit);
            await queue.DeleteMessageAsync(message);
        }
    }
}

Aqui, estamos usando a biblioteca Json.NET para serializar o fixit no formato JSON. Você pode usar qualquer abordagem de serialização que preferir. O JSON tem a vantagem de ser legível por humanos, sendo menos detalhado do que o XML.

O código de qualidade de produção adicionaria lógica de tratamento de erros, pausaria se o banco de dados ficasse indisponível, lidaria com a recuperação com mais limpeza, criaria a fila na inicialização do aplicativo e gerenciaria mensagens "suspeitas". (Uma mensagem suspeita é uma mensagem que não pode ser processada por algum motivo. Você não quer que mensagens suspeitas fiquem na fila, onde a função de trabalho tentará processá-las continuamente, falhar, tentar novamente, falhar e assim por diante.)

No aplicativo MVC de front-end, precisamos atualizar o código que cria uma nova tarefa. Em vez de colocar a tarefa no repositório, chame o SendMessageAsync método mostrado acima.

public async Task<ActionResult> Create(FixItTask fixittask, HttpPostedFileBase photo)
{
    if (ModelState.IsValid)
    {
        fixittask.CreatedBy = User.Identity.Name;
        fixittask.PhotoUrl = await photoService.UploadPhotoAsync(photo);
        //previous code:
        //await fixItRepository.CreateAsync(fixittask);
        //new code:
        await queueManager.SendMessageAsync(fixittask);
        return RedirectToAction("Success");
    }
    return View(fixittask);
}

Processando mensagens de fila

Para processar mensagens na fila, criaremos um serviço de back-end. O serviço de back-end executará um loop infinito que executa as seguintes etapas:

  1. Obtenha a próxima mensagem da fila.
  2. Desserialize a mensagem para uma tarefa Corrigir.
  3. Escreva a tarefa Corrigir no banco de dados.

Para hospedar o serviço de back-end, criaremos um Serviço de Nuvem do Azure que contém uma função de trabalho. Uma função de trabalho consiste em uma ou mais VMs que podem fazer o processamento de back-end. O código executado nessas VMs efetuará pull de mensagens da fila à medida que elas estiverem disponíveis. Para cada mensagem, desserializaremos o conteúdo JSON e gravaremos uma instância da entidade Fix It Task no banco de dados, usando o mesmo repositório que usamos anteriormente na camada da Web.

As etapas a seguir mostram como adicionar um projeto de função de trabalho a uma solução que tem um projeto Web padrão. Essas etapas já foram feitas no projeto Corrigir, que você pode baixar.

Primeiro, adicione um projeto do Serviço de Nuvem à solução do Visual Studio. Clique com o botão direito do mouse na solução e selecione Adicionar e, em seguida, Novo Projeto. No painel esquerdo, expanda Visual C# e selecione Nuvem.

Captura de tela das etapas para adicionar um novo menu de projeto no .Net Framework

Na caixa de diálogo Novo Serviço de Nuvem do Azure , expanda o nó Visual C# no painel esquerdo. Selecione Função de Trabalho e clique no ícone de seta para a direita.

A captura de tela a seguir mostra uma continuação da imagem anterior e mostra as diferentes seleções disponíveis para o Serviço de Nuvem do Azure, realçando a correta.

(Observe que você também pode adicionar uma função Web. Poderíamos executar o front-end Corrigir no mesmo Serviço de Nuvem em vez de executá-lo em um site do Azure. Isso tem algumas vantagens em tornar as conexões entre front-end e back-end mais fáceis de coordenar. No entanto, para manter essa demonstração simples, estamos mantendo o front-end em um aplicativo Web Serviço de Aplicativo do Azure e executando apenas o back-end em um Serviço de Nuvem.)

Um nome padrão é atribuído à função de trabalho. Para alterar o nome, passe o mouse sobre a função de trabalho no painel direito e clique no ícone de lápis.

Captura de tela mostrando o Projeto de Função de Trabalho, com as diferentes atribuições e como alterar os nomes.

Clique em OK para concluir a caixa de diálogo. Isso adiciona dois projetos à solução do Visual Studio.

  • um projeto do Azure que define o serviço de nuvem, incluindo informações de configuração.
  • Um projeto de função de trabalho que define a função de trabalho.

Captura de tela mostrando uma função de trabalho, definindo a função e mostrando a lista de opções de solução de projeto 'MyFixIt'.

Para obter mais informações, consulte Criando um projeto do Azure com o Visual Studio.

Dentro da função de trabalho, pesquisamos mensagens chamando o ProcessMessageAsync método da FixItQueueManager classe que vimos anteriormente.

public class WorkerRole : RoleEntryPoint
{
    public override void Run()
    {
        Task task = RunAsync(tokenSource.Token);
        try
        {
            task.Wait();
        }
        catch (Exception ex)
        {
            logger.Error(ex, "Unhandled exception in FixIt worker role.");
        }
    }

    private async Task RunAsync(CancellationToken token)
    {
        using (var scope = container.BeginLifetimeScope())
        {
            IFixItQueueManager queueManager = scope.Resolve<IFixItQueueManager>();
            while (!token.IsCancellationRequested)
            {
                try
                {
                    await queueManager.ProcessMessagesAsync();
                }
                catch (Exception ex)
                {
                    logger.Error(ex, "Exception in worker role Run loop.");
                }
                await Task.Delay(1000);
            }
        }
    }
    // Other code not shown.
}

O ProcessMessagesAsync método verifica se há uma mensagem aguardando. Se houver uma, ela desserializa a mensagem em uma FixItTask entidade e salva a entidade no banco de dados. Ele executa um loop até que a fila esteja vazia.

public async Task ProcessMessagesAsync()
{
    CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
    await queue.CreateIfNotExistsAsync();
    while (true)
    {
        CloudQueueMessage message = await queue.GetMessageAsync();
        if (message == null)
        {
            break;
        }
        FixItTask fixit = JsonConvert.DeserializeObject<FixItTask>(message.AsString);
        await _repository.CreateAsync(fixit);
        await queue.DeleteMessageAsync(message);
    }
}

A sondagem de mensagens de fila incorre em uma pequena cobrança de transação, portanto, quando não há nenhuma mensagem aguardando para ser processada, o método da função de trabalho aguarda um segundo antes de RunAsync sondar novamente chamando Task.Delay(1000).

Em um projeto Web, a adição de código assíncrono pode melhorar automaticamente o desempenho porque o IIS gerencia um pool de threads limitado. Esse não é o caso em um projeto de função de trabalho. Para melhorar a escalabilidade da função de trabalho, você pode escrever código multi-threaded ou usar código assíncrono para implementar a programação paralela. O exemplo não implementa programação paralela, mas mostra como tornar o código assíncrono para que você possa implementar a programação paralela.

Resumo

Neste capítulo, você viu como melhorar a capacidade de resposta, confiabilidade e escalabilidade do aplicativo implementando o padrão de trabalho centrado em fila.

Este é o último dos 13 padrões abordados neste livro eletrônico, mas há, naturalmente, muitos outros padrões e práticas que podem ajudá-lo a criar aplicativos de nuvem bem-sucedidos. O capítulo final fornece links para recursos para tópicos que não foram abordados nesses 13 padrões.

Recursos

Para obter mais informações sobre filas, consulte os recursos a seguir.

Documentação:

Vídeo:

  • FailSafe: criando Serviços de Nuvem escalonáveis e resilientes. Série de vídeos de nove partes de Ulrich Homann, Marc Mercuri e Mark Simms. Apresenta conceitos de alto nível e princípios arquitetônicos de maneira muito acessível e interessante, com histórias extraídos da experiência da CAT (Equipe de Consultoria de Clientes) da Microsoft com clientes reais. Para obter uma introdução ao serviço de Armazenamento do Azure e às filas, confira o episódio 5, começando às 35:13.