Como se conectar a uma instância do Redis a partir de um aplicativo de ambiente padrão do App Engine

É possível se conectar a uma instância do Redis a partir do ambiente padrão do App Engine usando o Acesso VPC sem servidor.

Configuração

Se você já instalou a Google Cloud CLI e criou um Redis é possível pular essas etapas.

  1. Instale a CLI gcloud e inicialize:

    gcloud init
    
  2. Siga o Guia de início rápido para criar uma instância do Redis. Anote a zona, o endereço IP e a porta da instância do Redis.

Configurar o acesso VPC sem servidor

Para se conectar a partir do aplicativo do App Engine à rede VPC autorizada da instância do Redis, configure o acesso VPC sem servidor.

  1. Encontre a rede autorizada da instância do Redis executando o comando:

    gcloud beta redis instances describe [INSTANCE_ID] --region [REGION]
    
  2. Siga as instruções em Como criar um conector para criar um conector de acesso VPC sem servidor. Certifique-se de criar o conector na mesma região do seu app e verificar se o conector está anexado à rede VPC autorizada da instância do Redis. Lembre-se do nome do conector.

Exemplo de aplicativo

Este aplicativo de servidor HTTP de amostra estabelece uma conexão com uma instância do Redis a partir de um aplicativo de ambiente padrão do App Engine.

Clone o repositório da linguagem de programação pretendida e navegue até a pasta que contém o código de amostra:

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

Este aplicativo de amostra incrementa um contador do Redis sempre que o endpoint / é acessado.

Go

Este aplicativo usa o cliente github.com/gomodule/redigo/redis. Instale-o executando o comando a seguir:

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 aplicação é baseada em servlets do Jet 3.1.

Ele usa a biblioteca Jedis (em inglês):

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

A classe AppServletContextListener é usada para criar um pool de conexão Redis de longa duração:


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
      }
    }
  }
}

A classe VisitCounterServlet é um servlet da Web que incrementa um contador do 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

Este aplicativo usa o módulo redis (em inglês).

{
  "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

Este aplicativo usa o Flask para veiculação na Web e o pacote redis-py (links em inglês) para se comunicar com a instância do 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)

Como preparar o aplicativo para implantação

Para acessar a instância do Redis, o aplicativo do App Engine precisa ser configurado para usar o conector de acesso VPC sem servidor, e você precisa fornecer os detalhes de conexão da instância do Redis.

  1. Crie um aplicativo do App Engine se você ainda não tiver um.

  2. Atualize a configuração do aplicativo para especificar o conector de acesso VPC sem servidor e o endereço IP e a porta da instância do Redis:

    Go

    Atualize o arquivo gae_standard_deployment/app.yaml:

    runtime: go111
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Consulte Arquivo de configuração do app.yaml para mais detalhes.

    Java

    Atualize o arquivo gae_standard_deployment/appengine-web.xml para especificar o conector de acesso VPC sem servidor:

    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
      <runtime>java8</runtime>
      <threadsafe>true</threadsafe>
      <vpc-access-connector>
        <name>projects/[PROJECT_ID]/locations/[REGION]/connectors/[CONNECTOR_NAME]</name>
      </vpc-access-connector>
    </appengine-web-app>

    E atualize o arquivo src/main/resources/application.properties com o endereço IP e a porta da sua instância do Redis:

    redis.host=REDIS_HOST_IP
    redis.port=6379

    Para mais informações sobre como configurar seu aplicativo, consulte Referência do appengine-web.xml.

    Node.js

    Atualize o arquivo gae_standard_deployment/app.yaml:

    runtime: nodejs10
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Consulte Arquivo de configuração do app.yaml para mais detalhes.

    Python

    Atualize o arquivo gae_standard_deployment/app.yaml:

    runtime: python37
    entrypoint: gunicorn -b :$PORT main:app
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Consulte Arquivo de configuração do app.yaml para mais detalhes.

Como implantar o aplicativo no ambiente padrão do App Engine

Para implantar o aplicativo:

  1. Copie os arquivos de configuração necessários para o diretório de origem:

    Go

    Copie os arquivos app.yaml e go.mod para o diretório de origem:

    cp gae_standard_deployment/{app.yaml,go.mod} .
    

    Java

    Copie o arquivo appengine-web.xml para o diretório de origem:

    mkdir -p src/main/webapp/WEB-INF
    cp gae_standard_deployment/appengine-web.xml src/main/webapp/WEB-INF/
    

    Node.js

    Copie o arquivo app.yaml para o diretório de origem:

    cp gae_standard_deployment/app.yaml .
    

    Python

    Copie o arquivo app.yaml para o diretório de origem:

    cp gae_standard_deployment/app.yaml .
    
  2. Execute o comando de implantação:

    Go

    gcloud app deploy
    

    Java

    mvn package appengine:stage
    gcloud app deploy target/appengine-staging/app.yaml
    

    Node.js

    gcloud app deploy
    

    Python

    gcloud app deploy
    

Após a conclusão da implantação, o comando gerará o URL em que você poderá visitar seu aplicativo. Se visitar esse URL, você verá a contagem na sua instância do Redis aumentar sempre que a página for carregada.