Memorizzazione nella cache dei dati con Memorystore

Le applicazioni web scalabili ad alte prestazioni spesso utilizzano una cache di dati distribuita in memoria davanti o al posto di una solida archiviazione permanente per alcune attività. Ti consigliamo di utilizzare Memorystore for Redis come servizio di memorizzazione nella cache. Tieni presente che Memorystore for Redis non offre un livello gratuito. Per maggiori dettagli, vedi Prezzi di Memorystore.

Prima di iniziare, assicurati che la tua app rimanga all'interno delle quote di Memorystore for Redis.

Quando utilizzare una cache in memoria

I dati di sessione, le preferenze utente e altri dati restituiti dalle query per le pagine web sono ottimi candidati per la memorizzazione nella cache. In generale, se una query eseguita di frequente restituisce un insieme di risultati che non devono essere visualizzati immediatamente nell'app, puoi memorizzare i risultati nella cache. Le richieste successive possono controllare la cache ed eseguire query sul database solo se i risultati sono assenti o sono scaduti.

Se archivi un valore solo in Memorystore senza eseguirne il backup nell'archiviazione permanente, assicurati che l'applicazione si comporti in modo accettabile se il valore scade e viene rimosso dalla cache. Ad esempio, se l'assenza improvvisa dei dati della sessione di un utente causa un malfunzionamento della sessione, questi probabilmente dovrebbero essere archiviati nel database oltre a Memorystore.

Informazioni sulle autorizzazioni di Memorystore

Ogni interazione con un servizio Google Cloud deve essere autorizzata. Ad esempio, per interagire con un database Redis ospitato da Memorystore, la tua app deve fornire le credenziali di un account autorizzato ad accedere a Memorystore.

Per impostazione predefinita, l'app fornisce le credenziali dell'account di servizio predefinito di App Engine, che è autorizzato ad accedere ai database nello stesso progetto della tua app.

Se una delle seguenti condizioni è vera, devi utilizzare una tecnica di autenticazione alternativa che fornisce esplicitamente le credenziali:

  • L'app e il database Memorystore si trovano in progetti Google Cloud diversi.

  • Hai modificato i ruoli assegnati all'account di servizio predefinito di App Engine.

Per informazioni sulle tecniche di autenticazione alternative, consulta Configurare l'autenticazione per le applicazioni di produzione server-server.

Panoramica dell'utilizzo di Memorystore

Per utilizzare Memorystore nella tua app:

  1. Configura Memorystore for Redis, che richiede la creazione di un'istanza Redis su Memorystore e un accesso VPC serverless che l'app utilizza per comunicare con l'istanza Redis.

  2. Installa una libreria client per Redis e utilizza i comandi Redis per memorizzare i dati nella cache.

    Memorystore for Redis è compatibile con qualsiasi libreria client per Redis.

    Go

    Questa guida descrive l'utilizzo della libreria client redigo per inviare comandi Redis dall'app.

    Java

    Questa guida descrive l'utilizzo della libreria client Jedis per inviare comandi Redis dall'app. Per maggiori dettagli sull'utilizzo di Jedis, consulta il wiki di Jedis.

    Node.js

    Questa guida descrive l'utilizzo della libreria client node_redis per inviare comandi Redis dall'app.

    PHP

    Questa guida descrive l'utilizzo della libreria client PHPRedis per inviare comandi Redis dall'app.

    Python

    Questa guida descrive l'utilizzo della libreria client redis-py 3.0 per inviare comandi Redis dall'app.

    Ruby

    Questa guida descrive l'utilizzo della libreria client redis-rb per inviare comandi Redis dalla tua app.

  3. Verifica gli aggiornamenti.

  4. Esegui il deployment dell'app in App Engine.

Configurazione di Memorystore for Redis

Per configurare Memorystore for Redis:

  1. Crea un'istanza Redis in Memorystore.

    Quando ti viene chiesto di selezionare una regione per l'istanza Redis, seleziona la stessa regione in cui si trova l'app App Engine.

  2. Prendi nota dell'indirizzo IP e del numero di porta dell'istanza Redis che crei. Utilizzerai queste informazioni quando crei un client Redis nel tuo codice.

  3. Connetti App Engine a una rete VPC. La tua app può comunicare con Memorystore solo tramite un connettore VPC.

    Assicurati di aggiungere le informazioni di connessione VPC al file app.yaml come descritto in Configurare l'utilizzo del connettore da parte dell'app.

Installazione delle dipendenze

Go

Per rendere la libreria client Redigo disponibile per la tua app quando viene eseguita in App Engine, aggiungila alle dipendenze dell'app. Ad esempio, se utilizzi un file go.mod per dichiarare le dipendenze, aggiungi la seguente riga al file go.mod:

modulo github.com/GoogleCloudPlatform/golang-samples/tree/master/memorystore/redis

Scopri di più su come specificare le dipendenze per l'app Go.

Java

Per rendere la libreria client Jedis disponibile per la tua app quando viene eseguita in App Engine, aggiungila alle dipendenze dell'app. Ad esempio, se utilizzi Maven, aggiungi la seguente dipendenza nel file pom.xml:

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

Node.js

Per rendere la libreria client node_redis disponibile per la tua app quando viene eseguita in App Engine, aggiungila al file package.json dell'app.

Ad esempio:

{
  "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"
  }
}

Scopri di più su come specificare le dipendenze per l'app Node.js.

PHP

Per rendere disponibile la libreria client PHPRedis per la tua app quando viene eseguita in App Engine, aggiungi l'estensione redis.so al file php.ini dell'app. Ad esempio:

; Enable the Redis extension on App Engine
extension=redis.so

Per saperne di più sull'attivazione delle estensioni PHP in App Engine, consulta Estensioni caricabili dinamicamente.

Python

Per rendere la libreria client Redis disponibile per la tua app quando viene eseguita in App Engine, aggiungi la seguente riga al file requirements.txt dell'app:

  redis

Il runtime Python 3 di App Engine caricherà automaticamente tutte le librerie del file requirements.txt dell'app quando esegui il deployment dell'app.

Per lo sviluppo locale, ti consigliamo di installare le dipendenze in un ambiente virtuale come venv.

Ruby

Per rendere disponibile la libreria client redis-rb per la tua app quando viene eseguita in App Engine, aggiungila al file Gemfile dell'app.

  source "https://cloud.google.com/memorystore"

  gem "redis-rb"

Creazione di un client Redis

Per interagire con un database Redis, il codice deve creare un client Redis per gestire la connessione al database Redis. Le seguenti sezioni descrivono la creazione di un client Redis utilizzando la libreria client Redis.

Specifica le variabili di ambiente

La libreria client Redis utilizza due variabili di ambiente per assemblare l'URL per il database Redis:

  • Una variabile per identificare l'indirizzo IP del database Redis creato in Memorystore.
  • Una variabile per identificare il numero di porta del database Redis che hai creato in Memorystore.

Ti consigliamo di definire queste variabili nel file app.yaml dell'app, anziché definirle direttamente nel codice. In questo modo è più semplice eseguire l'app in diversi ambienti, ad esempio in un ambiente locale e in App Engine. Scopri di più sulle variabili di ambiente nella pagina di riferimento di app.yaml.

Go

Ad esempio, aggiungi le seguenti righe al file app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Java

Ad esempio, aggiungi le seguenti righe al file app.yaml:

  env_variables:
       redis.host: '10.112.12.112'
       redis.port: '6379'

Node.js

Ad esempio, aggiungi le seguenti righe al file app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

PHP

Ad esempio, aggiungi le seguenti righe al file app.yaml:

  env_variables:
       REDIS_HOST: '10.112.12.112'
       REDIS_PORT: '6379'

Python

Ad esempio, aggiungi le seguenti righe al file app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Ruby

Ad esempio, aggiungi le seguenti righe al file app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Importazione di Redis e creazione del client in corso...

Go

Dopo aver definito le variabili di ambiente REDISHOST e REDISPORT, utilizza le righe seguenti per importare la libreria Redigo, creare un pool di connessioni e quindi recuperare un client Redis dal pool:


// 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

Quando utilizzi la libreria Jedis, ti consigliamo di creare un JedisPool e quindi di utilizzare il pool per creare un client. Le seguenti righe di codice utilizzano le variabili di ambiente redis.host e redis.port definite in precedenza per creare un pool:


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
      }
    }
  }
}

Per creare un client dal pool, utilizza il metodo JedisPool.getResource(). Ad esempio:


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

Dopo aver definito le variabili di ambiente REDISHOST e REDISPORT, puoi utilizzare le seguenti righe per importare la libreria node_redis e creare un client Redis:

'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);

PHP

Dopo aver definito le variabili di ambiente REDIS_HOST e REDIS_PORT, puoi utilizzare le seguenti righe per creare un client Redis:

<?php
/**
 * Copyright 2019 Google Inc.
 *
 * 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
 *
 *     http://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.
 */

// Only serve traffic from "/"
switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
    case '/':
        break;
    default:
        http_response_code(404);
        exit('Not Found');
}

// Connect to Memorystore from App Engine.
if (!$host = getenv('REDIS_HOST')) {
    throw new Exception('The REDIS_HOST environment variable is required');
}

# Memorystore Redis port defaults to 6379
$port = getenv('REDIS_PORT') ?: '6379';

try {
    $redis = new Redis();
    $redis->connect($host, $port);
} catch (Exception $e) {
    return print('Error: ' . $e->getMessage());
}

$value = $redis->incr('counter');

printf('Visitor number: %s', $value);

Python

Dopo aver definito le variabili di ambiente REDISHOST e REDISPORT, utilizza le seguenti righe per importare la libreria Redis-py e creare un client:

  import redis

  redis_host = os.environ.get('REDISHOST', 'localhost')
  redis_port = int(os.environ.get('REDISPORT', 6379))
  redis_client = redis.Redis(host=redis_host, port=redis_port)

Se hai utilizzato una versione precedente di Redis-py per altre app, potresti aver utilizzato la classe StrictClient anziché Client. Tuttavia, Redis-py ora consiglia Client anziché StrictClient.

Ruby

Nessuna informazione aggiuntiva per questo runtime.

Utilizzo dei comandi Redis per archiviare e recuperare i dati nella cache

Sebbene il database Memorystore Redis supporti la maggior parte dei comandi Redis, devi solo utilizzare alcuni comandi per archiviare e recuperare i dati dalla cache. La tabella seguente suggerisce i comandi Redis che puoi utilizzare per memorizzare i dati nella cache. Per sapere come chiamare questi comandi dall'app, visualizza la documentazione della libreria client.

Attività Comando Redis
Crea una voce nella cache dei dati e
imposta una scadenza per la voce
SETNX
MSETNX
Recupera i dati dalla cache GET
MGET
Sostituisci i valori della cache esistenti IMPOSTA
MSET
Aumentare o diminuire i valori numerici della cache INCRBY
INCRBY
DECR
DECRBY
Eliminare le voci dalla cache CANC
SCOLLEGA
Supporto di interazioni simultanee con la cache Consulta i dettagli sulle transazioni Redis.

Per Python, la libreria client Redis-py richiede che tutte le transazioni avvengano in una pipeline.

Test degli aggiornamenti in corso...

Quando testi la tua app in locale, valuta la possibilità di eseguire un'istanza locale di Redis per evitare di interagire con i dati di produzione (Memorystore non fornisce un emulatore). Per installare ed eseguire Redis in locale, segui le istruzioni riportate nella documentazione di Redis. Tieni presente che attualmente non è possibile eseguire Redis in locale su Windows.

Per ulteriori informazioni su come testare le app, consulta Test e deployment dell'applicazione.

Deployment dell'app

Quando l'app è in esecuzione nel server di sviluppo locale senza errori:

  1. Testa l'app su App Engine.

  2. Se l'app viene eseguita senza errori, utilizza la suddivisione del traffico per incrementare lentamente il traffico dell'app aggiornata. Monitora attentamente l'app per individuare eventuali problemi del database prima di instradare ulteriore traffico all'app aggiornata.