Cómo usar Cloud Pub/Sub con Go

Muchas aplicaciones necesitan realizar procesamientos en segundo plano fuera del contexto de una solicitud web. En este caso, la aplicación de muestra de Bookshelf le envía tareas a un trabajador independiente en segundo plano para que las ejecute. El trabajador recibe información desde la API de Google Libros y actualiza la información de los libros en la base de datos. Este ejemplo demuestra cómo configurar servicios independientes en Google App Engine, cómo ejecutar el proceso de un trabajador en el entorno flexible de App Engine y cómo administrar los eventos del ciclo de vida.

Esta página forma parte de un instructivo de varias páginas. Para comenzar desde el principio y leer las instrucciones de configuración, visita la app de Bookshelf para Go.

Configuraciones

  1. Ve al directorio que contiene el código de muestra:

    Linux/macOS

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/getting-started/bookshelf
    

    Windows

    cd %GOPATH%\src\github.com\GoogleCloudPlatform\golang-samples\getting-started\bookshelf
    

  2. Abre config.go para editar.

  3. Borra los comentarios de esta línea:

    // PubsubClient, err = configurePubsub("<your-project-id>")
    
  4. Reemplaza "<your-project-id>" por el ID de tu proyecto.

  5. Guarda y cierra config.go.

Cómo ejecutar la app en la máquina local

  1. Ejecuta la app para iniciar un servidor web local:

    cd app
    go run app.go auth.go template.go
    
  2. En el navegador web, ingresa la siguiente dirección:

    http://localhost:8080

    Puedes ver que la aplicación está ejecutándose, pero no agregues ningún libro todavía.

  3. El trabajador puede iniciarse de la misma manera que la app de frontend después de configurar la variable de entorno PORT:

    Linux/macOS

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/getting-started/bookshelf
    cd pubsub_worker
    PORT=8081 go run worker.go
    

    Windows

    cd %GOPATH%\src\github.com\GoogleCloudPlatform\golang-samples\getting-started\bookshelf
    cd pubsub_worker
    set PORT=8081
    go run worker.go
    

Puedes navegar a http://localhost:8081 para acceder a la instancia del trabajador. La página web del trabajador te muestra tu estado en relación con la cantidad de libros que ha procesado.

Ahora, agrega algunos libros famosos a la estantería. Si estás ejecutando la aplicación y la instancia del trabajador de manera local, puedes mirar cómo el trabajador actualiza la información de los libros en segundo plano.

Es importante que ejecutes el trabajador al menos una vez antes de agregar libros. El trabajador establece una suscripción a Pub/Sub para detectar eventos. Sin la suscripción, los eventos publicados en el tema se perderán y no verás ningún cambio en los datos de Bookshelf. Una vez que exista la suscripción, los eventos se agregarán a la cola, incluso si no hay trabajadores detectando eventos en ese instante. Cuando un trabajador se conecta en línea, Pub/Sub enviará todos los eventos en cola.

Presiona Control+C para salir del servidor web local.

Cómo implementar la app en el entorno flexible de App Engine

  1. En el directorio app, ingresa los siguientes comandos para implementar la muestra:

    Linux/macOS

    $ cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/getting-started/bookshelf
    
    # Deploy the worker
    cd pubsub_worker
    gcloud app deploy
    
    # Deploy the main app
    cd ../app
    gcloud app deploy
    

    Windows

    cd %GOPATH%\src\github.com\GoogleCloudPlatform\golang-samples\getting-started\bookshelf
    
    # Deploy the worker
    cd pubsub_worker
    gcloud app deploy
    
    # Deploy the main app
    cd ..\app
    gcloud app deploy
    

    Esos comandos despliegan los módulos frontend y del trabajador. Esto significa que no tienes que iniciar al trabajador por separado como lo hiciste al ejecutar la aplicación en tu computadora. Puedes leer más sobre los módulos en la documentación de App Engine.

  2. En el navegador web, ingresa la siguiente dirección. Reemplaza [YOUR_PROJECT_ID] por el ID del proyecto:

    https://[YOUR_PROJECT_ID].appspot.com
    
  3. En el navegador web, ingresa la siguiente dirección para llegar a la instancia del trabajador:

    https://worker-dot-[YOUR_PROJECT_ID].appspot.com
    

Si actualizas la app, podrás implementar la versión actualizada mediante el mismo comando que usaste para implementarla por primera vez. La implementación nueva crea una versión nueva de tu app y la convierte a la versión predeterminada. Las versiones anteriores de la app se conservan, al igual que sus instancias de VM asociadas. Ten en cuenta que todas estas instancias de VM y versiones de la app son recursos facturables.

Para reducir costos, borra las versiones no predeterminadas de la app.

Para borrar una versión de una app, haz lo siguiente:

  1. En GCP Console, dirígete a la página Versiones de App Engine.

    Ir a la página de Versiones

  2. Haz clic en la casilla de verificación junto a la versión de app no predeterminada que deseas borrar.
  3. Haz clic en el botón Borrar en la parte superior de la página para borrar la versión de la app.

Para obtener toda la información acerca de la limpieza de los recursos facturables, consulta la sección Limpieza en el paso final de este instructivo.

Estructura de la aplicación

Este diagrama muestra los componentes de la aplicación y la manera en que se conectan entre sí.

Estructura de la muestra de Cloud Pub/Sub

La aplicación publica eventos en Cloud Pub/Sub cada vez que se actualiza un libro en la base de datos. El trabajador, que se ejecuta por separado, detecta estos eventos. Cuando se recibe un evento, el trabajador realiza una solicitud a la API de Libros para obtener información sobre el libro y actualiza el registro del libro en la base de datos. Una vez que se actualice el registro, puedes actualizar la página de información del libro para ver la información nueva.

Cómo comprender el código

En esta sección se explica el código de la aplicación y su funcionamiento.

Cómo crear un tema y una suscripción

Antes de enviar y recibir mensajes en Cloud Pub/Sub, debes crear un tema y una suscripción. Cloud Pub/Sub te permite publicar mensajes sobre un tema para muchos suscriptores. En esta aplicación, sin embargo, solo hay un suscriptor.

Este código crea el tema y la suscripción mediante el uso de nombres de tema y suscripción constantes. Es seguro ejecutar estas dos líneas de código repetidamente. Por ejemplo, worker.go de la app de Bookshelf los llama en la función main:

// Create pubsub topic if it does not yet exist.
topic := bookshelf.PubsubClient.Topic(bookshelf.PubsubTopicID)
exists, err := topic.Exists(ctx)
if err != nil {
	log.Fatalf("Error checking for topic: %v", err)
}
if !exists {
	if _, err := bookshelf.PubsubClient.CreateTopic(ctx, bookshelf.PubsubTopicID); err != nil {
		log.Fatalf("Failed to create topic: %v", err)
	}
}

// Create topic subscription if it does not yet exist.
subscription = bookshelf.PubsubClient.Subscription(subName)
exists, err = subscription.Exists(ctx)
if err != nil {
	log.Fatalf("Error checking for subscription: %v", err)
}
if !exists {
	if _, err = bookshelf.PubsubClient.CreateSubscription(ctx, subName, pubsub.SubscriptionConfig{Topic: topic}); err != nil {
		log.Fatalf("Failed to create subscription: %v", err)
	}
}

Cómo publicar eventos

La aplicación envía un evento al tema que contiene el ID del libro que se actualizó. Esto le informará al trabajador qué libro debe procesar.

// publishUpdate notifies Pub/Sub subscribers that the book identified with
// the given ID has been added/modified.
func publishUpdate(bookID int64) {
	if bookshelf.PubsubClient == nil {
		return
	}

	ctx := context.Background()

	b, err := json.Marshal(bookID)
	if err != nil {
		return
	}
	topic := bookshelf.PubsubClient.Topic(bookshelf.PubsubTopicID)
	_, err = topic.Publish(ctx, &pubsub.Message{Data: b}).Get(ctx)
	log.Printf("Published update to Pub/Sub for Book ID %d: %v", bookID, err)
}

La aplicación llama a la función publishUpdate cada vez que un usuario crea o actualiza un libro.

La aplicación de trabajador

La aplicación worker es independiente y escucha a los eventos de Pub/Sub en vez de atender a una aplicación web orientada al usuario. Esto divide a la aplicación en dos servicios independientes que se comunican a través de Pub/Sub en lugar de directamente entre sí. Separar los servicios te permite configurar y escalar la cantidad de instancias de trabajador y de frontend de manera independiente.

Cómo escuchar eventos

Para recibir mensajes, un trabajador debe crear o usar una suscripción a un tema. El frontend publica los eventos en un tema específico y el trabajador se suscribe a ese mismo tema.

El trabajador utiliza la función subscribe para escuchar eventos y desencadenar el procesamiento:

func subscribe() {
	ctx := context.Background()
	err := subscription.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
		var id int64
		if err := json.Unmarshal(msg.Data, &id); err != nil {
			log.Printf("could not decode message data: %#v", msg)
			msg.Ack()
			return
		}

		log.Printf("[ID %d] Processing.", id)
		if err := update(id); err != nil {
			log.Printf("[ID %d] could not update: %v", id, err)
			msg.Nack()
			return
		}

		countMu.Lock()
		count++
		countMu.Unlock()

		msg.Ack()
		log.Printf("[ID %d] ACK", id)
	})
	if err != nil {
		log.Fatal(err)
	}
}

Procesamiento de libros

Para procesar el libro, el trabajador encuentra el libro según su ID, busca información adicional y luego guarda la información actualizada en la base de datos:

// update retrieves the book with the given ID, finds metata from the Books
// server and updates the database with the book's details.
func update(bookID int64) error {
	book, err := bookshelf.DB.GetBook(bookID)
	if err != nil {
		return err
	}

	vols, err := booksClient.Volumes.List(book.Title).Do()
	if err != nil {
		return err
	}

	if len(vols.Items) == 0 {
		return nil
	}

	info := vols.Items[0].VolumeInfo
	book.Title = info.Title
	book.Author = strings.Join(info.Authors, ", ")
	book.PublishedDate = info.PublishedDate
	if book.Description == "" {
		book.Description = info.Description
	}
	if book.ImageURL == "" && info.ImageLinks != nil {
		url := info.ImageLinks.Thumbnail
		// Replace http with https to prevent Content Security errors on the page.
		book.ImageURL = strings.Replace(url, "http://", "https://", 1)
	}

	return bookshelf.DB.UpdateBook(book)
}

Cómo ejecutar en Cloud Platform

De forma predeterminada, una aplicación que se ejecuta en el entorno flexible de App Engine debe responder a las solicitudes HTTP para indicar un estado saludable para el verificador de estado. Para satisfacer este requisito, el trabajador de pub/sub de Bookshelf escucha las solicitudes HTTP y responde con la cantidad de libros procesados:

// Publish a count of processed requests to the server homepage.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	countMu.Lock()
	defer countMu.Unlock()
	fmt.Fprintf(w, "This worker has processed %d books.", count)
})

port := "8080"
if p := os.Getenv("PORT"); p != "" {
	port = p
}
log.Fatal(http.ListenAndServe(":"+port, nil))

El trabajador necesita su propia configuración de módulos. Esta configuración es similar al archivo app.yaml que se usa para el frontend, pero la diferencia clave es la cláusula service: worker. Las aplicaciones en App Engine pueden tener varios servicios independientes. Significa que puedes implementar, configurar, escalar y actualizar fácilmente partes de tu aplicación de manera independiente.

runtime: go
env: flex
service: worker

resources:
  cpu: .5
  memory_gb: 1.3
  disk_size_gb: 10

automatic_scaling:
  min_num_instances: 1
  max_num_instances: 2
  cool_down_period_sec: 60
  cpu_utilization:
    target_utilization: 0.75

Limpieza

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este instructivo:

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, haz lo siguiente:

  1. En la GCP Console, dirígete a la página Proyectos.

    Ir a la página Proyectos

  2. En la lista de proyectos, selecciona el proyecto que deseas borrar y haz clic en Borrar.
  3. En el cuadro de diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra las versiones no predeterminadas de tu app

Si no quieres borrar tu proyecto, puedes borrar las versiones no predeterminadas de tu app para reducir los costos.

Para borrar una versión de una app, haz lo siguiente:

  1. En GCP Console, dirígete a la página Versiones de App Engine.

    Ir a la página de Versiones

  2. Haz clic en la casilla de verificación junto a la versión de app no predeterminada que deseas borrar.
  3. Haz clic en el botón Borrar en la parte superior de la página para borrar la versión de la app.

Borra tu instancia de Cloud SQL

Para borrar una instancia de Cloud SQL, haz lo siguiente:

  1. En GCP Console, ve a la página SQL Instances.

    Ir a la página SQL Instances.

  2. Selecciona el nombre de la instancia de SQL que quieres borrar.
  3. Haz clic en el botón Borrar en la parte superior de la página para borrar la instancia.

Borra tu depósito de Cloud Storage

Para borrar un depósito de Cloud Storage, haz lo siguiente:

  1. En la GCP Console, dirígete al navegador de Cloud Storage.

    Ir al navegador de Cloud Storage

  2. Haz clic en la casilla de verificación junto al depósito que deseas borrar.
  3. Haz clic en el botón Borrar en la parte superior del depósito.

Pasos siguientes

Obtén información sobre cómo ejecutar la muestra de Bookshelf para Go en Compute Engine.

Prueba otras características de Google Cloud Platform tú mismo. Revisa nuestros instructivos.

¿Te ha resultado útil esta página? Enviar comentarios: