Optymalizacja sieci

Dzięki prostocie Cloud Functions możesz szybko tworzyć kod i uruchamiać go w środowisku bezserwerowym. Przy umiarkowanej skali koszt uruchamiania funkcji jest niski, a optymalizacja kodu może nie wydawać się priorytetem. Jednak w miarę zwiększania skali wdrożenia optymalizacja kodu staje się coraz ważniejsza.

Z tego dokumentu dowiesz się, jak zoptymalizować sieć pod kątem funkcji. Oto niektóre korzyści z optymalizacji sieci:

  • Zmniejszenie czasu procesora poświęcanego na nawiązywanie nowych połączeń wychodzących przy każdym wywołaniu funkcji.
  • Zmniejszenie prawdopodobieństwa wyczerpania limitów połączeń lub DNS limitów.

Utrzymywanie trwałych połączeń

W tej sekcji znajdziesz przykłady utrzymywania trwałych połączeń w funkcji. Jeśli tego nie zrobisz, limity połączeń mogą się szybko wyczerpać.

W tej sekcji omówimy te scenariusze:

  • HTTP/S
  • Interfejsy API Google

Żądania HTTP/S

Poniższy zoptymalizowany fragment kodu pokazuje, jak utrzymywać trwałe połączenia zamiast tworzyć nowe połączenie 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. Przyjmuje obiekt żądania (flask.Request) i zwraca tekst odpowiedzi lub dowolny zestaw wartości, które można przekształcić w obiekt Response za pomocą funkcji make_response.

Dostęp do interfejsów API Google

W poniższym przykładzie używamy Cloud Pub/Sub, ale to podejście działa też w przypadku innych bibliotek klienta, np. Cloud Natural Language lub Cloud Spanner. Pamiętaj, że poprawa skuteczności może zależeć od bieżącej implementacji konkretnych bibliotek klienta.

Utworzenie obiektu klienta Pub/Sub powoduje nawiązanie 1 połączenia i wykonanie 2 zapytań DNS na wywołanie. Aby uniknąć niepotrzebnych połączeń i zapytań DNS, utwórz obiekt klienta Pub/Sub w zakresie globalnym, jak pokazano w poniższym przykładzie:

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 używa instancji biblioteki klienta w pamięci podręcznej, aby zmniejszyć liczbę połączeń wymaganych na wywołanie funkcji. Przyjmuje obiekt żądania (flask.Request) i zwraca tekst odpowiedzi lub dowolny zestaw wartości, które można przekształcić w obiekt Response za pomocą make_response.

Zmienna środowiskowa GCP_PROJECT jest ustawiana automatycznie w środowisku wykonawczym Python 3.7. W nowszych środowiskach wykonawczych pamiętaj, aby określić ją podczas wdrażania funkcji. Więcej informacji znajdziesz w artykule Konfigurowanie zmiennych środowiskowych.

Połączenia wychodzące

Limity czasu żądań wychodzących

W przypadku żądań z funkcji do sieci VPC obowiązuje limit czasu wynoszący 10 minut bezczynności. W przypadku żądań z funkcji do internetu obowiązuje limit czasu wynoszący 20 minut bezczynności.

Resetowanie połączeń wychodzących

Strumienie połączeń z funkcji do sieci VPC i internetu mogą być czasami zamykane i zastępowane, gdy podstawowa infrastruktura jest ponownie uruchamiana lub aktualizowana. Jeśli aplikacja ponownie wykorzystuje długotrwałe połączenia, zalecamy skonfigurowanie jej tak, aby ponownie nawiązywała połączenia, aby uniknąć ponownego użycia nieaktywnego połączenia.

Testowanie obciążenia funkcji

Aby zmierzyć, ile połączeń wykonuje średnio funkcja, wdróż ją jako funkcję HTTP i użyj platformy do testowania wydajności, aby wywoływać ją z określoną liczbą zapytań na sekundę. Jedną z możliwych opcji jest Artillery, którą można wywołać za pomocą jednego wiersza:

$ artillery quick -d 300 -r 30 URL

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

Po przeprowadzeniu testu sprawdź wykorzystanie limitu połączeń na stronie limitów interfejsu Cloud Functions API w Cloud Console. Jeśli wykorzystanie jest stale na poziomie około 30 (lub jego wielokrotności), oznacza to, że przy każdym wywołaniu nawiązujesz 1 (lub kilka) połączeń. Po zoptymalizowaniu kodu na początku testu powinno wystąpić tylko kilka (10–30) połączeń.

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