Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Ao desenvolver para Azure Functions usando Python, precisa de perceber como as suas funções funcionam e como esse desempenho afeta a forma como a sua aplicação de funções é escalada. A necessidade é mais importante ao desenhar aplicações de alto desempenho. Os principais fatores a considerar ao desenhar, escrever e configurar as suas aplicações de funções são a escalabilidade horizontal e as configurações de desempenho de throughput.
Dimensionamento horizontal
Por padrão, o Azure Functions monitoriza automaticamente a carga na sua aplicação e cria mais instâncias de host para Python conforme necessário. O Azure Functions utiliza limiares incorporados para diferentes tipos de trigger para decidir quando adicionar instâncias, como a idade das mensagens e o tamanho da fila para o QueueTrigger. Estes limiares não são configuráveis pelo utilizador. Para mais informações, consulte Escalabilidade orientada por eventos em Azure Functions.
Melhorando a performance do throughput
As configurações padrão são adequadas para a maioria das aplicações Azure Functions. No entanto, pode melhorar o desempenho do rendimento das suas aplicações utilizando configurações baseadas no seu perfil de carga de trabalho. O primeiro passo é perceber o tipo de carga de trabalho que estás a ter.
| Tipo de carga de trabalho | Características da Function App | Exemplos |
|---|---|---|
| ligados a E/S | • A aplicação precisa de lidar com muitas invocações simultâneas. • A aplicação processa um grande número de eventos de E/S, como chamadas de rede e leituras/gravações em disco. |
• APIs Web |
| dependente da CPU | • A aplicação realiza cálculos de longa duração, como redimensionamento de imagens. • A aplicação faz transformação de dados. |
• Processamento de dados • Inferência de aprendizagem automática |
Como as cargas de trabalho de funções no mundo real costumam ser uma mistura de I/O e CPU, deves perfilar a aplicação sob cargas de produção realistas.
Configurações específicas de desempenho
Depois de compreender o perfil de carga de trabalho da sua aplicação de funções, as seguintes são as configurações que pode usar para melhorar o desempenho de throughput das suas funções.
- Assíncrono
- Trabalhador multilingue
- Trabalhadores máximos dentro de um processo de trabalhadores de linguagem
- Ciclo de eventos
- Escala vertical
Assíncrono
Como Python é um tempo de execução de único thread, uma instância host para Python pode processar apenas uma invocação de função de cada vez por padrão. Para aplicações que processam um grande número de eventos de E/S e/ou estão limitadas por E/S, pode melhorar significativamente o desempenho ao executar funções de forma assíncrona.
Para executar uma função de forma assíncrona, use a async def instrução que executa a função diretamente com asyncio:
async def main():
await some_nonblocking_socket_io_op()
Aqui está um exemplo de uma função com trigger 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)
Uma função sem a async palavra-chave é executada automaticamente num pool de threads 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 o benefício total de executar funções de forma assíncrona, a operação/biblioteca de E/S usada no seu código precisa também de ter assíncrona implementada. A utilização de operações de E/S síncronas em funções definidas como assíncronas pode prejudicar o desempenho global. Se as bibliotecas que está a usar não tiverem a versão assíncrona implementada, pode beneficiar ainda assim de executar o seu código de forma assíncrona, gerindo o ciclo de eventos na sua aplicação.
Aqui estão alguns exemplos de bibliotecas clientes que implementaram padrões assíncronos:
- aiohttp - Cliente/servidor HTTP para asyncio
- Streams API - Primitivas assíncronas de alto nível prontas para async/await para trabalhar com conexão de rede
- Janus Queue - Fila segura para threads e compatível com asyncio para Python
- pyzmq - Python bindings para ZeroMQ
Compreender assíncrono em Python worker
Quando defines async antes de uma assinatura de função, Python marca a função como corrutina. Quando chamas a corrotina, ela pode ser agendada como uma tarefa num ciclo de eventos. Quando chamas await uma função assíncrona, ela regista uma continuação no ciclo de eventos, o que permite que o ciclo de eventos processe a próxima tarefa durante o tempo de espera.
No nosso Python Worker, o trabalhador partilha o ciclo de eventos com a função async do cliente e é capaz de lidar com múltiplos pedidos em simultâneo. Incentivamos fortemente os nossos clientes a utilizarem bibliotecas compatíveis com asyncio, como aiohttp e pyzmq. Seguir estas recomendações aumenta o débito da sua função em comparação com essas bibliotecas quando estas são implementadas de forma síncrona.
Observação
Se a sua função for declarada como async sem qualquer await dentro da sua implementação, o desempenho da sua função será gravemente afetado, pois o ciclo de eventos será bloqueado, o que impede o trabalhador Python de lidar com pedidos concorrentes.
Utilizar processos de trabalhadores em múltiplas línguas
Por padrão, cada instância de host de funções tem um único processo de trabalho de linguagem. Pode aumentar o número de processos de trabalho por host (até 10) usando a FUNCTIONS_WORKER_PROCESS_COUNT configuração da aplicação. Azure Functions tenta então distribuir uniformemente as invocações simultâneas de funções entre estes trabalhadores.
Para aplicações limitadas por CPU, deve definir o número de trabalhadores de linguagem igual ou superior ao número de núcleos disponíveis por aplicação funcional. Para saber mais, consulte SKUs de instância disponíveis.
As aplicações ligadas ao I/O também podem beneficiar de aumentar o número de processos de trabalho para além do número de núcleos disponíveis. Tenha em mente que definir o número de trabalhadores demasiado alto pode afetar o desempenho global devido ao aumento do número de mudanças de contexto necessárias.
O FUNCTIONS_WORKER_PROCESS_COUNT aplica-se a cada host que Azure Functions criar ao escalar a sua aplicação para responder à procura.
Defina os trabalhadores máximos dentro de um processo de trabalhadores de linguagem
Como mencionado na secção assíncrona, o interprete da linguagem Python trata funções e corrotinas de forma diferente. Uma corrotina é executada dentro do mesmo ciclo de eventos em que o trabalhador da linguagem corre. Por outro lado, uma invocação de função é executada dentro de um ThreadPoolExecutor, que é mantido pelo trabalhador da linguagem como um thread.
Pode definir o valor do número máximo de trabalhadores permitidos para executar funções de sincronização usando a configuração PYTHON_THREADPOOL_THREAD_COUNT aplicação. Este valor define o argumento max_worker do objeto ThreadPoolExecutor, o que permite Python usar um conjunto de threads no máximo max_worker para executar chamadas de forma assíncrona. O PYTHON_THREADPOOL_THREAD_COUNT aplica-se a cada trabalhador criado pelo hospedeiro Functions, e Python decide quando criar um novo thread ou reutilizar o thread idle existente. Para versões 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 para None.
Para aplicações com CPU, deves manter a definição num valor baixo, começando a partir de 1 e aumentando à medida que experimentas a tua carga de trabalho. Esta sugestão serve para reduzir o tempo gasto em trocas de contexto e permitir que tarefas limitadas pela CPU sejam concluídas.
Para aplicações vinculadas a I/O, deverá ver ganhos substanciais ao aumentar 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 depois ajustar com base nos valores de rendimento que estás a ver.
Para aplicações de cargas de trabalho mistas, deve equilibrar as configurações FUNCTIONS_WORKER_PROCESS_COUNT e PYTHON_THREADPOOL_THREAD_COUNT para maximizar o throughput. Para perceber no que as suas aplicações funcionais dedicam mais tempo, recomendamos que as perfilem e definam os valores de acordo com os seus comportamentos. Para saber mais sobre estas definições de aplicação, consulte Utilizar múltiplos processos de trabalho.
Observação
Embora estas recomendações se apliquem tanto a funções ativadas por HTTP como por não HTTP, poderá ser necessário ajustar outras configurações específicas de gatilho para funções não ativadas por HTTP para obter o desempenho esperado das suas aplicações de funções. Para mais informações sobre isto, consulte as Práticas recomendadas para Azure Functions fiáveis.
Gerir o ciclo de eventos
Deve usar bibliotecas de terceiros compatíveis com asyncio. Se nenhuma das bibliotecas de terceiros responder às suas necessidades, também pode gerir os ciclos de eventos no Azure Functions. Gerir ciclos de eventos dá-lhe mais flexibilidade na gestão de recursos de computação e também torna possível envolver bibliotecas de I/O síncronas em corrotinas.
Existem muitos documentos oficiais Python úteis que discutem as Coroutines and Tasks e Event Loop utilizando a biblioteca incorporada asyncio.
Tomemos como exemplo a seguinte biblioteca requests, este excerto de código usa a biblioteca asyncio para envolver o requests.get() método numa coroutine, executando múltiplos pedidos web para SAMPLE_URL em simultâneo.
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
Pode conseguir obter mais unidades de processamento, especialmente em operação limitada por CPU, ao atualizar para o plano premium com especificações mais elevadas. Com unidades de processamento mais poderosas, pode ajustar o número de processos de trabalho de acordo com o número de núcleos disponíveis para alcançar um maior grau de paralelismo.
Próximos passos
Para mais informações sobre o desenvolvimento Azure Functions Python, consulte os seguintes recursos: