O objetivo deste artigo é demonstrar a implementação de caching distribuído em aplicações criadas com o ASP.NET Core 1.0, utilizando para isto o Azure Redis Cache como meio de armazenamento.

Introdução

Comumente associadas a cenários que exigem uma melhor performance em aplicações Web, as técnicas de caching se baseiam no armazenamento temporário de dados (geralmente em memória) a fim de possibilitar um processamento mais rápido de requisições HTTP. Embora uma alternativa bastante simples, há cenários nos quais esta abordagem pode apresentar limitações.

Situações envolvendo a execução de múltiplas instâncias de uma mesma aplicação ilustram bem os problemas ao se optar pelo cache em memória. Diferentes resultados poderão ser apresentados aos usuários, já que não existe uma sincronização dos dados temporários entre as várias instâncias consideradas.

A reinicialização de um servidor ou o deployment de um projeto também resultará na perda dos dados em cache. Ao se escalar uma aplicação com novos servidores e/ou instâncias haverá ainda todo um processamento adicional, a fim de incluir em cache os dados requeridos pelo projeto em questão.

O uso de um mecanismo de cache distribuído poderia ser uma resposta às dificuldades aqui levantadas. Com este tipo de recurso dados armazenados podem sobreviver a reinicializações em servidores, atualizações de uma aplicação e ainda se manter consistentes por mais que um sistema possua múltiplas instâncias ativas.

Uma solução bastante popular em termos de cache distribuído é o Redis. Trabalhando com dados organizados em chaves e valores e dispondo de um repositório para armazenamento em memória, o Redis é um projeto open source e que conta com uma implementação oferecida como serviço no Microsoft Azure: trata-se do Azure Redis Cache.

Neste artigo será demonstrado o uso do Azure Redis Cache, com isto acontecendo através da implementação de uma aplicação ASP.NET Core 1.0.

Exemplo de utilização

Para implementar o projeto descrito neste artigo foram utilizados como recursos:
  • O Microsoft Visual Studio Community 2015 Update 3 como IDE de desenvolvimento;
  • O .NET Core 1.0;
  • O ASP.NET Core 1.0;
  • O Azure Redis Cache como mecanismo de cache distribuído.
Os passos necessários para a construção deste exemplo estão detalhados nas próximas seções.

Criando um novo recurso do Azure Redis Cache

Para criar um novo recurso do Azure Redis Cache será necessário acessar primeiramente o portal do Microsoft Azure:

https://portal.azure.com/

Logo após a autenticação acionar o item Novo, na sequência Dados + Armazenamento e, finalmente, Cache Redis:



Aparecerá neste momento uma tela para a configuração do novo recurso:
  • No campo Nome DNS foi informado o valor redistnwiki;
  • Selecionar a criação de um novo grupo de recursos, definindo como nome do mesmo TesteRedis.


Após alguns segundos, ao acionar a opção Todos os recursos, será possível constatar a presença do recurso redistnwiki:

Utilizando o Azure Redis Cache

Será necessário agora criar um projeto do tipo ASP.NET Core Web Application (.NET Core) chamado TesteRedisCache:



Selecionar então o template Web Application em ASP.NET Core Templates:



Incluir na sequência os packages Microsoft.Extensions.Caching.Redis.Core e Newtonsoft.Json ao projeto TesteRedisCache:





No arquivo appsettings.json deverão ser informados o endereço (host) para acesso ao Redis na nuvem, além da chave primária do recurso criado anteriormente. O host pode ser obtido no painel que contém as informações sobre o recurso redistnwiki:



Para a chave primária será necessário acionar a opção Mostrar chaves de acesso... e, em seguida, o campo Primária:



Como próximo passo modificar o arquivo appsettings.json, incluindo no mesmo o elemento ConexaoRedis. Neste item constará a string de conexão, formado pelo host e a chave primária de acesso ao Azure Redis Cache:

{
  "ConexaoRedis": "NOME_HOST,port: 6380,ssl = true,abortConnect=false,password=CHAVE_PRIMÁRIA",
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

A classe Startup também passará por alterações:
  • A integração com o Azure Redis Cache será ativada no método ConfigureServices, acionando-se para isto a operação AddDistributedRedisCache via instância do tipo IServiceCollection (namespace Microsoft.Extensions.DependencyInjection);
  • O método AddDistributedRedisCache receberá como parâmetro uma expressão lambda, na qual será gerada uma instância da classe RedisCacheOptions (namespace Microsoft.Extensions.Caching.Redis) contendo as configurações para acesso ao cache do Redis obtidas via propriedade Configuration.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace TesteRedisCache
{
    public class Startup
    {
        ...
 
        public IConfigurationRoot Configuration { get; }
 
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDistributedRedisCache(options =>
            {
                options.Configuration =
                    Configuration.GetSection("ConexaoRedis").Value;
                options.InstanceName = "TesteRedisCache";
            });
 
            ...
        }
 
        ...
 
        }
    }
}

Quanto ao armazenamento de dados, o Redis suporta por default strings e arrays de bytes. Uma solução para a inclusão de objetos complexos neste mecanismo seria o uso de uma string JSON, representando a instância vinculada ao cache. Com a finalidade de demonstrar tal prática será implementada uma classe chamada TipoComplexo:

namespace TesteRedisCache
{
    public class TipoComplexo
    {
        public string Texto { get; set; }
        public int ValorInteiro { get; set; }
        public double ValorNumerico { get; set; }
    }
}

A classe HomeController também passará por ajustes:
  • Um atributo privado baseado na interface IDistributedCache (namespace Microsoft.Extensions.Caching.Distributed) precisará ser especificado na definição do tipo HomeController, com o intuito de possibilitar o acesso ao recurso criado no Azure Redis Cache;
  • Esta referência de IDistributedCache será preenchida no construtor de HomeController, por meio do mecanismo nativo de injeção de dependências do ASP.NET Core 1.0;
  • O método privado ArmazenarValorCache será responsável pela inclusão de uma chave e seu respectivo valor no cache do Redis. Para isto será invocado o método SetString de IDistributedCache, que receberá como parâmetros a chave, o valor string correspondente e uma instância da classe DistributedCacheEntryOptions (namespace Microsoft.Extensions.Caching.Distributed);
  • Quanto à referência de DistributedCacheEntryOptions, a chamada ao método SetAbsoluteExpiration indicará o tempo máximo de validade de um par chave-valor. Este período iniciará tão logo o mesmo tenha sido incluído no cache (2 minutos para a aplicação de exemplo);
  • Na Action Index serão adicionados 2 itens ao cache (TesteString e TesteObjetoComplexo);
  • No caso da chave TesteString, uma sequência de texto associada à variável testeString será incluída no Redis via método ArmazenarValorCache (desde que este último não conte ainda com tal elemento);
  • Já a chave TesteObjetoComplexo dependerá de uma instância de TipoComplexo. O armazenamento de tal referência envolve a serialização da mesma através da operação SerializeObject do tipo JsonConvert (namespace Newtonsoft.Json), com a obtenção de uma string que será repassada ao método ArmazenarValorCache. Ao se recuperar esta string acontecerá o processo inverso, com a conversão da mesma ao acionar a operação DeserializeObject de JsonConvert;
  • Os valores associados às chaves TesteString e TesteObjetoComplexo serão finalmente vinculados à propriedade ViewBag, a fim de permitir a exibição dos dados em cache na View Index.cshtml.
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
 
namespace TesteRedisCache.Controllers
{
    public class HomeController : Controller
    {
        private IDistributedCache _cache;
 
        public HomeController(IDistributedCache cache)
        {
            _cache = cache;
        }
 
        private void ArmazenarValorCache(
            string chave, string valor)
        {
            DistributedCacheEntryOptions opcoesCache =
                new DistributedCacheEntryOptions();
            opcoesCache.SetAbsoluteExpiration(
                TimeSpan.FromMinutes(2));
 
            _cache.SetString(chave, valor, opcoesCache);
        }
 
        public IActionResult Index()
        {
            string testeString =
                _cache.GetString("TesteString");
            if (testeString == null)
            {
                testeString = "Valor de teste";
                ArmazenarValorCache("TesteString", testeString);
            }
            ViewBag.TesteString = testeString;
 
            TipoComplexo objetoComplexo = null;
            string strObjetoComplexo =
                _cache.GetString("TesteObjetoComplexo");
            if (strObjetoComplexo == null)
            {
                objetoComplexo = new TipoComplexo();
                objetoComplexo.Texto = "Valor de exemplo";
                objetoComplexo.ValorInteiro = 2016;
                objetoComplexo.ValorNumerico = 1914.99;
 
                strObjetoComplexo =
                    JsonConvert.SerializeObject(objetoComplexo);
                ArmazenarValorCache(
                    "TesteObjetoComplexo", strObjetoComplexo);
            }
            else
            {
                objetoComplexo = JsonConvert
                    .DeserializeObject<TipoComplexo>(strObjetoComplexo);
            }
            ViewBag.ObjetoComplexo = objetoComplexo;
 
            return View();
        }
 
        ...
    }
}

Na próxima listagem estão as modificações esperadas para a View Index.cshtml (já considerando os valores associados ao objeto ViewBag):

...
 
<h2>Teste de utilização do Azure Redis Cache</h2>
 
<h3>
    <b>Valor da chave TesteString:</b>
    @ViewBag.TesteString
</h3>
 
<h3>
    <b>Valor da chave TesteObjetoComplexo:</b>
    {Texto: @ViewBag.ObjetoComplexo.Texto,
     ValorInteiro: @ViewBag.ObjetoComplexo.ValorInteiro,
     ValorNumerico: @ViewBag.ObjetoComplexo.ValorNumerico}
</h3>

Testes

Na imagem a seguir está a tela inicial da aplicação TesteRedisCache:



Acessando o recurso criado no Azure Redis Cache via utilitário Redis Desktop Manager será possível observar as duas chaves geradas, bem como seus respectivos valores:





OBSERVAÇÃO: o Redis Desktop Manager é um utilitário cross-plataform e open source, estando voltado ao gerenciamento de bancos de dados criados com o Redis.

Conclusão

Embora o exemplo descrito neste artigo possa também utilizar um servidor on-premise do Redis, a opção pelo serviço oferecido equivalente pelo Microsoft Azure traz uma série de vantagens. A facilidade de uso e gerenciamento, a possibilidade de integração com as principais plataformas de desenvolvimento (.NET é apenas uma das alternativas existentes), além de um alto desempenho estão entre os fatores que podem servir de justificativa para a adoção do Azure Redis Cache.

Referências

ASP.NET Core 1.0 - Documentation
http://docs.asp.net/en/latest/

Azure Redis Cache
https://azure.microsoft.com/en-us/services/cache/

Redis Desktop Manager
https://redisdesktop.com/