Aktifkan percobaan ulang fungsi berdasarkan peristiwa

Dokumen ini menjelaskan cara mengaktifkan percobaan ulang untuk fungsi berdasarkan peristiwa. Percobaan ulang otomatis tidak tersedia untuk fungsi HTTP.

Semantik percobaan ulang

Fungsi Cloud Run menyediakan setidaknya sekali eksekusi fungsi berbasis peristiwa untuk setiap peristiwa yang dikeluarkan oleh sumber peristiwa. Secara default, jika pemanggilan fungsi dihentikan dengan error, fungsi tersebut tidak akan dipanggil lagi dan peristiwa akan dihapus. Saat Anda mengaktifkan percobaan ulang pada fungsi berbasis peristiwa, fungsi Cloud Run akan mencoba kembali pemanggilan fungsi yang gagal hingga berhasil diselesaikan, atau periode percobaan ulang berakhir.

Periode percobaan ulang ini akan berakhir setelah 24 jam. Fungsi Cloud Run mencoba kembali fungsi berbasis peristiwa yang baru dibuat menggunakan strategi backoff eksponensial, dengan backoff yang meningkat antara 10 dan 600 detik.

Jika percobaan ulang tidak diaktifkan untuk suatu fungsi, yang merupakan setelan default, fungsi tersebut akan selalu melaporkan keberhasilan eksekusinya, dan kode respons 200 OK mungkin muncul dalam log-nya. Ini bisa terjadi meskipun fungsi mengalami error. Untuk medapatkan informasi yang jelas saat fungsi Anda mengalami error, pastikan untuk melaporkan error dengan tepat.

Alasan fungsi berbasis peristiwa gagal diselesaikan

Terkadang, fungsi keluar sebelum waktunya karena terjadi error internal, dan secara default fungsi tersebut akan atau tidak akan dicoba ulang secara otomatis.

Biasanya, fungsi berbasis peristiwa dapat gagal diselesaikan karena terjadi error pada kode fungsi itu sendiri. Alasan terjadinya hal ini meliputi:

  • Fungsi berisi bug dan runtime menampilkan pengecualian.
  • Fungsi tersebut tidak dapat menjangkau endpoint layanan, atau waktu habis saat mencoba melakukannya.
  • Fungsi sengaja menampilkan pengecualian (misalnya, saat parameter menggagalkan validasi).
  • Fungsi Node.js menampilkan promise yang ditolak, atau meneruskan nilai non-null ke callback.

Dalam kasus ini, fungsi akan berhenti dijalankan secara default dan peristiwa itu akan dihapus. Untuk mencoba kembali fungsi ini saat terjadi error, Anda dapat mengubah kebijakan coba lagi default dengan menyetel properti "coba lagi jika gagal". Hal ini menyebabkan peristiwa tersebut dicoba ulang berulang kali hingga fungsi berhasil diselesaikan atau waktu tunggu percobaan ulang berakhir.

Mengaktifkan atau menonaktifkan percobaan ulang

Untuk mengaktifkan atau menonaktifkan percobaan ulang, Anda dapat menggunakan alat command line gcloud atau Google Cloud Console. Secara default, percobaan ulang dinonaktifkan.

Konfigurasikan percobaan ulang dari alat command line gcloud

Untuk mengaktifkan percobaan ulang menggunakan alat command line gcloud, sertakan flag --retry saat men-deploy fungsi Anda:

gcloud functions deploy FUNCTION_NAME --retry FLAGS...

Untuk menonaktifkan percobaan ulang, deploy ulang fungsi tanpa tanda --retry:

gcloud functions deploy FUNCTION_NAME FLAGS...

Mengonfigurasi percobaan ulang dari konsol

Jika Anda membuat fungsi baru:

  1. Dari layar Create Function, di bagian Trigger, pilih jenis peristiwa yang akan bertindak sebagai pemicu untuk fungsi Anda.
  2. Centang kotak Retry on failure untuk mengaktifkan percobaan ulang.

Jika Anda mengupdate fungsi yang sudah ada:

  1. Dari halaman Cloud Run functions Overview, klik nama fungsi yang ingin Anda update untuk membuka layar Function details, lalu pilih Edit dari panel menu untuk menampilkan panel Trigger.
  2. Centang atau hapus centang pada kotak Retry on failure untuk mengaktifkan atau menonaktifkan percobaan ulang.

Praktik terbaik

Bagian ini menjelaskan praktik terbaik untuk menggunakan percobaan ulang.

Menggunakan percobaan ulang untuk menangani error sementara

Karena fungsi terus dicoba ulang hingga berhasil dijalankan, error permanen, seperti bug, harus dihilangkan dari kode Anda melalui pengujian menyeluruh sebelum percobaan ulang diaktifkan. Percobaan ulang paling baik digunakan untuk menangani kegagalan yang terputus-putus atau sementara yang sangat mungkin akan teratasi melalui percobaan ulang, seperti endpoint layanan yang tidak stabil atau waktu tunggu habis.

Menetapkan kondisi akhir agar percobaan ulang tidak terjebak dalam loop tanpa henti

Salah satu praktik terbaik adalah melindungi fungsi Anda dari loop terus-menerus saat menggunakan percobaan ulang. Anda dapat melakukannya dengan menyertakan kondisi akhir yang ditetapkan dengan baik, sebelum fungsi mulai diproses. Ingat bahwa teknik ini hanya berfungsi jika fungsi Anda berhasil dimulai dan dapat mengevaluasi kondisi akhir.

Pendekatan yang sederhana tetapi efektif adalah dengan menghapus peristiwa yang memiliki stempel waktu lebih lama dari rentang tertentu. Langkah ini dapat menghindari eksekusi berlebihan saat kegagalan terus-menerus terjadi atau berlangsung lebih lama daripada perkiraan.

Misalnya, cuplikan kode ini menghapus semua peristiwa yang lebih lama dari 10 detik:

Node.js

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

/**
 * Cloud Event Function that only executes within
 * a certain time period after the triggering event
 *
 * @param {object} event The Cloud Functions event.
 * @param {function} callback The callback function.
 */
functions.cloudEvent('avoidInfiniteRetries', (event, callback) => {
  const eventAge = Date.now() - Date.parse(event.time);
  const eventMaxAge = 10000;

  // Ignore events that are too old
  if (eventAge > eventMaxAge) {
    console.log(`Dropping event ${event} with age ${eventAge} ms.`);
    callback();
    return;
  }

  // Do what the function is supposed to do
  console.log(`Processing event ${event} with age ${eventAge} ms.`);

  // Retry failed function executions
  const failed = false;
  if (failed) {
    callback('some error');
  } else {
    callback();
  }
});

Python

from datetime import datetime, timezone

# The 'python-dateutil' package must be included in requirements.txt.
from dateutil import parser

import functions_framework


@functions_framework.cloud_event
def avoid_infinite_retries(cloud_event):
    """Cloud Event Function that only executes within a certain
    time period after the triggering event.

    Args:
        cloud_event: The cloud event associated with the current trigger
    Returns:
        None; output is written to Stackdriver Logging
    """
    timestamp = cloud_event["time"]

    event_time = parser.parse(timestamp)
    event_age = (datetime.now(timezone.utc) - event_time).total_seconds()
    event_age_ms = event_age * 1000

    # Ignore events that are too old
    max_age_ms = 10000
    if event_age_ms > max_age_ms:
        print("Dropped {} (age {}ms)".format(cloud_event["id"], event_age_ms))
        return "Timeout"

    # Do what the function is supposed to do
    print("Processed {} (age {}ms)".format(cloud_event["id"], event_age_ms))
    return  # To retry the execution, raise an exception here

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
)

func init() {
	functions.CloudEvent("FiniteRetryPubSub", FiniteRetryPubSub)
}

// MessagePublishedData contains the full Pub/Sub message
// See the documentation for more details:
// https://cloud.google.com/eventarc/docs/cloudevents#pubsub
type MessagePublishedData struct {
	Message PubSubMessage
}

// PubSubMessage is the payload of a Pub/Sub event.
// See the documentation for more details:
// https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
type PubSubMessage struct {
	Data []byte `json:"data"`
}

// FiniteRetryPubSub demonstrates how to avoid inifinite retries.
func FiniteRetryPubSub(ctx context.Context, e event.Event) error {
	var msg MessagePublishedData
	if err := e.DataAs(&msg); err != nil {
		return fmt.Errorf("event.DataAs: %w", err)
	}

	// Ignore events that are too old.
	expiration := e.Time().Add(10 * time.Second)
	if time.Now().After(expiration) {
		log.Printf("event timeout: halting retries for expired event '%q'", e.ID())
		return nil
	}

	// Add your message processing logic.
	return processTheMessage(msg)
}

Java


import com.google.cloud.functions.CloudEventsFunction;
import io.cloudevents.CloudEvent;
import java.time.Duration;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.logging.Logger;

public class RetryTimeout implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(RetryTimeout.class.getName());
  private static final long MAX_EVENT_AGE = 10_000;

  /**
   * Cloud Event Function that only executes within
   * a certain time period after the triggering event
   */
  @Override
  public void accept(CloudEvent event) throws Exception {
    ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC);
    ZonedDateTime timestamp = event.getTime().atZoneSameInstant(ZoneOffset.UTC);

    long eventAge = Duration.between(timestamp, utcNow).toMillis();

    // Ignore events that are too old
    if (eventAge > MAX_EVENT_AGE) {
      logger.info(String.format("Dropping event with timestamp %s.", timestamp));
      return;
    }

    // Process events that are recent enough
    // To retry this invocation, throw an exception here
    logger.info(String.format("Processing event with timestamp %s.", timestamp));
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.PubSub.V1;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace TimeBoundedRetries;

public class Function : ICloudEventFunction<MessagePublishedData>
{
    private static readonly TimeSpan MaxEventAge = TimeSpan.FromSeconds(10);
    private readonly ILogger _logger;

    // Note: for additional testability, use an injectable clock abstraction.
    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
    {
        string textData = data.Message.TextData;

        DateTimeOffset utcNow = DateTimeOffset.UtcNow;

        // Every PubSub CloudEvent will contain a timestamp.
        DateTimeOffset timestamp = cloudEvent.Time.Value;
        DateTimeOffset expiry = timestamp + MaxEventAge;

        // Ignore events that are too old.
        if (utcNow > expiry)
        {
            _logger.LogInformation("Dropping PubSub message '{text}'", textData);
            return Task.CompletedTask;
        }

        // Process events that are recent enough.
        // If this processing throws an exception, the message will be retried until either
        // processing succeeds or the event becomes too old and is dropped by the code above.
        _logger.LogInformation("Processing PubSub message '{text}'", textData);
        return Task.CompletedTask;
    }
}

Ruby

require "functions_framework"

FunctionsFramework.cloud_event "avoid_infinite_retries" do |event|
  # Use the event timestamp to determine the event age.
  event_age_secs = Time.now - event.time.to_time
  event_age_ms = (event_age_secs * 1000).to_i

  max_age_ms = 10_000
  if event_age_ms > max_age_ms
    # Ignore events that are too old.
    logger.info "Dropped #{event.id} (age #{event_age_ms}ms)"

  else
    # Do what the function is supposed to do.
    logger.info "Handling #{event.id} (age #{event_age_ms}ms)..."
    failed = true

    # Raise an exception to signal failure and trigger a retry.
    raise "I failed!" if failed
  end
end

PHP


/**
 * This function shows an example method for avoiding infinite retries in
 * Google Cloud Functions. By default, functions configured to automatically
 * retry execution on failure will be retried indefinitely - causing an
 * infinite loop. To avoid this, we stop retrying executions (by not throwing
 * exceptions) for any events that are older than a predefined threshold.
 */

use Google\CloudFunctions\CloudEvent;

function avoidInfiniteRetries(CloudEvent $event): void
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    $eventId = $event->getId();

    // The maximum age of events to process.
    $maxAge = 10; // 10 seconds

    // The age of the event being processed.
    $eventAge = time() - strtotime($event->getTime());

    // Ignore events that are too old
    if ($eventAge > $maxAge) {
        fwrite($log, 'Dropping event ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL);
        return;
    }

    // Do what the function is supposed to do
    fwrite($log, 'Processing event: ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL);

    // infinite_retries failed function executions
    $failed = true;
    if ($failed) {
        throw new Exception('Event ' . $eventId . ' failed; retrying...');
    }
}

Membedakan antara fungsi yang dapat dicoba ulang dan error fatal

Jika fungsi Anda mengaktifkan percobaan ulang, error yang tidak ditangani akan memicu percobaan ulang. Pastikan kode Anda menangkap error yang seharusnya tidak menyebabkan percobaan ulang.

Node.js

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

/**
 * Register a Cloud Event Function that demonstrates
 * how to toggle retries using a promise
 *
 * @param {object} event The Cloud Event for the function trigger.
 */
functions.cloudEvent('retryPromise', cloudEvent => {
  // The Pub/Sub event payload is passed as the CloudEvent's data payload.
  // See the documentation for more details:
  // https://cloud.google.com/eventarc/docs/cloudevents#pubsub
  const base64PubsubMessage = cloudEvent.data.message.data;
  const jsonString = Buffer.from(base64PubsubMessage, 'base64').toString();

  const tryAgain = JSON.parse(jsonString).retry;

  if (tryAgain) {
    throw new Error('Retrying...');
  } else {
    console.error('Not retrying...');
    return Promise.resolve();
  }
});

/**
 * Cloud Event Function that demonstrates
 * how to toggle retries using a callback
 *
 * @param {object} event The Cloud Event for the function trigger.
 * @param {function} callback The callback function.
 */
functions.cloudEvent('retryCallback', (cloudEvent, callback) => {
  // The Pub/Sub event payload is passed as the CloudEvent's data payload.
  // See the documentation for more details:
  // https://cloud.google.com/eventarc/docs/cloudevents#pubsub
  const base64PubsubMessage = cloudEvent.data.message.data;
  const jsonString = Buffer.from(base64PubsubMessage, 'base64').toString();

  const tryAgain = JSON.parse(jsonString).retry;
  const err = new Error('Error!');

  if (tryAgain) {
    console.error('Retrying:', err);
    callback(err);
  } else {
    console.error('Not retrying:', err);
    callback();
  }
});

Python

import base64
import json

import functions_framework
from google.cloud import error_reporting


error_client = error_reporting.Client()


@functions_framework.cloud_event
def retry_or_not(cloud_event):
    """Cloud Event Function that demonstrates how to toggle retries.

    Args:
        cloud_event: The cloud event with a Pub/Sub data payload
    Returns:
        None; output is written to Stackdriver Logging
    """

    # The Pub/Sub event payload is passed as the CloudEvent's data payload.
    # See the documentation for more details:
    # https://cloud.google.com/eventarc/docs/cloudevents#pubsub
    encoded_pubsub_message = cloud_event.data["message"]["data"]

    # Retry based on a user-defined parameter
    try_again = json.loads(base64.b64decode(encoded_pubsub_message).decode())["retry"]

    try:
        raise RuntimeError("I failed you")
    except RuntimeError:
        error_client.report_exception()
        if try_again:
            raise  # Raise the exception and try again
        else:
            pass  # Swallow the exception and don't retry

Go

package tips

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
)

func init() {
	functions.CloudEvent("RetryPubSub", RetryPubSub)
}

// MessagePublishedData contains the full Pub/Sub message
// See the documentation for more details:
// https://cloud.google.com/eventarc/docs/cloudevents#pubsub
type MessagePublishedData struct {
	Message PubSubMessage
}

// PubSubMessage is the payload of a Pub/Sub event.
// See the documentation for more details:
// https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
type PubSubMessage struct {
	Data []byte `json:"data"`
}

// RetryPubSub demonstrates how to toggle using retries.
func RetryPubSub(ctx context.Context, e event.Event) error {
	var msg MessagePublishedData
	if err := e.DataAs(&msg); err != nil {
		return fmt.Errorf("event.DataAs: %w", err)
	}

	name := string(msg.Message.Data)
	if name == "" {
		name = "World"
	}

	// A misconfigured client will stay broken until the function is redeployed.
	client, err := MisconfiguredDataClient()
	if err != nil {
		log.Printf("MisconfiguredDataClient (retry denied):  %v", err)
		// A nil return indicates that the function does not need a retry.
		return nil
	}

	// Runtime error might be resolved with a new attempt.
	if err = FailedWriteOperation(client, name); err != nil {
		log.Printf("FailedWriteOperation (retry expected): %v", err)
		// A non-nil return indicates that a retry is needed.
		return err
	}

	return nil
}

Java


import com.google.cloud.functions.CloudEventsFunction;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import functions.eventpojos.PubSubBody;
import io.cloudevents.CloudEvent;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.logging.Logger;

public class RetryPubSub implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(RetryPubSub.class.getName());

  // Use Gson (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  @Override
  public void accept(CloudEvent event) throws Exception {
    if (event.getData() == null) {
      logger.warning("No data found in event!");
      return;
    }

    // Extract Cloud Event data and convert to PubSubBody
    String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
    PubSubBody body = gson.fromJson(cloudEventData, PubSubBody.class);

    String encodedData = body.getMessage().getData();
    String decodedData =
        new String(Base64.getDecoder().decode(encodedData), StandardCharsets.UTF_8);

    // Retrieve and decode PubSubMessage data into a JsonElement.
    // Function is expecting a user-supplied JSON message which determines whether
    // to retry or not.
    JsonElement jsonPubSubMessageElement = gson.fromJson(decodedData, JsonElement.class);

    boolean retry = false;
    // Get the value of the "retry" JSON parameter, if one exists
    if (jsonPubSubMessageElement != null && jsonPubSubMessageElement.isJsonObject()) {
      JsonObject jsonPubSubMessageObject = jsonPubSubMessageElement.getAsJsonObject();

      if (jsonPubSubMessageObject.has("retry")
          && jsonPubSubMessageObject.get("retry").getAsBoolean()) {
        retry = true;
      }
    }

    // Retry if appropriate
    if (retry) {
      // Throwing an exception causes the execution to be retried
      throw new RuntimeException("Retrying...");
    } else {
      logger.info("Not retrying...");
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.PubSub.V1;
using Microsoft.Extensions.Logging;
using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Retry;

public class Function : ICloudEventFunction<MessagePublishedData>
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
    {
        bool retry = false;
        string text = data.Message?.TextData;

        // Get the value of the "retry" JSON parameter, if one exists.
        if (!string.IsNullOrEmpty(text))
        {
            JsonElement element = JsonSerializer.Deserialize<JsonElement>(data.Message.TextData);

            retry = element.TryGetProperty("retry", out var property) &&
                property.ValueKind == JsonValueKind.True;
        }

        // Throwing an exception causes the execution to be retried.
        if (retry)
        {
            throw new InvalidOperationException("Retrying...");
        }
        else
        {
            _logger.LogInformation("Not retrying...");
        }
        return Task.CompletedTask;
    }
}

Ruby

require "functions_framework"

FunctionsFramework.cloud_event "retry_or_not" do |event|
  try_again = event.data["retry"]

  begin
    # Simulate a failure
    raise "I failed!"
  rescue RuntimeError => e
    logger.warn "Caught an error: #{e}"
    if try_again
      # Raise an exception to return a 500 and trigger a retry.
      logger.info "Trying again..."
      raise ex
    else
      # Return normally to end processing of this event.
      logger.info "Giving up."
    end
  end
end

PHP


use Google\CloudFunctions\CloudEvent;

function tipsRetry(CloudEvent $event): void
{
    $cloudEventData = $event->getData();
    $pubSubData = $cloudEventData['message']['data'];

    $json = json_decode(base64_decode($pubSubData), true);

    // Determine whether to retry the invocation based on a parameter
    $tryAgain = $json['some_parameter'];

    if ($tryAgain) {
        /**
         * Functions with automatic retries enabled should throw exceptions to
         * indicate intermittent failures that a retry might fix. In this
         * case, a thrown exception will cause the original function
         * invocation to be re-sent.
         */
        throw new Exception('Intermittent failure occurred; retrying...');
    }

    /**
     * If a function with retries enabled encounters a non-retriable
     * failure, it should return *without* throwing an exception.
     */
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
    fwrite($log, 'Not retrying' . PHP_EOL);
}

Membuat fungsi berbasis peristiwa yang dapat dicoba ulang jadi bersifat idempoten

Fungsi berdasarkan peristiwa yang dapat dicoba lagi harus bersifat idempoten. Berikut adalah beberapa panduan umum untuk membuat fungsi bersifat idempoten:

  • Banyak API eksternal (seperti Stripe) yang dapat Anda gunakan untuk menyuplai kunci idempotensi sebagai parameter. Jika menggunakan API seperti itu, Anda harus menggunakan ID peristiwa sebagai kunci idempotensi.
  • Idempotensi berfungsi dengan baik pada pengiriman "setidaknya sekali" karena memberikan keamanan untuk mencoba ulang. Jadi, salah satu praktik terbaik yang umum untuk menulis kode tepercaya adalah dengan menggabungkan idempotensi dengan percobaan ulang.
  • Pastikan kode Anda idempoten secara internal. Contoh:
    • Pastikan mutasi bisa terjadi lebih dari sekali tanpa mengubah hasilnya.
    • Jalankan kueri pada status database dalam transaksi sebelum memutasikan status tersebut.
    • Pastikan semua efek samping bersifat idempoten.
  • Lakukan pemeriksaan transaksi di luar fungsi, terlepas dari kode. Sebagai contoh, pertahankan status di suatu tempat yang mencatat bahwa ID peristiwa tertentu telah diproses.
  • Tangani panggilan fungsi duplikat yang tidak umum. Misalnya, jalankan proses pembersihan terpisah yang akan melakukan pembersihan setelah panggilan fungsi duplikat.

Mengonfigurasi kebijakan percobaan ulang

Bergantung pada kebutuhan fungsi Cloud Run Anda, kebijakan percobaan ulang juga dapat dikonfigurasi secara langsung. Dengan begitu, Anda akan dapat menyiapkan kombinasi berikut:

  • Mempersingkat periode percobaan ulang dari 7 hari menjadi hanya 10 menit.
  • Mengubah waktu backoff minimum dan maksimum untuk strategi percobaan ulang backoff eksponensial.
  • Mengubah strategi percobaan ulang untuk mencoba ulang dengan seketika.
  • Mengonfigurasi topik yang dihentikan pengirimannya.
  • Menetapkan jumlah upaya pengiriman maksimum dan minimum.

Untuk mengonfigurasi kebijakan percobaan ulang:

  1. Tulis fungsi HTTP.
  2. Gunakan Pub/Sub API untuk membuat langganan Pub/Sub, dengan menentukan URL fungsi sebagai target.

Lihat dokumentasi Pub/Sub mengenai penanganan kegagalan untuk mengetahui informasi selengkapnya tentang cara mengonfigurasi Pub/Sub secara langsung.

Langkah berikutnya