W tym dokumencie opisujemy sprawdzone metody projektowania, wdrażania, testowania i wdrażania Cloud Functions.
Poprawność
W tej sekcji opisujemy ogólne sprawdzone metody projektowania i wdrażaniaCloud Functions.
Pisanie funkcji idempotentnych
Funkcje powinny dawać ten sam wynik, nawet jeśli są wywoływane wielokrotnie. Dzięki temu możesz ponowić wywołanie, jeśli poprzednie wywołanie nie powiedzie się w trakcie wykonywania kodu. Więcej informacji znajdziesz w artykule o ponawianiu funkcji wywoływanych przez zdarzenia.
Nie uruchamiaj aktywności w tle
Aktywność w tle to wszystko, co dzieje się po zakończeniu działania funkcji.
Wywołanie funkcji kończy się, gdy funkcja zwraca wartość lub w inny sposób sygnalizuje zakończenie, np. przez wywołanie argumentu callback
w funkcjach opartych na zdarzeniach w Node.js. Żaden kod uruchomiony po zakończeniu działania nie będzie miał dostępu do procesora i nie będzie wykonywany.
Dodatkowo, gdy kolejne wywołanie zostanie wykonane w tym samym środowisku, aktywność w tle zostanie wznowiona, co zakłóci nowe wywołanie. Może to prowadzić do nieoczekiwanych zachowań i trudnych do zdiagnozowania błędów. Dostęp do sieci po zakończeniu działania funkcji zwykle powoduje zresetowanie połączeń (ECONNRESET
kod błędu).
Aktywność w tle można często wykryć w dziennikach poszczególnych wywołań, wyszukując wszystko, co zostało zarejestrowane po wierszu informującym o zakończeniu wywołania. Aktywność w tle może być czasami głębiej ukryta w kodzie, zwłaszcza w przypadku operacji asynchronicznych, takich jak wywołania zwrotne lub timery. Sprawdź kod, aby upewnić się, że wszystkie operacje asynchroniczne zakończą się przed zakończeniem działania funkcji.
Zawsze usuwaj pliki tymczasowe
Pamięć dysku lokalnego w katalogu tymczasowym to system plików w pamięci. Pliki, które zapisujesz, zajmują pamięć dostępną dla funkcji, a czasami są zachowywane między wywołaniami. Jeśli nie usuniesz tych plików, może to spowodować błąd braku pamięci i zimny start.
Pamięć używaną przez poszczególne funkcje możesz sprawdzić, wybierając je na liście funkcji w konsoli Google Cloud i wybierając wykres Zużycie pamięci.
Jeśli potrzebujesz dostępu do długoterminowego przechowywania, rozważ użycie Cloud Run montowania woluminów z Cloud Storage lub woluminów NFS.
Wymagania dotyczące pamięci podczas przetwarzania większych plików można zmniejszyć za pomocą potokowego przetwarzania danych. Możesz na przykład przetworzyć plik w Cloud Storage, tworząc strumień odczytu, przekazując go przez proces oparty na strumieniu i zapisując strumień wyjściowy bezpośrednio w Cloud Storage.
Platforma funkcji
Aby mieć pewność, że te same zależności są instalowane spójnie w różnych środowiskach, zalecamy uwzględnienie biblioteki Functions Framework w menedżerze pakietów i przypisanie zależności do konkretnej wersji Functions Framework.
Aby to zrobić, uwzględnij preferowaną wersję w odpowiednim pliku blokady (np. package-lock.json
w przypadku Node.js lub requirements.txt
w przypadku Pythona).
Jeśli Functions Framework nie jest wyraźnie wymieniony jako zależność, zostanie automatycznie dodany podczas procesu kompilacji przy użyciu najnowszej dostępnej wersji.
Narzędzia
W tej sekcji znajdziesz wytyczne dotyczące korzystania z narzędzi do wdrażania, testowania i używania Cloud Functions.
Programowanie lokalne
Wdrożenie funkcji zajmuje trochę czasu, więc często szybciej jest przetestować kod funkcji lokalnie.
Deweloperzy Firebase mogą używać emulatora Cloud Functions wiersza poleceń Firebase.Unikanie przekroczenia limitu czasu wdrożenia podczas inicjalizacji
Jeśli wdrażanie funkcji zakończy się niepowodzeniem z powodu przekroczenia limitu czasu, prawdopodobnie oznacza to, że kod w zakresie globalnym funkcji zbyt długo się wykonuje podczas procesu wdrażania.
Interfejs wiersza poleceń Firebase ma domyślny limit czasu na wykrywanie funkcji podczas wdrażania. Jeśli logika inicjowania w kodzie źródłowym funkcji (wczytywanie modułów, wykonywanie wywołań sieciowych itp.) przekroczy ten limit czasu, wdrożenie może się nie powieść.
Aby uniknąć przekroczenia limitu czasu, skorzystaj z jednej z tych strategii:
(Zalecane) użyj onInit()
, aby odroczyć inicjację
Użyj haka onInit()
, aby uniknąć uruchamiania kodu inicjowania podczas wdrażania. Kod wewnątrz haka onInit()
będzie uruchamiany tylko wtedy, gdy funkcja zostanie wdrożona w Cloud Functions, a nie podczas samego procesu wdrażania.
Node.js
const { onInit } = require('firebase-functions/v2/core'); const { onRequest } = require('firebase-functions/v2/https'); // Example of a slow initialization task function slowInitialization() { // Simulate a long-running operation (e.g., loading a large model, network request). return new Promise(resolve => { setTimeout(() => { console.log("Slow initialization complete"); resolve("Initialized Value"); }, 20000); // Simulate a 20-second delay }); } let initializedValue; onInit(async () => { initializedValue = await slowInitialization(); }); exports.myFunction = onRequest((req, res) => { // Access the initialized value. It will be ready after the first invocation. res.send(`Value: ${initializedValue}`); });
Python
from firebase_functions.core import init from firebase_functions import https_fn import time # Example of a slow initialization task def _slow_initialization(): time.sleep(20) # Simulate a 20-second delay print("Slow initialization complete") return "Initialized Value" _initialized_value = None @init def initialize(): global _initialized_value _initialized_value = _slow_initialization() @https_fn.on_request() def my_function(req: https_fn.Request) -> https_fn.Response: # Access the initialized value. It will be ready after the first invocation. return https_fn.Response(f"Value: {_initialized_value}")
(Alternatywnie) Zwiększ czas oczekiwania na wykrycie
Jeśli nie możesz zmodyfikować kodu, aby używać onInit()
, możesz zwiększyć limit czasu wdrażania interfejsu CLI za pomocą zmiennej środowiskowej FUNCTIONS_DISCOVERY_TIMEOUT
:
$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions
Wysyłanie e-maili za pomocą SendGrid
Cloud Functions nie zezwala na połączenia wychodzące na porcie 25, więc nie możesz nawiązywać niezabezpieczonych połączeń z serwerem SMTP. Zalecany sposób wysyłania e-maili to korzystanie z usługi innej firmy, takiej jak SendGrid. Inne opcje wysyłania e-maili znajdziesz w samouczku Wysyłanie e-maili z instancji Google Compute Engine.
Wydajność
W tej sekcji znajdziesz sprawdzone metody optymalizacji skuteczności.
Unikaj niskiej współbieżności
Uruchomienia „na zimno” są kosztowne, więc możliwość ponownego wykorzystania ostatnio uruchomionych instancji podczas nagłego wzrostu obciążenia to świetna optymalizacja, która pozwala sobie z nim poradzić. Ograniczenie współbieżności ogranicza możliwość wykorzystania istniejących instancji, co powoduje więcej uruchomień „na zimno”.
Zwiększenie współbieżności pomaga odraczać wiele żądań na instancję, co ułatwia obsługę skoków obciążenia.Mądrze korzystaj z zależności
Funkcje są bezstanowe, więc środowisko wykonawcze jest często inicjowane od zera (podczas tzw. uruchomienia „na zimno”). Gdy nastąpi uruchomienie „na zimno”, oceniany jest globalny kontekst funkcji.
Jeśli Twoje funkcje importują moduły, czas ładowania tych modułów może zwiększyć opóźnienie wywołania podczas uruchomienia „na zimno”. Możesz skrócić to opóźnienie, a także czas potrzebny na wdrożenie funkcji, prawidłowo wczytując zależności i nie wczytując tych, których funkcja nie używa.
Używanie zmiennych globalnych do ponownego wykorzystywania obiektów w przyszłych wywołaniach
Nie ma gwarancji, że stan funkcji zostanie zachowany w przypadku przyszłych wywołań. Cloud Functions często ponownie wykorzystuje środowisko wykonawcze poprzedniego wywołania. Jeśli zadeklarujesz zmienną w zakresie globalnym, jej wartość może być ponownie używana w kolejnych wywołaniach bez konieczności ponownego obliczania.
W ten sposób możesz przechowywać w pamięci podręcznej obiekty, których ponowne utworzenie przy każdym wywołaniu funkcji może być kosztowne. Przeniesienie takich obiektów z treści funkcji do zakresu globalnego może znacznie zwiększyć wydajność. Poniższy przykład tworzy ciężki obiekt tylko raz na instancję funkcji i udostępnia go wszystkim wywołaniom funkcji docierającym do danej instancji:
Node.js
console.log('Global scope'); const perInstance = heavyComputation(); const functions = require('firebase-functions'); exports.function = functions.https.onRequest((req, res) => { console.log('Function invocation'); const perFunction = lightweightComputation(); res.send(`Per instance: ${perInstance}, per function: ${perFunction}`); });
Python
import time from firebase_functions import https_fn # Placeholder def heavy_computation(): return time.time() # Placeholder def light_computation(): return time.time() # Global (instance-wide) scope # This computation runs at instance cold-start instance_var = heavy_computation() @https_fn.on_request() def scope_demo(request): # Per-function scope # This computation runs every time this function is called function_var = light_computation() return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
Ta funkcja HTTP przyjmuje obiekt żądania (flask.Request
) i zwraca tekst odpowiedzi lub dowolny zestaw wartości, które można przekształcić w obiekt Response
za pomocą funkcji make_response
.
Szczególnie ważne jest buforowanie połączeń sieciowych, odwołań do bibliotek i obiektów klienta interfejsu API w zakresie globalnym. Przykłady znajdziesz w artykule Optymalizacja sieci.
Ograniczanie uruchomień „na zimno” przez ustawienie minimalnej liczby instancji
Domyślnie Cloud Functions skaluje liczbę instancji na podstawie liczby żądań przychodzących. Możesz zmienić to domyślne działanie, ustawiając minimalną liczbę instancji, które Cloud Functions musi utrzymywać w gotowości do obsługi żądań. Ustawienie minimalnej liczby instancji ogranicza uruchomienia „na zimno” aplikacji. Jeśli Twoja aplikacja jest wrażliwa na opóźnienia, zalecamy ustawienie minimalnej liczby instancji i zakończenie inicjowania w momencie wczytywania.
Więcej informacji o tych opcjach środowiska wykonawczego znajdziesz w sekcji Kontrolowanie skalowania.Uwagi dotyczące uruchamiania „na zimno” i inicjowania
Inicjowanie globalne następuje w momencie wczytywania. Bez tego pierwsza prośba musiałaby zakończyć inicjowanie i wczytywanie modułów, co spowodowałoby większe opóźnienie.
Inicjowanie globalne ma jednak również wpływ na uruchomienia „na zimno”. Aby zminimalizować ten wpływ, zainicjuj tylko to, co jest potrzebne do pierwszego żądania, aby utrzymać jak najniższy czas oczekiwania na pierwsze żądanie.
Jest to szczególnie ważne, jeśli skonfigurowano minimalną liczbę instancji zgodnie z opisem powyżej w przypadku funkcji wrażliwej na opóźnienia. W takim przypadku ukończenie inicjowania w momencie wczytywania i buforowanie przydatnych danych sprawia, że pierwsze żądanie nie musi tego robić i jest obsługiwane z niskim czasem oczekiwania.
Jeśli zainicjujesz zmienne w zakresie globalnym, w zależności od języka długi czas inicjowania może powodować 2 rodzaje zachowań: w przypadku niektórych kombinacji języków i bibliotek asynchronicznych platforma funkcji może działać asynchronicznie i natychmiast zwracać wynik, co powoduje dalsze działanie kodu w tle i może prowadzić do problemów, takich jak brak dostępu do procesora. Aby tego uniknąć, zablokuj inicjowanie modułu w sposób opisany poniżej. Dzięki temu żądania nie będą obsługiwane do czasu zakończenia inicjowania. – z drugiej strony, jeśli inicjowanie jest synchroniczne, długi czas inicjowania spowoduje dłuższe uruchomienia „na zimno”, co może stanowić problem, zwłaszcza w przypadku funkcji o niskiej współbieżności podczas skoków obciążenia.
Przykład wstępnego rozgrzewania asynchronicznej biblioteki Node.js
Node.js z Firestore to przykład asynchronicznej biblioteki Node.js. Aby skorzystać z parametru min_instances, poniższy kod kończy wczytywanie i inicjowanie w momencie wczytywania, blokując wczytywanie modułu.
Używany jest TLA, co oznacza, że wymagany jest ES6. Możesz użyć rozszerzenia .mjs
w przypadku kodu Node.js lub dodać type: module
do pliku package.json.
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
Node.js
import Firestore from '@google-cloud/firestore'; import * as functions from '@google-cloud/functions-framework'; const firestore = new Firestore({preferRest: true}); // Pre-warm firestore connection pool, and preload our global config // document in cache. In order to ensure no other request comes in, // block the module loading with a synchronous global request: const config = await firestore.collection('collection').doc('config').get(); functions.http('fetch', (req, res) => { // Do something with config and firestore client, which are now preloaded // and will execute at lower latency. });
Przykłady globalnej inicjalizacji
Node.js
const functions = require('firebase-functions'); let myCostlyVariable; exports.function = functions.https.onRequest((req, res) => { doUsualWork(); if(unlikelyCondition()){ myCostlyVariable = myCostlyVariable || buildCostlyVariable(); } res.status(200).send('OK'); });
Python
from firebase_functions import https_fn # Always initialized (at cold-start) non_lazy_global = file_wide_computation() # Declared at cold-start, but only initialized if/when the function executes lazy_global = None @https_fn.on_request() def lazy_globals(request): global lazy_global, non_lazy_global # This value is initialized only if (and when) the function is called if not lazy_global: lazy_global = function_specific_computation() return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
Ta funkcja HTTP używa globalnych zmiennych inicjowanych z opóźnieniem. Przyjmuje obiekt żądania (flask.Request
) i zwraca tekst odpowiedzi lub dowolny zestaw wartości, które można przekształcić w obiekt Response
za pomocą funkcji make_response
.
Jest to szczególnie ważne, jeśli w jednym pliku definiujesz kilka funkcji, a każda z nich używa innych zmiennych. Jeśli nie używasz leniwej inicjalizacji, możesz marnować zasoby na zmienne, które są inicjowane, ale nigdy nie są używane.
Dodatkowe materiały
Więcej informacji o optymalizacji wydajności znajdziesz w filmie „Google Cloud Performance Atlas” (Cloud FunctionsCzas zimnego rozruchu).