Como executar tarefas assíncronas

É possível usar o Cloud Tasks para enfileirar com segurança uma tarefa e processá-la de maneira assíncrona por um serviço do Cloud Run. Os casos de uso típicos incluem:

  • Como preservar solicitações por meio de incidentes de produção inesperados
  • Como suavizar picos de tráfego atrasando o trabalho que não é voltado para o usuário
  • Como acelerar o tempo de resposta do usuário delegando operações lentas em segundo plano para serem processadas por outro serviço, como atualizações de banco de dados ou processamento em lote
  • Como limitar a taxa de chamadas a serviços de apoio, como bancos de dados e APIs de terceiros

Nesta página, mostramos como colocar em fila tarefas que são enviadas com segurança por meio do protocolo HTTPS para um serviço particular do Cloud Run. Descrevemos o comportamento necessário para o serviço particular do Cloud Run, as permissões necessárias da conta de serviço, a criação da fila de tarefas e a criação de tarefas.

Antes de começar

Ative a API Cloud Tasks no projeto que você está usando.

Como implantar um serviço do Cloud Run para processar tarefas

Para implantar um serviço que aceite tarefas enviadas para a fila de tarefas, implante o serviço da mesma forma que qualquer outro serviço do Cloud Run. O serviço do Cloud Run precisa retornar um código HTTP 200 para confirmar o processamento adequado da tarefa.

As tarefas serão enviadas para esse serviço do Cloud Run como solicitações HTTPS pelo Cloud Tasks.

Como criar uma fila de tarefas

Para criar uma fila de tarefas, use o comando

gcloud tasks queues create QUEUE-ID

substituindo QUEUE-ID pelo nome que você quer dar à fila de tarefas: ele precisa ser exclusivo no projeto. Se você for solicitado a criar um aplicativo do App Engine no seu projeto, responda y para criá-lo. O Cloud Tasks usa isso para a fila: escolha o mesmo local que você está usando para o serviço do Cloud Run.

A configuração padrão da fila de tarefas funcionará na maioria dos casos. No entanto, é possível definir diferentes limites de taxa e repetir parâmetros, se quiser.

Como criar uma conta de serviço para associar às tarefas

Você precisa criar uma conta de serviço que será associada às tarefas enfileiradas. Essa conta de serviço precisa ter o papel do IAM de Chamador do Cloud Run para permitir que a fila de tarefas envie tarefas para o serviço Cloud Run. .

Console

  1. No Console do Cloud, acesse a página Contas de serviço.

    Acessar a página "Contas de serviço"

  2. Selecione um projeto.

  3. Insira um nome de conta de serviço a ser exibido no Console do Cloud.

    O Console do Cloud gerará um ID de conta de serviço com base nesse nome. Edite o ID se for necessário. Não será possível alterar o ID depois.

  4. Opcional: digite uma descrição da conta de serviço.

  5. Clique em Criar.

  6. Clique no campo Selecionar um papel.

  7. Em Todos os papéis, selecione Cloud Run > Cloud Run Invoker.

  8. Clique em Concluído.

Linha de comando

  1. Crie a conta de serviço:

    gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \
       --display-name "DISPLAYED_SERVICE_ACCOUNT_NAME"

    Substitua:

    • SERVICE_ACCOUNT_NAME por um nome em letras minúsculas exclusivo no projeto do Google Cloud, por exemplo, my-invoker-service-account-name.
    • DISPLAYED_SERVICE_ACCOUNT_NAME pelo nome que você quer exibir para essa conta de serviço, por exemplo, no console, My Invoker Service Account.
  2. Para o Cloud Run, conceda à conta de serviço permissão para invocar o serviço:

    gcloud run services add-iam-policy-binding SERVICE \
       --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
       --role=roles/run.invoker

    Substitua:

    • SERVICE pelo nome do serviço que você quer que seja invocado pelo Cloud Tasks;
    • SERVICE_ACCOUNT_NAME pelo nome da conta de serviço;
    • PROJECT_ID pelo ID de projeto do Google Cloud.

Como criar tarefas HTTP com tokens de autenticação

Ao criar uma tarefa para enviar à fila de tarefas, você especifica o projeto, o local, o nome da fila, o e-mail da conta de serviço criada anteriormente para associar a tarefas, o URL do serviço particular do Cloud Run que executará a tarefa e quaisquer outros dados que seja necessário enviar.

Consulte a documentação da API Cloud Tasks para ver detalhes sobre o corpo da solicitação de tarefa. Observe que as solicitações que contêm payloads de dados precisam usar o método HTTP PUT ou POST.

O código que enfileira as tarefas precisa ter as permissões de IAM necessárias para fazer isso, como o papel de Enfileirador do Cloud Tasks. Seu código terá as permissões de IAM necessárias se você usar a conta de serviço padrão no Cloud Run.

Os exemplos a seguir mostram como criar solicitações de tarefa que também incluem a criação de um token de cabeçalho. Os tokens OIDC são usados nos exemplos abaixo. Se for um token OAuth, substitua o parâmetro OIDC pelo parâmetro OAuth apropriado da linguagem ao criar uma solicitação.

Python

"""Create a task for a given queue with an arbitrary payload."""

from google.cloud import tasks_v2

# Create a client.
client = tasks_v2.CloudTasksClient()

# TODO(developer): Uncomment these lines and replace with your values.
# project = 'my-project-id'
# queue = 'my-queue'
# location = 'us-central1'
# url = 'https://example.com/task_handler?param=value'
# audience = 'https://example.com/task_handler'
# service_account_email = 'service-account@my-project-id.iam.gserviceaccount.com';
# payload = 'hello'

# Construct the fully qualified queue name.
parent = client.queue_path(project, location, queue)

# Construct the request body.
task = {
    "http_request": {  # Specify the type of request.
        "http_method": tasks_v2.HttpMethod.POST,
        "url": url,  # The full url path that the task will be sent to.
        "oidc_token": {
            "service_account_email": service_account_email,
            "audience": audience,
        },
    }
}

if payload is not None:
    # The API expects a payload of type bytes.
    converted_payload = payload.encode()

    # Add the payload to the request.
    task["http_request"]["body"] = converted_payload

# Use the client to build and send the task.
response = client.create_task(request={"parent": parent, "task": task})

print("Created task {}".format(response.name))
return response

Observe o arquivo requirements.txt:

google-cloud-tasks==2.7.1

Java

import com.google.cloud.tasks.v2.CloudTasksClient;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.cloud.tasks.v2.HttpRequest;
import com.google.cloud.tasks.v2.OidcToken;
import com.google.cloud.tasks.v2.QueueName;
import com.google.cloud.tasks.v2.Task;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.nio.charset.Charset;

public class CreateHttpTaskWithToken {

  public static void main(String[] args) throws IOException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project-id";
    String locationId = "us-central1";
    String queueId = "my-queue";
    String serviceAccountEmail =
        "java-docs-samples-testing@java-docs-samples-testing.iam.gserviceaccount.com";
    createTask(projectId, locationId, queueId, serviceAccountEmail);
  }

  // Create a task with a HTTP target and authorization token using the Cloud Tasks client.
  public static void createTask(
      String projectId, String locationId, String queueId, String serviceAccountEmail)
      throws IOException {

    // Instantiates a client.
    try (CloudTasksClient client = CloudTasksClient.create()) {
      String url =
          "https://example.com/taskhandler"; // The full url path that the request will be sent to
      String payload = "Hello, World!"; // The task HTTP request body

      // Construct the fully qualified queue name.
      String queuePath = QueueName.of(projectId, locationId, queueId).toString();

      // Add your service account email to construct the OIDC token.
      // in order to add an authentication header to the request.
      OidcToken.Builder oidcTokenBuilder =
          OidcToken.newBuilder().setServiceAccountEmail(serviceAccountEmail);

      // Construct the task body.
      Task.Builder taskBuilder =
          Task.newBuilder()
              .setHttpRequest(
                  HttpRequest.newBuilder()
                      .setBody(ByteString.copyFrom(payload, Charset.defaultCharset()))
                      .setHttpMethod(HttpMethod.POST)
                      .setUrl(url)
                      .setOidcToken(oidcTokenBuilder)
                      .build());

      // Send create task request.
      Task task = client.createTask(queuePath, taskBuilder.build());
      System.out.println("Task created: " + task.getName());
    }
  }
}

Observe o arquivo pom.xml:

<?xml version='1.0' encoding='UTF-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.google.cloud</groupId>
  <artifactId>cloudtasks-snippets</artifactId>
  <packaging>jar</packaging>
  <name>Google Cloud Tasks Snippets</name>
  <url>https://github.com/googleapis/java-tasks</url>

  <!--
    The parent pom defines common style checks and testing strategies for our samples.
    Removing or replacing it should not affect the execution of the samples in anyway.
  -->
  <parent>
    <groupId>com.google.cloud.samples</groupId>
    <artifactId>shared-configuration</artifactId>
    <version>1.0.23</version>
  </parent>

  <properties>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>24.1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-tasks</artifactId>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.google.truth</groupId>
      <artifactId>truth</artifactId>
      <version>1.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Go

import (
	"context"
	"fmt"

	cloudtasks "cloud.google.com/go/cloudtasks/apiv2"
	taskspb "google.golang.org/genproto/googleapis/cloud/tasks/v2"
)

// createHTTPTaskWithToken constructs a task with a authorization token
// and HTTP target then adds it to a Queue.
func createHTTPTaskWithToken(projectID, locationID, queueID, url, email, message string) (*taskspb.Task, error) {
	// Create a new Cloud Tasks client instance.
	// See https://godoc.org/cloud.google.com/go/cloudtasks/apiv2
	ctx := context.Background()
	client, err := cloudtasks.NewClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewClient: %v", err)
	}
	defer client.Close()

	// Build the Task queue path.
	queuePath := fmt.Sprintf("projects/%s/locations/%s/queues/%s", projectID, locationID, queueID)

	// Build the Task payload.
	// https://godoc.org/google.golang.org/genproto/googleapis/cloud/tasks/v2#CreateTaskRequest
	req := &taskspb.CreateTaskRequest{
		Parent: queuePath,
		Task: &taskspb.Task{
			// https://godoc.org/google.golang.org/genproto/googleapis/cloud/tasks/v2#HttpRequest
			MessageType: &taskspb.Task_HttpRequest{
				HttpRequest: &taskspb.HttpRequest{
					HttpMethod: taskspb.HttpMethod_POST,
					Url:        url,
					AuthorizationHeader: &taskspb.HttpRequest_OidcToken{
						OidcToken: &taskspb.OidcToken{
							ServiceAccountEmail: email,
						},
					},
				},
			},
		},
	}

	// Add a payload message if one is present.
	req.Task.GetHttpRequest().Body = []byte(message)

	createdTask, err := client.CreateTask(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("cloudtasks.CreateTask: %v", err)
	}

	return createdTask, nil
}

Node.js

// Imports the Google Cloud Tasks library.
const {CloudTasksClient} = require('@google-cloud/tasks');

// Instantiates a client.
const client = new CloudTasksClient();

async function createHttpTaskWithToken() {
  // TODO(developer): Uncomment these lines and replace with your values.
  // const project = 'my-project-id';
  // const queue = 'my-queue';
  // const location = 'us-central1';
  // const url = 'https://example.com/taskhandler';
  // const serviceAccountEmail = 'client@<project-id>.iam.gserviceaccount.com';
  // const payload = 'Hello, World!';

  // Construct the fully qualified queue name.
  const parent = client.queuePath(project, location, queue);

  const task = {
    httpRequest: {
      httpMethod: 'POST',
      url,
      oidcToken: {
        serviceAccountEmail,
      },
    },
  };

  if (payload) {
    task.httpRequest.body = Buffer.from(payload).toString('base64');
  }

  console.log('Sending task:');
  console.log(task);
  // Send create task request.
  const request = {parent: parent, task: task};
  const [response] = await client.createTask(request);
  const name = response.name;
  console.log(`Created task ${name}`);
}
createHttpTaskWithToken();

Observe o arquivo package.json:

{
  "name": "appengine-cloudtasks",
  "description": "Google App Engine Cloud Tasks example.",
  "license": "Apache-2.0",
  "author": "Google Inc.",
  "private": true,
  "engines": {
    "node": ">=10"
  },
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "mocha",
    "start": "node server.js"
  },
  "dependencies": {
    "@google-cloud/tasks": "^2.4.2",
    "body-parser": "^1.18.3",
    "express": "^4.16.3"
  },
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^8.0.0",
    "uuid": "^8.0.0"
  }
}

A seguir