Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Ao desenvolver para Azure Functions usando Python, você precisa entender como suas funções são executadas e como esse desempenho afeta a maneira como seu aplicativo de funções é dimensionado. A necessidade é mais importante ao criar aplicativos de alto desempenho. Os principais fatores a serem considerados ao projetar, desenvolver e configurar seus aplicativos de funções são as configurações de escala horizontal e desempenho de taxa de transferência.
Dimensionamento em escala horizontal
Por padrão, Azure Functions monitora automaticamente a carga em seu aplicativo e cria mais instâncias de host para Python conforme necessário. Azure Functions usa limites internos para diferentes tipos de gatilho para decidir quando adicionar instâncias, como a idade das mensagens e o tamanho da fila para QueueTrigger. Esses limites não são configuráveis pelo usuário. Para obter mais informações, consulte Escalo controlado por eventos em Azure Functions.
Melhorando o desempenho da taxa de transferência
As configurações padrão são adequadas para a maioria dos aplicativos Azure Functions. No entanto, você pode melhorar o desempenho da taxa de transferência de seus aplicativos empregando configurações com base em seu perfil de carga de trabalho. A primeira etapa é entender o tipo de carga de trabalho que você está executando.
| Tipo de carga de trabalho | Características do aplicativo de funções | Exemplos |
|---|---|---|
| Limite de E/S | • O aplicativo precisa lidar com muitas invocações simultâneas. • O aplicativo processa um grande número de eventos de E/S, como chamadas de rede e leitura/gravações em disco. |
• APIs da Web |
| Limitado pela CPU | • O aplicativo faz cálculos de execução longa, como redimensionamento de imagem. • O aplicativo faz a transformação de dados. |
• Processamento de dados • Inferência de aprendizado de máquina |
Como as cargas de trabalho reais geralmente são uma combinação de E/S e CPU, você deve analisar o aplicativo sob cargas de produção realistas.
Configurações específicas de desempenho
Depois de entender o perfil de carga de trabalho do aplicativo de funções, veja a seguir as configurações que você pode usar para melhorar o desempenho da taxa de transferência de suas funções.
- Async
- Trabalho de vários idiomas
- Máximo de trabalhos em um processo de trabalho de idioma
- Loop de eventos
- Dimensionamento vertical
Async
Como Python é um runtime de thread único, uma instância de host para Python pode processar apenas uma invocação de função por vez por padrão. Para aplicativos que processam um grande número de eventos de E/S e/ou são associados a E/S, você pode melhorar significativamente o desempenho executando funções de forma assíncrona.
Para executar uma função de forma assíncrona, use a instrução async def, que executa a função com asyncio diretamente.
async def main():
await some_nonblocking_socket_io_op()
Aqui está um exemplo de uma função com gatilho HTTP que usa o cliente http aiohttp :
import aiohttp
import azure.functions as func
async def main(req: func.HttpRequest) -> func.HttpResponse:
async with aiohttp.ClientSession() as client:
async with client.get("PUT_YOUR_URL_HERE") as response:
return func.HttpResponse(await response.text())
return func.HttpResponse(body='NotFound', status_code=404)
Função sem a palavra-chave async é executada automaticamente em um pool de threads do ThreadPoolExecutor.
# Runs in a ThreadPoolExecutor threadpool. Number of threads is defined by PYTHON_THREADPOOL_THREAD_COUNT.
# The example is intended to show how default synchronous functions are handled.
def main():
some_blocking_socket_io()
Para obter todo o benefício de executar funções de forma assíncrona, a operação/biblioteca de E/S usada em seu código também precisa ter a assíncrona implementada. O uso de operações de E/S síncronas em funções definidas como assíncronas pode prejudicar o desempenho geral. Se as bibliotecas que você está usando não tiverem a versão assíncrona implementada, você ainda poderá se beneficiar de executar seu código de forma assíncrona gerenciando o loop de eventos em seu aplicativo.
Aqui estão alguns exemplos de bibliotecas de clientes que implementaram padrões assíncronos:
- aiohttp – cliente/servidor de HTTP para asyncio
- API de Streams – primitivos de alto nível com suporte a async/await para trabalhar com a conexão de rede
- Janus Queue - Fila segura para threads e compatível com asyncio no Python
- pyzmq – associações de Python para ZeroMQ
Noções básicas sobre a assíncrona no trabalho Python
Quando você define async na frente de uma assinatura de função, Python marca a função como uma coroutina. Quando você chama a co-rotina, ela pode ser agendada como uma tarefa em um loop de eventos. Quando você chama await uma função assíncrona, ela registra uma continuação no loop de eventos, o que permite que o loop de eventos processe a próxima tarefa durante o tempo de espera.
Em nosso Python Worker, o trabalhador compartilha o loop de eventos com a função async do cliente e é capaz de lidar com várias solicitações simultaneamente. Incentivamos fortemente nossos clientes a usar bibliotecas compatíveis com asyncio, como aiohttp e pyzmq. Seguir essas recomendações aumenta a taxa de transferência da função em comparação com essas bibliotecas quando implementadas de forma síncrona.
Observação
Se sua função for declarada como async sem qualquer await dentro de sua implementação, o desempenho da função será severamente afetado, pois o loop de eventos será bloqueado, impedindo o worker Python de lidar com solicitações simultâneas.
Usar vários processos de trabalho de linguagem
Por padrão, cada instância de host do Functions possui um único processo language worker. Você pode aumentar o número de processos de trabalho por host (até 10) usando a configuração do FUNCTIONS_WORKER_PROCESS_COUNT aplicativo. Azure Functions tenta distribuir uniformemente invocações de função simultânea entre esses trabalhadores.
Para aplicativos com limite de CPU, é necessário definir o número de trabalhos de linguagem como igual ou superior ao número de núcleos disponíveis por cada aplicativo de funções. Para saber mais, consulte SKUs de instância disponíveis.
Os aplicativos com limite de E/S também podem se beneficiar do aumento do número de processos de trabalho além do número de núcleos disponíveis. Tenha em mente que definir o número de trabalhadores muito alto pode afetar o desempenho geral devido ao aumento do número de opções de contexto necessárias.
O FUNCTIONS_WORKER_PROCESS_COUNT se aplica a cada host que Azure Functions cria ao dimensionar seu aplicativo para atender à demanda.
Configurar o máximo de trabalhos em um processo de trabalho de idioma
Conforme mencionado na seção assíncrona, o "language worker" do Python trata funções e coroutines de forma diferente. Uma corrotina é executada dentro do mesmo loop de eventos em que o trabalho de idioma é executado. Por outro lado, uma invocação de função é executada em um ThreadPoolExecutor, que é mantido pelo trabalho de linguagem como um thread.
Você pode definir o valor do máximo de trabalhos permitidos para executar funções de sincronização usando a configuração do aplicativo PYTHON_THREADPOOL_THREAD_COUNT . Esse valor define o argumento max_worker do objeto ThreadPoolExecutor, que permite Python usar um pool de no máximo max_worker threads para executar chamadas de forma assíncrona. O PYTHON_THREADPOOL_THREAD_COUNT se aplica a cada trabalho criado pelo host do Functions e Python decide quando criar um novo thread ou reutilizar o thread ocioso existente. Para versões de Python mais antigas (ou seja, 3.8, 3.7 e 3.6), max_worker valor é definido como 1. Para Python versão 3.9, max_worker está definido como None.
Para aplicativos com limite de CPU, você deve manter a configuração em um número baixo, começando em 1 e aumentar à medida que experimenta a carga de trabalho. Essa sugestão visa reduzir o tempo gasto em trocas de contexto e permitir que tarefas intensivas de CPU sejam concluídas.
Para aplicativos com limite de E/S, é possível ver ganhos substanciais aumentando o número de threads trabalhando em cada invocação. A recomendação é começar com o padrão Python (o número de núcleos) + 4 e ajustar com base nos valores de taxa de transferência que você está vendo.
Para aplicativos de cargas de trabalho mistas, você deve balancear ambas as configurações FUNCTIONS_WORKER_PROCESS_COUNT e PYTHON_THREADPOOL_THREAD_COUNT para maximizar a taxa de transferência. Para entender no que seus aplicativos de funções gastam mais tempo, recomendamos criação de perfil e definição dos valores de acordo com seus comportamentos. Para saber mais sobre essas configurações de aplicativo, consulte Usar vários processos de trabalho.
Observação
Embora essas recomendações se apliquem a funções disparadas por HTTP e não HTTP, talvez seja necessário ajustar outras configurações específicas de gatilho para funções não disparadas por HTTP para obter o desempenho esperado de seus aplicativos de funções. Para obter mais informações sobre isso, consulte este Melhores Práticas para Azure Functions Confiáveis.
Gerenciando o loop de eventos
Você deve usar bibliotecas de terceiros compatíveis com asyncio. Se nenhuma das bibliotecas de terceiros atender às suas necessidades, você também poderá gerenciar os loops de eventos no Azure Functions. O gerenciamento de loops de eventos oferece mais flexibilidade no gerenciamento de recursos de computação e também possibilita encapsular bibliotecas de E/S síncronas em coroutinas.
Há muitos documentos oficiais Python úteis que discutem as Coroutines and Tasks e Event Loop usando a biblioteca interna asyncio.
Use a biblioteca de requests a seguir como exemplo, este snippet de código usa a biblioteca asyncio para encapsular o método requests.get() em uma corrotina, executando várias requisições da web para SAMPLE_URL simultaneamente.
import asyncio
import json
import logging
import azure.functions as func
from time import time
from requests import get, Response
async def invoke_get_request(eventloop: asyncio.AbstractEventLoop) -> Response:
# Wrap requests.get function into a coroutine
single_result = await eventloop.run_in_executor(
None, # using the default executor
get, # each task call invoke_get_request
'SAMPLE_URL' # the url to be passed into the requests.get function
)
return single_result
async def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
eventloop = asyncio.get_event_loop()
# Create 10 tasks for requests.get synchronous call
tasks = [
asyncio.create_task(
invoke_get_request(eventloop)
) for _ in range(10)
]
done_tasks, _ = await asyncio.wait(tasks)
status_codes = [d.result().status_code for d in done_tasks]
return func.HttpResponse(body=json.dumps(status_codes),
mimetype='application/json')
Dimensionamento vertical
Você pode obter mais unidades de processamento, especialmente na operação associada à CPU, atualizando para o plano Premium com especificações mais altas. Com unidades de processamento mais altas, você pode ajustar o número de processos de trabalho de acordo com o número de núcleos disponíveis e obter maior grau de paralelismo.
Próximas Etapas
Para obter mais informações sobre Azure Functions Python desenvolvimento, consulte os seguintes recursos:
- guia do desenvolvedor Azure Functions Python
- Práticas recomendadas para Azure Functions
- referência de desenvolvedor Azure Functions