Almacenar datos en caché con Memorystore

Las aplicaciones web escalables de alto rendimiento suelen usar una caché de datos distribuida en memoria antes o en lugar de un almacenamiento persistente robusto para algunas tareas. Te recomendamos que uses Memorystore para Redis como servicio de almacenamiento en caché. Ten en cuenta que Memorystore para Redis no ofrece un nivel gratuito. Para obtener más información, consulta la página Precios de Memorystore.

Antes de empezar, asegúrate de que tu aplicación no supere las cuotas de Memorystore para Redis.

Cuándo usar una caché de memoria

Los datos de sesión, las preferencias de los usuarios y otros datos devueltos por las consultas de páginas web son buenos candidatos para el almacenamiento en caché. Por lo general, si una consulta que se ejecuta con frecuencia devuelve un conjunto de resultados que no tienen que aparecer en tu aplicación inmediatamente, puedes almacenar en caché los resultados. Las solicitudes posteriores pueden comprobar la caché y solo consultar la base de datos si los resultados no están o han caducado.

Si almacenas un valor solo en Memorystore sin crear una copia de seguridad en el almacenamiento persistente, asegúrate de que tu aplicación se comporte de forma aceptable si el valor caduca y se elimina de la caché. Por ejemplo, si la ausencia repentina de los datos de sesión de un usuario provocara que la sesión no funcionara correctamente, esos datos probablemente deberían almacenarse en la base de datos, además de en Memorystore.

Información sobre los permisos de Memorystore

Cada interacción con un servicio de Google Cloud debe autorizarse. Por ejemplo, para interactuar con una base de datos Redis alojada en Memorystore, tu aplicación debe proporcionar las credenciales de una cuenta que tenga autorización para acceder a Memorystore.

De forma predeterminada, tu aplicación proporciona las credenciales de la cuenta de servicio predeterminada de App Engine, que está autorizada para acceder a las bases de datos del mismo proyecto que tu aplicación.

Si se cumple alguna de las siguientes condiciones, debes usar una técnica de autenticación alternativa que proporcione credenciales de forma explícita:

  • Tu aplicación y la base de datos de Memorystore están en proyectos diferentes.Google Cloud

  • Has cambiado los roles asignados a la cuenta de servicio predeterminada de App Engine.

Para obtener información sobre técnicas de autenticación alternativas, consulta el artículo Configurar la autenticación para aplicaciones de producción de servidor a servidor.

Información general sobre el uso de Memorystore

Para usar Memorystore en tu aplicación, sigue estos pasos:

  1. Configura Memorystore para Redis. Para ello, debes crear una instancia de Redis en Memorystore y crear un acceso a VPC sin servidor que tu aplicación utilice para comunicarse con la instancia de Redis.

  2. Instala una biblioteca de cliente para Redis y usa comandos de Redis para almacenar datos en caché.

    Memorystore para Redis es compatible con cualquier biblioteca de cliente de Redis.

    Go

    En esta guía se describe cómo usar la biblioteca de cliente redigo para enviar comandos de Redis desde tu aplicación.

    Java

    En esta guía se describe cómo usar la biblioteca de cliente Jedis para enviar comandos de Redis desde tu aplicación. Para obtener más información sobre cómo usar Jedis, consulta la wiki de Jedis.

    Node.js

    En esta guía se describe cómo usar la biblioteca de cliente node_redis para enviar comandos de Redis desde tu aplicación.

    PHP

    En esta guía se describe cómo usar la biblioteca de cliente PHPRedis para enviar comandos de Redis desde tu aplicación.

    Python

    En esta guía se describe cómo usar la biblioteca de cliente redis-py 3.0 para enviar comandos de Redis desde tu aplicación.

    Ruby

    En esta guía se describe cómo usar la biblioteca de cliente redis-rb para enviar comandos de Redis desde tu aplicación.

  3. Prueba las actualizaciones.

  4. Despliega tu aplicación en App Engine.

Configurar Memorystore para Redis

Para configurar Memorystore para Redis, sigue estos pasos:

  1. Crea una instancia de Redis en Memorystore.

    Cuando se te pida que selecciones una región para tu instancia de Redis, elige la misma región en la que se encuentra tu aplicación de App Engine.

  2. Anota la dirección IP y el número de puerto de la instancia de Redis que crees. Usará esta información cuando cree un cliente de Redis en su código.

  3. Conecta tu aplicación de App Engine a una red de VPC. Tu aplicación solo puede comunicarse con Memorystore a través de un conector VPC.

    Asegúrate de añadir la información de conexión de la VPC al archivo app.yaml tal como se describe en el artículo Configurar la aplicación para que use el conector.

Instalar dependencias

Go

Para que la biblioteca de cliente de redigo esté disponible para tu aplicación cuando se ejecute en App Engine, añade la biblioteca a las dependencias de tu aplicación. Por ejemplo, si usas un archivo go.mod para declarar dependencias, añade la siguiente línea a tu archivo go.mod:

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

Consulta más información sobre cómo especificar las dependencias de tu aplicación Go.

Java

Para que la biblioteca de cliente de Jedis esté disponible para tu aplicación cuando se ejecute en App Engine, añade la biblioteca a las dependencias de tu aplicación. Por ejemplo, si usa Maven, añada la siguiente dependencia a su archivo pom.xml:

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

Node.js

Para que la biblioteca de cliente node_redis esté disponible para tu aplicación cuando se ejecute en App Engine, añade la biblioteca al archivo package.json de tu aplicación.

Por ejemplo:

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

Consulta más información sobre cómo especificar las dependencias de tu aplicación Node.js.

PHP

Para que la biblioteca de cliente de PHPRedis esté disponible para tu aplicación cuando se ejecute en App Engine, añade la extensión redis.so al archivo php.ini de tu aplicación. Por ejemplo:

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

Para obtener más información sobre cómo habilitar extensiones de PHP en App Engine, consulta el artículo sobre las extensiones cargables dinámicamente.

Python

Para que la biblioteca de cliente redis-py esté disponible para tu aplicación cuando se ejecute en App Engine, añade la siguiente línea al archivo requirements.txt de tu aplicación:

  redis

El entorno de ejecución de Python 3 de App Engine subirá automáticamente todas las bibliotecas del archivo requirements.txt de tu aplicación cuando despliegues la aplicación.

Para el desarrollo local, te recomendamos que instales las dependencias en un entorno virtual, como venv.

Ruby

Para que la biblioteca de cliente redis-rb esté disponible para tu aplicación cuando se ejecute en App Engine, añade la biblioteca al archivo Gemfile de tu aplicación.

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

  gem "redis-rb"

Crear un cliente de Redis

Para interactuar con una base de datos Redis, tu código debe crear un cliente Redis para gestionar la conexión con tu base de datos Redis. En las siguientes secciones se describe cómo crear un cliente de Redis con la biblioteca de cliente de Redis.

Especificar variables de entorno

La biblioteca de cliente de Redis usa dos variables de entorno para crear la URL de tu base de datos Redis:

  • Variable para identificar la dirección IP de la base de datos Redis que has creado en Memorystore.
  • Variable para identificar el número de puerto de la base de datos Redis que has creado en Memorystore.

Te recomendamos que definas estas variables en el archivo app.yaml de tu aplicación en lugar de hacerlo directamente en el código. De esta forma, te resultará más fácil ejecutar tu aplicación en diferentes entornos, como un entorno local y App Engine. Consulta más información sobre las variables de entorno en la página de referencia de app.yaml.

Go

Por ejemplo, añade las siguientes líneas a tu archivo app.yaml:

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

Java

Por ejemplo, añade las siguientes líneas a tu archivo app.yaml:

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

Node.js

Por ejemplo, añade las siguientes líneas a tu archivo app.yaml:

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

PHP

Por ejemplo, añade las siguientes líneas a tu archivo app.yaml:

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

Python

Por ejemplo, añade las siguientes líneas a tu archivo app.yaml:

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

Ruby

Por ejemplo, añade las siguientes líneas a tu archivo app.yaml:

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

Importar Redis y crear el cliente

Go

Después de definir las variables de entorno REDISHOST y REDISPORT, usa las siguientes líneas para importar la biblioteca redigo, crear un grupo de conexiones y, a continuación, obtener un cliente de Redis del grupo:


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

Cuando usas la biblioteca Jedis, te recomendamos que crees un JedisPool y, a continuación, utilices el pool para crear un cliente. Las siguientes líneas de código usan las variables de entorno redis.host y redis.port que has definido anteriormente para crear un grupo:


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

Para crear un cliente a partir del grupo, usa el método JedisPool.getResource(). Por ejemplo:


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

Una vez que hayas definido las variables de entorno REDISHOST y REDISPORT, puedes usar las siguientes líneas para importar la biblioteca node_redis y crear un cliente de 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

Después de definir las variables de entorno REDIS_HOST y REDIS_PORT, puedes usar las siguientes líneas para crear un cliente de 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

Después de definir las variables de entorno REDISHOST y REDISPORT, usa las siguientes líneas para importar la biblioteca redis-py y crear un cliente:

  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)

Si has usado una versión anterior de redis-py para otras aplicaciones, es posible que hayas usado la clase StrictClient en lugar de Client. Sin embargo, redis-py ahora recomienda Client en lugar de StrictClient.

Ruby

No hay información adicional sobre este tiempo de ejecución.

Usar comandos de Redis para almacenar y recuperar datos en la caché

Aunque la base de datos Redis de Memorystore admite la mayoría de los comandos de Redis, solo tienes que usar unos pocos para almacenar y recuperar datos de la caché. En la siguiente tabla se sugieren comandos de Redis que puedes usar para almacenar datos en caché. Para saber cómo llamar a estos comandos desde tu aplicación, consulta la documentación de tu biblioteca de cliente.

Tarea Comando de Redis
Crea una entrada en la caché de datos y
define un tiempo de vencimiento para la entrada.
SETNX
MSETNX
Recuperar datos de la caché GET
MGET
Reemplazar los valores de caché actuales SET
MSET
Incrementar o reducir valores numéricos de la caché INCR
INCRBY
DECR
DECRBY
Eliminar entradas de la caché DEL
UNLINK
Admite interacciones simultáneas con la caché Consulta los detalles sobre las transacciones de Redis.

En Python, la biblioteca de cliente redis-py requiere que todas las transacciones se produzcan en un pipeline.

Probar las actualizaciones

Cuando pruebes tu aplicación de forma local, te recomendamos que ejecutes una instancia local de Redis para evitar interactuar con datos de producción (Memorystore no proporciona un emulador). Para instalar y ejecutar Redis de forma local, sigue las instrucciones de la documentación de Redis. Ten en cuenta que, por el momento, no es posible ejecutar Redis de forma local en Windows.

Para obtener más información sobre cómo probar tus aplicaciones, consulta Probar y desplegar una aplicación.

Desplegar una aplicación

Una vez que tu aplicación se ejecute en el servidor de desarrollo local sin errores, haz lo siguiente:

  1. Prueba la aplicación en App Engine.

  2. Si la aplicación se ejecuta sin errores, usa la división del tráfico para aumentar lentamente el tráfico de tu aplicación actualizada. Monitoriza la aplicación de cerca para detectar cualquier problema con la base de datos antes de dirigir más tráfico a la aplicación actualizada.