Optymalizacja sieci

Prostota Cloud Functions pozwala szybko opracowywać kod i uruchamiać go w środowisku bez serwera. W umiarkowanym zakresie koszt uruchamiania funkcji jest niski, a optymalizacja kodu może nie wydawać się priorytetem. Jednak wraz ze zwiększaniem skali wdrożenia optymalizacja kodu staje się coraz ważniejsza.

W tym dokumencie opisano, jak zoptymalizować sieć pod kątem funkcji. Oto kilka korzyści płynących z optymalizacji sieci:

  • Skrócenie czasu procesora poświęcanego na nawiązywanie nowych połączeń wychodzących w przypadku każdego wywołania funkcji.
  • Zmniejsz prawdopodobieństwo wyczerpania limitu połączeń lub limitów DNS.

Utrzymywanie trwałych połączeń

W tej sekcji znajdziesz przykłady utrzymywania trwałych połączeń w funkcji. Jeśli tego nie zrobisz, szybko wyczerpiesz limit połączeń.

W tej sekcji omówiono te scenariusze:

  • HTTP/S
  • Interfejsy API Google

Żądania HTTP/S

Poniżej podany jest zoptymalizowany fragment kodu, który pokazuje, jak utrzymywać trwałe połączenia zamiast tworzyć nowe połączenia przy każdym wywołaniu funkcji:

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!")
    

Ta funkcja HTTP używa puli połączeń do wysyłania żądań HTTP. 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.

Dostęp do interfejsów API Google

Przykład poniżej korzysta z Cloud Pub/Sub, ale to podejście działa też w przypadku innych bibliotek klienta, takich jak Cloud Natural Language czy Cloud Spanner. Pamiętaj, że poprawa wydajności może zależeć od bieżącej implementacji poszczególnych bibliotek klienta.

Utworzenie obiektu klienta Pub/Sub powoduje jedno połączenie i 2 zapytania DNS na wywołanie. Aby uniknąć niepotrzebnych połączeń i zapytań DNS, utwórz obiekt klienta Pub/Sub w zakresie globalnym, jak pokazano w przykładzie poniżej:

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")
    

Ta funkcja HTTP korzysta z pamięci podręcznej w przypadku instancji biblioteki klienta, aby zmniejszyć liczbę połączeń wymaganych na wywołanie funkcji. 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.

Zmienna środowiskowa GCP_PROJECT jest ustawiana automatycznie w środowisku wykonawczym Pythona 3.7. W późniejszych wersjach runtime’a pamiętaj, aby podać go podczas wdrażania funkcji. Zapoznaj się z artykułem Konfigurowanie zmiennych środowiskowych.

Połączenia wychodzące

Limity czasu żądań wychodzących

W przypadku żądań wysyłanych przez Twoją funkcję do sieci VPC występuje limit czasu po 10 minutach bezczynności. W przypadku żądań wysyłanych przez Twoją funkcję do internetu występuje limit czasu po 20 minutach bezczynności.

Resetowanie połączeń wychodzących

Strumienie połączeń z funkcją do sieci VPC i do internetu mogą być od czasu do czasu zamykane i zastępowane, gdy podstawowa infrastruktura zostanie ponownie uruchomiona lub zaktualizowana. Jeśli aplikacja używa wielokrotnie połączeń długotrwałych, zalecamy skonfigurowanie jej tak, aby ponownie nawiązywała połączenia, aby uniknąć ponownego korzystania z nieaktywnych połączeń.

Testowanie obciążenia funkcji

Aby zmierzyć, ile połączeń tworzy funkcja w średniej, po prostu wdróż ją jako funkcję HTTP i używ do jej wywołania określonej platformy do testowania wydajności. Jednym z możliwych wyborów jest Artillery, którą możesz wywołać za pomocą pojedynczego wiersza:

$ artillery quick -d 300 -r 30 URL

To polecenie pobiera podany adres URL z częstotliwością 30 zapytań na sekundę przez 300 sekund.

Po przeprowadzeniu testu sprawdź wykorzystanie limitu połączeń na Cloud Functions stronie limitu interfejsu API w Cloud Console. Jeśli wykorzystanie jest stale na poziomie około 30 (lub wielokrotności tej wartości), oznacza to, że na każde wywołanie nawiązujesz 1 (lub więcej) połączenie. Po optymalizacji kodu zobaczysz kilka (10–30) połączeń tylko na początku testu.

Na tej samej stronie możesz też porównać koszt procesora przed i po optymalizacji na wykresie dotyczącej limitu procesora.