Almacenar datos en caché con Memorystore

Las aplicaciones web escalables de alto rendimiento suelen usar caché de datos en memoria distribuida frente a un almacenamiento persistente robusto para algunas tareas o en lugar de él. Te recomendamos usar Memorystore para Redis como servicio de almacenamiento en caché. Ten en cuenta que Memorystore para Redis no proporciona un nivel gratuito. Consulta Precios de Memorystore para obtener más información.

Antes de comenzar, asegúrate de que la app permanezca dentro de las cuotas de Memorystore para Redis.

Cuándo usar una memoria caché

Los datos de sesión, las preferencias de usuario y otros datos que muestran las consultas para páginas web son buenos candidatos a la hora de almacenar en caché. Por lo general, si una consulta que se ejecuta con frecuencia muestra un conjunto de resultados que no necesitan aparecer en la app de inmediato, puedes almacenarlos en caché. En las solicitudes subsiguientes, se puede verificar la caché y consultar la base de datos solo si los resultados están ausentes o ya vencieron.

Si almacenas un valor solo en Memorystore sin crear una copia de seguridad en el almacenamiento persistente, asegúrate de que la aplicación se comporte de manera aceptable si el valor vence y se quita de la caché. Por ejemplo, si la ausencia repentina de los datos de sesión de un usuario provoca una falla de la sesión, esos datos 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 estar autorizada. Por ejemplo, a fin de interactuar con una base de datos de Redis que se aloja en Memorystore, la app debe proporcionar las credenciales de una cuenta autorizada para acceder a Memorystore.

De forma predeterminada, la app proporciona las credenciales de la cuenta de servicio predeterminada de App Engine, que está autorizada para acceder a las bases de datos en el mismo proyecto que la app.

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

  • La app y la base de datos de Memorystore están en diferentes proyectos de Google Cloud.

  • Cambiaste las funciones asignadas a la cuenta de servicio predeterminada de App Engine.

Si deseas obtener información sobre técnicas de autenticación alternativas, consulta Configura la autenticación para aplicaciones de producción de servidor a servidor.

Descripción general del uso de Memorystore

Para usar Memorystore en tu app, haz lo siguiente:

  1. Configura Memorystore para Redis, que requiere que crees una instancia de Redis en Memorystore y un Acceso a VPC sin servidores que la app usará a fin de comunicarse con la instancia de Redis.

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

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

    Go

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

    Java

    En esta guía, se describe el uso de la biblioteca cliente de Jedis a fin de enviar comandos de Redis desde tu app. Para obtener más información sobre el uso de Jedis, consulta la wiki de Jedis.

    Node.js

    En esta guía, se describe cómo usar la biblioteca cliente node_redis a fin de enviar comandos de Redis desde tu app.

    PHP

    En esta guía, se describe el uso de la biblioteca cliente de PHPRedis para enviar comandos de Redis desde tu app.

    Python

    En esta guía, se describe el uso de la biblioteca cliente de redis-py 3.0 para enviar comandos de Redis desde tu app.

    Ruby

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

  3. Prueba las actualizaciones.

  4. Implementa la app en App Engine.

Configura Memorystore para Redis

Si deseas configurar Memorystore para Redis, debes hacer lo siguiente:

  1. Crea una instancia de Redis en Memorystore.

    Cuando se te solicite seleccionar una región para la instancia de Redis, selecciona la misma región en la que se encuentra la app de App Engine.

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

  3. Conecta tu App Engine a una red de VPC. La app solo puede comunicarse con Memorystore a través de un conector de VPC.

    Asegúrate de agregar la información de conexión de VPC al archivo app.yaml como se describe en Configura la app para usar un conector.

Instala dependencias

Go

Agrega la biblioteca cliente de redigo a las dependencias de tu app para que esté disponible cuando se ejecute en App Engine. Por ejemplo, si usas un archivo go.mod para declarar dependencias, agrega la siguiente línea al archivo go.mod:

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

Obtén más información sobre cómo especificar dependencias para tu app de Go.

Java

Agrega la biblioteca cliente de Jedis a las dependencias de tu app para que esté disponible cuando se ejecute en App Engine. Por ejemplo, si usas Maven, agrega la siguiente dependencia en el archivo pom.xml:

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

Node.js

Si deseas que la biblioteca cliente node_redis esté disponible en tu app cuando se ejecute en App Engine, agrégala al archivo package.json de la app.

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

Obtén más información sobre la especificación de dependencias para tu app de Node.js.

PHP

Para que la biblioteca cliente de PHPRedis esté disponible en tu aplicación cuando se ejecuta en App Engine, agrega 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 las extensiones de PHP en App Engine, consulta Extensiones de carga dinámica.

Python

A fin de que la biblioteca cliente de redis-py esté disponible en tu app cuando se ejecute en App Engine, agrega la siguiente línea al archivo requirements.txt de la app:

  redis

En el entorno de ejecución de Python 3 de App Engine, se subirán de forma automática todas las bibliotecas del archivo requirements.txt de tu app cuando implementes la app.

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

Ruby

Si deseas que la biblioteca cliente redis-rb esté disponible en tu app cuando se ejecute en App Engine, agrégala al archivo Gemfile de la app.

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

  gem "redis-rb"

Crea un cliente de Redis

Para interactuar con una base de datos de Redis, tu código debe crear un cliente de Redis a fin de administrar la conexión a tu base de datos de Redis. En las siguientes secciones, se describe la creación de un cliente de Redis mediante la biblioteca cliente de Redis.

Especifica las variables de entorno

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

  • Una variable para identificar la dirección IP de la base de datos de Redis que creaste en Memorystore
  • Una variable para identificar el número de puerto de la base de datos de Redis que creaste en Memorystore

Te recomendamos definir estas variables en el archivo app.yaml de la app en lugar de definirlas directamente en el código. Esto facilita la ejecución de la app en diferentes entornos, como un entorno local y App Engine. Obtén más información sobre las variables de entorno en la página de referencia de app.yaml.

Go

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

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

Java

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

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

Node.js

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

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

PHP

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

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

Python

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

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

Ruby

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

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

Importa Redis y crea el cliente

Go

Luego de definir las variables de entorno REDISHOST y REDISPORT, usa las siguientes líneas para importar la biblioteca de redigo, crear un grupo de conexiones y recuperar 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 uses la biblioteca de Jedis, te recomendamos crear un JedisPool y, luego, usar el grupo para crear un cliente. En las siguientes líneas de código, se usan las variables de entorno redis.host y redis.port que definiste antes 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
      }
    }
  }
}

Si deseas crear un cliente desde el 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

Luego de definir 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, puede 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 de 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 usaste una versión anterior de redis-py para otras apps, tal vez usaste la clase StrictClient en lugar de Client. Sin embargo, redis-py ahora recomienda Client en lugar de StrictClient.

Ruby

No hay información adicional para este entorno de ejecución.

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

Si bien la base de datos de Redis para Memorystore admite la mayoría de los comandos de Redis, solo debes usar algunos comandos a fin de almacenar y recuperar datos de la caché. En la siguiente tabla, se sugieren los comandos de Redis que puedes usar para almacenar datos en caché. Para saber cómo llamar a estos comandos desde la app, consulta la documentación de la biblioteca cliente.

Tarea Comando de Redis
Crear una entrada en la caché de datos y
establecer una fecha de vencimiento para la entrada
SETNX
MSETNX
Recuperar los datos de la caché GET
MGET
Reemplazar los valores existentes de la caché SET
MSET
Aumentar o disminuir los valores numéricos de la caché INCR
INCRBY
DECR
DECRBY
Borrar las entradas de la caché DEL
UNLINK
Admitir interacciones simultáneas con la caché Consulta los detalles sobre las transacciones de Redis.

Para Python, la biblioteca cliente de redis-py requiere que todas las transacciones se realicen en una canalización.

Prueba las actualizaciones

Cuando pruebes la app de forma local, considera ejecutar una instancia local de Redis para evitar la interacción con los datos de producción (Memorystore no proporciona un emulador). Para instalar y ejecutar Redis de forma local, sigue las instrucciones en la documentación de Redis. Ten en cuenta que en este momento no es posible ejecutar Redis de manera local en Windows.

Para obtener más información sobre cómo probar tus apps, consulta Implementa y prueba tu app.

Implementa la app

Una vez que la app se ejecute en el servidor de desarrollo local sin errores, haz lo siguiente:

  1. Prueba la app en App Engine.

  2. Si la app se ejecuta sin errores, usa la división de tráfico para aumentar el tráfico de la app actualizada. Supervísala de cerca a fin de detectar cualquier problema en la base de datos antes de enrutar más tráfico a la app actualizada.