Como se conectar a uma instância do Redis usando um aplicativo de ambiente flexível do App Engine

Os aplicativos do App Engine precisam estar na mesma rede autorizada da instância do Redis para serem acessados.

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.

Aplicativo de amostra

Este aplicativo de servidor HTTP de amostra estabelece uma conexão com uma instância do Redis usando uma instância do ambiente flexível 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, a instância do App Engine precisa ser implantada na mesma rede que a instância do Redis, e você precisa fornecer os detalhes de conexão da instância do Redis. Para encontrar a rede, o endereço IP e a porta autorizados da sua instância do Redis, execute o comando a seguir:

 gcloud redis instances describe [INSTANCE_ID] --region [REGION]
  1. Crie um aplicativo do App Engine.

  2. Atualize a configuração do aplicativo para especificar o endereço IP, a porta e a rede da sua instância do Redis:

    Go

    Atualize o arquivo 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

    Para saber mais detalhes, consulte Como configurar o aplicativo com app.yaml.

    Java

    Atualize o arquivo gae_flex_deployment/app.yaml para especificar a rede da instância do Redis:

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

    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 Como configurar seu aplicativo com app.yaml.

    Node.js

    Atualize o arquivo 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

    Para saber mais detalhes, consulte Como configurar o aplicativo com app.yaml.

    Python

    Atualize o arquivo 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

    Para saber mais detalhes, consulte Como configurar o aplicativo com app.yaml.

Como implantar o aplicativo no ambiente flexível do App Engine

Para implantar o aplicativo:

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

    Go

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

    cp gae_flex_deployment/app.yaml .
    

    Java

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

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

    Node.js

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

    cp gae_flex_deployment/app.yaml .
    

    Python

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

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

    Go

    gcloud app deploy
    

    Isso pode levar alguns minutos.

    Java

    mvn appengine:deploy
    

    Isso pode levar alguns minutos.

    Node.js

    gcloud app deploy
    

    Isso pode levar alguns minutos.

    Python

    gcloud app deploy
    

    Isso pode levar alguns minutos.

Após a conclusão da implantação, acesse o aplicativo no URL a seguir, substituindo [PROJECT_ID] pelo código do projeto do Google Cloud:

https://[PROJECT_ID].appspot.com

A contagem na sua instância do Redis aumenta sempre que o aplicativo é acessado.