Ausführungsumgebung von Cloud Functions-Funktionen

Cloud Functions-Funktionen werden in einer vollständig verwalteten, serverlosen Umgebung ausgeführt, in der Google sich um die Verwaltung der Infrastruktur, Betriebssysteme und Laufzeitumgebungen kümmert. Jede Cloud Functions-Funktion wird in ihrem eigenen isolierten, sicheren Ausführungskontext bereitgestellt und automatisch skaliert. Ihr Lebenszyklus ist von dem anderer Funktionen unabhängig.

Laufzeiten

Cloud Functions unterstützt mehrere Sprachlaufzeiten. Sie benötigen den Laufzeit-ID-Wert, wenn Sie Funktionen über die Befehlszeile oder über Terraform bereitstellen.

Laufzeit Basis-Image Laufzeit-ID
Node.js 16 (empfohlen) Ubuntu 18.04 nodejs16
Node.js 14 Ubuntu 18.04 nodejs14
Node.js 12 Ubuntu 18.04 nodejs12
Node.js 10 Ubuntu 18.04 nodejs10
Node.js 8 (verworfen) Ubuntu 18.04 nodejs8
Node.js 6 (außer Betrieb genommen) Debian 8 nodejs6
Python 3.10 (empfohlen) Ubuntu 22.04 python310
Python 3.9 Ubuntu 18.04 python39
Python 3.8 Ubuntu 18.04 python38
Python 3.7 Ubuntu 18.04 python37
Go 1.16 (empfohlen) Ubuntu 18.04 go116
Go 1.13 Ubuntu 18.04 go113
Go 1.11 Ubuntu 18.04 go111
Java 17 (empfohlen) Ubuntu 22.04 Java17
Java 11 Ubuntu 18.04 java11
.NET Core 3.1 (empfohlen) Ubuntu 18.04 dotnet3
Ruby 3.0 (empfohlen) Ubuntu 18.04 ruby30
Ruby 2.7 Ubuntu 18.04 ruby27
Ruby 2.6 Ubuntu 18.04 ruby26
PHP 8.1 (empfohlen) Ubuntu 18.04 php81
PHP 7.4 Ubuntu 18.04 php74

Sofern nicht anders angegeben, werden Aktualisierungen und Sicherheitspatches beim Bereitstellen einer Funktion auf Laufzeiten und deren Abhängigkeiten angewendet. Dazu gehören Aktualisierungen und Patches, die von einer Sprachcommunity erstellt werden und nach einem Testzeitraum auf Stabilität verfügbar sind. In gleicher Weise kann Cloud Functions Aktualisierungen auf andere Aspekte der Ausführungsumgebung anwenden, z. B. das Betriebssystem oder eingeschlossene Pakete. Diese Aktualisierungen tragen zur Sicherheit Ihrer Funktion bei.

Verhalten von Autoscaling

Cloud Functions sind serverlos, Sie müssen sich bei der Codeausführung also nicht um die darunterliegende Infrastruktur wie Server oder virtuelle Maschinen kümmern. Nach der Bereitstellung werden die Funktionen automatisch verwaltet und skaliert.

Cloud Functions verarbeitet eingehende Anfragen, indem diese den Instanzen Ihrer Funktion zugewiesen werden. Abhängig von der Anzahl der Anfragen sowie der Anzahl der vorhandenen Funktionsinstanzen kann Cloud Functions eine Anfrage einer vorhandenen Instanz zuweisen oder eine neue erstellen.

Wenn das Volumen der eingehenden Anfragen die Anzahl der vorhandenen Instanzen überschreitet, kann Cloud Functions mehrere neue Instanzen zur Verarbeitung von Anfragen starten. Dieses automatische Skalierungsverhalten ermöglicht es Cloud Functions, viele Anfragen parallel zu verarbeiten, wobei jede eine andere Instanz Ihrer Funktion verwendet.

In einigen Fällen kann eine unbegrenzte Skalierung unerwünscht sein. Zur Behebung dieses Problems können Sie in Cloud Functions eine maximale Anzahl von Instanzen konfigurieren, die zu einer bestimmten Zeit für eine bestimmte Funktion koexistieren können.

Zustandslosigkeit

Wenn Sie die automatische Verwaltung und Skalierung der Funktionen aktivieren möchten, müssen sie zustandslos sein. Ein Funktionsaufruf darf nicht auf dem Speicherzustand basieren, der durch einen vorherigen Aufruf festgelegt wurde. Aufrufe können von verschiedenen Funktionsinstanzen verarbeitet werden, die nicht die gleichen globalen Variablen, Speicher, Dateisysteme oder andere Zustände haben.

Wenn Sie den Status für Funktionsaufrufe freigeben müssen, sollte die Funktion einen Dienst wie Memorystore, Datastore, Firestore oder Cloud Storage verwenden, um Daten zu speichern. Weitere Informationen zu Datenbank- und Speicheroptionen von Google Cloud finden Sie unter Google Cloud-Datenbanken und Google Cloud-Speicherprodukte.

Nebenläufigkeit

Jede Instanz einer Funktion verarbeitet jeweils nur eine einzige Anfrage. Das bedeutet, dass keine zweite Anfrage an dieselbe Instanz weitergeleitet werden kann, während Ihr Code eine Anfrage verarbeitet. Die ursprüngliche Anfrage kann also die gesamte von Ihnen zugewiesene Anzahl von Ressourcen (Speicher und CPU) nutzen.

Da gleichzeitige Anfragen von verschiedenen Funktionsinstanzen verarbeitet werden, teilen diese keine Variablen oder lokalen Arbeitsspeicher. Weitere Informationen finden Sie unter Zustandslosigkeit und Lebensdauer von Funktionsinstanzen.

Kaltstarts

Eine neue Funktionsinstanz wird in zwei Fällen gestartet:

  • Wenn Sie die Funktion bereitstellen

  • Wenn die Funktion als Reaktion auf eine gestiegene Arbeitslast oder als Ersatz für eine andere Instanz automatisch erstellt wird.

Beim Starten einer neuen Funktionsinstanz werden die Laufzeit und der Code geladen. Anfragen, die den Start einer Funktionsinstanz (Kaltstart) enthalten, können langsamer sein als Anfragen, die an vorhandene Funktionsinstanzen weitergeleitet werden. Wenn Ihre Funktion eine konstante Workload verarbeitet, ist die Anzahl der Kaltstarts normalerweise vernachlässigbar, es sei denn, sie stürzt häufig ab und die Funktionsumgebung muss neu gestartet werden.

Wenn Ihr Funktionscode eine nicht abgefangene Ausnahme auslöst oder den aktuellen Prozess zum Abstürzen bringt, kann die Funktionsinstanz neu gestartet werden. Das kann zu Kaltstarts und damit einer höheren Latenz führen. Daher empfehlen wir, Ausnahmen abzufangen und andernfalls die Beendigung des aktuellen Prozesses zu vermeiden. Unter Fehler melden erfahren Sie, wie Sie Fehler in Cloud Functions verarbeiten und melden.

Wenn Ihre Funktion latenzempfindlich ist, sollten Sie eine Mindestzahl von Instanzen festlegen, um Kaltstarts zu vermeiden.

Lebensdauer einer Funktionsinstanz

Funktionsinstanzen sind in der Regel ausfallsicher und werden von nachfolgenden Funktionsaufrufen wiederverwendet, es sei denn, die Anzahl der Instanzen wird verkleinert, da der laufende Traffic fehlt oder die Funktion abstürzt. Das bedeutet, wenn eine Funktionsausführung endet, kann dieselbe Funktionsinstanz einen weiteren Funktionsaufruf verarbeiten.

Funktionsbereich oder globaler Bereich

Beim Aufrufen einer einzelnen Funktion wird nur der als Einstiegspunkt deklarierte Funktionsteil ausgeführt. Der globale Geltungsbereich des Funktionsquellcodes wird nur bei Kaltstarts und nicht bei Instanzen ausgeführt, die bereits initialisiert wurden.

Node.js

// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
exports.scopeDemo = (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

Python

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # Per-function scope
    # This computation runs every time this function is called
    function_var = light_computation()
    return 'Instance: {}; function: {}'.format(instance_var, function_var)

Go


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class Scopes implements HttpFunction {
  // Global (instance-wide) scope
  // This computation runs at instance cold-start.
  // Warning: Class variables used in functions code must be thread-safe.
  private static final int INSTANCE_VAR = heavyComputation();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Per-function scope
    // This computation runs every time this function is called
    int functionVar = lightComputation();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Instance: %s; function: %s", INSTANCE_VAR, functionVar);
  }

  private static int lightComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).sum();
  }

  private static int heavyComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

Ruby

# Global (instance-wide) scope.
# This block runs on cold start, before any function is invoked.
#
# Note: It is usually best to run global initialization in an on_startup block
# instead at the top level of the Ruby file. This is because top-level code
# could be executed to verify the function during deployment, whereas an
# on_startup block is run only when an actual function instance is starting up.
FunctionsFramework.on_startup do
  instance_data = perform_heavy_computation

  # To pass data into function invocations, the best practice is to set a
  # key-value pair using the Ruby Function Framework's built-in "set_global"
  # method. Functions can call the "global" method to retrieve the data by key.
  # (You can also use Ruby global variables or "toplevel" local variables, but
  # they can make it difficult to isolate global data for testing.)
  set_global :my_instance_data, instance_data
end

FunctionsFramework.http "tips_scopes" do |_request|
  # Per-function scope.
  # This method is called every time this function is called.
  invocation_data = perform_light_computation

  # Retrieve the data computed by the on_startup block.
  instance_data = global :my_instance_data

  "instance: #{instance_data}; function: #{invocation_data}"
end

Sie können globale Variablen als Leistungsoptimierung verwenden. Sie dürfen sich jedoch bei vorherigen Funktionsaufrufen nicht auf den im globalen Bereich festgelegten Status verlassen. Weitere Informationen finden Sie unter Zustandslosigkeit.

Sie können davon ausgehen, dass für jede Funktionsinstanz der globale Geltungsbereich genau einmal ausgeführt wurde, bevor Ihr Funktionscode aufgerufen wird. Sie dürfen jedoch nicht von der Gesamtzahl oder dem Zeitpunkt der Ausführungen im globalen Bereich abhängig sein, da diese je nach Autoscaling-Aktivität variieren können.

Zeitlicher Ablauf der Funktionsausführung

Eine Funktion hat nur für die Dauer der Funktionsausführung Zugriff auf die zugewiesenen Ressourcen (Speicher und CPU). Code, der außerhalb des Ausführungszeitraums ausgeführt wird, wird nicht garantiert ausgeführt und kann jederzeit angehalten werden. Daher sollten Sie das Ende der Ausführung der Funktion immer korrekt signalisieren und vermeiden, dass Code darüber hinaus ausgeführt wird. Weitere Informationen finden Sie unter HTTP-Funktionen, Hintergrundfunktionen und CloudEvent-Funktionen.

Die Funktionsausführung unterliegt auch der Zeitüberschreitung der Funktion. Weitere Informationen finden Sie unter Funktionszeitlimit.

Berücksichtigen Sie bei der Initialisierung Ihrer Anwendung den zeitlichen Ablauf der Ausführung. Hintergrundaufgaben sollten während der Initialisierung nicht im globalen Bereich erstellt werden, da sie außerhalb der Dauer einer Anfrage ausgeführt werden.

Ausführungsgarantien

Eine Funktion wird normalerweise einmal für jedes eingehende Ereignis aufgerufen. Cloud Functions kann den einmaligen Aufruf aufgrund von Unterschieden in den Fehlerszenarien aber nicht in allen Fällen garantieren.

Die maximale oder minimale Häufigkeit, mit der eine Funktion für ein einzelnes Ereignis aufgerufen werden kann, hängt von ihrem Typ ab:

  • HTTP-Funktionen werden höchstens einmal aufgerufen. Das liegt daran, dass HTTP-Aufrufe synchron erfolgen. Jeder Fehler, der während des Funktionsaufrufs auftritt, wird ohne einen erneuten Versuch zurückgegeben. Es wird erwartet, dass der Aufrufer der HTTP-Funktion die Fehler behandelt und bei Bedarf den Aufruf wiederholt.

  • Ereignisgesteuerte Funktionen werden mindestens einmal aufgerufen. Das liegt an der asynchronen Behandlung von Ereignissen, bei denen kein Aufrufer auf die Antwort wartet. In seltenen Fällen kann es vorkommen, dass das System eine ereignisgesteuerte Funktion mehr als einmal aufruft, um die Zustellung des Ereignisses sicherzustellen. Wenn ein ereignisbasierter Funktionsaufruf mit einem Fehler fehlschlägt, wird die Funktion nur dann wieder aufgerufen, wenn für diese Funktion Wiederholungsversuche bei Fehlern aktiviert sind.

Damit sich die Funktion bei wiederholten Ausführungsversuchen korrekt verhält, sollten Sie sie idempotent machen, indem Sie sie so implementieren, dass die gewünschten Ergebnisse (und Nebeneffekte) auch dann erzeugt werden, wenn ein Ereignis mehrmals bereitgestellt wird. Für HTTP-Funktionen bedeutet das, dass der gewünschte Wert geliefert wird, selbst wenn der Aufrufer die Aufrufe an den HTTP-Funktionsendpunkt wiederholt. Weitere Informationen dazu, wie Sie Ihre Funktion idempotent machen, finden Sie unter Ereignisgesteuerte Funktionen wiederholen.

Speicher- und Dateisystem

Jeder Funktion ist eine bestimmte Menge an Arbeitsspeicher zugewiesen. Sie können die Speichermenge während der Bereitstellung konfigurieren. Weitere Informationen finden Sie unter Speicherlimits.

Die Funktionsausführungsumgebung enthält ein speicherinternes Dateisystem, das die mit Ihrer Funktion bereitgestellten Quelldateien und Verzeichnisse enthält (siehe Quellcode strukturieren). Das Verzeichnis mit den Quelldateien ist schreibgeschützt, der Rest des Dateisystems ist jedoch beschreibbar (außer für Dateien, die vom Betriebssystem verwendet werden). Die Verwendung des Dateisystems wird auf die Speichernutzung einer Funktion angerechnet.

Ihre Funktion kann mithilfe von Standardmethoden in jeder Programmiersprache mit dem Dateisystem interagieren.

Netzwerk

Die Funktion kann über Standardmethoden in jeder Programmiersprache auf das öffentliche Internet zugreifen, sei es über integrierte Bibliotheken, die von den Laufzeitbibliotheken oder Drittanbieterbibliotheken angeboten werden, die Sie als Abhängigkeiten hinzufügen.

Versuchen Sie, Netzwerkverbindungen in mehreren Funktionsaufrufen wiederzuverwenden, wie unter Netzwerke optimieren beschrieben. Eine Verbindung, die 10 Minuten lang nicht verwendet wurde, wird vom System möglicherweise geschlossen. Weitere Versuche, eine geschlossene Verbindung zu verwenden, führen zu einem Fehler "Verbindung zurückgesetzt". Ihr Code sollte daher entweder eine Bibliothek verwenden, die geschlossene Verbindungen verarbeiten kann, oder diese explizit behandeln, wenn Netzwerkkonstrukte auf niedriger Ebene verwendet werden.

Funktionsisolation

Jede bereitgestellte Funktion ist von allen anderen Funktionen isoliert, selbst wenn diese von derselben Quelldatei stammen. Sie teilen weder Arbeitsspeicher, globale Variablen, Dateisysteme noch andere Zustände.

Zur Freigabe von Daten für alle bereitgestellten Funktionen können Sie Dienste wie Memorystore, Datastore, Firestore oder Cloud Storage verwenden. Alternativ können Sie mit den entsprechenden Triggern eine Funktion aus einer anderen aufrufen und die erforderlichen Daten weitergeben. Führen Sie zum Beispiel eine HTTP-Anfrage an den Endpunkt einer HTTP-Funktion aus oder veröffentlichen Sie eine Nachricht an ein Pub- / Sub-Thema, um eine Pub/Sub-Funktion auszulösen.