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

Os aplicativos do App Engine precisam estar na mesma região e na mesma rede da instância do Redis para serem autorizados a acessar a instância do Redis.

Configurar

Essas etapas podem ser ignoradas se você tiver instalado o Cloud SDK e criado uma instância do Redis.

  1. Instale o Cloud SDK 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.

Exemplo de app

Com este aplicativo de servidor HTTP de amostra, é estabelecida uma conexão com uma instância do Redis a partir de uma instância de ambiente flexível do App Engine.

Em primeiro lugar, clone o repositório na linguagem de programação escolhida:

Go

Neste aplicativo, é usado o cliente github.com/garyburd/redigo/redis.

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

Java

Neste aplicativo, é usado o cliente Jedis.

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

Node.js

Neste aplicativo, é usado o módulo node_redis.

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

Python

Neste aplicativo, é usado o pacote redis-py.

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

Com este código de amostra, um contador do Redis é incrementado toda vez que o ponto de extremidade "/" é acessado.

Go

Primeiro, instale o Redigo:

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.NewPool(func() (redis.Conn, error) {
		return redis.Dial("tcp", redisAddr)
	}, maxConnections)

	http.HandleFunc("/", incrementHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Java

Esta aplicação é baseada em servlets do Jet 3.1.

Adicione a biblioteca Jedis ao seu pom.xml conforme mostrado aqui:

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

A classe AppServletContextListener é usada para criar um pool de conexões 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, usado para incrementar 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());
    }
  }
}

Atualize application.properties para corresponder ao endereço IP e ao número da porta da instância do Redis. Se você não tiver certeza do endereço IP ou do número da porta da sua instância, execute o seguinte comando:

   gcloud redis instances describe [INSTANCE_ID] --region [REGION]
redis.host=REDIS_HOST_IP
redis.port=6379

Node.js

{
  "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": ">=8.13.0"
  },
  "dependencies": {
    "redis": "^2.8.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

Flask==1.0.2
gunicorn==19.9.0
redis==3.1.0
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 'Visitor number: {}'.format(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. See entrypoint in app.yaml.
    app.run(host='127.0.0.1', port=8080, debug=True)

Como preparar o aplicativo para implantação

Crie um aplicativo do App Engine na mesma região da instância do Redis.

Para acessar a instância do Redis, a instância do App Engine precisa ser implantada na mesma rede da instância do Redis. Se você não souber qual é a rede autorizada da sua instância Redis, digite o seguinte comando:

 gcloud beta redis instances describe [INSTANCE_ID] --region [REGION]

Go

app.yaml:

# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

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 garantir que a instância do ambiente flexível seja implantada na rede autorizada, atualize a seção da rede app.yaml e especifique o parâmetro "name". Para saber mais detalhes, consulte Configuração do aplicativo com app.yaml.

Java

app.yaml:

runtime: java
env: flex

Node.js

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 garantir que a instância do ambiente flexível seja implantada na rede autorizada, atualize a seção da rede app.yaml e especifique o parâmetro "name". Para saber mais detalhes, consulte Configuração do aplicativo com app.yaml.

Python

app.yaml:

runtime: python
env: flex
entrypoint: gunicorn -b :$PORT main:app

runtime_config:
  python_version: 3

# update with Redis instance host IP, port
env_variables:
  REDISHOST:  redis-ip
  REDISPORT: 6379

# update with Redis instance network name
network:
  name: default
Para garantir que a instância do ambiente flexível seja implantada na rede autorizada, atualize a seção da rede app.yaml e especifique o parâmetro "name". Para saber mais detalhes, consulte Configuração do aplicativo com app.yaml.

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

Implante o aplicativo:

Go

  1. Atualize app.yaml com o IP e a porta do Redis.
  2. Digite o seguinte comando para implantar o aplicativo:

    gcloud app deploy
    

    Talvez isso leve alguns minutos.

  3. Use seu código de projeto personalizado na URL https://projectid.appspot.com e envie uma solicitação GET via cURL ou pelo navegador.

Java

  1. Atualize src/main/resources/application.properties com o host e o IP do Redis.
  2. Copie o arquivo app.yaml para o diretório de origem:

    mkdir -p src/main/appengine
    cp flex_deployment/app.yaml src/main/appengine/
    
  3. Adicione o plug-in de implantação do Maven para App Engine ao seu pom.xml:

     <!-- Deployment plugin for App Engine Flexible -->
     <plugin>
       <groupId>com.google.cloud.tools</groupId>
       <artifactId>appengine-maven-plugin</artifactId>
       <version>1.3.2</version>
    </plugin>

  4. Digite o seguinte comando para implantar o aplicativo:

    mvn appengine:deploy
    

    Talvez isso leve alguns minutos.

  5. Use seu código de projeto personalizado na URL https://projectid.appspot.com e envie uma solicitação GET via cURL ou pelo navegador.

Node.js

  1. Atualize app.yaml com o IP e a porta do Redis.
  2. Digite o seguinte comando para implantar o aplicativo:

    gcloud app deploy
    

    Talvez isso leve alguns minutos.

  3. Use seu código de projeto personalizado na URL https://projectid.appspot.com e envie uma solicitação GET via cURL ou pelo navegador.

Python

  1. Atualize app.yaml com o IP e a porta do Redis.
  2. Digite o seguinte comando para implantar o aplicativo:

    gcloud app deploy
    

    Talvez isso leve alguns minutos.

  3. Use seu código de projeto personalizado na URL https://projectid.appspot.com e envie uma solicitação GET via cURL ou pelo navegador.