Förbättra dataflödesprestanda för Python-appar i Azure Functions

När du utvecklar för Azure Functions med Python måste du förstå hur dina funktioner fungerar och hur den prestandan påverkar hur funktionsappen skalas. Behovet är viktigare när du utformar appar med hög prestanda. De viktigaste faktorerna att tänka på när du utformar, skriver och konfigurerar dina funktionsappar är horisontell skalning och prestandakonfigurationer för dataflöde.

Horisontell skalning

Som standard övervakar Azure Functions automatiskt belastningen på ditt program och skapar fler värdinstanser för Python efter behov. Azure Functions använder inbyggda tröskelvärden för olika utlösartyper för att bestämma när instanser ska läggas till, till exempel ålder på meddelanden och köstorlek för QueueTrigger. Dessa tröskelvärden kan inte konfigureras av användaren. Mer information finns i Händelsedriven skalning i Azure Functions.

Förbättra dataflödesprestanda

Standardkonfigurationerna är lämpliga för de flesta Azure Functions-program. Du kan dock förbättra prestandan för dina programs dataflöde genom att använda konfigurationer baserat på din arbetsbelastningsprofil. Det första steget är att förstå vilken typ av arbetsbelastning du kör.

Typ av arbetsbelastning Egenskaper för funktionsapp Exempel
I/O-bunden • Appen måste hantera många samtidiga anrop.
• Appen bearbetar ett stort antal I/O-händelser, till exempel nätverksanrop och diskläsning/skrivningar.
• Webb-API:er
CPU-bunden • Appen utför tidskrävande beräkningar, till exempel storleksändring av avbildningar.
• Appen utför datatransformering.
• Databehandling
• Slutsatsdragning för maskininlärning

Eftersom verkliga funktionsarbetsbelastningar vanligtvis är en blandning av I/O och CPU-bundna bör du profilera appen under realistiska produktionsbelastningar.

Prestandaspecifika konfigurationer

När du har förstått arbetsbelastningsprofilen för din funktionsapp är följande konfigurationer som du kan använda för att förbättra dataflödesprestandan för dina funktioner.

Asynkrona

Eftersom Python är en entrådad körning kan en värdinstans för Python endast bearbeta en funktionsanrop i taget som standard. För program som bearbetar ett stort antal I/O-händelser och/eller är I/O-bundna kan du förbättra prestanda avsevärt genom att köra funktioner asynkront.

Om du vill köra en funktion asynkront använder du -instruktionen async def , som kör funktionen med asyncio direkt:

async def main():
    await some_nonblocking_socket_io_op()

Här är ett exempel på en funktion med HTTP-utlösare som använder aiohttp http-klienten:

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)

En funktion utan nyckelordet async körs automatiskt i en ThreadPoolExecutor-trådpool:

# 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()

För att uppnå den fulla fördelen med att köra funktioner asynkront måste även den I/O-åtgärd/bibliotek som används i koden ha asynkront implementerad. Användning av synkrona I/O-åtgärder i funktioner som definieras som asynkrona kan skada den övergripande prestandan. Om biblioteken du använder inte har en asynkron version implementerad kan du fortfarande ha nytta av att köra koden asynkront genom att hantera händelseloopen i din app.

Här är några exempel på klientbibliotek som har implementerat asynkrona mönster:

  • aiohttp – Http-klient/server för asyncio
  • Flöden API – Asynkrona/invänta-redo primitiver på hög nivå för att arbeta med nätverksanslutning
  • Janus Queue – Trådsäker asynkron medveten kö för Python
  • pyzmq – Python-bindningar för ZeroMQ
Förstå asynkronisering i Python Worker

När du definierar async framför en funktionssignatur markerar Python funktionen som en coroutine. När du anropar coroutine kan den schemaläggas som en aktivitet i en händelseloop. När du anropar await en asynkron funktion registreras en fortsättning i händelseloopen, vilket gör att händelseloopen kan bearbeta nästa uppgift under väntetiden.

I vår Python Worker delar arbetaren händelseloopen med kundens async funktion och kan hantera flera begäranden samtidigt. Vi rekommenderar starkt att våra kunder använder asynkrona kompatibla bibliotek, till exempel aiohttp och pyzmq. Genom att följa dessa rekommendationer ökar funktionens dataflöde jämfört med de biblioteken när det implementeras synkront.

Kommentar

Om funktionen deklareras som async utan någon await i implementeringen påverkas funktionens prestanda allvarligt eftersom händelseloopen blockeras, vilket förbjuder Python-arbetaren att hantera samtidiga begäranden.

Använda flera språkarbetsprocesser

Som standard har varje Functions-värdinstans en enda språkarbetsprocess. Du kan öka antalet arbetsprocesser per värd (upp till 10) med hjälp av programinställningen FUNCTIONS_WORKER_PROCESS_COUNT . Azure Functions försöker sedan distribuera samtidiga funktionsanrop jämnt mellan dessa arbetare.

För CPU-bundna appar bör du ange att antalet språkarbetare ska vara samma som eller högre än antalet kärnor som är tillgängliga per funktionsapp. Mer information finns i Tillgängliga instans-SKU :er.

I/O-bundna appar kan också ha nytta av att öka antalet arbetsprocesser utöver antalet tillgängliga kärnor. Tänk på att inställningen av antalet arbetare som är för högt kan påverka den övergripande prestandan på grund av det ökade antalet nödvändiga kontextväxlar.

FUNCTIONS_WORKER_PROCESS_COUNT Gäller för varje värd som Azure Functions skapar när du skalar ut ditt program för att möta efterfrågan.

Konfigurera maximalt antal arbetare i en språkarbetsprocess

Som nämnts i asynkroniseringsavsnittet behandlar Python-språkarbetaren funktioner och koroutiner på olika sätt. En coroutine körs i samma händelseloop som språkarbetaren körs på. Å andra sidan körs ett funktionsanrop i en ThreadPoolExecutor, som underhålls av språkarbetaren som en tråd.

Du kan ange värdet för maximalt antal arbetare som tillåts för att köra synkroniseringsfunktioner med hjälp av PYTHON_THREADPOOL_THREAD_COUNT-programinställningen . Det här värdet anger argumentet för max_worker ThreadPoolExecutor-objektet, vilket gör att Python kan använda en pool med högst trådar max_worker för att köra anrop asynkront. PYTHON_THREADPOOL_THREAD_COUNT Gäller för varje arbetare som Functions-värden skapar, och Python bestämmer när en ny tråd ska skapas eller återanvända den befintliga inaktiva tråden. För äldre Python-versioner (dvs. 3.8, 3.7och 3.6), max_worker anges värdet till 1. För Python-version 3.9max_worker är är inställt på None.

För CPU-bundna appar bör du behålla inställningen till ett lågt antal, från och med 1 och öka när du experimenterar med din arbetsbelastning. Det här förslaget är att minska den tid som ägnas åt kontextväxlar och tillåta att CPU-bundna uppgifter slutförs.

För I/O-bundna appar bör du se betydande vinster genom att öka antalet trådar som arbetar med varje anrop. Rekommendationen är att börja med Python-standardvärdet (antalet kärnor) + 4 och sedan justera baserat på de dataflödesvärden du ser.

För appar med blandade arbetsbelastningar bör du balansera både FUNCTIONS_WORKER_PROCESS_COUNT och PYTHON_THREADPOOL_THREAD_COUNT konfigurationer för att maximera dataflödet. För att förstå vad dina funktionsappar spenderar mest tid på rekommenderar vi att du profilerar dem och anger värdena enligt deras beteenden. Mer information om de här programinställningarna finns i Använda flera arbetsprocesser.

Kommentar

Även om dessa rekommendationer gäller för både HTTP- och icke-HTTP-utlösta funktioner, kan du behöva justera andra utlösarspecifika konfigurationer för icke-HTTP-utlösta funktioner för att få den förväntade prestandan från dina funktionsappar. Mer information om detta finns i den här metodtipsen för tillförlitliga Azure Functions.

Hantera händelseloop

Du bör använda asynkrona bibliotek från tredje part. Om inget av biblioteken från tredje part uppfyller dina behov kan du även hantera händelselooparna i Azure Functions. Genom att hantera händelseslingor får du större flexibilitet när det gäller hantering av beräkningsresurser, och det gör det också möjligt att omsluta synkrona I/O-bibliotek i koroutiner.

Det finns många användbara officiella Python-dokument som diskuterar Coroutines och Uppgifter och Händelseloop med hjälp av det inbyggda asyncio-biblioteket .

Ta följande begärandebibliotek som exempel. Det här kodfragmentet använder asyncio-biblioteket för att omsluta requests.get() metoden till en coroutine och köra flera webbbegäranden för att SAMPLE_URL samtidigt.

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')

Vertikal skalning

Du kanske kan få fler bearbetningsenheter, särskilt i cpu-bunden drift, genom att uppgradera till premiumplan med högre specifikationer. Med högre bearbetningsenheter kan du justera antalet arbetsprocesser enligt antalet tillgängliga kärnor och uppnå högre grad av parallellitet.

Nästa steg

Mer information om Azure Functions Python-utveckling finns i följande resurser: