Um dos fatores mais importantes na construção de alto desempenho, aplicações Web escaláveis é a capacidade de guardar itens, se os objetos de dados, páginas ou partes de uma página na memória do tempo inicial que forem solicitadas. Você pode armazenar esses itens no servidor Web ou outro software no fluxo de solicitação, como o servidor proxy ou browser. Isto permite-lhe evitar informações recriando que satisfeito um pedido anterior, nomeadamente as informações que demanda tempo significativa do processador ou outros recursos. Conhecido como cache, ele permite que você use um número de técnicas para armazenar a saída de página ou dados de aplicativos através de requisições HTTP e reutilizá-lo. Assim, o servidor não tem de recriar a informação, poupando tempo e de recursos.
O ASP.NET fornece dois tipos de cache que você pode usar para criar aplicações Web de alto desempenho. O primeiro é chamado de cache de saída, que permite armazenar páginas dinâmicas e respostas de controle do usuário em qualquer dispositivo de cache HTTP 1.1 com capacidade de no fluxo de saída, a partir do servidor de origem para o navegador solicitante. No solicitações subseqüentes, a página ou código de controle do usuário não é executado, a saída em cache é usada para satisfazer o pedido. O segundo tipo de armazenamento em cache é tradicional aplicação cache de dados, que você pode usar para programaticamente armazenar objetos arbitrários, como conjuntos de dados, a memória do servidor para que sua aplicação pode economizar o tempo e os recursos que preciso para recriá-los.
Cache de páginas ASP.NET
O ASP.NET permite que você armazene em cache o conteúdo da resposta inteira para páginas dinâmicas em HTTP 1.1 mecanismos capazes, incluindo navegadores, servidores proxy, eo servidor Web onde o aplicativo reside. Isso fornece uma maneira poderosa para você aumentar o desempenho de seus aplicativos da Web. Chamado de cache de saída, que permite que as solicitações subseqüentes para uma página específica a ser satisfeitas a partir do cache para o código que cria inicialmente a página não precisa ser executado em cima dos pedidos subseqüentes.
Usando esta técnica para armazenar em cache páginas mais acessadas do seu site pode aumentar o rendimento do seu servidor Web, geralmente medido em solicitações por segundo, substancialmente. Você tem a escolha de uma API de alto nível declarativo ou uma API de baixo nível programático ao manipular o cache de saída para uma página. Você pode usar o antigo, incluindo a diretiva @ OutputCache no arquivo. Aspx para a página. A diretiva @ OutputCache pode satisfazer quase todas as necessidades comuns que você pode ter quando você deseja armazenar em cache saída de uma página.
A directiva seguinte, quando incluído em um arquivo. Aspx, estabelece uma validade de 60 segundos para a saída em cache de uma página gerada dinamicamente.
<%@ OutputCache Duration=”60″ VaryByParam=”None” %>
CUIDADO Quando você usa a diretiva @ OutputCache, a duração e os atributos VaryByParam são obrigatórios. Se você não incluí-los, um erro de interpretação ocorre quando a página for solicitada. Se você não quiser usar a funcionalidade que o atributo VaryByParam fornece, você deve definir o seu valor para None. Para mais informações sobre como usar o atributo VaryByParam, consulte Armazenando Múltiplas Versões de uma Página.
O ASP.NET também inclui um conjunto de APIs que controle de cache de saída vencimentos e políticas para uma página programaticamente através da classe HttpCachePolicy. Esta classe, seus métodos e suas propriedades estão disponíveis através da propriedade HttpResponse.Cache. Por sua vez, você pode acessar essa propriedade do objeto Page através da propriedade Page.Response. Por exemplo, o código a seguir, quando incluído no bloco de uma página de código-declaração ou a sua classe code-behind, estabelece uma validade de 60 segundos usando o método HttpCachePolicy.SetExpires para a página gerada dinamicamente.
C#
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
[Visual Basic]
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))
Depois de ter ativado o cache de saída, o HTTP GET inicial pedido para a página coloca o seu conteúdo dinâmico na cache de saída para a quantidade de tempo que você especificar. O cache de saída satisfaz subseqüentes solicitações GET, na cabeça ou POST para essa página até que a quantidade de tempo que você especificar expirar. Você pode ativar ou desativar a saída da página cache para cache de dispositivos com capacidade de no fluxo de solicitação declarativamente ou programaticamente também. Na diretiva @ OutputCache para uma página que você pode usar o atributo Location para especificar se a saída da página podem ser armazenadas em servidores de proxy, os clientes do navegador, o servidor Web de origem, ou todos ou nenhum destes. Você pode fazer o mesmo por meio de programação usando o método HttpCachePolicy.
SetCacheability para especificar o valor de enumeração HttpCacheability apropriado para sua página. Para mais informações, consulte Definindo a Cacheabilidade de uma página. Respostas geradas por requisições GET com os parâmetros de seqüência de consulta ou pedidos POST de formulário com parâmetros também podem ser armazenadas em cache, mas o cache para os parâmetros passados ??devem ser explicitamente habilitados usando o atributo @ OutputCache directiva VaryByParam. Para mais informações, consulte Armazenando Múltiplas Versões de uma Página. Lembre-se que todas as manipulações que você quer fazer programação para o cache de saída devem ser feitas no bloco de código-declaração de um arquivo. Aspx, ou em uma classe code-behind associado ao arquivo. Aspx.
O cache de respostas é a forma mais simples de se tirar proveito do sistema de cache do ASP.NET, sem necessidade de redesenho ou alterações de código – o conteúdo a ser enviado ao cliente (resposta) é armazenado em memória e disponibilizado para as próximas requisições. De fato, todo o conteúdo dinâmico pode ser armazenado em mecanismos que suportem o HTTP 1.1 (servidor web, navegadores e proxies) de forma que requisições subsequentes sejam servidas direto do cache sem execução de código. Podemos, portanto, a definir onde ocorrerá este caching – esta definição é feita por meio do atributo Location, na diretiva de OutputCache, ou ainda, por meio do método HttpCachePolicy.SetCacheability dentro do código.
Figura 1. As requisições são servidas direto do cache de resposta, sem execução de código
É necessário, também, ter controle sobre a duração da vida desta resposta no cache – deve-se especificar este tempo de expiração na própria página (.aspx) por meio do atributo Duration na diretiva de OutputCache.
Praticamente todas as aplicações web possuem páginas dinâmicas que recebem parâmetros transmitidos por meio do protocolo http (get ou post). Vale lembrar que os parâmetros são submetidos c/ a requisição e podem ser especificados na querystring (GET) ou por meio de POST. Estes parâmetros visam definir o comportamento das páginas que os recebem – quais dados deverão ser exibidos/armazenados, formato de exibição, etc…
Tomemos como exemplo uma requisição feita aos servidores da NW Traders, onde procuramos exibir os produtos de uma determinada categoria – ver diagrama ao lado. Podemos para tal especificar uma página que recebe o parâmetro CatID, apresentando um grid descritivo dos produtos desta categoria e seus respectivos fornecedores. Para facilitar nossa vida o dba da NW Traders disponibilizou uma Stored Procedure (ProdutosPorCategoria – listagem 1.) que recebe exatamente este CatID como parâmetro e retorna os produtos em questão.
Listagem 1. Stored Procedure -CategoriaEstoque
CREATE PROCEDURE dbo.ProdutosPorCategoria @CatID int AS SELECT Products.ProductName, Products.ProductID, Products.UnitPrice, Products.QuantityPerUnit, Suppliers.CompanyName, Suppliers.City FROM Categories INNER JOIN Products ON Categories.CategoryID = Products.CategoryID INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID WHERE (Categories.CategoryID = @CatID) RETURN
Apesar de estarmos falando da mesma página (ProdutosPorCategoria.aspx), percebe-se facilmente que, se pretendemos colocar esta informação em cache, deverão existir múltiplas versões baseadas no parâmetro CatID. Devemos instruir estas versões por meio do atributo VaryByParam. Vale lembrar que os atributos VaryByParam e Duration são obrigatórios (ver Tabela 1. abaixo).
Nossa diretiva de OutputCache, a ser adicionada na ProdutosPorCategoria.aspx fica assim:
<%@ OutputCache Duration="120" VaryByParam="CatID" %> Para testar a funcionalidade, basta navegar em três ou quatro categorias e depois pausar o SQL Server. Verificamos que, ao selecionarmos novamente as categorias já visualizadas, a informação será servida direto do cache de respostas. Se tentarmos obter informações não “cacheadas” – outros CatID(s), será tentado um acesso à fonte de dados e uma exceção será erguida. Lembre-se, ainda, que a diretiva está definindo que estas respostas devem permanecer no cache por 120 segundos.
Sugerimos que seja testado o comportamento com:
<%@ OutputCache Duration="120" VaryByParam="none" %>
| Duration | Obrigatório. Tempo, em segundos, especificando o período de existência da página no cache; |
| Location | Especifica onde a resposta deve ser colocada em cache. “Any” (padrão): pode estar localizada no navegador, no servidor proxy ou servidor que processou a requisição; “Client”: O cache fica alocado no navegador que originou a requisição; “Downstream”: pode ser “caheado” em qualquer dispositivo que suporte o http 1.1, exceto o servidor web; “None”: desabilitado; “Server”: cache de respostas alocado no servidor web; “ServerAndClient”: Nega a permanência do cache de resposta em proxies; |
| VaryByParam | brigatório. Define, por meio dos parâmetros presentes nas requisições, que múltiplas versões de uma mesma página devem ser mantidas no cache. “none” define uma única versão e “*” especifica diferentes versões para cada parâmetro ou combinação de parâmetros – os parâmetros devem ser separados por “;”; |
| VaryByHeader | Varia o cache baseando-se em um header específico. O header sempre é enviado pelo navegador nas requisições. Podemos, por exemplo, variar pela linguagem do cliente: VaryByHeader=”Accept-Language”; |
| VaryByCustom | Permite que variações customizadas sejam especificadas no global.asax – pode-se variar versões por “Browser” (padrão) ou ainda alguma variável de sessão – neste caso a versão fica associada a um determinado cliente. |
Tabela 1. Descrição dos atributos da diretiva de OutputCache
Existem situações onde o “cacheamento” de toda uma página de resposta não é adequado. Um caso óbvio, seria àquele no qual o desenho da aplicação requerer que algumas partes/seções de uma página específica tenham versões específicas para um determinado grupo de usuários (e.g. menu baseado no perfil do usuário) e que outras partes sejam comuns à todos (e.g. conteúdo genérico c/ notícias gerais). Este é o cenário ideal para o uso do fragment caching.
O cache de fragmentos refere-se ao cache de UCs (user controls – arquivos .ascx) e suporta os mesmos atributos do cache de repostas (com exceção, por motivos óbvios, do location). Estes UCs suportam, ainda, o atributo VaryByControl que define a existência de múltiplas versões, no cache, do mesmo user control, baseadas nos valores assumidos pelos controles membros (embutidos no UC) – um DropDownList por exemplo. Por padrão cada user control é “cacheado” para cada página em separado, mas podemos definir, se for o caso, que todas as páginas hospedeiras (que embutem tais controles) terão a mesma versão em cache. Fazemos isto por meio do atributo shared definido como true (o valor padrão é false).
Se Varybycontrol é especificado o atributo VaryByParam pode ser omitido:
<%@ OutputCache Duration="120" VaryByControl="CatID" Shared="true" %>
Finalmente, vale lembrar que podemos utilizar o cache de resposta manipulando os métodos HttpCachePolicy.SetExpires e HttpCachePolicy.SetCacheability por meio da propriedade HttpResponse.Cache. O código a seguir, configura um tempo de expiração de 20 segundos:
Response.Cache.SetExpires(DateTime.Now.AddSeconds(20));
Como podemos perceber na Figura 1., ao requisitarmos algum recurso colocado no cache de respostas, não ocorre execução de código, já no cache de objetos tomaremos algumas decisões em tempo de execução. Todo desenvolvedor web que estiver lendo estas linhas se lembrará de objetos sempre presente no nosso dia a dia: Session e Application. O ASP.NET nos apresenta um novo objeto para acesso a pares chave/valor – o objeto Cache. Vale lembrar que o escopo do cache do ASP.NET é o application domain de forma que não podemos acessa-lo a partir de outras aplicações ou processos. Outro ponto relevante é que o objeto de Cache é recriado a cada reinício da aplicação, o que nos lembra uma certa similaridade com o objeto Application. A principal diferença entre estes os dois reside no fato de que o objeto de Cache proporciona funcionalidade específicas, tais como dependências e políticas de expiração.
Suponhamos que o arquiteto da aplicação NW Traders especifique que a relação de categorias dos produtos comercializados pela empresa, que é relativamente pequena e não é alterada com frequência – uma vez por semana em média, deva ficar em cache o maior tempo possível, mas que, no entanto, esta relação expire imediatamente ao serem alteradas, removidas ou inseridas novas categorias no banco de dados. Este cenário foi escolhido por apresentar uma das dificuldades encontrada pelo cache de objetos: não está prevista uma forma de se estabelecer uma dependência direta com o banco de dados. Podemos, no entanto, forçar uma dependência por meio de artifícios técnicos.
Quando adicionamos um item ao cache de objetos, definimos algumas dependências que vão forçar a remoção deste item em determinadas circunstâncias. Vejamos a assinatura do método Insert:
public void Insert(string, object, CacheDependency, DateTime, TimeSpan, CacheItemPriority, CacheItemRemovedCallback);
Podemos, por meio do construtor, definir alguns comportamentos interessantes:
Especificamos um método (RecuperaCategorias) que controlará a existência da relação de categorias no cache de objetos.
Listagem 2. Método RecuperaCategorias (C#) que define a expiração, no cache, da relação de categorias;
private DataSet RecuperaCategorias()
{
DataSet ds = new DataSet();
if (Cache["dsCategorias"]==null)
{
SqlConnection cn = new SqlConnection("data source=servidorsql;
integrated security=true;initial catalog=northwind");
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [Categories]",cn);
da.SelectCommand.CommandType = CommandType.Text;
da.Fill(ds, "Categorias");
System.Web.Caching.CacheDependency DepArq = new System.Web.Caching.CacheDependency
(@"\\servidorsql\rede$\Categorias.XML");
Cache.Insert("dsCategorias", ds, DepArq, System.Web.Caching.Cache
.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Normal, null);
}
else
{
ds = (DataSet)(Cache["dsCategorias"]);
}
return ds;
}
Na Listagem 2. vemos que, no sevidor web, a relação de categorias é colocada em cache com uma dependência de arquivos apontando para um diretório na rede (no SQL Server) “\\SERVIDORSQL\REDE$\Categorias.XML” – lembre-se que devemos configurar o compartilhamento e as respectivas permissões , ou obteremos: “Failed to start monitoring changes to ‘\\servidorsql\rede$\’”.
Ok, mas como instruir o Banco de dados a acessar este diretório local e alterar o arquivo Categorias.xml (c:\rede\Categorias.xml)? Por meio de um aplicação console disparada a partir de um trigger (chamamos este procedimento de artifício técnico).
Listagem 3. Função InvalidaCache – UDF (User Defined Function) criada no catálogo Northwind (SQL Server);
CREATE FUNCTION InvalidaCache (@TableName VarChar(25)) RETURNS INT AS BEGIN DECLARE @CMDS VarChar(100) SET @CMDS = 'c:\rede\SQLDependency.exe ' + @TableName EXEC Master..xp_cmdshell @CMDS RETURN 0 END
Listagem 3. Gatilho InvalidaCache – Trigger criado na tabela Categories do catálogo Northwind (SQL Server);
CREATE TRIGGER InvalidaCacheCat ON Categories AFTER INSERT, DELETE, UPDATE AS BEGIN EXEC InvalidaCache 'Categorias' END
Listagem 4. Aplicação console (VB) que irá alterar o arquivo local (deve estar no diretório “c:\rede\” – SQL Server)
Imports System Imports System.IO Namespace SQLDependency Module Invalidate Dim path As String = "c:\rede\" Sub Main(ByVal CmdArgs() As String) Try Dim _table As String = CmdArgs(0).ToString() Dim _r As New Random(Date.Now.Millisecond) Dim _value As String = "" path = path & _table & ".xml" Dim _SWriter As New StreamWriter(New FileStream(path, FileMode.Open, FileAccess.Write)) _SWriter.Write(_value) _SWriter.Close() Catch ex As Exception Throw ex 'no futuro, quem sabe..... End Try End Sub End Module End Namespace
Para fins de teste executamos, no servidor de dados, a aplicação console (Listagem 4.) passando como parâmetro o nome da tabela supostamente alterada: Categories. A aplicação irá alterar o arquivo com o nome da tabela no disco, o que, em nosso caso, vai invalidar o item no cache do servidor web. Um teste mais realista pode ser feito alterando-se o nome de uma das categorias e percebendo que o arquivo c:\rede\Categorias.xml é alterado. Feito isto, o servidor web irá invalidar a relação de categorias (Dataset) colocada em cache por meio da dependência associada ao arquivo remoto \\SERVIDORSQL\REDE$\Categorias.xml.
Últimos Comentários