Sdílet prostřednictvím


Zvýšení výkonu propustnosti aplikací Python v Azure Functions

Při vývoji pro Azure Functions pomocí Python je potřeba pochopit, jak vaše funkce fungují a jak tento výkon ovlivňuje způsob škálování 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í

Ve výchozím nastavení Azure Functions automaticky monitoruje zatížení vaší aplikace a podle potřeby vytvoří více instancí hostitele pro Python. Azure Functions používá integrované prahové hodnoty pro různé typy aktivačních událostí k automatickému rozhodnutí, 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 Uventované škálování v 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 spouštíte nebo provozujete.

Typ úlohy Charakteristiky aplikace funkcí Příklady
omezený vstupně-výstupními operacemi • 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
• Inference strojového učení

Vzhledem k tomu, že pracovní zátěže reálného světa jsou obvykle kombinací vstupně-výstupních operací a náročné na CPU, měli byste aplikaci profilovat při realistickém produkčním zatížení.

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, instance hostitele pro Python může ve výchozím nastavení zpracovat 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 streamů – základní asynchronní/await-ready primitivy pro práci se síťovým připojením
  • Janus Queue – vláknově bezpečná fronta s podporou asyncio pro Python
  • pyzmq – vazby Python pro ZeroMQ
Pochopení asynchronního programování v Pythonu workeru

Když před hlavičkou funkce definujete async, Python funkci označí jako korutinu. Když voláte korutinu, můžete ji synchronizovat jako úkol do cyklu událostí. Když zavoláte await v asynchronní funkci, zaregistruje se pokračování ve smyčce událostí, což umožňuje smyčce událostí zpracovat další úkol během doby čekání.

V našem pracovníkovi v Pythonu sdílí pracovník smyčku událostí s funkcí zákazníka async a je schopný 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éhokoli await uvnitř implementace, bude výkon vaší funkce vážně ovlivněn, protože smyčka událostí bude blokována, což zakáže pracovní proces Python zpracovávat souběžné požadavky.

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

Ve výchozím nastavení má každá instance hostitele Služby Functions jeden pracovní proces jazyka. Pomocí nastavení aplikace můžete zvýšit počet pracovních procesů na hostitele (až 10) pomocí nastavení aplikace 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 jazykových pracovníků tak, aby byl stejný nebo vyšší než počet jader dostupných pro každou aplikační funkci. 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čů.

FUNCTIONS_WORKER_PROCESS_COUNT se vztahuje na každý hostitel, který Azure Functions vytvoří při škálování aplikace tak, aby vyhověla poptávce.

Nastavení maximálního počtu pracovníků v rámci procesu jazykového pracovníka

Jak je uvedeno v asynchronní sekci, procesor jazyka Python pracuje s funkcemi a korutinami 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í argument max_worker objektu ThreadPoolExecutor, který umožňuje Python použít fond maximálně max_worker vláken ke asynchronnímu provádění volání. PYTHON_THREADPOOL_THREAD_COUNT platí 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. U starších verzí Python(to znamená 3.8, 3.7 a 3.6), je hodnota max_worker nastavená na 1. U Python verze 3.9 je max_worker nastavena na None.

U aplikací vázaných na procesor byste měli zachovat nastavení na nízké číslo, počínaje 1 a postupně ho zvyšovat, jak budete experimentovat s vaší ú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 nastavením Pythonu (tj. počet jader) + 4 a poté provádět úpravy na základě hodnot propustnosti, které pozorujete.

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é 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í v 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, které se zabývají Coroutines a Tasks a Event Loop pomocí integrované knihovny asyncio.

Jako příklad použijte knihovnu následujících požadavků . Tento fragment kódu používá knihovnu asyncio k zabalení requests.get() metody do korutiny a souběžné spuštění 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 Azure Functions Python najdete v následujících zdrojích informací: