Zvýšení výkonu propustnosti aplikací v Pythonu ve službě Azure Functions

Při vývoji pro Azure Functions pomocí Pythonu potřebujete pochopit, jak vaše funkce fungují a jak tento výkon ovlivňuje způsob škálování vaší aplikace funkcí. Potřeba je důležitější při návrhu vysoce výkonných aplikací. Hlavními faktory, které je potřeba zvážit při návrhu, zápisu a konfiguraci aplikací funkcí, jsou horizontální škálování a konfigurace výkonu propustnosti.

Horizontální škálování

Azure Functions ve výchozím nastavení automaticky monitoruje zatížení vaší aplikace a podle potřeby vytváří další instance hostitele pro Python. Azure Functions používá předdefinované prahové hodnoty pro různé typy triggerů, aby se rozhodlo, kdy přidat instance, jako je stáří zpráv a velikost fronty pro QueueTrigger. Tyto prahové hodnoty nejsou konfigurovatelné uživatelem. Další informace najdete v tématu Škálování řízené událostmi ve službě Azure Functions.

Zvýšení výkonu propustnosti

Výchozí konfigurace jsou vhodné pro většinu aplikací Azure Functions. Výkon propustnosti aplikací ale můžete zlepšit použitím konfigurací založených na vašem profilu úloh. Prvním krokem je pochopení typu úlohy, kterou používáte.

Typ úlohy Charakteristiky aplikace funkcí Příklady
Vstupně-výstupní operace • Aplikace musí zpracovávat mnoho souběžných vyvolání.
• Aplikace zpracovává velký počet vstupně-výstupních událostí, jako jsou síťová volání a čtení a zápisy na disku.
• Webová rozhraní API
Vázané na procesor • Aplikace používá dlouhotrvající výpočty, jako je změna velikosti obrázku.
• Aplikace provede transformaci dat.
• Zpracování dat
• Odvození strojového učení

Vzhledem k tomu, že úlohy reálného světa jsou obvykle kombinací vstupně-výstupních operací a procesoru vázané na procesor, měli byste aplikaci profilovat v realistických produkčních zatíženích.

Konfigurace specifické pro výkon

Po pochopení profilu úloh aplikace funkcí jsou následující konfigurace, které můžete použít ke zlepšení výkonu propustnosti vašich funkcí.

Async

Vzhledem k tomu, že Python je modul runtime s jedním vláknem, může instance hostitele pro Python zpracovat ve výchozím nastavení pouze jedno vyvolání funkce. U aplikací, které zpracovávají velký počet vstupně-výstupních událostí nebo jsou vstupně-výstupní operace vázané, můžete výrazně zvýšit výkon spouštěním funkcí asynchronně.

Pokud chcete funkci spustit asynchronně, použijte async def příkaz, který funkci spouští přímo pomocí asyncio :

async def main():
    await some_nonblocking_socket_io_op()

Tady je příklad funkce s triggerem HTTP, který používá klienta 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)

Funkce bez klíčového async slova se spouští automaticky ve fondu vláken 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()

Aby bylo možné dosáhnout plné výhody asynchronního spouštění funkcí, musí být implementovaná také asynchronní operace/knihovna vstupně-výstupních operací a knihoven, která se používá ve vašem kódu. Použití synchronních vstupně-výstupních operací ve funkcích, které jsou definovány jako asynchronní , může poškodit celkový výkon. Pokud knihovny, které používáte, nemají implementovanou asynchronní verzi, můžete i nadále využívat spouštění kódu asynchronně díky smyčce událostí ve vaší aplikaci.

Tady je několik příkladů klientských knihoven, které implementovaly asynchronní vzory:

  • aiohttp – Klient/server HTTP pro asyncio
  • rozhraní API Toky – základní asynchronní/await-ready primitivy pro práci se síťovým připojením
  • Fronta Janus – fronta s podporou asynchronních vláken pro Python
  • pyzmq – vazby Pythonu pro ZeroMQ
Principy asynchronní synchronizace v pracovním procesu Pythonu

Když definujete async před podpisem funkce, Python funkci označí jako korutinu. Když voláte korutinu, můžete ji naplánovat jako úkol do smyčky událostí. Když zavoláte await asynchronní funkci, zaregistruje pokračování do smyčky událostí, což umožňuje, aby smyčka událostí zpracovávala další úkol během doby čekání.

V našem pracovním procesu Pythonu pracovní proces sdílí smyčku událostí s funkcí zákazníka async a dokáže zpracovávat více požadavků současně. Důrazně doporučujeme našim zákazníkům používat asyncio kompatibilní knihovny, jako je aiohttp a pyzmq. Po provedení těchto doporučení zvýšíte propustnost vaší funkce v porovnání s těmito knihovnami při synchronní implementaci.

Poznámka:

Pokud je vaše funkce deklarována jako async bez jakékoli await implementace, výkon funkce bude vážně ovlivněn, protože smyčka událostí bude blokována, což zakáže pracovnímu procesu Pythonu zpracovávat souběžné požadavky.

Použití více jazykových pracovních procesů

Ve výchozím nastavení má každá instance hostitele služby Functions jeden jazykový pracovní proces. Pomocí nastavení aplikace můžete zvýšit počet pracovních procesů na hostitele (až 10 FUNCTIONS_WORKER_PROCESS_COUNT ). Azure Functions se pak pokusí rovnoměrně distribuovat souběžné vyvolání funkcí napříč těmito pracovními procesy.

U aplikací vázaných na procesor byste měli nastavit počet pracovních procesů jazyka, které mají být stejné nebo vyšší než počet jader dostupných pro každou aplikaci funkcí. Další informace najdete v tématu Dostupné skladové položky instance.

Aplikace vázané na vstupně-výstupní operace můžou také těžit z zvýšení počtu pracovních procesů nad počet dostupných jader. Mějte na paměti, že nastavení příliš vysokého počtu pracovních procesů může mít vliv na celkový výkon kvůli zvýšenému počtu požadovaných kontextových přepínačů.

Platí FUNCTIONS_WORKER_PROCESS_COUNT pro každého hostitele, kterého Azure Functions vytvoří při horizontálním navýšení kapacity aplikace tak, aby vyhovovala poptávce.

Nastavení maximálního počtu pracovních procesů v rámci procesu pracovního procesu jazyka

Jak je uvedeno v asynchronní části, pracovní proces jazyka Python pracuje s funkcemi a koruty odlišně. Korutin se spouští ve stejné smyčce událostí, na které běží pracovní proces jazyka. Na druhou stranu se vyvolání funkce spouští v rámci ThreadPoolExecutorutoru, který udržuje pracovní proces jazyka jako vlákno.

Pomocí nastavení aplikace PYTHON_THREADPOOL_THREAD_COUNT můžete nastavit hodnotu maximálního počtu pracovních procesů povolených pro spouštění synchronizačních funkcí. Tato hodnota nastaví max_worker argument ThreadPoolExecutor objektu, který Umožňuje Pythonu použít fond většiny max_worker vláken ke spouštění volání asynchronně. Platí PYTHON_THREADPOOL_THREAD_COUNT pro každý pracovní proces, který hostitel Functions vytvoří, a Python rozhodne, kdy vytvořit nové vlákno nebo znovu použít existující nečinné vlákno. Pro starší verze Pythonu (tj 3.8. , 3.7a 3.6) max_worker je hodnota nastavená na 1. Pro verzi 3.9max_worker Pythonu je nastavená hodnota None.

U aplikací vázaných na procesor byste měli zachovat nastavení na nízké číslo, počínaje 1 a při experimentování s úlohou. Tento návrh je zkrátit dobu strávenou na kontextových přepínačích a umožnit dokončení úloh vázaných na procesor.

U vstupně-výstupních aplikací byste měli vidět značné zisky zvýšením počtu vláken pracujících na každém vyvolání. Doporučujeme začít s výchozím Pythonem (počet jader) + 4 a pak upravit na základě hodnot propustnosti, které vidíte.

U aplikací se smíšenými úlohami byste měli vyvážit obě FUNCTIONS_WORKER_PROCESS_COUNT konfigurace PYTHON_THREADPOOL_THREAD_COUNT , abyste maximalizovali propustnost. Abyste pochopili, na čem vaše aplikace funkcí tráví nejvíce času, doporučujeme je profilovat a nastavit hodnoty podle jejich chování. Další informace o těchto nastaveních aplikace najdete v tématu Použití více pracovních procesů.

Poznámka:

I když se tato doporučení vztahují na funkce aktivované protokolem HTTP i jiné než HTTP, možná budete muset upravit další konfigurace specifické pro triggery pro funkce, které nejsou aktivované protokolem HTTP, abyste z aplikací funkcí získali očekávaný výkon. Další informace o tom najdete v těchto osvědčených postupech pro spolehlivé služby Azure Functions.

Správa smyčky událostí

Měli byste použít asyncio kompatibilní knihovny třetích stran. Pokud žádná z knihoven třetích stran nevyhovuje vašim potřebám, můžete také spravovat smyčky událostí ve službě Azure Functions. Správa smyček událostí poskytuje větší flexibilitu při správě výpočetních prostředků a umožňuje také zabalit synchronní vstupně-výstupní knihovny do korutin.

Existuje mnoho užitečných oficiálních dokumentů Pythonu probíraných coroutines a Tasks a Event Loop pomocí integrované knihovny asyncio .

Jako příklad použijte následující knihovnu požadavků . Tento fragment kódu používá knihovnu asyncio k zabalení requests.get() metody do korutiny, která současně spouští více webových požadavků na 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')

Vertikální škálování

Možná budete moct získat více jednotek zpracování, zejména v operacích vázaném na procesor, upgradem na plán Premium s vyššími specifikacemi. S vyššími jednotkami zpracování můžete upravit počet pracovních procesů podle počtu dostupných jader a dosáhnout vyššího stupně paralelismu.

Další kroky

Další informace o vývoji v Pythonu pro Azure Functions najdete v následujících zdrojích informací: