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

Per accedervi, le applicazioni App Engine devono trovarsi sulla stessa rete autorizzata dell'istanza Redis.

Configurazione

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

  1. Installa gcloud CLI 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 dell'ambiente flessibile di App Engine.

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

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 ogni volta che si accede all'endpoint /.

Go

Questa applicazione utilizza il client github.com/gomodule/redigo/redis. Installalo eseguendo questo 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

Questa applicazione è basata su servlet Jetty 3.1.

Utilizza la libreria Jedis:

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

La classe AppServletContextListener viene utilizzata per creare un pool di connessioni 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": ">=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

Questa applicazione utilizza Flask per la pubblicazione web e il pacchetto redis-py per comunicare con l'istanza 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)

Preparazione dell'applicazione per il deployment

Per accedere all'istanza Redis, devi eseguire il deployment dell'istanza di App Engine nella stessa rete autorizzata dell'istanza Redis e devi fornire i dettagli della connessione dell'istanza Redis. Esegui il comando seguente per trovare la porta, l'indirizzo IP e la rete autorizzati dell'istanza Redis:

 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 dell'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, vedi Configurazione dell'app con app.yaml.

    Java

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

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

    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 Configurazione dell'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, vedi 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, vedi 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 di 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 l'app al seguente URL, sostituendo [PROJECT_ID] con il tuo ID progetto Google Cloud:

https://[PROJECT_ID].appspot.com

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