Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Podczas tworzenia aplikacji dla Azure Functions przy użyciu Python musisz zrozumieć, jak działają funkcje i jak wydajność wpływa na sposób skalowania aplikacji funkcji. Potrzeba jest ważniejsza podczas projektowania wysoce wydajnych aplikacji. Głównymi czynnikami, które należy wziąć pod uwagę podczas projektowania, pisania i konfigurowania aplikacji funkcji, są skalowanie poziome i konfiguracje wydajności przetwarzania.
Skalowanie w poziomie
Domyślnie Azure Functions automatycznie monitoruje obciążenie aplikacji i tworzy więcej wystąpień hosta dla Python zgodnie z potrzebami. Azure Functions używa wbudowanych progów dla różnych typów wyzwalaczy, aby zdecydować, w którym dodać instancje, takie jak wiek komunikatów i rozmiar kolejki dla QueueTrigger. Te progi nie są konfigurowalne przez użytkownika. Aby uzyskać więcej informacji, zobacz Skalowanie zorientowane na zdarzenia w Azure Functions.
Zwiększanie wydajności przepustowości
Domyślne konfiguracje są odpowiednie dla większości aplikacji Azure Functions. Można jednak zwiększyć wydajność przepływności aplikacji, stosując konfiguracje na podstawie profilu obciążenia. Pierwszym krokiem jest zrozumienie typu uruchomionego obciążenia.
| Typ obciążenia | Charakterystyka aplikacji funkcyjnej | Przykłady |
|---|---|---|
| Ograniczony przez we/wy | • Aplikacja musi obsługiwać wiele współbieżnych wywołań. • Aplikacja przetwarza dużą liczbę zdarzeń we/wy, takich jak wywołania sieciowe i odczyt/zapisy dysku. |
• Internetowe interfejsy API |
| zależne od procesora | • Aplikacja wykonuje długotrwałe obliczenia, takie jak zmiana rozmiaru obrazu. • Aplikacja wykonuje transformację danych. |
• Przetwarzanie danych • Wnioskowanie uczenia maszynowego |
Ponieważ obciążenia rzeczywistych funkcji są zwykle kombinacją operacji we/wy i związanych z CPU, należy profilować aplikację przy realistycznych obciążeniach produkcyjnych.
Konfiguracje specyficzne dla wydajności
Po zapoznaniu się z profilem obciążenia aplikacji funkcji poniżej przedstawiono konfiguracje, których można użyć do poprawy wydajności przepływności funkcji.
- Async
- Wielojęzyczny pracownik
- Maksymalna liczba procesów wewnątrz procesu roboczego dla języka
- Pętla zdarzeń
- Skalowanie w pionie
Async
Ponieważ Python jest jednowątkowym środowiskiem uruchomieniowym wystąpienie hosta dla Python może przetwarzać tylko jedną wywołanie funkcji w danym momencie. W przypadku aplikacji, które przetwarzają dużą liczbę zdarzeń we/wy i/lub są obciążone operacjami we/wy, można znacznie zwiększyć wydajność, uruchamiając funkcje asynchronicznie.
Aby uruchomić funkcję asynchronicznie, użyj async def instrukcji , która uruchamia funkcję bezpośrednio z asyncio :
async def main():
await some_nonblocking_socket_io_op()
Oto przykład funkcji z wyzwalaczem HTTP, który używa 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)
Funkcja bez słowa kluczowego async jest uruchamiana automatycznie w puli wątków 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 osiągnąć pełną korzyść z uruchamiania funkcji asynchronicznie, operacja we/wy/biblioteka używana w kodzie musi być również zaimplementowana asynchronicznie. Użycie synchronicznych operacji we/wy w funkcjach zdefiniowanych jako asynchroniczne może zaszkodzić ogólnej wydajności. Jeśli biblioteki, których używasz, nie mają zaimplementowanej wersji asynchronicznej, nadal możesz skorzystać z asynchronicznego uruchamiania kodu, zarządzając pętlą zdarzeń w aplikacji.
Oto kilka przykładów bibliotek klienckich, które zaimplementowały wzorce asynchroniczne:
- aiohttp — klient/serwer HTTP dla asyncio
- Interfejs API strumieni — asynchroniczne/gotowe do oczekiwania typy pierwotne wysokiego poziomu do pracy z połączeniem sieciowym
- Janus Queue — kolejka bezpieczna dla wątków, świadoma asyncio dla Pythona
- pyzmq — powiązania Python dla zeroMQ
Opis asynchronicznego procesu roboczego Python
Podczas definiowania async przed podpisem funkcji Python oznacza funkcję jako kohroutynę. Podczas wywoływania kohroutyny można ją zaplanować jako zadanie w pętli zdarzeń. Wywołanie await w funkcji asynchronicznej powoduje zarejestrowanie kontynuacji w pętli zdarzeń, dzięki czemu pętla zdarzeń będzie przetwarzać następne zadanie w czasie oczekiwania.
W naszym Python Worker, worker współużytkuje pętlę zdarzeń z funkcją async klienta i jest zdolny do obsługiwania wielu żądań jednocześnie. Zdecydowanie zachęcamy naszych klientów do korzystania z bibliotek zgodnych z asyncio, takich jak aiohttp i pyzmq. Wykonanie tych zaleceń zwiększa przepływność funkcji w porównaniu z tymi bibliotekami po zaimplementowaniu synchronicznie.
Uwaga / Notatka
Jeśli funkcja jest zadeklarowana jako async bez żadnego await wewnątrz implementacji, wydajność funkcji będzie poważnie pogorszona, ponieważ pętla zdarzeń zostanie zablokowana, co uniemożliwi pracownikowi Pythona obsługę żądań współbieżnych.
Używanie wielu procesów roboczych dla języków
Domyślnie każde wystąpienie hosta Azure Functions ma jeden proces roboczy języka. Przy użyciu FUNCTIONS_WORKER_PROCESS_COUNT ustawienia aplikacji można zwiększyć liczbę procesów roboczych na hosta (do 10). Azure Functions następnie stara się równomiernie rozłożyć równoczesne wywołania funkcji między tymi procesami roboczymi.
W przypadku aplikacji intensywnie korzystających z procesora CPU należy ustawić liczbę procesów roboczych języka na równą lub większą niż liczba rdzeni dostępnych dla aplikacji funkcji. Aby dowiedzieć się więcej, zobacz Dostępne SKU instancji.
Aplikacje wymagające intensywnego użycia operacji we/wy mogą również czerpać korzyści z zwiększenia liczby procesów roboczych, przekraczając liczbę dostępnych rdzeni. Należy pamiętać, że ustawienie zbyt wielu pracowników może mieć wpływ na ogólną wydajność ze względu na zwiększoną liczbę wymaganych przełączeń kontekstu.
FUNCTIONS_WORKER_PROCESS_COUNT dotyczy każdego hosta, który Azure Functions tworzy podczas skalowania aplikacji w celu spełnienia wymagań.
Konfiguracja maksymalnej liczby pracowników w procesie roboczym języka
Jak wspomniano w sekcji async, moduł języka Python traktuje funkcje i korutyny inaczej. Coroutine jest uruchamiana w ramach tej samej pętli zdarzeń, na której działa interpreter języka. Z drugiej strony wywołanie funkcji jest uruchamiane w ramach ThreadPoolExecutor, który jest utrzymywany przez pracownika języka jako wątek.
Wartość maksymalnej liczby procesów roboczych dozwolonych dla uruchamiania funkcji synchronizacji można ustawić przy użyciu ustawienia aplikacji PYTHON_THREADPOOL_THREAD_COUNT . Ta wartość ustawia argument max_worker obiektu ThreadPoolExecutor, który pozwala Python używać puli co najwyżej max_worker wątków do wykonywania wywołań asynchronicznie.
PYTHON_THREADPOOL_THREAD_COUNT dotyczy każdego procesu roboczego tworzonego przez hosta usługi Functions, a Python decyduje, kiedy utworzyć nowy wątek lub ponownie użyć istniejącego bezczynnego wątku. W przypadku starszych wersji Python (czyli 3.8, 3.7 i 3.6) wartość max_worker jest ustawiona na 1. W przypadku wersji Pythona 3.9max_worker jest ustawiona na wartość None.
W przypadku aplikacji powiązanych z procesorem CPU należy zachować ustawienie na małą liczbę, począwszy od 1 i zwiększając się w miarę eksperymentowania z obciążeniem. Ta sugestia polega na skróceniu czasu spędzonego na przełącznikach kontekstowych i umożliwieniu ukończenia zadań powiązanych z procesorem CPU.
W przypadku aplikacji obciążonych operacjami we/wy, możesz osiągnąć znaczne korzyści, zwiększając liczbę wątków pracujących przy każdym wywołaniu. Zaleceniem jest rozpoczęcie od wartości domyślnej Python (liczba rdzeni) + 4, a następnie dostosowanie na podstawie wyświetlanych wartości przepływności.
W przypadku aplikacji dla obciążeń mieszanych należy zrównoważyć zarówno konfiguracje FUNCTIONS_WORKER_PROCESS_COUNT, jak i PYTHON_THREADPOOL_THREAD_COUNT w celu zmaksymalizowania przepływności. Aby zrozumieć, na czym aplikacje funkcji spędzają najwięcej czasu, zalecamy profilowanie tych aplikacji i dostosowywanie ustawień zgodnie z ich zachowaniami. Aby dowiedzieć się więcej o tych ustawieniach aplikacji, zobacz Używanie wielu procesów roboczych.
Uwaga / Notatka
Chociaż te zalecenia dotyczą zarówno funkcji wyzwalanych przez protokół HTTP, jak i tych wyzwalanych przez inne protokoły, może być konieczne dostosowanie specyficznych konfiguracji dla funkcji wyzwalanych przez protokoły inne niż HTTP, aby uzyskać oczekiwaną wydajność z aplikacji funkcji. Aby uzyskać więcej informacji na ten temat, zapoznaj się z tym Najlepsze praktyki dotyczące niezawodnych Azure Functions.
Zarządzanie pętlą zdarzeń
Należy użyć bibliotek innych firm zgodnych z asyncio. Jeśli żadna z bibliotek innych firm nie spełnia Twoich potrzeb, możesz również zarządzać pętlami zdarzeń w Azure Functions. Zarządzanie pętlami zdarzeń zapewnia większą elastyczność w zarządzaniu zasobami obliczeniowymi, a także umożliwia zawijanie synchronicznych bibliotek we/wy do kohroutyn.
Istnieje wiele przydatnych oficjalnych dokumentów Pythona omawiających Coroutines i Tasks oraz Event Loop przy użyciu wbudowanej biblioteki asyncio.
Weźmy na przykład następującą bibliotekę requests, w tym fragmencie kodu użyto biblioteki asyncio do przekształcenia metody requests.get() w coroutine, uruchamiając wiele żądań do SAMPLE_URL jednocześnie.
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')
Skalowanie w pionie
Może być możliwe uzyskanie większej liczby jednostek przetwarzania, zwłaszcza w przypadku operacji powiązanej z procesorem CPU, przez uaktualnienie do planu w warstwie Premium z wyższymi specyfikacjami. Dzięki wyższym jednostkom przetwarzania można dostosować liczbę procesów roboczych zgodnie z liczbą dostępnych rdzeni i osiągnąć wyższy stopień równoległości.
Dalsze kroki
Aby uzyskać więcej informacji na temat programowania Azure Functions Python, zobacz następujące zasoby:
- przewodnik dla deweloperów Azure Functions Python
- Best practices for Azure Functions
- Azure Functions dokumentacja dla deweloperów