Mengoptimalkan jaringan

Dengan Cloud Functions yang mudah digunakan, Anda dapat mengembangkan kode dengan cepat dan menjalankannya di lingkungan tanpa server. Pada skala sedang, biaya untuk menjalankan fungsi masih rendah dan pengoptimalan kode tidak dianggap sebagai prioritas tinggi. Akan tetapi seiring peningkatan skala penerapan Anda, pengoptimalan kode akan semakin penting.

Dokumen ini menjelaskan cara mengoptimalkan jaringan fungsi Anda. Beberapa manfaat mengoptimalkan jaringan adalah sebagai berikut:

  • Mengurangi waktu CPU yang diperlukan untuk membuat koneksi baru di setiap panggilan fungsi.
  • Mengurangi kemungkinan kehabisan quotas koneksi atau DNS.

Mempertahankan Koneksi Persisten

Bagian ini menunjukkan contoh cara mempertahankan koneksi persisten dalam suatu fungsi. Jika cara ini tidak diikuti, Anda bisa kehabisan kuota koneksi dengan cepat.

Skenario yang dibahas pada bagian ini:

  • HTTP/S
  • Google API

Permintaan HTTP/S

Cuplikan kode yang dioptimalkan di bawah ini menunjukkan cara mempertahankan koneksi yang persisten, bukan membuat koneksi baru pada setiap pemanggilan fungsi:

Node.js

const fetch = require('node-fetch');

const http = require('http');
const https = require('https');

const functions = require('@google-cloud/functions-framework');

const httpAgent = new http.Agent({keepAlive: true});
const httpsAgent = new https.Agent({keepAlive: true});

/**
 * HTTP Cloud Function that caches an HTTP agent to pool HTTP connections.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
functions.http('connectionPooling', async (req, res) => {
  try {
    // TODO(optional): replace this with your own URL.
    const url = 'https://www.example.com/';

    // Select the appropriate agent to use based on the URL.
    const agent = url.includes('https') ? httpsAgent : httpAgent;

    const fetchResponse = await fetch(url, {agent});
    const text = await fetchResponse.text();

    res.status(200).send(`Data: ${text}`);
  } catch (err) {
    res.status(500).send(`Error: ${err.message}`);
  }
});

Python

import functions_framework
import requests

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

@functions_framework.http
def connection_pooling(request):
    """
    HTTP Cloud Function that uses a connection pool to make HTTP requests.
    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>.
    """

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

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

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
	"time"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

var urlString = "https://example.com"

// client is used to make HTTP requests with a 10 second timeout.
// http.Clients should be reused instead of created as needed.
var client = &http.Client{
	Timeout: 10 * time.Second,
}

func init() {
	functions.HTTP("MakeRequest", MakeRequest)
}

// MakeRequest is an example of making an HTTP request. MakeRequest uses a
// single http.Client for all requests to take advantage of connection
// pooling and caching. See https://godoc.org/net/http#Client.
func MakeRequest(w http.ResponseWriter, r *http.Request) {
	resp, err := client.Get(urlString)
	if err != nil {
		http.Error(w, "Error making request", http.StatusInternalServerError)
		return
	}
	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("Bad StatusCode: %d", resp.StatusCode)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "ok")
}

PHP

Sebaiknya gunakan Framework HTTP Guzzle PHP untuk mengirim permintaan HTTP, karena framework tersebut menangani koneksi persisten secara otomatis.

Mengakses Google API

Contoh di bawah menggunakan Cloud Pub/Sub, tetapi pendekatan ini juga dapat diterapkan untuk library klien lainnya, seperti Cloud Natural Language atau Cloud Spanner. Perhatikan bahwa peningkatan performa dapat bergantung pada library klien tertentu yang diimplementasikan saat ini.

Pembuatan objek klien Pub/Sub akan menghasilkan satu koneksi dan dua kueri DNS di setiap panggilan. Untuk menghindari koneksi dan kueri DNS yang tidak diperlukan, buat objek klien Pub/Sub dalam cakupan global, seperti yang ditunjukkan pada contoh di bawah ini:

Node.js

const functions = require('@google-cloud/functions-framework');
const {PubSub} = require('@google-cloud/pubsub');
const pubsub = new PubSub();

/**
 * HTTP Cloud Function that uses a cached client library instance to
 * reduce the number of connections required per function invocation.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} req.body Cloud Function request context body.
 * @param {String} req.body.topic The Cloud Pub/Sub topic to publish to.
 * @param {Object} res Cloud Function response context.
 */
functions.http('gcpApiCall', (req, res) => {
  const topic = pubsub.topic(req.body.topic);

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

Python

import os

import functions_framework
from google.cloud import pubsub_v1

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

@functions_framework.http
def gcp_api_call(request):
    """
    HTTP Cloud Function that uses a cached client library instance to
    reduce the number of connections required per function invocation.
    Args:
        request (flask.Request): The request object.
    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>.
    """

    """
    The `GCP_PROJECT` environment variable is set automatically in the Python 3.7 runtime.
    In later runtimes, it must be specified by the user upon function deployment.
    See this page for more information:
        https://cloud.google.com/functions/docs/configuring/env-var#python_37_and_go_111
    """
    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 "1 message published"

Go


// Package contexttip is an example of how to use Pub/Sub and context.Context in
// a Cloud Function.
package contexttip

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"

	"cloud.google.com/go/pubsub"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

// client is a global Pub/Sub client, initialized once per instance.
var client *pubsub.Client
var once sync.Once

// createClient creates the global pubsub Client
func createClient() {
	// GOOGLE_CLOUD_PROJECT is a user-set environment variable.
	var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
	// err is pre-declared to avoid shadowing client.
	var err error

	// client is initialized with context.Background() because it should
	// persist between function invocations.
	client, err = pubsub.NewClient(context.Background(), projectID)
	if err != nil {
		log.Fatalf("pubsub.NewClient: %v", err)
	}
}

func init() {
	// register http function
	functions.HTTP("PublishMessage", PublishMessage)
}

type publishRequest struct {
	Topic   string `json:"topic"`
	Message string `json:"message"`
}

// PublishMessage publishes a message to Pub/Sub. PublishMessage only works
// with topics that already exist.
func PublishMessage(w http.ResponseWriter, r *http.Request) {
	// use of sync.Once ensures client is only created once.
	once.Do(createClient)
	// Parse the request body to get the topic name and message.
	p := publishRequest{}

	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
		log.Printf("json.NewDecoder: %v", err)
		http.Error(w, "Error parsing request", http.StatusBadRequest)
		return
	}

	if p.Topic == "" || p.Message == "" {
		s := "missing 'topic' or 'message' parameter"
		log.Println(s)
		http.Error(w, s, http.StatusBadRequest)
		return
	}

	m := &pubsub.Message{
		Data: []byte(p.Message),
	}
	// Publish and Get use r.Context() because they are only needed for this
	// function invocation. If this were a background function, they would use
	// the ctx passed as an argument.
	id, err := client.Topic(p.Topic).Publish(r.Context(), m).Get(r.Context())
	if err != nil {
		log.Printf("topic(%s).Publish.Get: %v", p.Topic, err)
		http.Error(w, "Error publishing message", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Message published: %v", id)
}

Pengujian Muatan pada Fungsi Anda

Untuk mengukur rata-rata jumlah koneksi yang dijalankan fungsi Anda, cukup deploy fungsi tersebut sebagai fungsi HTTP dan gunakan framework pengujian performa untuk memanggilnya di QPS tertentu. Salah satu pilihan yang dapat digunakan adalah Artillery, yang dapat Anda panggil dengan satu baris:

$ artillery quick -d 300 -r 30 URL

Perintah ini mengambil URL yang diberikan pada 30 QPS selama 300 detik.

Setelah menjalankan pengujian, periksa pemakaian kuota koneksi Anda di halaman kuota Cloud Functions API di Cloud Console. Jika pemakaian Anda sekitar 30 (atau kelipatannya) secara konsisten, berarti Anda membuat 1 (atau beberapa) koneksi di setiap pemanggilan. Setelah mengoptimalkan kode, seharusnya Anda akan melihat beberapa (10-30) koneksi saja yang terjadi di awal pengujian.

Anda juga bisa membandingkan biaya CPU sebelum dan setelah pengoptimalan plot kuota CPU di halaman yang sama.