Como armazenar dados em cache com o Memorystore

É comum que, para algumas tarefas, os aplicativos da Web escalonáveis de alto desempenho usem um cache de dados distribuído na memória em frente a um armazenamento permanente robusto ou no lugar dele. Recomendamos o uso do Memorystore para Redis como serviço de armazenamento em cache. O Memorystore para Redis não oferece um nível gratuito. Veja Preços do Memorystore para mais detalhes.

Antes de começar, verifique se o aplicativo permanecerá dentro das cotas do Memorystore para Redis.

Quando usar um cache de memória

Os dados da sessão, as preferências do usuário e outros dados retornados por consultas a páginas da Web são bons candidatos para armazenamento em cache. Em geral, se uma consulta executada com frequência retorna um conjunto de resultados que não precisam aparecer no seu aplicativo imediatamente, é possível armazená-los em cache. As solicitações posteriores só poderão verificar o cache e consultar o banco de dados se os resultados estiverem ausentes ou tiverem expirado.

Se você armazenar um valor apenas no Memorystore sem fazer backup no armazenamento permanente, verifique se o aplicativo se comporta de maneira aceitável quando o valor expirar e for removido do cache. Por exemplo, se a ausência repentina dos dados da sessão de um usuário fizer a sessão funcionar incorretamente, esses dados provavelmente serão armazenados no banco de dados, além do Memorystore.

Noções básicas sobre as permissões do Memorystore

Toda interação com um serviço do Google Cloud precisa ser autorizada. Por exemplo, para interagir com um banco de dados do Redis hospedado pelo Memorystore, seu aplicativo precisa fornecer as credenciais de uma conta que está autorizada a acessar o Memorystore.

Por padrão, o aplicativo fornece as credenciais da conta de serviço padrão do App Engine, que está autorizada a acessar bancos de dados no mesmo projeto que o aplicativo.

Se alguma das seguintes condições for verdadeira, você precisará usar uma técnica de autenticação alternativa que forneça credenciais explicitamente:

  • Seu aplicativo e o banco de dados do Memorystore estão em projetos diferentes do Google Cloud.

  • Você alterou os papéis atribuídos à conta de serviço padrão do App Engine.

Para saber mais sobre técnicas de autenticação alternativas, veja Como configurar a autenticação para aplicativos de produção de servidor para servidor.

Visão geral do uso do Memorystore

Para usar o Memorystore no seu aplicativo:

  1. Configure o Memorystore para Redis, que exige que você crie uma instância do Redis no Memorystore e um acesso VPC sem servidor que o aplicativo usará para se comunicar com a instância do Redis.

  2. Instale uma biblioteca de cliente para o Redis e use os comandos do Redis para armazenar dados em cache.

    O Memorystore para Redis é compatível com qualquer biblioteca de cliente para Redis.

    Go

    Este guia descreve o uso da biblioteca de cliente redigo para enviar comandos do Redis a partir do seu aplicativo.

    Java

    Neste guia, descrevemos o uso da biblioteca de cliente Jedis para enviar comandos do Redis pelo seu app. Para detalhes sobre como usar o Jedis, consulte a wiki do Jedis (links em inglês).

    Node.js

    Este guia descreve como usar a biblioteca de cliente node_redis para enviar comandos do Redis a partir do aplicativo.

    PHP

    Este guia descreve o uso da biblioteca de cliente PHPRedis para enviar comandos do Redis a partir do seu aplicativo.

    Python

    Este guia descreve como usar a biblioteca de cliente redis-py 3.0 para enviar comandos do Redis a partir do seu aplicativo.

    Ruby

    Neste guia, descrevemos o uso da biblioteca de cliente redis-rb para enviar comandos Redis do seu app.

  3. Teste as atualizações.

  4. Implante o aplicativo no App Engine.

Como configurar o Memorystore para Redis

Para configurar o Memorystore para Redis:

  1. Crie uma instância do Redis no Memorystore.

    Quando solicitado a selecionar uma região para sua instância do Redis, escolha a mesma região em que o aplicativo do App Engine está localizado.

  2. Anote o endereço IP e o número da porta da instância do Redis criada. Você usará essas informações ao criar um cliente Redis no seu código.

  3. Conecte o App Engine a uma rede VPC. Seu aplicativo só pode se comunicar com o Memorystore por meio de um conector de VPC.

    Não se esqueça de adicionar as informações de conexão de VPC ao arquivo app.yaml, conforme descrito em Como configurar seu aplicativo para usar um conector.

Como instalar dependências

Go

Para disponibilizar a biblioteca de cliente redigo para seu aplicativo quando ele for executado no App Engine, adicione a biblioteca às dependências do aplicativo. Por exemplo, se você usar um arquivo go.mod para declarar dependências, adicione a seguinte linha ao arquivo go.mod:

módulo github.com/GoogleCloudPlatform/golang-samples/tree/master/memorystore/redis

Saiba mais sobre como especificar dependências para seu aplicativo Go.

Java

Para disponibilizar a biblioteca de cliente Jedis para seu app quando ele for executado no App Engine, adicione a biblioteca às dependências do app. Por exemplo, se você usa o Maven, adicione a seguinte dependência ao arquivo pom.xml:

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

Node.js

Para disponibilizar a biblioteca de cliente node_redis para seu aplicativo quando ele for executado no App Engine, adicione a biblioteca ao arquivo package.json do aplicativo.

Exemplo:

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

Saiba mais sobre como especificar dependências para seu aplicativo Node.js.

PHP

Para disponibilizar a biblioteca de cliente PHPRedis para o aplicativo quando ele for executado no App Engine, adicione a extensão redis.so ao arquivo php.ini do aplicativo. Exemplo:

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

Saiba mais sobre como ativar extensões PHP no App Engine em Extensões dinamicamente carregáveis.

Python

Para disponibilizar a biblioteca de cliente redis-py para seu aplicativo quando ele for executado no App Engine, adicione a seguinte linha ao arquivo requirements.txt do aplicativo:

  redis

O ambiente de execução do Python 3 do App Engine fará o upload automático de todas as bibliotecas do arquivo requirements.txt do aplicativo quando você implantar o aplicativo.

Para desenvolvimento local, recomendamos que você instale as dependências em um ambiente virtual como venv.

Ruby

Para disponibilizar a biblioteca de cliente redis-rb para o app quando ele for executado no App Engine, adicione a biblioteca ao arquivo Gemfile do app.

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

  gem "redis-rb"

Como criar um cliente Redis

Para interagir com um banco de dados Redis, seu código precisa criar um cliente Redis para gerenciar a conexão com o banco de dados Redis. Nas seções a seguir descrevemos a criação de um cliente Redis usando a biblioteca de cliente Redis.

Como especificar variáveis de ambiente

A biblioteca de cliente Redis usa duas variáveis de ambiente para montar o URL do banco de dados do Redis:

  • Uma variável para identificar o endereço IP do banco de dados do Redis criado no Memorystore.
  • Uma variável para identificar o número da porta do banco de dados do Redis criado no Memorystore.

Recomendamos que você defina essas variáveis no arquivo app.yaml do aplicativo em vez de defini-las diretamente no código. Isso facilita a execução do aplicativo em diferentes ambientes, como um ambiente local e o App Engine. Saiba mais sobre as variáveis de ambiente na página de referência do app.yaml.

Go

Por exemplo, adicione as seguintes linhas ao seu arquivo app.yaml:

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

Java

Por exemplo, adicione as seguintes linhas ao seu arquivo app.yaml:

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

Node.js

Por exemplo, adicione as seguintes linhas ao seu arquivo app.yaml:

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

PHP

Por exemplo, adicione as seguintes linhas ao seu arquivo app.yaml:

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

Python

Por exemplo, adicione as seguintes linhas ao seu arquivo app.yaml:

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

Ruby

Por exemplo, adicione as seguintes linhas ao seu arquivo app.yaml:

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

Como importar o Redis e criar o cliente

Go

Depois de definir as variáveis de ambiente REDISHOST e REDISPORT, use as seguintes linhas para importar a biblioteca redigo, criar um pool de conexões e, em seguida, recuperar um cliente Redis do 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

Ao usar a biblioteca Jedis, recomendamos que você crie um JedisPool (em inglês) e use o pool para criar um cliente. As seguintes linhas de código usam as variáveis de ambiente redis.host e redis.port definidas anteriormente para criar um 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
      }
    }
  }
}

Para criar um cliente a partir do pool, use o método JedisPool.getResource(). Exemplo:


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

Depois de definir as variáveis de ambiente REDISHOST e REDISPORT, use as seguintes linhas para importar a biblioteca node_redis e criar um cliente 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

Depois de definir as variáveis de ambiente REDIS_HOST e REDIS_PORT, use as seguintes linhas para criar um cliente 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

Depois de definir as variáveis de ambiente REDISHOST e REDISPORT, use as seguintes linhas para importar a biblioteca redis-py e criar um 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)

Se você usou uma versão mais antiga do redis-py em outros aplicativos, talvez tenha usado a classe StrictClient em vez de Client. No entanto, o redis-py agora recomenda Client em vez de StrictClient.

Ruby

Nenhuma informação adicional para este ambiente de execução.

Como usar comandos do Redis para armazenar e recuperar dados no cache

Embora o banco de dados do Memorystore para Redis seja compatível com a maioria dos comandos do Redis, você só precisa usar alguns deles para armazenar e recuperar dados do cache. A tabela a seguir sugere comandos do Redis que você pode usar para armazenar dados em cache. Para saber como chamar esses comandos a partir do aplicativo, veja a documentação da sua biblioteca de cliente.

Tarefa Comando do Redis
Criar uma entrada no cache de dados e
definir um prazo de validade para a entrada
SETNX
MSETNX
Recuperar dados do cache GET
MGET
Substituir valores de cache existentes SET
MSET
Aumentar ou diminuir valores de cache numéricos INCR
INCRBY
DECR
DECRBY
Excluir entradas do cache DEL
UNLINK
Fornecer suporte a interações simultâneas com o cache Veja detalhes sobre as transações do Redis.

Para Python 3, a biblioteca de cliente redis-py exige que todas as transações ocorram em um pipeline.

Como testar suas atualizações

Ao testar seu aplicativo localmente, considere a execução de uma instância local do Redis para evitar a interação com dados de produção (o Memorystore não fornece um emulador). Para instalar e executar o Redis localmente, siga as instruções na documentação do Redis. Ainda não é possível executar o Redis localmente no Windows.

Para mais informações sobre como testar os apps, consulte Como testar e implantar o aplicativo.

Implantar o app

Quando seu aplicativo estiver em execução no servidor de desenvolvimento local sem erros:

  1. Teste o aplicativo no App Engine.

  2. Se o aplicativo for executado sem erros, use a divisão de tráfego para aumentar gradualmente o tráfego para o aplicativo atualizado. Monitore de perto o aplicativo em busca de problemas no banco de dados antes de direcionar mais tráfego para o aplicativo atualizado.