ASP .NET Core - Usando cache distribuído com Redis
Hoje veremos o que é e como usar o cache distribuído usando o Redis. |
Eu já tratei da utilização do cache em aplicações ASP .NET Core em outros artigos (veja as referências) e hoje vamos focar na utilização do cache distribuído em aplicações ASP.NET Core.
O cache distribuído é um cache externo à aplicação que é compartilhado entre as instâncias de uma aplicação normalmente mantido como um serviço externo para os servidores de aplicativos que o acessam.
A ASP .NET Core suporta tanto o cache em memória como o cache distribuído e este recurso pode melhorar o desempenho e a escalabilidade das aplicações ASP .NET Core sendo esses os principais motivos de usá-lo.
As vantagens do cache distribuído são:
No entanto, a implementação do Cache distribuído é específica do aplicativo, e, isso significa que existem vários provedores de cache que oferecem suporte a caches distribuídos. Na ASP .NET Core podemos implementar o cache distribuído usando o SQL Server, o Redis, o NCache, etc.
Como neste artigo vamos focar na utilização do Redis, vamos falar mais pouco sobre ele.(novamente)
Apresentando o Redis
O
Redis é um armazenamento de dados de código
aberto que pode ser usado como banco de dados, cache ou intermediário de
mensagens. Ele suporta muitas estruturas de dados como string, hashes,
listas, consultas e tipos complexos. É um banco de dados No SQL baseado no
armazenamento de dados no formato
valor-chave extremamente rápido que é escrito em C. Não é a toa que ele é usado
por empresas como Stackoverflow, Flickr, Github.
Em nosso contexto, o Redis é uma ótima opção
para implementar um cache de alta disponibilidade para reduzir a latência de
acesso aos dados e melhorar o tempo de resposta do aplicativo. Como resultado,
podemos reduzir bastante a carga de nosso banco de dados.
O Redis foi desenvolvido para ser executado em sistemas operacionais Linux, OSX e BSD. Oficialmente, ele não tem suporte para sistema operacional baseado em Windows. Mas há várias portes disponíveis para o Windows 10. Em geral é usada uma máquina secundária que cuida do cache, onde o Redis é executado e pode servir como uma memória cache para vários aplicativos na nuvem.
Instalando o Redis no Windows
Para fazer a instalação do Redis no Windows podemos baixar o pacote ZIP a partir deste repositório : GitHub Repo
Extraia a pasta Zip e abra o arquivo redis-server.exe. Pronto, o servidor Redis agora está sendo executado em sua máquina. Não feche esta janela.
Essa seria a opção de instalar o Redis na sua máquina local.
Criando um contêiner Docker
Outra opção é usar um contêiner Docker com uma imagem do Redis. Para isso você precisa ter o Docker instalado.
Estou usando o Docker for Desktop for Windows e para levantar um contêiner com o Redis podemos abrir uma janela de comandos via PowerShell e digitar:
docker run --name local-redis -p 6379:6379 -d redis
Para conferir o contêiner em execução digite o comando : docker container ps
Vamos agora entrar no contêiner e emitir alguns comandos para testar o Redis. Para isso digite o seguinte comando:
docker exec -it local-redis sh
Você verá o sinal # indicando que entrou no contêiner.
Digite redis-cli que é a interface de linha de comando do Redis a partir de onde podemos enviar comandos ao Redis e ler as respostas enviadas pelo servidor, diretamente do terminal.
A seguir digite ping e terá a resposta PONG.
Vamos testar alguns comandos como atribuir uma chave usando o comando set , obter o valor e a seguir incrementar um contador:
Para sair do cliente digite exit e para sair do contêiner digite novamente exit.
Com isso vemos que o Redis esta funcionando corretamente no contêiner Docker criado.
Neste exemplo eu vou usar o contêiner Docker e assim não vou instalar o Redis localmente.
Acessando e usando um cache distribuído com IDistributedCache
Para poder acessar os objetos na implementação de um cache distribuído podemos usar a interface IDistributedCache que fornece os seguintes métodos :
Assim a interface IDistributedCache representa um cache distribuído de valores serializados e deverá ser usada para acessar e trabalhar com o cache distribuído que for implementado.
Entretanto a ASP.NET Core não fornece uma implementação padrão para esta interface, e assim cada uma das implementações de cache distribuído são fornecidas via pacotes NuGet.
Integrando o Redis com a ASP .NET Core
Para mostrar a utilização do cache distribuído usando o Redis vamos criar uma aplicação ASP .NET Core Web API que vai acessar a tabela Orders do banco de dados Northwind usando o EF Core e retornar a lista de todos os pedidos. Teremos assim algo e torno de 1000 registros e poderemos comparar o desempenho usando o cache.
Como propósito do artigo é mostrar como usar o cache distribuído eu não vou mostrar detalhes de implementação da API que foi criada.
Para poder acessar e usar o cache distribuído usando o Redis teremos que incluir no projeto o seguinte pacote :
Depois disso, temos que configurar nosso aplicativo para suportar o cache do Redis e especificar a porta em que o Redis está disponível.
Para fazer isso, abra o arquivo Startup.cs e no método ConfigureServices e adicione o seguinte código:
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
|
A seguir temos o código do controlador OrdersController criado na pasta Controllers:
using Aspn_Redis1.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Aspn_Redis1.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
private readonly IDistributedCache distributedCache;
private readonly NorthwindContext context;
public OrdersController(NorthwindContext context, IDistributedCache distributedCache)
{
this.context = context;
this.distributedCache = distributedCache;
}
[HttpGet("normal")]
public async Task<IActionResult> GetAllOrders()
{
var orderList = await context.Orders.ToListAsync();
return Ok(orderList);
}
[HttpGet("redis")]
public async Task<IActionResult> GetAllOrdersUsingRedisCache()
{
var cacheKey = "orderList";
string serializedOrderList;
var orderList = new List<Order>();
var redisOrderList = await distributedCache.GetAsync(cacheKey);
if (redisOrderList != null)
{
serializedOrderList = Encoding.UTF8.GetString(redisOrderList);
orderList = JsonConvert.DeserializeObject<List<Order>>(serializedOrderList);
}
else
{
orderList = await context.Orders.ToListAsync();
serializedOrderList = JsonConvert.SerializeObject(orderList);
redisOrderList = Encoding.UTF8.GetBytes(serializedOrderList);
var options = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(DateTime.Now.AddMinutes(10))
.SetSlidingExpiration(TimeSpan.FromMinutes(2));
await distributedCache.SetAsync(cacheKey, redisOrderList, options);
}
return Ok(orderList);
}
}
}
|
No código da API definimos dois endpoints :
Injetamos no construtor do controlador o contexto do EF Core e a interface IDistributedCache :
private readonly IDistributedCache distributedCache; private readonly NorthwindContext context; public OrdersController(NorthwindContext context, IDistributedCache distributedCache)
{
this.context = context;
this.distributedCache = distributedCache;
}
|
Para ter acesso a um valor do cache usamos o método GetAsync() que retorna o valor como um array de bytes;
var redisOrderList = await distributedCache.GetAsync(cacheKey);
E precisamos desserializar o array de bytes obtidos se ele existir no cache:
if (redisOrderList
!= null) { serializedOrderList = Encoding.UTF8.GetString(redisOrderList); orderList = JsonConvert.DeserializeObject<List<Order>>(serializedOrderList); } |
Caso contrário obtemos os dados e usando o método SetAsync() incluímos os dados no cache:
else { orderList = await context.Orders.ToListAsync(); serializedOrderList = JsonConvert.SerializeObject(orderList); redisOrderList = Encoding.UTF8.GetBytes(serializedOrderList); var options = new DistributedCacheEntryOptions() .SetAbsoluteExpiration(DateTime.Now.AddMinutes(10)) .SetSlidingExpiration(TimeSpan.FromMinutes(2)); await distributedCache.SetAsync(cacheKey, redisOrderList, options); } |
Para não correr o risco de ter dados desatualizados no cache vamos definir um prazo de expiração para o cache usando a classe DistributedCacheEntryOptions atribuindo valores aos métodos de extensão SetAbsoluteExpiration e SetSlidingExpiration.
Assim no primeiro acesso o tempo gasto para obter os dados deverá ser maior e a seguir os dados estarão no cache e o retorno deverá ser mais rápido.
Testando a implementação
Estamos usando o Swagger em nosso projeto ASP .NET Core e ao executar o projeto teremos a exibição dos dois endpoints criados na API conforme imagem a seguir:
No entanto vamos usar o Postman para testar o acesso aos endpoints.
Para instalar o Postman acesse esse link : https://www.getpostman.com/apps ou abra o Google Chrome e digite postam e a seguir clique no link: Postman - Chrome Web Store
A seguir abrindo o Postman vamos realizar o primeiro acesso: https://localhost:44349/api/Orders/redis
Abaixo temos o resultado obtido e tempo gasto que foi algo em torno de 1328 ms:
A seguir vamos fazer a segunda chamada e agora o cache já esta definido no Redis pela chave orderList. Assim, a segunda chamada será direta para o cache do Redis, o que deveria reduzir significativamente o tempo de resposta.
Como podemos confirmar na figura abaixo o tempo gasto foi 60 ms, ou seja, menos da metade do primeiro acesso:
A percepção do tempo gasto será maior se a quantidade de dados for maior, mas já vimos que usar o cache distribuído reduz bastante o desempenho de acesso aos dados.
Em outro artigo vamos mostrar como fazer a mesma coisa usando o SQL Server.
Pegue o projeto aqui: Aspn_Redis1.zip (sem as referências)
"Aquele que diz: "Eu o conheço (Jesus)",
mas não guarda os seus mandamentos, esse é mentiroso, e a verdade não está
nele."
1 João 2:4
Referências:
ASP .NET Core 2 - MiniCurso Básico
ASP .NET Core - Macoratti
Conceitos - .NET Framework versus .NET Core
ASP .NET Core - Conceitos Básicos - Macoratti.net
ASP .NET Core MVC - CRUD básico com ADO .NET
ASP .NET Core - Implementando a segurança com ...
ASP .NET Core - Apresentando Razor Pages
Minicurso ASP .NET Core 2.0 - Autenticação com JWT - I
Minicurso ASP .NET Core 2.0 - Autenticação com JWT - II
ASP.NET Core - Implementando o Cache
Apresentando o Redis
Primeiros passos com o Memurai