Porady i wskazówki

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 ponownym próbie użycia funkcji opartych na zdarzeniach.

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 trudnych do zdiagnozowania błędów. Dostęp do sieci po zakończeniu funkcji zwykle prowadzi do zresetowania połączeń (ECONNRESET kod błędu).

Aktywność w tle można często wykryć w dziennikach poszczególnych wywołań, znajdują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 wywołania zwrotne lub liczniki czasu. Sprawdź kod, aby upewnić się, że wszystkie operacje asynchroniczne zostały zakończone, zanim zakończysz funkcję.

Zawsze usuwaj pliki tymczasowe

Lokalna pamięć dyskowa w katalogu tymczasowym to pamięć podręczna w pamięci. 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.

Nie próbuj zapisywać danych poza katalogiem tymczasowym i używaj metod niezależnych od platformy/systemu operacyjnego do tworzenia ścieżek plików.

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

Gdy wdrażasz funkcję, framework funkcji jest automatycznie dodawany jako zależność, korzystając z jego bieżącej wersji. Aby zapewnić spójne instalowanie tych samych zależności w różnych środowiskach, zalecamy przypięcie funkcji do konkretnej wersji interfejsu Functions Framework.

Aby to zrobić, dodaj preferowaną wersję w odpowiednim pliku blokady (na przykład package-lock.json w przypadku Node.js lub requirements.txt w przypadku Pythona).

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. Zalecane jest wysyłanie e-maili za pomocą usługi SendGrid. Inne opcje wysyłania e-maili znajdziesz w tym samouczku wysyłania e-maili z instancji dotyczącym Google Compute Engine.

Wydajność

W tej sekcji znajdziesz sprawdzone metody optymalizacji skuteczności.

Rozważnie korzystaj z zależności

Funkcja nie ma stanu, dlatego środowisko wykonawcze jest często inicjowane od zera (podczas tzw. uruchamiania na zimno). W przypadku uruchomienia „na zimno” jest oceniany globalny kontekst funkcji.

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 będzie zachowany w przyszłych wywołaniach. Jednak Cloud Functions często odświeża środowisko wykonawcze 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 pobiera 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ą 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 artykule Optymalizacja Networking.

opóźnione inicjowanie zmiennych globalnych,

Jeśli inicjujesz zmienne w zakresie globalnym, kod inicjujący będzie zawsze wykonywany przez wywołanie zimnego startu, co zwiększy opóźnienie funkcji. W niektórych przypadkach powoduje to sporadyczne upływy czasu do wywoływania usług, które nie są odpowiednio obsługiwane w bloku try/catch. Jeśli niektóre obiekty nie są używane na wszystkich ścieżkach kodu, rozważ ich inicjowanie w ramach leniwego inicjowania:

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 zainicjowanych leniwie. 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 zdefiniujesz kilka funkcji w jednym pliku, a różne funkcje korzystają z różnych zmiennych. Jeśli nie korzystasz z leniwego inicjowania, możesz tracić zasoby na zmienne, które są zainicjowane, ale nigdy nie są używane.

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.

Więcej informacji o tych opcjach w czasie wykonywania znajdziesz w artykule Kontrolowanie zachowania skalowania.

Dodatkowe materiały

Więcej informacji o optymalizacji wydajności znajdziesz w filmie „Google Cloud Performance Atlas” poświęcony Cloud Functionsczasowi zimnego rozruchu.