Conectarse a una instancia de Redis desde una aplicación del entorno flexible de App Engine.

Las aplicaciones de App Engine deben estar en la misma red autorizada que la instancia de Redis para acceder a ella.

Configuración

Si ya instalaste Google Cloud CLI y creaste una instancia de Redis, puedes omitir estos pasos.

  1. Instala la CLI de gcloud y realiza la inicialización:

    gcloud init
    
  2. Sigue la Guía de inicio rápido para crear una instancia de Redis. Anota la zona, la dirección IP y el puerto de la instancia de Redis.

Aplicación de muestra

Esta aplicación de servidor HTTP de muestra establece una conexión con una instancia de Redis desde una instancia de entorno flexible de App Engine.

Clona el repositorio de tu lenguaje de programación deseado y navega a la carpeta que contiene el código de muestra:

Go

git clone https://github.com/GoogleCloudPlatform/golang-samples
cd golang-samples/memorystore/redis

Java

git clone https://github.com/GoogleCloudPlatform/java-docs-samples
cd java-docs-samples/memorystore/redis

Node.js

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples
cd nodejs-docs-samples/memorystore/redis

Python

git clone https://github.com/GoogleCloudPlatform/python-docs-samples
cd python-docs-samples/memorystore/redis

Esta aplicación de muestra aumenta un contador de Redis cada vez que se accede al extremo /.

Go

En esta aplicación, se usa el cliente github.com/gomodule/redigo/redis. Para instalarla, ejecuta el siguiente comando:

go get github.com/gomodule/redigo/redis

// Command redis is a basic app that connects to a managed Redis instance.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func incrementHandler(w http.ResponseWriter, r *http.Request) {
	conn := redisPool.Get()
	defer conn.Close()

	counter, err := redis.Int(conn.Do("INCR", "visits"))
	if err != nil {
		http.Error(w, "Error incrementing visitor counter", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Visitor number: %d", counter)
}

func main() {
	redisHost := os.Getenv("REDISHOST")
	redisPort := os.Getenv("REDISPORT")
	redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	const maxConnections = 10
	redisPool = &redis.Pool{
		MaxIdle: maxConnections,
		Dial:    func() (redis.Conn, error) { return redis.Dial("tcp", redisAddr) },
	}

	http.HandleFunc("/", incrementHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

Java

Esta aplicación se basa en el servlet Jetty 3.1.

Usa la biblioteca de Jedis:

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>5.1.0</version>
</dependency>

La clase AppServletContextListener se usa para crear un grupo de conexiones de Redis de larga duración:


package com.example.redis;

import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@WebListener
public class AppServletContextListener implements ServletContextListener {

  private Properties config = new Properties();

  private JedisPool createJedisPool() throws IOException {
    String host;
    Integer port;
    config.load(
        Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("application.properties"));
    host = config.getProperty("redis.host");
    port = Integer.valueOf(config.getProperty("redis.port", "6379"));

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // Default : 8, consider how many concurrent connections into Redis you will need under load
    poolConfig.setMaxTotal(128);

    return new JedisPool(poolConfig, host, port);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool != null) {
      jedisPool.destroy();
      event.getServletContext().setAttribute("jedisPool", null);
    }
  }

  // Run this before web application is started
  @Override
  public void contextInitialized(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool == null) {
      try {
        jedisPool = createJedisPool();
        event.getServletContext().setAttribute("jedisPool", jedisPool);
      } catch (IOException e) {
        // handle exception
      }
    }
  }
}

La clase VisitCounterServlet es un servlet web que incrementa un contador de Redis.


package com.example.redis;

import java.io.IOException;
import java.net.SocketException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@WebServlet(name = "Track visits", value = "")
public class VisitCounterServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      JedisPool jedisPool = (JedisPool) req.getServletContext().getAttribute("jedisPool");

      if (jedisPool == null) {
        throw new SocketException("Error connecting to Jedis pool");
      }
      Long visits;

      try (Jedis jedis = jedisPool.getResource()) {
        visits = jedis.incr("visits");
      }

      resp.setStatus(HttpServletResponse.SC_OK);
      resp.getWriter().println("Visitor counter: " + String.valueOf(visits));
    } catch (Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

Node.js

En esta aplicación, se usa el módulo redis.

{
  "name": "memorystore-redis",
  "description": "An example of using Memorystore(Redis) with Node.js",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "redis": "^4.0.0"
  }
}

'use strict';
const http = require('http');
const redis = require('redis');

const REDISHOST = process.env.REDISHOST || 'localhost';
const REDISPORT = process.env.REDISPORT || 6379;

const client = redis.createClient(REDISPORT, REDISHOST);
client.on('error', err => console.error('ERR:REDIS:', err));

// create a server
http
  .createServer((req, res) => {
    // increment the visit counter
    client.incr('visits', (err, reply) => {
      if (err) {
        console.log(err);
        res.status(500).send(err.message);
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(`Visitor number: ${reply}\n`);
    });
  })
  .listen(8080);

Python

Esta aplicación usa Flask para la entrega web y el paquete redis-py a fin de comunicarse con la instancia de Redis.

Flask==3.0.3
gunicorn==22.0.0
redis==5.0.1
Werkzeug==3.0.3
import logging
import os

from flask import Flask
import redis

app = Flask(__name__)

redis_host = os.environ.get("REDISHOST", "localhost")
redis_port = int(os.environ.get("REDISPORT", 6379))
redis_client = redis.StrictRedis(host=redis_host, port=redis_port)


@app.route("/")
def index():
    value = redis_client.incr("counter", 1)
    return f"Visitor number: {value}"


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )


if __name__ == "__main__":
    # This is used when running locally. Gunicorn is used to run the
    # application on Google App Engine and Cloud Run.
    # See entrypoint in app.yaml or Dockerfile.
    app.run(host="127.0.0.1", port=8080, debug=True)

Preparar la aplicación para la implementación

Para acceder a la instancia de Redis, la instancia de App Engine debe implementarse en la misma red que la instancia de Redis, y debes proporcionar los detalles de conexión de tu instancia de Redis. Para encontrar la red, la dirección IP y el puerto autorizados de tu instancia de Redis, ejecuta el siguiente comando:

 gcloud redis instances describe [INSTANCE_ID] --region [REGION]
  1. Crear una aplicación de App Engine

  2. Actualiza la configuración de la aplicación para especificar la dirección IP, el puerto y la red de la instancia de Redis:

    Go

    Actualiza el archivo gae_flex_deployment/app.yaml:

    runtime: go
    env: flex
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

    Consulta Configurar tu aplicación con app.yaml para obtener más detalles.

    Java

    Actualiza el archivo gae_flex_deployment/app.yaml para especificar la red de tu instancia de Redis:

    runtime: java
    env: flex
    
    # Update with Redis instance network name
    network:
      name: default

    Actualiza el archivo src/main/resources/application.properties mediante la dirección IP y el puerto de tu instancia de Redis:

    redis.host=REDIS_HOST_IP
    redis.port=6379

    Para obtener más información sobre cómo configurar tu app, consulta Configura tu app con app.yaml.

    Node.js

    Actualiza el archivo gae_flex_deployment/app.yaml:

    runtime: nodejs
    env: flex
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

    Consulta Configurar tu aplicación con app.yaml para obtener más detalles.

    Python

    Actualiza el archivo gae_flex_deployment/app.yaml:

    runtime: python
    env: flex
    entrypoint: gunicorn -b :$PORT main:app
    
    runtime_config:
      python_version: 3
    
    # Update with Redis instance IP and port
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

    Consulta Configurar tu aplicación con app.yaml para obtener más detalles.

Implementa la app en el entorno flexible de App Engine

Para implementar la aplicación, haz lo siguiente:

  1. Copia los archivos de configuración necesarios en el directorio del código fuente:

    Go

    Copia el archivo app.yaml en el directorio fuente:

    cp gae_flex_deployment/app.yaml .
    

    Java

    Copia el archivo app.yaml en el directorio fuente:

    mkdir -p src/main/appengine
    cp gae_flex_deployment/app.yaml src/main/appengine/
    

    Node.js

    Copia el archivo app.yaml en el directorio fuente:

    cp gae_flex_deployment/app.yaml .
    

    Python

    Copia el archivo app.yaml en el directorio fuente:

    cp gae_flex_deployment/app.yaml .
    
  2. Ejecuta el comando de implementación:

    Go

    gcloud app deploy
    

    Esto puede llevar algunos minutos.

    Java

    mvn appengine:deploy
    

    Esto puede llevar algunos minutos.

    Node.js

    gcloud app deploy
    

    Esto puede llevar algunos minutos.

    Python

    gcloud app deploy
    

    Esto puede llevar algunos minutos.

Una vez que se complete la implementación, visita tu app en la siguiente URL y reemplaza [PROJECT_ID] por el ID del proyecto de Google Cloud:

https://[PROJECT_ID].appspot.com

El conteo en tu instancia de Redis aumenta cada vez que la app recibe una visita.