Connessione a un'istanza Redis da un'applicazione dell'ambiente flessibile di App Engine

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Le applicazioni di App Engine devono trovarsi sulla stessa rete autorizzata dall'istanza Redis per accedervi.

Configurazione

Se hai già installato Google Cloud CLI e creato un'istanza Redis, puoi saltare questi passaggi.

  1. Installa l'interfaccia a riga di comando gcloud e inizializza:

    gcloud init
    
  2. Segui la guida rapida per creare un'istanza Redis. Prendi nota della zona, dell'indirizzo IP e della porta dell'istanza Redis.

Applicazione di esempio

Questa applicazione server HTTP di esempio stabilisce una connessione a un'istanza Redis da un'istanza di ambiente flessibile di App Engine.

Clona il repository per il linguaggio di programmazione desiderato e vai alla cartella che contiene il codice di esempio:

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

Questa applicazione di esempio incrementa un contatore Redis a ogni accesso all'endpoint /.

Go

Questa applicazione utilizza il client github.com/gomodule/redigo/redis. Installala eseguendo il comando seguente:

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

Questa applicazione è basata su servlet Jetty 3.1.

Utilizza la libreria Jedis:

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

La classe AppServletContextListener viene utilizzata per creare un pool di connessione Redis di lunga durata:


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 classe VisitCounterServlet è un servlet web che incrementa un contatore 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

Questa applicazione utilizza il modulo 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": ">=12.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

Questa applicazione utilizza Flask per la pubblicazione web e il pacchetto redis-py per comunicare con l'istanza Redis.

Flask==2.1.0
gunicorn==20.1.0
redis==4.1.1
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 and Cloud Run.
    # See entrypoint in app.yaml or Dockerfile.
    app.run(host='127.0.0.1', port=8080, debug=True)

Preparazione dell'applicazione per il deployment

Per accedere all'istanza Redis, è necessario eseguire il deployment dell'istanza App Engine sulla stessa rete autorizzata dell'istanza Redis e devi fornire i dettagli della connessione dell'istanza Redis. Per trovare la rete, l'indirizzo IP e la porta dell'istanza Redis, esegui il comando seguente:

 gcloud redis instances describe [INSTANCE_ID] --region [REGION]
  1. Crea un'applicazione App Engine.

  2. Aggiorna la configurazione dell'app per specificare l'indirizzo IP, la porta e la rete della tua istanza Redis:

    Go

    Aggiorna il file 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

    Per ulteriori dettagli, consulta Configurazione dell'app con app.yaml.

    Java

    Aggiorna il file gae_flex_deployment/app.yaml per specificare la rete Redis della tua istanza:

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

    E aggiorna il file src/main/resources/application.properties con l'indirizzo IP e la porta dell'istanza Redis:

    redis.host=REDIS_HOST_IP
    redis.port=6379

    Per ulteriori informazioni sulla configurazione dell'app, vedi Configurare l'app con app.yaml.

    Node.js

    Aggiorna il file 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

    Per ulteriori dettagli, consulta Configurazione dell'app con app.yaml.

    Python

    Aggiorna il file 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

    Per ulteriori dettagli, consulta Configurazione dell'app con app.yaml.

Deployment dell'applicazione nell'ambiente flessibile di App Engine

Per eseguire il deployment dell'applicazione:

  1. Copia i file di configurazione necessari nella directory di origine:

    Go

    Copia il file app.yaml nella directory di origine:

    cp gae_flex_deployment/app.yaml .
    

    Java

    Copia il file app.yaml nella directory di origine:

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

    Node.js

    Copia il file app.yaml nella directory di origine:

    cp gae_flex_deployment/app.yaml .
    

    Python

    Copia il file app.yaml nella directory di origine:

    cp gae_flex_deployment/app.yaml .
    
  2. Esegui il comando Deployment:

    Go

    gcloud app deploy
    

    L'operazione potrebbe richiedere alcuni minuti.

    Java

    mvn appengine:deploy
    

    L'operazione potrebbe richiedere alcuni minuti.

    Node.js

    gcloud app deploy
    

    L'operazione potrebbe richiedere alcuni minuti.

    Python

    gcloud app deploy
    

    L'operazione potrebbe richiedere alcuni minuti.

Al termine del deployment, visita la tua app al seguente URL, sostituendo [PROJECT_ID] con il tuo ID progetto Google Cloud:

https://[PROJECT_ID].appspot.com

Il conteggio dell'istanza Redis aumenta ogni volta che l'app viene visitata.