Melhore o desempenho e a confiabilidade do Azure Functions

Este artigo fornece orientações para melhorar o desempenho e confiabilidade de seus aplicativos de funções sem servidor. Para obter um conjunto mais geral de melhores práticas para o Azure Functions, consulte Melhores práticas para o Azure Functions.

A seguir, estão as melhores práticas para criar e projetar soluções sem servidor usando o Azure Functions.

Evite funções grandes de longa duração

As funções grandes de longa duração podem causar problemas de tempo limite inesperados. Para saber mais sobre os tempos limite de um determinado plano de hospedagem, confira duração do tempo limite do aplicativo de funções.

Uma função pode se tornar grande por causa de muitas dependências do Node.js. A importação dessas dependências pode causar aumento no tempo de carregamento resultando em tempos limite inesperados. As dependências são carregadas explícita e implicitamente. Um único módulo carregado pelo seu código pode carregar seus próprios módulos adicionais.

Sempre que possível, refatore funções grandes em conjuntos menores de funções que funcionem juntos e retornem respostas rápidas. Por exemplo, um webhook ou a função do gatilho HTTP pode exigir uma resposta de confirmação dentro de um certo limite de tempo; é comum de webhooks exigirem uma resposta imediata. Você pode passar o conteúdo do gatilho HTTP para uma fila para ser processado por uma função de gatilho de fila. Essa abordagem permite adiar o trabalho real e retornar uma resposta imediata.

Certifique-se de que as tarefas em segundo plano sejam concluídas

Quando sua função inicia tarefas, retornos de chamada, threads e processos, eles devem ser concluídos antes que o código de função retorne. Como o Functions não acompanha esses threads em segundo plano, o desligamento do site pode ocorrer independentemente do status do thread em segundo plano, o que pode causar um comportamento não intencional em suas funções.

Por exemplo, se uma função iniciar uma tarefa em segundo plano e retornar uma resposta bem-sucedida antes da conclusão da tarefa, o runtime do Functions considerará a execução como tendo sido concluída com êxito, independentemente do resultado da tarefa em segundo plano. Se essa tarefa em segundo plano estiver executando um trabalho essencial, ela poderá ser precedida pelo desligamento do site, deixando esse trabalho em um estado desconhecido.

Comunicação entre funções

As Durable Functions e os Aplicativos Lógicos do Azure são projetados para gerenciar transições de estado e comunicação entre diversas funções.

Quando as Funções Duráveis ou os Aplicativos Lógicos não são usados para integrar com várias funções, geralmente é melhor usar filas de armazenamento para comunicação entre funções. O principal motivo é que as filas de armazenamento são mais baratas e muito mais fáceis de provisionar do que outras opções de armazenamento.

Mensagens individuais em uma fila de armazenamento estão limitadas ao tamanho de 64 KB. Se você precisar passar mensagens maiores entre as funções, uma fila do Barramento de Serviço do Azure poderá ser usada para dar suporte a mensagens com tamanhos de até 256 KB na camada Standard e até 100 MB na camada Premium.

Tópicos de barramento de serviço são úteis se você precisar de filtragem de mensagens antes do processamento.

Hubs de eventos são úteis para oferecer suporte a comunicações de alto volume.

Grave funções para serem sem estado

As funções devem ser sem estado e idempotentes se possível. Associe informações de estado necessárias aos seus dados. Por exemplo, um pedido sendo processado provavelmente teria um membro state associado. Uma função pode processar um pedido com base no estado enquanto a função em si permanece sem estado.

Funções de idempotentes são recomendadas especialmente com gatilhos de timer. Por exemplo, se você tiver algo que absolutamente deve ser executado uma vez por dia, grave-o para que possa ser executado a qualquer momento durante o dia com os mesmos resultados. A função pode ser encerrada quando não houver nenhum trabalho para um dia específico. Também se uma execução anterior tiver falhado ao concluir, a próxima execução deve continuar de onde a anterior parou. Isso é particularmente importante para associações baseadas em mensagens que tentam novamente a falha. Para ter mais informações, consulte Como projetar o Azure Functions para obter uma entrada idêntica.

Grave funções defensivas

Suponha que sua função pode encontrar uma exceção a qualquer momento. Projete suas funções com a capacidade de continuar de um ponto de falha anterior durante a próxima execução. Considere um cenário que requeira as seguintes ações:

  1. Consulta de 10.000 linhas em um banco de dados.
  2. Crie uma mensagem de fila para cada uma das linhas para processar ainda mais adiante na linha.

Dependendo de quão complexo é o sistema, você pode ter: serviços posteriores envolvidos se comportando mal, interrupções de rede ou limites de cota alcançados, etc. Tudo isso pode afetar sua função a qualquer momento. Você precisa para projetar suas funções para estarem preparadas para isso.

Como o seu código reage se ocorrer uma falha após a inserção de 5.000 desses itens em uma fila para processamento? Controle itens em um conjunto concluído. Caso contrário, você pode inseri-los de novo posteriormente. Essa inserção dupla pode ter um impacto sério em seu fluxo de trabalho, portanto, torne suas funções idempotentes.

Se um item da fila já tiver sido processado, permita que sua função seja no-op.

Tire proveito de medidas defensivas já fornecidas para componentes usados na plataforma Azure Functions. Por exemplo, consulte Tratamento de mensagens suspeitas na fila na documentação de gatilhos e associações de fila de Armazenamento do Microsoft Azure.

Para funções baseadas em HTTP, considere estratégias de controle de versão da API com Gerenciamento de API do Azure. Por exemplo, se você precisar atualizar o aplicativo de funções baseado em HTTP, implante a nova atualização em um aplicativo de funções separado e use as revisões ou versões do Gerenciamento de API para direcionar os clientes para a nova versão ou revisão. Depois que todos os clientes estiverem usando a versão ou a revisão e não houver mais execuções no aplicativo de funções anterior, é possível desprovisionar o aplicativo de funções anterior.

Práticas recomendadas da organização de funções

Como parte de sua solução, você pode desenvolver e publicar várias funções. Essas funções geralmente são combinadas em um único aplicativo de funções, mas também podem ser executadas em aplicativos de funções separados. Nos planos de hospedagem Premium e dedicado (serviço de aplicativo), vários aplicativos de funções também podem compartilhar os mesmos recursos executando no mesmo plano. A forma como você agrupa suas funções e seus aplicativos de função pode afetar o desempenho, o dimensionamento, a configuração, a implantação e a segurança de sua solução geral. Não há regras que se apliquem a todos os cenários, portanto, considere as informações nesta seção ao planejar e desenvolver suas funções.

Organizar funções de desempenho e dimensionamento

Cada função que você cria tem uma superfície de memória. Embora essa superfície seja geralmente pequena, ter muitas funções em um aplicativo de funções pode levar à inicialização mais lenta do seu aplicativo em novas instâncias. Isso também significa que o uso de memória geral do seu aplicativo de funções pode ser maior. É difícil dizer quantas funções devem estar em um único aplicativo, o que depende de sua carga de trabalho específica. No entanto, se sua função armazenar muitos dados na memória, considere ter menos funções em um único aplicativo.

Se você executar vários aplicativos de funções em um único plano Premium ou dedicado (Serviço de Aplicativo), todos esses aplicativos compartilharão dos mesmos recursos alocados ao plano. Se você tiver um aplicativo de funções com um requisito de memória muito maior do que os outros, ele usará uma quantidade desproporcional de recursos de memória em cada instância na qual o aplicativo é implantado. Como isso pode deixar menos memória disponível para os outros aplicativos em cada instância, talvez você queira executar um aplicativo de funções de alto uso de memória como este em seu próprio plano de hospedagem separado.

Observação

Ao usar o Plano de consumo, recomendamos que você sempre coloque cada aplicativo em seu próprio plano, já que os aplicativos são dimensionados independentemente mesmo assim. Para mais informações, consulte vários aplicativos no mesmo plano.

Considere se você deseja agrupar funções com perfis de carga diferentes. Por exemplo, se você tiver uma função que processa muitos milhares de mensagens da fila e outra chamada apenas ocasionalmente, mas que tenha altos requisitos de memória, talvez você queira implantá-las em aplicativos de funções separados para que eles tenham seus próprios conjuntos de recursos e eles dimensionem independentemente uns dos outros.

Organizar funções para configuração e implantação

Os aplicativos de funções têm um arquivo, que é usado para configurar o comportamento avançado de gatilhos host.json de função e o Azure Functions runtime. As alterações no arquivo host.json se aplicam a todas as funções dentro do aplicativo. Se você tiver algumas funções que precisam de configurações personalizadas, considere move-las para seu próprio aplicativo de funções.

Todas as funções em seu projeto local são implantadas juntas como um conjunto de arquivos em seu aplicativo de funções no Azure. Talvez seja necessário implantar funções individuais separadamente ou usar recursos como slots de implantação para algumas funções e não para outras. Nesses casos, você deve implantar essas funções (em projetos de código separados) em diferentes aplicativos de funções.

Organizar funções por privilégio

As cadeias de conexão e outras credenciais armazenadas nas configurações do aplicativo dão a todas as funções no aplicativo de funções o mesmo conjunto de permissões no recurso associado. Considere minimizar o número de funções com acesso a credenciais específicas movendo funções que não usam essas credenciais para um aplicativo de funções separado. Você sempre pode usar técnicas como encadeamento de funções para passar dados entre funções em diferentes aplicativos de funções.

Melhores práticas de escalabilidade

Há uma série de fatores que afetam como as instâncias do aplicativo de funções são escaladas. Os detalhes são fornecidos na documentação de dimensionamento de função. A seguir, estão algumas das melhores práticas para garantir a escalabilidade ideal para um aplicativo de funções.

Gerenciar e compartilhar conexões

Reutilize conexões para recursos externos, sempre que possível. Veja como gerenciar conexões no Azure Functions.

Evite compartilhar contas de armazenamento

Ao criar um aplicativo de funções, você deve associá-lo a uma conta de armazenamento. A conexão da conta de armazenamento é mantida na configuração de aplicativo AzureWebJobsStorage.

Use uma conta de armazenamento separada para cada aplicativo de funções para maximizar o desempenho. Isso será especialmente importante ao disparar as funções do Durable Functions ou do Hub de Eventos, que geram um alto volume de transações de armazenamento. Quando a lógica de aplicativo interagir com o Armazenamento do Azure, de modo direto (usando o SDK de Armazenamento) ou por meio de uma das associações de armazenamento, você deverá usar uma conta de armazenamento dedicada. Por exemplo, caso uma função disparada pelo Hub de Eventos esteja gravando dados no armazenamento de blobs, use duas contas de armazenamento – uma para o aplicativo de funções e outra para os blobs que estão sendo armazenados pela função.

Não misture códigos de teste e de produção no mesmo aplicativo de funções

As funções em um aplicativo de funções compartilham recursos. Por exemplo, a memória é compartilhada. Se você estiver usando um aplicativo de funções em produção, não adicione recursos e funções de teste nele. Ele pode causar sobrecarga inesperada durante a execução de código de produção.

Cuidado com o que você carrega em seus aplicativos de funções de produção. A memória é dividida igualmente entre cada função no aplicativo.

Se você tiver um assembly compartilhado referenciado em várias funções .Net, coloque-o em uma pasta compartilhada comum. Caso contrário, você pode implantar acidentalmente várias versões do mesmo binário que se comportam de maneira diferente entre as funções.

Não use o log detalhado no código de produção, que tem um impacto negativo no desempenho.

Usar o código assíncrono, mas evitar chamadas de bloqueio

A programação assíncrona é uma prática recomendada, especialmente ao bloquear operações de e/s envolvidas.

No entanto, sempre evite fazer referência à propriedade Result ou chamar o método Wait em um instância Task. Essa abordagem pode levar ao esgotamento de thread.

Dica

Se você planeja usar as ligações HTTP ou WebHook, planeje evitar o esgotamento de porta que pode ser causado pela instanciação incorreta do HttpClient. Para saber mais, confira Como gerenciar conexões no Azure Functions.

Usar vários processos de trabalho de linguagem

Por padrão, qualquer instância de host para o Functions usa um único processo de trabalho. Para melhorar o desempenho, especialmente com tempos de execução de thread único como Python, use o FUNCTIONS_WORKER_PROCESS_COUNT para aumentar o número de processos de trabalho por host (até 10). O Azure Functions tenta distribuir uniformemente invocações de função simultâneas entre esses trabalhos.

O FUNCTIONS_WORKER_PROCESS_COUNT se aplica a cada host que o Functions cria quando escala horizontalmente seu aplicativo para atender à demanda.

Receber mensagens em lote sempre que possível

Alguns gatilhos, como o Hub de Eventos, habilitam o recebimento de um lote de mensagens em uma única invocação. As mensagens em lote têm um desempenho melhor. É possível configurar o tamanho máximo do lote no arquivo host.json, conforme detalhado na documentação de referência do host.json

Em funções do C#, é possível alterar o tipo para uma matriz fortemente tipada. Por exemplo, em vez de EventData sensorEvent, a assinatura do método pode ser EventData[] sensorEvent. Em outras linguagens, será necessário definir explicitamente a propriedade de cardinalidade no function.json como many, a fim de habilitar o envio em lote, conforme mostrado aqui.

Configurar comportamentos de host para lidar melhor com a simultaneidade

O arquivo host.json no aplicativo de funções permite configurar os comportamentos do runtime e do gatilho do host. Além do envio em lote de comportamentos, é possível gerenciar a simultaneidade de diversos gatilhos. Geralmente, ajustar os valores nessas opções pode ajudar a escalar cada instância adequadamente para as demandas das funções invocadas.

As configurações no arquivo dos hosts se aplicam a todas as funções do aplicativo, em uma única instância da função. Por exemplo, se você tiver um aplicativo de funções com duas funções HTTP e as solicitações maxConcurrentRequests simultâneas definidas como 25, uma solicitação para qualquer gatilho HTTP será contada como uma das 25 solicitações simultâneas compartilhadas. Se esse aplicativo de funções for escalado para dez instâncias, as dez funções permitirão efetivamente 250 solicitações simultâneas (10 instâncias * 25 solicitações simultâneas por instância).

Outras opções de configuração do host podem ser encontradas no documento de configuração de host.json.

Próximas etapas

Para saber mais, consulte os recursos a seguir: