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

Le applicazioni App Engine devono trovarsi sulla stessa rete autorizzata dell'istanza Redis per potervi accedere.

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 viene eseguito l'accesso all'endpoint /.

Go

Questa applicazione utilizza il client github.com/gomodule/redigo/redis. Per installarla, esegui 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 Jetty 3.1 e su servlet.

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 sul web e il pacchetto redis-py per comunicare con l'istanza Redis.

Flask==3.0.0
gunicorn==22.0.0
redis==5.0.1
Werkzeug==3.0.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 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, il deployment dell'istanza di App Engine deve essere eseguito sulla stessa rete autorizzata dell'istanza Redis e devi fornire i dettagli di connessione dell'istanza Redis. Puoi trovare la rete, l'indirizzo IP e la porta autorizzati dell'istanza Redis eseguendo questo comando:

 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 dell'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 della tua istanza Redis:

    redis.host=REDIS_HOST_IP
    redis.port=6379

    Per ulteriori informazioni sulla configurazione dell'app, consulta 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, 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 deploy:

    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 l'ID progetto Google Cloud:

https://[PROJECT_ID].appspot.com

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