Netzwerke optimieren

Die Einfachheit von Cloud Functions ermöglicht es Ihnen, Code schnell zu entwickeln und in einer serverlosen Umgebung auszuführen. Bei mittlerer Skalierung sind die Kosten für das Ausführen von Funktionen gering und es besteht möglicherweise kein unmittelbarer Bedarf dafür, den Code zu optimieren. Je größer Ihre Bereitstellung jedoch wird, desto wichtiger wird auch die Optimierung des Codes.

In diesem Dokument wird beschrieben, wie Sie das Netzwerk für Ihre Funktionen optimieren. Eine Netzwerkoptimierung bieten unter anderem folgende Vorteile:

  • Sie können die CPU-Zeit reduzieren, die für das Erstellen neuer ausgehender Verbindungen bei jedem Funktionsaufruf benötigt wird.
  • Sie können die Wahrscheinlichkeit verringern, dass Kontingente für Verbindungen oder DNS aufgebraucht werden.

Persistente Verbindungen aufrechterhalten

In diesem Abschnitt wird anhand von Beispielen gezeigt, wie Sie in einer Funktion persistente Verbindungen aufrechterhalten. Ohne persistente Verbindungen können die Verbindungskontingente schnell aufgebraucht sein.

Die folgenden Szenarien werden in diesem Abschnitt behandelt:

  • HTTP/S
  • Google APIs

HTTP/S-Anfragen

Mit dem folgenden optimierten Code-Snippet wird veranschaulicht, wie Sie persistente Verbindungen aufrechterhalten, anstatt bei jedem Funktionsaufruf eine neue Verbindung zu erstellen:

Node.js

const http = require('http');
const functions = require('firebase-functions');

// Setting the `keepAlive` option to `true` keeps
// connections open between function invocations
const agent = new http.Agent({keepAlive: true});

exports.function = functions.https.onRequest((request, response) => {
    req = http.request({
        host: '',
        port: 80,
        path: '',
        method: 'GET',
        agent: agent, // Holds the connection open after the first invocation
    }, res => {
        let rawData = '';
        res.setEncoding('utf8');
        res.on('data', chunk => { rawData += chunk; });
        res.on('end', () => {
            response.status(200).send(`Data: ${rawData}`);
        });
    });
    req.on('error', e => {
        response.status(500).send(`Error: ${e.message}`);
    });
    req.end();
});

Python

from firebase_functions import https_fn
import requests

# Create a global HTTP session (which provides connection pooling)
session = requests.Session()

@https_fn.on_request()
def connection_pooling(request):

    # The URL to send the request to
    url = "http://example.com"

    # Process the request
    response = session.get(url)
    response.raise_for_status()
    return https_fn.Response("Success!")
    

Diese HTTP-Funktion verwendet einen Verbindungspool, um HTTP-Anfragen zu stellen. Es nimmt ein Anfrageobjekt (flask.Request) an und gibt den Antworttext oder einen beliebigen Satz von Werten zurück, der mit make_response in ein Response-Objekt umgewandelt werden kann.

Auf Google APIs zugreifen

Im folgenden Beispiel wird Cloud Pub/Sub eingesetzt. Der Ansatz funktioniert jedoch auch für andere Clientbibliotheken, z. B. Cloud Natural Language oder Cloud Spanner. Die erzielten Leistungsverbesserungen können von der aktuellen Implementierung bestimmter Clientbibliotheken abhängen.

Beim Erstellen eines Pub/Sub-Clientobjekts werden eine Verbindung und zwei DNS-Abfragen pro Aufruf erzeugt. Erstellen Sie das Pub/Sub-Clientobjekt global, um unnötige Verbindungen und DNS-Abfragen zu vermeiden, wie im folgenden Beispiel gezeigt:

Node.js

const PubSub = require('@google-cloud/pubsub');
const functions = require('firebase-functions');
const pubsub = PubSub();

exports.function = functions.https.onRequest((req, res) => {
    const topic = pubsub.topic('');

    topic.publish('Test message', err => {
        if (err) {
            res.status(500).send(`Error publishing the message: ${err}`);
        } else {
            res.status(200).send('1 message published');
        }
    });
});

Python

import os

from firebase_functions import https_fn
from google.cloud import pubsub_v1

# from firebase_functions import https_fn
# Create a global Pub/Sub client to avoid unneeded network activity
pubsub = pubsub_v1.PublisherClient()

@https_fn.on_request()
def gcp_api_call(request):

    project = os.getenv("GCP_PROJECT")
    request_json = request.get_json()

    topic_name = request_json["topic"]
    topic_path = pubsub.topic_path(project, topic_name)

    # Process the request
    data = b"Test message"
    pubsub.publish(topic_path, data=data)

    return https_fn.Response("1 message published")
    

Diese HTTP-Funktion verwendet eine im Cache gespeicherte Instanz einer Clientbibliothek, um die Anzahl der Verbindungen zu reduzieren, die pro Funktionsaufruf benötigt werden. Es nimmt ein Anfrageobjekt (flask.Request) an und gibt den Antworttext oder einen beliebigen Satz von Werten zurück, der mit make_response in ein Response-Objekt umgewandelt werden kann.

Die Umgebungsvariable GCP_PROJECT wird in der Python 3.7-Laufzeit automatisch festgelegt. Bei neueren Laufzeiten müssen Sie dies bei der Funktionsbereitstellung angeben. Weitere Informationen finden Sie unter Umgebungsvariablen konfigurieren.

Ausgehende Verbindungen

Zeitlimits für ausgehende Anfragen

Bei Anfragen von Ihrer Funktion an das VPC-Netzwerk kommt es nach 10 Minuten Inaktivität zu einer Zeitüberschreitung. Bei Anfragen von Ihrer Funktion an das Internet kommt es nach 20 Minuten Inaktivität zu einer Zeitüberschreitung.

Ausgehende Verbindungen werden zurückgesetzt

Verbindungsstreams von Ihrer Funktion zu VPC und dem Internet können gelegentlich beendet und ersetzt werden, wenn die zugrunde liegende Infrastruktur neu gestartet oder aktualisiert wird. Wenn Ihre Anwendung langlebige Verbindungen wiederverwendet, sollten Sie Ihre Anwendung so konfigurieren, dass Verbindungen wiederhergestellt werden, um eine Wiederverwendung einer inaktiven Verbindung zu vermeiden.

Lasttests für eine Funktion ausführen

Wenn Sie messen möchten, wie viele Verbindungen im Durchschnitt von einer Funktion erstellt werden, können Sie sie einfach als HTTP-Funktion bereitstellen und in einem Leistungstest-Framework bei einer bestimmten Anzahl von Abfragen pro Sekunde aufrufen. Dazu können Sie z. B. Artillery verwenden, das sich mit einer einzigen Zeile aufrufen lässt:

$ artillery quick -d 300 -r 30 URL

Mit diesem Befehl wird die angegebene URL 300 Sekunden lang bei 30 Abfragen pro Sekunde abgerufen.

Nach dem Test überprüfen Sie die Nutzungswerte Ihres Verbindungskontingents in der Cloud Console auf der Kontingentseite der Cloud Functions API. Wenn die Nutzung beständig bei 30 Abfragen oder einem Vielfachen davon liegt, erstellt die Funktion mit jedem Aufruf eine (oder mehrere) Verbindungen. Nach der Optimierung des Codes sollten nur zu Beginn des Tests ein paar wenige Verbindungen (10–30) erzeugt werden.

Auf derselben Seite können Sie im Diagramm für das CPU-Kontingent auch die CPU-Kosten vor und nach der Optimierung vergleichen.