in ASP.NET

ASP.NET OutputCache usando Memcached

O OutputCache é sem dúvida um do recursos mais essenciais do ASP.NET, ele está disponível desde a primeira versão do ASP.NET. Usar o OutputCache ajuda a melhorar em muito a performance de nossos projetos, evitando vários processamentos desnecessários, armazenando em memória o resultado final de uma página ou um controller, evitando assim que as mesmas informações sejam reprocessadas.

Abaixo segue um diagrama básico do seu funcionamento:

O ASP.NET 4.0 traz um modelo de provider para o módulo de OutputCache, facilitando as possibilidades para criar soluções de armazenamento de paginas em cache. Assim podemos facilmente estender o OutputCache de nossas aplicações, sendo necessário implementar o nosso provider com base na classe System.Web.Caching.OutputCacheProvider.

Existem diversos cenários e ambientes para a implementação do OutputCache, em cenários de Farm que não usam uma solução de cache centralizada, uma página que utiliza OutputCache fica na memória de cada um dos servidores do Farm, em uma solução centralizada, os vários servidores do farm consumiriam a mesma pagina que está cacheada em memória.

A idéia deste post é demonstrar como criar um Provider Custom para o OutputCache que armazene as informações em um servidor de Cache, neste exemplo eu utilizarei o Memcached.

Memcached

Memcached (http://memcached.org/) é um sistema distribuído de alto desempenho para o armazenamento de objetos em memória, é open source. Funciona armazenando e retornando objetos genéricos com base em um chaves. Seu design simples promove a implantação rápida, facilidade de desenvolvimento, resolve muitos problemas de performance em cache da grandes volumes. Com diversas API’s para várias linguagens.

Claro que, ao utilizar o Memcached em cenários de produção, é indispensável pensar em no mínimo duas instâncias, para garantir a continuidade de nossas aplicações em caso de falha em um dos servidores de Memcached.

Ao inicializar o Memcached para testes, no prompt, podemos utilizar os seguintes parâmetros:

C:\memcache>memcached.exe -vv -m512

-vv =verbose mode

-m521 = o memcached pode utilizar até 512 mbs de ram

Só é preciso descompactar o Memcached em um diretório e executa-lo.

Download Memcache

Para .NET eu utilizarei a seguinte biblioteca Memcached

Começando

Inicialmente eu criei dois projetos, um chamado DemoCache.Web que é em ASP.NET MVC3 e outro Class Library chamado DemoCache.Cache o qual eu já referenciei no projeto Web.

No Class Library eu adicionei e referenciei as DLL’s que eu utilizarei para acessar o Memcached, também adicionei referencia do System.Web ao projeto:

Com isso já podemos começar a desenvolver nosso provider para o OutputCache.

Vou criar uma classe chamada OutputCacheMemcachedProvider que herde de OutputCacheProvider (System.Web.Caching)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Caching;

namespace DemoCache.Cache
{
    public class OutputCacheMemcachedProvider : OutputCacheProvider
    {
        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            throw new NotImplementedException();
        }

        public override object Get(string key)
        {
            throw new NotImplementedException();
        }

        public override void Remove(string key)
        {
            throw new NotImplementedException();
        }

        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            throw new NotImplementedException();
        }
    }
}

Vamos criar 2 métodos, o primeiro servirá para transformar a chave(key) passada pelo ASP.NET em um Hash, formatando ela de uma maneira ideal para utilizar.

private string MD5(string value)
{
    var cryptoServiceProvider = new MD5CryptoServiceProvider();
    var bytes = Encoding.UTF8.GetBytes(value);
    var builder = new StringBuilder();

    bytes = cryptoServiceProvider.ComputeHash(bytes);

    foreach (var b in bytes)
        builder.Append(b.ToString("x2").ToLower());

    return builder.ToString();
}

O segundo método servirá para inicializar a conexão com o Memcached, ele pega uma key no arquivo de configuração, na qual pode ter vários endereços separados por virgula:

private MemcachedClient InitMemcached()
{
    var servers = ConfigurationManager.AppSettings["memCachedServers"].Split(',');
    var pool = SockIOPool.GetInstance();
    pool.SetServers(servers);
    pool.Initialize();

    return new MemcachedClient();
}

Chave no web.config

<add key="memCachedServers" value="127.0.0.1:11211"/>

Agora vamos implementar os métodos para manipular o OutputCache, são eles: Add, Get, Remove e Set.

O primeiro serve para adicionar os valores ao cache:

public override object Add(string key, object entry, DateTime utcExpiry)
{
    var memcachedClient = InitMemcached();
    var chave = MD5(key);

    utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);

    if (memcachedClient.KeyExists(chave))
    {
        return memcachedClient.Get(chave);
    }
    else
    {
        memcachedClient.Set(chave, entry, utcExpiry);
        return entry;
    }
}

O outro é o Get, ele retornará o valor que está em cache ou retornará null

public override object Get(string key)
{
    var memcachedClient = InitMemcached();
    var chave = MD5(key);

    if (memcachedClient.KeyExists(chave))
        return memcachedClient.Get(chave);
    else
        return null;
}

O método Remove não será muito utilizado, já que o próprio Memcached expirará os conteúdos

public override void Remove(string key)
{
    var memcachedClient = InitMemcached();
    var chave = MD5(key);

    memcachedClient.Delete(chave);
    return;
}

O último método necessário será o Set, ele atualizará a expiração ou inserirá o valor no cache.

public override void Set(string key, object entry, DateTime utcExpiry)
{
    var memcachedClient = InitMemcached();
    var chave = MD5(key);

    utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);

    memcachedClient.Set(chave, entry, utcExpiry);
    return;
}

Por fim, a classe do nosso Provider ficou da seguinte maneira:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web.Caching;
using Memcached.ClientLibrary;
using System.Configuration;

namespace DemoCache.Cache
{
    public class OutputCacheMemcachedProvider : OutputCacheProvider
    {
        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            var memcachedClient = InitMemcached();
            var chave = MD5(key);

            utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);

            if (memcachedClient.KeyExists(chave))
            {
                return memcachedClient.Get(chave);
            }
            else
            {
                memcachedClient.Set(chave, entry, utcExpiry);
                return entry;
            }
        }

        private MemcachedClient InitMemcached()
        {
            var servers = ConfigurationManager.AppSettings["memCachedServers"].Split(',');
            var pool = SockIOPool.GetInstance();
            pool.SetServers(servers);
            pool.Initialize();

            return new MemcachedClient();
        }

        private string MD5(string value)
        {
            var cryptoServiceProvider = new MD5CryptoServiceProvider();
            var bytes = Encoding.UTF8.GetBytes(value);
            var builder = new StringBuilder();

            bytes = cryptoServiceProvider.ComputeHash(bytes);

            foreach (var b in bytes)
                builder.Append(b.ToString("x2").ToLower());

            return builder.ToString();
        }

        public override object Get(string key)
        {
            var memcachedClient = InitMemcached();
            var chave = MD5(key);

            if (memcachedClient.KeyExists(chave))
                return memcachedClient.Get(chave);
            else
                return null;
        }

        public override void Remove(string key)
        {
            var memcachedClient = InitMemcached();
            var chave = MD5(key);

            memcachedClient.Delete(chave);
            return;
        }

        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            var memcachedClient = InitMemcached();
            var chave = MD5(key);

            utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);

            memcachedClient.Set(chave, entry, utcExpiry);
            return;
        }
    }
}

Para utilizarmos nos nosso projetos basta inserir no web.config, dentro do system.web a referencia para o nosso Provider

<caching>
  <outputCache defaultProvider="OutputCacheProvider">
    <providers>
      <add name="OutputCacheProvider" type="DemoCache.Cache.OutputCacheMemcachedProvider" />
    </providers>
  </outputCache>
</caching>

Isso fará com que o OutputCache de nossa aplicação já utilize o provider que criamos.

Projeto para download

Espero que este post seja útil,

estou a disposição para dúvidas, criticas e sugestões

 

Rodolfo

 

 

 

 

 

 

  • http://www.alexandretarifa.com.br/?p=259 Desenvolvimento Web com ASP.NET – tecnologias complementares : Alexandre Tarifa

    […] […]

  • Adrianod Rodrigues

    @Rodolfo

    Parece bastante interessante essa solução. Mas qual seria a utilidade de utilizar o memcache ao invés do OutputCache default do MVC 3?

  • Anonymous

    Opa Adriano, para cada tipo de armazenamento existe seu respectivo “custo”, performance e escalabilidade. Utilizar um servidor de cache pode ser um pouco menos performático para armazenar as informações, entretanto em alguns cenários utilizar um servidor, ou vários servidores de cache pode ser muito performático, imagine um cenário que temos 5 servidores web, a mesma informação estará duplicada na memória de cada um deles (no caso do outputcache default) já utilizando o memcached, é possível criar uma estrutura melhor (“memória”) e todos os servidores do farm consumirem a mesma informação que está no memcached, assim também é possivel ter um perfil de memória menor nos servidores web.

  • http://www.facebook.com/profile.php?id=1041621606 André Mansur

    Sendo assim, a vantagem existiria somente pra manter um servidor de cache compartilhado?

    Eu tenho uma duvida, o que eh mais vantajoso: implementar um cache com o memcache (ou qualquer outro) e deixar em memoria uma lista de todos os produtos de uma loja (ex), ou salvar isso em disco e usar um lucene da vida pra buscas? Qual seria mais rápido?

    Obrigado.

  • http://www.facebook.com/people/Charles-Silva/100001557377047 Charles Silva

    Excelente!

  • http://www.rodolfofadino.com.br/2013/10/cache-net-outputcache/ Cache + .NET: OutputCache – // Rodolfo Fadino

    […] 4.0 o ASP.NET tornou possível a extensibilidade do OutputCache através de providers, neste post (ASP.NET OutputCache usando Memcached) eu demonstro como criar um provider e armazenar o OutputCache no […]