Share via


Doorvoerprestaties van Python-apps in Azure Functions verbeteren

Wanneer u ontwikkelt voor Azure Functions met behulp van Python, moet u begrijpen hoe uw functies presteren en hoe deze prestaties van invloed zijn op de manier waarop uw functie-app wordt geschaald. De behoefte is belangrijker bij het ontwerpen van zeer goed presterende apps. De belangrijkste factoren die u moet overwegen bij het ontwerpen, schrijven en configureren van uw functies-apps zijn horizontale configuraties voor schaalaanpassing en doorvoerprestaties.

Horizontale schaalaanpassing

Azure Functions bewaakt standaard automatisch de belasting van uw toepassing en maakt zo nodig meer hostexemplaren voor Python. Azure Functions maakt gebruik van ingebouwde drempelwaarden voor verschillende triggertypen om te bepalen wanneer exemplaren moeten worden toegevoegd, zoals de leeftijd van berichten en de wachtrijgrootte voor QueueTrigger. Deze drempelwaarden kunnen niet door de gebruiker worden geconfigureerd. Zie Gebeurtenisgestuurd schalen in Azure Functions voor meer informatie.

Doorvoerprestaties verbeteren

De standaardconfiguraties zijn geschikt voor de meeste Azure Functions-toepassingen. U kunt echter de prestaties van de doorvoer van uw toepassingen verbeteren door configuraties te gebruiken op basis van uw workloadprofiel. De eerste stap is het begrijpen van het type workload dat u uitvoert.

Workloadtype Kenmerken van functie-app Voorbeelden
I/O-gebonden • App moet veel gelijktijdige aanroepen verwerken.
• App verwerkt een groot aantal I/O-gebeurtenissen, zoals netwerkoproepen en schijflees-/schrijfbewerkingen.
• Web-API's
CPU-gebonden • De app voert langdurige berekeningen uit, zoals het wijzigen van het formaat van afbeeldingen.
• App voert gegevenstransformatie uit.
• Gegevensverwerking
• Machine learning-deductie

Aangezien werkbelastingen van echte functies meestal een combinatie van I/O- en CPU-afhankelijk zijn, moet u de app onder realistische productiebelastingen profilen.

Prestatiespecifieke configuraties

Nadat u het workloadprofiel van uw functie-app hebt begrepen, zijn de volgende configuraties die u kunt gebruiken om de doorvoerprestaties van uw functies te verbeteren.

Async

Omdat Python een runtime met één thread is, kan een hostexemplaren voor Python standaard slechts één functie aanroepen tegelijk verwerken. Voor toepassingen die een groot aantal I/O-gebeurtenissen verwerken en/of I/O-gebonden zijn, kunt u de prestaties aanzienlijk verbeteren door functies asynchroon uit te voeren.

Als u een functie asynchroon wilt uitvoeren, gebruikt u de async def instructie waarmee de functie rechtstreeks met asynchroon wordt uitgevoerd:

async def main():
    await some_nonblocking_socket_io_op()

Hier volgt een voorbeeld van een functie met HTTP-trigger die gebruikmaakt van aiohttp http-client:

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)

Een functie zonder het async trefwoord wordt automatisch uitgevoerd in een ThreadPoolExecutor-threadgroep:

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

Om het volledige voordeel van het asynchroon uitvoeren van functies te bereiken, moet ook de I/O-bewerking/bibliotheek die in uw code wordt gebruikt, asynchroon worden geïmplementeerd. Het gebruik van synchrone I/O-bewerkingen in functies die zijn gedefinieerd als asynchroon , kan de algehele prestaties schaden . Als er geen asynchrone versie is geïmplementeerd voor de bibliotheken die u gebruikt, is het mogelijk dat u uw code asynchroon uitvoert door de gebeurtenislus in uw app te beheren.

Hier volgen enkele voorbeelden van clientbibliotheken die asynchrone patronen hebben geïmplementeerd:

  • aiohttp - Http-client/-server voor asynchroon
  • Streams-API : asynchrone/wacht-ready primitieven voor gebruik met netwerkverbinding
  • Janus Queue - Thread-safe asyncio-aware wachtrij voor Python
  • pyzmq - Python-bindingen voor ZeroMQ
Asynchroon in Python-werkrol

Wanneer u definieert async vóór een functiehandtekening, markeert Python de functie als een coroutine. Wanneer u de coroutine aanroept, kan deze worden gepland als een taak in een gebeurtenislus. Wanneer u een asynchrone functie aanroept await , wordt er een vervolg geregistreerd in de gebeurtenislus, waardoor de gebeurtenislus de volgende taak tijdens de wachttijd kan verwerken.

In onze Python Worker deelt de werkrol de gebeurtenislus met de functie van async de klant en kan deze meerdere aanvragen gelijktijdig verwerken. We raden onze klanten sterk aan om gebruik te maken van asynchrone compatibele bibliotheken, zoals aiohttp en pyzmq. Als u deze aanbevelingen volgt, wordt de doorvoer van uw functie verhoogd in vergelijking met die bibliotheken wanneer deze synchroon worden geïmplementeerd.

Notitie

Als uw functie wordt gedeclareerd als async geen enkele await binnen de implementatie, worden de prestaties van uw functie ernstig beïnvloed omdat de gebeurtenislus wordt geblokkeerd, waardoor de Python-werkrol geen gelijktijdige aanvragen kan verwerken.

Werkprocessen met meerdere talen gebruiken

Standaard heeft elk Functions-hostexemplaar één taalwerkrolproces. U kunt het aantal werkprocessen per host (maximaal 10) verhogen met behulp van de FUNCTIONS_WORKER_PROCESS_COUNT toepassingsinstelling. Vervolgens probeert Azure Functions gelijktijdige functieaanroepen gelijkmatig over deze werkrollen te verdelen.

Voor CPU-afhankelijke apps moet u instellen dat het aantal taalwerkers hetzelfde is als of hoger is dan het aantal kernen dat per functie-app beschikbaar is. Zie Beschikbare exemplaar-SKU's voor meer informatie.

I/O-gebonden apps kunnen ook profiteren van het verhogen van het aantal werkprocessen buiten het aantal beschikbare kernen. Houd er rekening mee dat het instellen van het aantal werknemers dat te hoog is, invloed kan hebben op de algehele prestaties vanwege het toegenomen aantal vereiste contextswitches.

Dit FUNCTIONS_WORKER_PROCESS_COUNT geldt voor elke host die Azure Functions maakt bij het uitschalen van uw toepassing om aan de vraag te voldoen.

Maximale werkrollen instellen binnen een taalwerkproces

Zoals vermeld in de asynchrone sectie, behandelt de Python-taalwerker functies en coroutines anders. Een coroutine wordt uitgevoerd binnen dezelfde gebeurtenislus waarop de taalwerker wordt uitgevoerd. Aan de andere kant wordt een functieaanroep uitgevoerd in een ThreadPoolExecutor, die wordt onderhouden door de taalwerker als een thread.

U kunt de waarde instellen van het maximum aantal werkrollen dat is toegestaan voor het uitvoeren van synchronisatiefuncties met behulp van de PYTHON_THREADPOOL_THREAD_COUNT toepassingsinstelling. Met deze waarde wordt het max_worker argument van het ThreadPoolExecutor-object ingesteld, waarmee Python een pool van maximaal max_worker threads kan gebruiken om asynchroon aanroepen uit te voeren. Dit PYTHON_THREADPOOL_THREAD_COUNT geldt voor elke werkrol die door de Functions-host wordt gemaakt en Python bepaalt wanneer een nieuwe thread moet worden gemaakt of de bestaande niet-actieve thread opnieuw moet worden gebruikt. Voor oudere Python-versies (dat wil gezegd, 3.83.7en 3.6) max_worker is de waarde ingesteld op 1. Voor python-versie 3.9max_worker is ingesteld op None.

Voor CPU-afhankelijke apps moet u de instelling op een laag aantal houden, beginnend vanaf 1 en toenemen tijdens het experimenteren met uw workload. Deze suggestie is het verminderen van de tijd die wordt besteed aan contextswitches en het toestaan van CPU-afhankelijke taken.

Voor I/O-gebonden apps ziet u aanzienlijke voordelen door het aantal threads dat aan elke aanroep werkt te verhogen. De aanbeveling is om te beginnen met de standaard Python-standaardwaarde (het aantal kernen) + 4 en pas vervolgens aan op basis van de doorvoerwaarden die u ziet.

Voor apps met gemengde workloads moet u beide FUNCTIONS_WORKER_PROCESS_COUNT en PYTHON_THREADPOOL_THREAD_COUNT configuraties verdelen om de doorvoer te maximaliseren. Als u wilt weten waar uw functie-apps het meeste tijd aan besteden, raden we u aan ze te profileren en de waarden in te stellen op basis van hun gedrag. Zie Meerdere werkprocessen gebruiken voor meer informatie over deze toepassingsinstellingen.

Notitie

Hoewel deze aanbevelingen van toepassing zijn op zowel HTTP- als niet-HTTP-geactiveerde functies, moet u mogelijk andere triggerspecifieke configuraties aanpassen voor niet-HTTP-geactiveerde functies om de verwachte prestaties van uw functie-apps te verkrijgen. Raadpleeg deze aanbevolen procedures voor betrouwbare Azure Functions voor meer informatie hierover.

Gebeurtenislus beheren

U moet asynchrone compatibele bibliotheken van derden gebruiken. Als geen van de bibliotheken van derden aan uw behoeften voldoet, kunt u ook de gebeurtenislussen in Azure Functions beheren. Het beheren van gebeurtenislussen biedt u meer flexibiliteit in het beheer van rekenresources en het maakt het ook mogelijk om synchrone I/O-bibliotheken in coroutines te verpakken.

Er zijn veel nuttige officiële Python-documenten die de Coroutines en Taken en Gebeurtenislus bespreken met behulp van de ingebouwde asynchrone bibliotheek.

Neem de volgende aanvraagbibliotheek als voorbeeld. In dit codefragment wordt de asyncio-bibliotheek gebruikt om de requests.get() methode in een coroutine te verpakken en meerdere webaanvragen tegelijkertijd uit te voeren voor SAMPLE_URL.

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

Verticale schaalaanpassing

Mogelijk kunt u meer verwerkingseenheden krijgen, met name in cpu-afhankelijke bewerkingen, door een upgrade uit te voeren naar een Premium-abonnement met hogere specificaties. Met hogere verwerkingseenheden kunt u het aantal werkprocessen aanpassen op basis van het aantal beschikbare kernen en een hogere mate van parallelle uitvoering bereiken.

Volgende stappen

Zie de volgende resources voor meer informatie over het ontwikkelen van Azure Functions Python: