O desempenho é um parâmetro essencial a ser considerado ao projetar qualquer peça de software. É particularmente importante quando se trata do que acontece nos bastidores.
Nós, como desenvolvedores e tecnólogos, adotamos vários ajustes e implementações para melhorar o desempenho. É aqui que o cache entra em jogo.
O armazenamento em cache é definido como um mecanismo para armazenar dados ou arquivos em um local de armazenamento temporário, de onde pode ser acessado instantaneamente sempre que necessário.
Atualmente, o cache tornou-se um item obrigatório em aplicativos da web. Podemos usar o Redis para sobrecarregar nossas APIs da web – criadas usando o Node.js e o MongoDB.
Redis: Visão geral de um leigo
Redis, de acordo com a documentação oficial, é definido como um armazenamento de estrutura de dados em memória usado como banco de dados, intermediário de mensagens ou armazenamento em cache. Ele suporta estruturas de dados como strings, hashes, listas, conjuntos, conjuntos classificados com consultas de intervalo, bitmaps, hiperloglogs, índices geoespaciais com consultas e fluxos de raio.
Ok, existem várias estruturas de dados ali. Apenas para simplificar, quase todas as estruturas de dados suportadas podem ser condensadas em uma forma de string ou outra. Você terá mais clareza à medida que avançamos na implementação.
Mas uma coisa é clara. O Redis é poderoso e, quando usado corretamente, pode tornar nossos aplicativos não apenas mais rápidos, mas incrivelmente eficientes. Chega de conversa. Vamos sujar as mãos.
Vamos conversar sobre código
Antes de começarmos, você precisará obter a configuração do redis em seu sistema local. Você pode seguir isso configuração rápida processo para obter o redis instalado e funcionando.
Feito? Legal. Vamos começar. Temos um aplicativo simples criado no Express que utiliza uma instância no MongoDB Atlas para ler e gravar dados.
Temos duas principais APIs criadas no /blogs
arquivo de rota.
...// GET - Fetches all blog posts for required userblogsRouter.route('/:user') .get(async (req, res, next) => { const blogs = await Blog.find({ user: req.params.user }); res.status(200).json({ blogs, }); });// POST - Creates a new blog postblogsRouter.route('/') .post(async (req, res, next) => { const existingBlog = await Blog.findOne({ title: req.body.title }); if (!existingBlog) { let newBlog = new Blog(req.body); const result = await newBlog.save(); return res.status(200).json({ message: `Blog ${result.id} is successfully created`, result, }); } res.status(200).json({ message: 'Blog with same title exists', }); }); ...
Polvilhando alguns Redis Goodness
Começamos baixando o pacote npm redis
para se conectar ao servidor redis local.
const mongoose = require('mongoose');const redis = require('redis');const util = require('util');const redisUrl = 'redis://127.0.0.1:6379';const client = redis.createClient(redisUrl);client.hget = util.promisify(client.hget);...
Nós fazemos uso do utils.promisify
função para transformar o client.hget
para retornar uma promessa em vez de um retorno de chamada. Você pode ler mais sobre promisification
aqui.
A conexão Redis está no lugar. Antes de começarmos a escrever mais códigos de cache, vamos dar um passo atrás e tentar entender quais são os requisitos que precisamos atender e os prováveis desafios que podemos enfrentar.
Nossa estratégia de armazenamento em cache deve ser capaz de abordar os seguintes pontos.
- Armazenar em cache a solicitação de todas as postagens do blog para um usuário específico
- Limpar cache sempre que uma nova postagem no blog é criada
Os prováveis desafios dos quais devemos ter cuidado ao seguir nossa estratégia são:
- A maneira correta de lidar com a criação de chaves para armazenar dados em cache
- Lógica de expiração de cache e expiração forçada para manter a atualização do cache
- Implementação reutilizável da lógica de armazenamento em cache
Tudo certo. Temos nossos pontos anotados e redis conectados. Para o próximo passo.
Substituindo a função padrão do Mongoose Exec
Queremos que nossa lógica de cache seja reutilizável. E não apenas reutilizável, também queremos que seja o primeiro ponto de verificação antes de fazer qualquer consulta ao banco de dados. Isso pode ser feito facilmente usando um simples truque de retrocesso na função exec do mangusto.
...const exec = mongoose.Query.prototype.exec;...mongoose.Query.prototype.exec = async function() { ... const result = await exec.apply(this, arguments); console.log('Data Source: Database'); return result;}...
Utilizamos o objeto prototype do mangusto para adicionar nosso código lógico de cache como a primeira execução na consulta.
Adicionando cache como uma consulta
Para indicar quais consultas devem ser colocadas em cache, criamos uma consulta de mangusto. Nós fornecemos a capacidade de passar o user
para ser usado como um hashkey através do options
objeto.
...mongoose.Query.prototype.cache = function(options = {}) { this.enableCache = true; this.hashKey = JSON.stringify(options.key || 'default'); return this;};...
Feito isso, podemos usar facilmente o cache(
consulta junto com as consultas que queremos armazenar em cache da seguinte maneira.
... const blogs = await Blog .find({ user: req.params.user }) .cache({ key: req.params.user }); ...
Criando a lógica do cache
Criamos uma consulta reutilizável comum para indicar quais consultas precisam ser armazenadas em cache. Vamos em frente e escreva a lógica central de armazenamento em cache.
...mongoose.Query.prototype.exec = async function() { if (!this.enableCache) { console.log('Data Source: Database'); return exec.apply(this, arguments); } const key = JSON.stringify(Object.assign({}, this.getQuery(), { collection: this.mongooseCollection.name, })); const cachedValue = await client.hget(this.hashKey, key); if (cachedValue) { const parsedCache = JSON.parse(cachedValue); console.log('Data Source: Cache'); return Array.isArray(parsedCache) ? parsedCache.map(doc => new this.model(doc)) : new this.model(parsedCache); } const result = await exec.apply(this, arguments); client.hmset(this.hashKey, key, JSON.stringify(result), 'EX', 300); console.log('Data Source: Database'); return result;};...
Sempre que usamos o cache()
consulta junto com nossa consulta principal, definimos o enableCache
chave para ser verdade.
Se a chave for falsa, retornamos o principal exec
consulta como padrão. Caso contrário, primeiro formamos a chave para buscar e armazenar / atualizar os dados do cache.
Nós usamos o collection
nome juntamente com a consulta padrão como o nome da chave em nome da exclusividade. A chave de hash usada é o nome do user
que já definimos anteriormente no cache()
definição de função.
Os dados em cache são buscados usando o client.hget()
função que requer a chave de hash e a chave conseqüente como parâmetros.
Nota: Nós sempre usamos
JSON.parse()
ao buscar quaisquer dados dos redis. E da mesma forma, usamosJSON.stringify()
na chave e nos dados antes de armazenar qualquer coisa no redis. Isso é feito porque o redis não suporta estruturas de dados JSON.
Depois de obtermos os dados em cache, temos que transformar cada um dos objetos em cache em um modelo de mangusto. Isso pode ser feito simplesmente usando new this.model(
.
Se o cache não contiver os dados necessários, fazemos uma consulta no banco de dados. Depois, retornando os dados para a API, atualizamos o cache usando client.hmset()
. Também definimos um tempo de expiração padrão do cache de 300 segundos. Isso é personalizável com base na sua estratégia de cache.
A lógica de armazenamento em cache está em vigor. Também definimos um tempo de expiração padrão. A seguir, forçaremos a expiração do cache sempre que uma nova postagem no blog for criada.
Expiração Forçada do Cache
Em certos casos, como quando um usuário cria uma nova postagem no blog, o usuário espera que a nova postagem esteja disponível quando buscar todas as postagens.
Para fazer isso, precisamos limpar o cache relacionado a esse usuário e atualizá-lo com novos dados. Então, temos que forçar a expiração. Podemos fazer isso invocando o del()
função fornecida por redis.
...module.exports = { clearCache(hashKey) { console.log('Cache cleaned'); client.del(JSON.stringify(hashKey)); }}...
Também devemos ter em mente que estaremos forçando a expiração em várias rotas. Uma maneira extensível é usar esse clearCache()
como um middleware e chame-o assim que qualquer consulta relacionada a uma rota concluir a execução.
const { clearCache } = require('../services/cache');module.exports = async (req, res, next) => { // wait for route handler to finish running await next(); clearCache(req.body.user);}
Esse middleware pode ser facilmente chamado em uma rota específica da seguinte maneira.
...blogsRouter.route('/') .post(cleanCache, async (req, res, next) => { ... } ...
E nós terminamos. Eu concordo que era muito código. Mas com essa última parte, configuramos o redis com nosso aplicativo e resolvemos quase todos os desafios prováveis. É hora de ver nossa estratégia de cache em ação.
Redis em Ação
Nós fazemos uso de Carteiro como o cliente da API para ver nossa estratégia de cache em ação. Aqui vamos nós. Vamos executar as operações da API, uma por uma.
- Criamos uma nova postagem no blog usando o
/blogs
rota
2. Em seguida, buscamos todas as postagens de blog relacionadas ao usuário tejaz
3. Buscamos todas as postagens do blog para o usuário tejaz
mais uma vez.
Você pode ver claramente que, quando buscamos no cache, o tempo gasto diminuiu de 409ms para 24ms. Isso sobrecarrega sua API diminuindo o tempo gasto em quase 95%.
Além disso, podemos ver claramente que as operações de expiração e atualização de cache funcionam conforme o esperado.
Você pode encontrar o código fonte completo no redis-express
pasta aqui.
tarique93102 / trechos de artigos
Repositório contendo aplicativos de protótipo e trechos de código relacionados à disseminação de conceitos – tarique93102 / article-snippets
Conclusão
O armazenamento em cache é uma etapa obrigatória para qualquer aplicativo com desempenho eficiente e uso intensivo de dados. O Redis ajuda você a conseguir isso facilmente em seus aplicativos da web. É uma ferramenta super poderosa e, se usada corretamente, pode definitivamente proporcionar uma excelente experiência para desenvolvedores e usuários ao redor.
Você pode encontrar o conjunto completo de comandos redis aqui. Você pode usá-lo com redis-cli
para monitorar seus dados de cache e processos de aplicativos.
As possibilidades oferecidas por qualquer tecnologia em particular são verdadeiramente infinitas. Se você tiver alguma dúvida, entre em contato comigo em LinkedIn
.
Enquanto isso, continue codificando.