Optymalizacja sieci

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

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

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

Utrzymywanie stałych połączeń

W tej sekcji znajdziesz przykłady tego, jak utrzymywać trwałe połączenia w ramach funkcji. Jeśli tego nie zrobisz, Twoje limity połączeń mogą zostać szybko wyczerpane.

W tej sekcji opisano następujące scenariusze:

  • HTTP/S
  • interfejsów API Google,

Żądania HTTP/S

Zoptymalizowany fragment kodu poniżej 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 wykorzystuje pulę połączeń do wysyłania żądań HTTP. Zabiera 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ą make_response.

Dostęp do interfejsów API Google

Poniższy przykład korzysta z Cloud Pub/Sub, ale to podejście działa też w przypadku innych bibliotek klienta, na przykład Cloud Natural Language lub Cloud Spanner. Pamiętaj, że poprawa wydajności może zależeć od bieżącej implementacji poszczególnych bibliotek klienckich.

Utworzenie obiektu klienta Pub/Sub powoduje utworzenie 1 połączenia i 2 zapytań DNS na wywołanie. Aby uniknąć zbędnych 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 korzysta z instancji biblioteki klienta przechowywanej w pamięci podręcznej, aby zmniejszyć liczbę połączeń wymaganych na jedno wywołanie funkcji. Zabiera 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ą make_response.

Zmienna środowiskowa GCP_PROJECT jest ustawiana automatycznie w środowisku wykonawczym Pythona 3.7. W późniejszych środowiskach wykonawczych określ to podczas wdrażania funkcji. Przeczytaj artykuł Konfigurowanie zmiennych środowiskowych.

Testowanie wczytywania funkcji

Aby zmierzyć, ile połączeń średnio wykonuje Twoja funkcja, po prostu wdróż ją jako funkcję HTTP i użyj platformy do testowania wydajności do wywołania jej przy określonej liczbie zapytań/s. Jednym z możliwych sposobów jest Artillery, który można wywołać, używając jednego wiersza:

$ artillery quick -d 300 -r 30 URL

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

Po przeprowadzeniu testu sprawdź wykorzystanie limitu połączenia na stronie limitów interfejsu Cloud Functions API w konsoli Cloud. Jeśli wykorzystanie jest stale na poziomie około 30 (lub wielokrotności), w przypadku każdego wywołania ustanawiasz co najmniej 1 połączenie. Po zoptymalizowaniu kodu powinno pojawić się kilka połączeń (10–30) dopiero na początku testu.

Na tej samej stronie możesz też porównać koszt procesora przed optymalizacją i po optymalizacji na wykresie wykorzystania procesorów.