W tym dokumencie znajdziesz sprawdzone metody projektowania, implementowania, testowania i wdrażania Cloud Functions.
Poprawność
Ta sekcja zawiera ogólne sprawdzone metody projektowania i wdrażania Cloud Functions.
Pisanie funkcji idempotentnych
Funkcje powinny dawać ten sam wynik, nawet jeśli są wywoływane wielokrotnie. Dzięki temu możesz ponownie wywołać funkcję, jeśli poprzednie wywołanie nie powiodło się w połowie wykonywania kodu. Więcej informacji znajdziesz w artykule o powtarzaniu 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 zwróci wartość lub w inny sposób zasygnalizuje zakończenie, np. przez wywołanie argumentu callback
w funkcjach sterowanych zdarzeniami w Node.js. Żaden kod uruchamiany po łagodnym zakończeniu nie ma dostępu do procesora i nie będzie się rozwijać.
Ponadto, gdy w tym samym środowisku zostanie wywołana kolejna metoda, Twoja aktywność w tle zostanie wznowiona, co zakłóci nowe wywołanie. Może to prowadzić do nieoczekiwanego działania i błędów, które są trudne do zdiagnozowania. Dostęp do sieci po zakończeniu działania funkcji zwykle powoduje zresetowanie połączeń (kod błędu ECONNRESET
).
Aktywność w tle można często wykryć w dziennikach poszczególnych wywołań, wybierając wszystko, co zostało zapisane po wierszu informującym o zakończeniu wywołania. Aktywność w tle może być czasami ukryta głębiej w kodzie, zwłaszcza gdy występują operacje asynchroniczne, takie jak funkcje zwrotne lub minutniki. Sprawdź kod, aby upewnić się, że wszystkie operacje asynchroniczne zostały zakończone, zanim zakończysz funkcję.
Zawsze usuwaj pliki tymczasowe
Pamięć lokalna dysku w katalogu tymczasowym to pamięć podręczna w pamięci operacyjnej. Tworzone przez Ciebie pliki zużywają pamięć dostępną dla funkcji i czasami są przechowywane między wywołaniami. Nieusunięcie tych plików może w ostatecznym rozrachunku doprowadzić do błędu braku pamięci i następnego zimnego uruchomienia.
Pamięć używaną przez poszczególne funkcje możesz sprawdzić, wybierając je na liście funkcji w konsoli Google Cloud i klikając wykres Wykorzystanie pamięci.
Jeśli potrzebujesz dostępu do długoterminowego miejsca do przechowywania, rozważ użycie Cloud Run mountów wolumenów z Cloud Storage lub wolumenów NFS.
Możesz zmniejszyć wymagania dotyczące pamięci podczas przetwarzania większych plików za pomocą rurociągu. Możesz na przykład przetworzyć plik w Cloud Storage, tworząc strumień odczytu, przekazując go przez proces oparty na strumieniach i zapisując strumień wyjściowy bezpośrednio w Cloud Storage.
Platforma funkcji
Aby zapewnić spójne instalowanie tych samych zależności we wszystkich środowiskach, zalecamy uwzględnienie biblioteki Functions Framework w menedżerze pakietów i przypięcie zależności do konkretnej wersji Functions Framework.
Aby to zrobić, dodaj preferowaną wersję do odpowiedniego pliku blokady (na przykład package-lock.json
w przypadku Node.js lub requirements.txt
w przypadku Pythona).
Jeśli funkcja Functions Framework nie jest wymieniona jako zależność, zostanie automatycznie dodana podczas procesu kompilacji, korzystając z najnowszej dostępnej wersji.
Narzędzia
W tej sekcji znajdziesz wskazówki dotyczące korzystania z narzędzi do implementowania, testowania i interakcji z usługą Cloud Functions.
Lokalny proces programowania
Wdrażanie funkcji zajmuje trochę czasu, dlatego często szybciej jest przetestować kod funkcji lokalnie.
Deweloperzy Firebase mogą korzystać z emulacji wiersza poleceń Cloud Functions Firebase.Wysyłanie e-maili za pomocą Sendgrid
Cloud Functions nie zezwala na połączenia wychodzące przez port 25, więc nie można nawiązywać niezabezpieczonych połączeń z serwerem SMTP. Zalecaną metodą wysyłania e-maili jest korzystanie z usługi zewnętrznej, takiej jak SendGrid. Inne opcje wysyłania e-maili znajdziesz w samouczku wysyłania e-maili z instancji na temat Google Compute Engine.
Skuteczność
W tej sekcji znajdziesz sprawdzone metody optymalizacji skuteczności.
Unikaj niskiej liczby jednoczesnych użytkowników
Uruchomienia „na zimno” są kosztowne, dlatego możliwość ponownego użycia niedawno uruchomionych instancji podczas nagłego wzrostu obciążenia to świetna optymalizacja pozwalająca radzić sobie z obciążeniem. Ograniczenie współbieżności ogranicza możliwości wykorzystania istniejących instancji, co powoduje większą liczbę uruchamiania „na zimno”.
Zwiększanie współbieżności pomaga odroczyć wiele żądań na instancję, co ułatwia obsługę skumulowanych obciążeń.Rozważnie korzystaj z zależności
Funkcja nie ma stanu, dlatego środowisko wykonawcze jest często inicjowane od podstaw (podczas tzw. uruchamiania na zimno). Gdy nastąpi uruchomienie „na zimno”, funkcja jest oceniana w kontekście globalnym.
Jeśli Twoje funkcje importują moduły, czas ich wczytywania może wydłużać opóźnienie wywołania podczas uruchamiania „na zimno”. Możesz zmniejszyć to opóźnienie, a także czas potrzebny do wdrożenia funkcji, prawidłowo wczytując zależności i nie wczytując zależności, których Twoja funkcja nie używa.
Używanie zmiennych globalnych do ponownego używania obiektów w przyszłych wywołaniach
Nie ma gwarancji, że stan funkcji zostanie zachowany na potrzeby przyszłych wywołań. Jednak Cloud Functions często wykorzystuje środowisko wykonania z poprzedniego wywołania. Jeśli zadeklarujesz zmienną w zakresie globalnym, jej wartość może być używana ponownie w kolejnych wywołaniach bez konieczności ponownego obliczania.
Dzięki temu możesz przechowywać w pamięci podręcznej obiekty, które mogą być kosztowne do odtworzenia przy każdym wywołaniu funkcji. Przeniesienie takich obiektów z ciał funkcji do zakresu globalnego może znacznie poprawić wydajność. W tym przykładzie ciężki obiekt jest tworzony tylko raz na instancję funkcji i udostępniany wszystkim wywołaniom funkcji, które docierają 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 przechowywanie w pamięci podręcznej połączeń sieciowych, odwołań do bibliotek i obiektów klienta interfejsu API w zakresie globalnym. Przykłady znajdziesz w sekcji 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 aplikacja jest wrażliwa na opóźnienia, zalecamy ustawienie minimalnej liczby instancji i dokończenie inicjalizacji w momencie wczytywania.
Więcej informacji o tych opcjach w czasie wykonywania znajdziesz w artykule Kontrolowanie zachowania skalowania.Informacje o uruchomieniu „na zimno” i inicjalizacji
Inicjowanie globalne odbywa się w momencie wczytywania. Bez tego pierwsze żądanie musiałoby przeprowadzić inicjalizację i załadować moduły, co wydłużyłoby czas oczekiwania.
Inicjowanie globalne ma jednak również wpływ na uruchomienia „na zimno”. Aby zminimalizować ten wpływ, inicjuj tylko to, co jest potrzebne do pierwszego żądania, aby opóźnienie pierwszego żądania było jak najmniejsze.
Jest to szczególnie ważne, jeśli skonfigurujesz minimalną liczbę instancji zgodnie z opisem powyżej w przypadku funkcji wrażliwej na opóźnienia. W takim przypadku wykonanie inicjalizacji w momencie wczytywania i zapisanie przydatnych danych do pamięci podręcznej sprawia, że pierwsza prośba nie musi być inicjowana i jest obsługiwana z niskim opóźnieniem.
Jeśli inicjujesz zmienne w zakresie globalnym, w zależności od języka długi czas inicjowania może powodować 2 zachowania: – w przypadku niektórych kombinacji języków i bibliotek asynchronicznych framework funkcji może działać asynchronicznie i zwracać natychmiast, co powoduje, że kod nadal działa w tle, co może powodować problemy, takie jak brak możliwości uzyskania dostępu do procesora. Aby tego uniknąć, należy zablokować inicjalizację modułu, jak opisano poniżej. Dzięki temu żądania nie są też obsługiwane, dopóki nie zakończy się inicjalizacja. – z drugiej strony, jeśli inicjalizacja jest synchroniczna, długi czas inicjalizacji spowoduje dłuższe uruchomienia „na zimno”, co może być problemem, zwłaszcza w przypadku funkcji o niskiej współbieżności podczas skoków obciążenia.
Przykład wstępnego wczytania asynchronicznej biblioteki node.js
Node.js z Firestore to przykład asynchronicznej biblioteki node.js. Aby korzystać z min_instances, kod ten kończy wczytywanie i inicjowanie w momencie wczytywania, blokując wczytywanie modułu.
Używa TLA, co oznacza, że wymagany jest ES6. Możesz użyć rozszerzenia .mjs
dla 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 zlazy-initialized globals. Funkcja ta przyjmuje obiekt żądania (flask.Request
) i zwraca tekst odpowiedzi lub dowolny zbiór 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 pojedynczym pliku zdefiniujesz kilka funkcji, a różne funkcje używają różnych zmiennych. Jeśli nie użyjesz leniwej inicjalizacji, możesz marnować zasoby na zmienne, które są inicjowane, ale nigdy nie używane.
Dodatkowe materiały
Więcej informacji o optymalizacji wydajności znajdziesz w filmie „Google Cloud Performance Atlas” Cloud Functions Czas uruchamiania z zimnego stanu.