Wskazówki & wydziwianie

W tym dokumencie opisano najlepsze praktyki dotyczące projektowania, wdrażania, testowania i wdrażania Cloud Functions.

Poprawność

W tej sekcji opisano ogólne najlepsze praktyki dotyczące projektowania i wdrażania Cloud Functions.

Napisz funkcje idempotentne

Twoje funkcje powinny dawać ten sam wynik, nawet jeśli są wywoływane wiele razy. Dzięki temu możesz ponowić próbę wywołania, jeśli poprzednie wywołanie nie powiedzie się w trakcie wykonywania kodu. Aby uzyskać więcej informacji, zobacz ponawianie prób funkcji sterowanych zdarzeniami .

Nie rozpoczynaj działań w tle

Aktywność w tle to wszystko, co dzieje się po zakończeniu funkcji. Wywołanie funkcji kończy się, gdy funkcja zwróci lub w inny sposób zasygnalizuje zakończenie, na przykład poprzez wywołanie argumentu callback w funkcjach sterowanych zdarzeniami Node.js. Jakikolwiek kod uruchomiony po łagodnym zakończeniu nie może uzyskać dostępu do procesora i nie spowoduje żadnego postępu.

Ponadto, gdy w tym samym środowisku wykonywane jest kolejne wywołanie, działanie w tle zostaje wznowione, zakłócając nowe wywołanie. Może to prowadzić do nieoczekiwanych zachowań i błędów trudnych do zdiagnozowania. Dostęp do sieci po zakończeniu działania funkcji zwykle prowadzi do zresetowania połączeń (kod błędu ECONNRESET ).

Aktywność w tle często można wykryć w dziennikach poszczególnych wywołań, znajdując wszystko, co jest zarejestrowane po wierszu informującym o zakończeniu wywołania. Aktywność w tle może czasami być ukryta głębiej w kodzie, zwłaszcza gdy obecne są operacje asynchroniczne, takie jak wywołania zwrotne lub liczniki czasu. Przejrzyj swój kod, aby upewnić się, że wszystkie operacje asynchroniczne zostały zakończone przed zakończeniem funkcji.

Zawsze usuwaj pliki tymczasowe

Lokalna pamięć dyskowa w katalogu tymczasowym to system plików w pamięci. Zapisywane pliki zużywają pamięć dostępną dla Twojej funkcji i czasami utrzymują się pomiędzy wywołaniami. Nieusunięcie tych plików może ostatecznie doprowadzić do błędu braku pamięci i późniejszego zimnego startu.

Możesz zobaczyć ilość pamięci wykorzystywanej przez daną funkcję wybierając ją na liście funkcji w Konsoli GCP i wybierając Wykres wykorzystania pamięci .

Nie próbuj pisać poza katalogiem tymczasowym i pamiętaj, aby do tworzenia ścieżek plików używać metod niezależnych od platformy/systemu operacyjnego.

Możesz zmniejszyć wymagania dotyczące pamięci podczas przetwarzania większych plików za pomocą potokowania. Na przykład możesz przetworzyć plik w Cloud Storage, tworząc strumień odczytu, przepuszczając go przez proces oparty na strumieniu i zapisując strumień wyjściowy bezpośrednio w Cloud Storage.

Ramy funkcji

Podczas wdrażania funkcji struktura funkcji jest automatycznie dodawana jako zależność przy użyciu jej bieżącej wersji. Aby mieć pewność, że te same zależności są spójnie instalowane w różnych środowiskach, zalecamy przypięcie funkcji do określonej wersji Framework funkcji.

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

Narzędzia

W tej sekcji znajdują się wskazówki dotyczące używania narzędzi do wdrażania, testowania i interakcji z Cloud Functions.

Rozwój lokalny

Wdrażanie funkcji zajmuje trochę czasu, dlatego często szybsze jest przetestowanie kodu funkcji lokalnie.

Programiści Firebase mogą korzystać z emulatora funkcji chmurowych Firebase CLI .

Użyj Sendgrid do wysyłania e-maili

Cloud Functions nie zezwala na połączenia wychodzące na porcie 25, dlatego nie można nawiązywać niezabezpieczonych połączeń z serwerem SMTP. Zalecanym sposobem wysyłania wiadomości e-mail jest użycie SendGrid . Inne opcje wysyłania wiadomości e-mail znajdziesz w samouczku Wysyłanie wiadomości e-mail z instancji dla Google Compute Engine.

Wydajność

W tej sekcji opisano najlepsze praktyki optymalizacji wydajności.

Używaj zależności mądrze

Ponieważ funkcje są bezstanowe, środowisko wykonawcze jest często inicjowane od zera (podczas tak zwanego zimnego startu ). Kiedy następuje zimny start, oceniany jest globalny kontekst funkcji.

Jeśli funkcje importują moduły, czas ładowania tych modułów może zwiększyć opóźnienie wywołania podczas zimnego startu. Możesz zmniejszyć to opóźnienie, a także czas potrzebny do wdrożenia funkcji, poprawnie ładując zależności i nie ładując zależności, których funkcja nie używa.

Użyj zmiennych globalnych, aby ponownie wykorzystać obiekty w przyszłych wywołaniach

Nie ma gwarancji, że stan Funkcji Cloud zostanie zachowany na potrzeby przyszłych wywołań. Jednak Cloud Functions często odtwarza środowisko wykonawcze poprzedniego wywołania. Jeśli zadeklarujesz zmienną w zasięgu globalnym, jej wartość będzie mogła zostać ponownie użyta w kolejnych wywołaniach bez konieczności ponownego obliczania.

W ten sposób możesz buforować obiekty, których odtworzenie przy każdym wywołaniu funkcji może być kosztowne. Przeniesienie takich obiektów z treści funkcji do zakresu globalnego może skutkować znaczną poprawą wydajności. 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}`);
});

Pyton

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 zestaw wartości, który można przekształcić w obiekt Response za pomocą make_response .

Szczególnie ważne jest buforowanie połączeń sieciowych, odniesień do bibliotek i obiektów klientów API w zakresie globalnym. Przykłady można znaleźć w sekcji Optymalizacja sieci .

Wykonaj leniwą inicjalizację zmiennych globalnych

Jeśli zainicjujesz zmienne w zasięgu globalnym, kod inicjujący będzie zawsze wykonywany poprzez wywołanie zimnego startu, co zwiększy opóźnienie Twojej funkcji. W niektórych przypadkach powoduje to sporadyczne przekroczenia limitu czasu wywoływanych usług, jeśli nie są one odpowiednio obsługiwane w bloku try / catch . Jeśli niektóre obiekty nie są używane we wszystkich ścieżkach kodu, rozważ leniwą inicjalizację ich na żądanie:

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');
});

Pyton

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 leniwie inicjowanych wartości globalnych. Pobiera obiekt żądania ( flask.Request ) i zwraca tekst odpowiedzi lub dowolny zestaw wartości, który można przekształcić w obiekt Response za pomocą make_response .

Jest to szczególnie ważne, jeśli definiujesz kilka funkcji w jednym pliku, a różne funkcje korzystają z różnych zmiennych. Jeśli nie użyjesz leniwej inicjalizacji, możesz marnować zasoby na zmienne, które są inicjowane, ale nigdy nie są używane.

Ogranicz zimne starty, ustawiając minimalną liczbę instancji

Domyślnie Cloud Functions skaluje liczbę instancji na podstawie liczby przychodzących żądań. Możesz zmienić to domyślne zachowanie, ustawiając minimalną liczbę instancji, które Cloud Functions muszą utrzymywać w gotowości do obsługi żądań. Ustawienie minimalnej liczby instancji ogranicza zimne uruchamianie aplikacji. Jeśli aplikacja jest wrażliwa na opóźnienia, zalecamy ustawienie minimalnej liczby instancji.

Aby uzyskać więcej informacji na temat tych opcji środowiska wykonawczego, zobacz Kontrolowanie zachowania skalowania .

Dodatkowe zasoby

Więcej informacji na temat optymalizacji wydajności można znaleźć w filmie „Google Cloud Performance Atlas” Funkcje chmury Czas zimnego rozruchu .