Применение инфраструктуры кеширования в ASP.NET

от автора

Полтора года назад я написал статью про кеширование в ASP.NET MVC, в которой описал как повысить производительность ASP.NET MVC приложения за счет кеширования как на сервере, так и на клиенте. В комментариях к статье было упомянто много дополнительных способов для управления кешированием в ASP.NET.

В том посте я расскажу как использовать возможности инфраструктуры ASP.NET для управления кешированием.

HTTP-кеширование (revisited)

В прошлом посте был монструозный пример кода для реализации HTTP кеширования при отдаче состояния корзины:

Пример кода

[HttpGet] public ActionResult CartSummary() {     //Кеширование только на клиенте, обновление при каждом запросе     this.Response.Cache.SetCacheability(System.Web.HttpCacheability.Private);     this.Response.Cache.SetMaxAge(TimeSpan.Zero);      var cart = ShoppingCart.GetCart(this.HttpContext);     var cacheKey = "shooting-cart-" + cart.ShoppingCartId;     var cachedPair = (Tuple<DateTime, int>)this.HttpContext.Cache[cacheKey];      if (cachedPair != null) //Если данные есть в кеше на сервере     {         //Устанавливаем Last-Modified         this.Response.Cache.SetLastModified(cachedPair.Item1);          var lastModified = DateTime.MinValue;          //Обрабатываем Conditional Get         if (DateTime.TryParse(this.Request.Headers["If-Modified-Since"], out lastModified)                 && lastModified >= cachedPair.Item1)         {             return new NotModifiedResult();         }          ViewData["CartCount"] = cachedPair.Item2;     }     else //Если данных нет в кеше на сервере     {         //Текущее время, округленное до секунды         var now = DateTime.Now;         now = new DateTime(now.Year, now.Month, now.Day,                             now.Hour, now.Minute, now.Second);          //Устанавливаем Last-Modified         this.Response.Cache.SetLastModified(now);          var count = cart.GetCount();         this.HttpContext.Cache[cacheKey] = Tuple.Create(now, count);         ViewData["CartCount"] = count;     }      return PartialView("CartSummary"); } 

для сравнения — исходный вариант (без кеширования)

        public ActionResult CartSummary()         {             var cart = ShoppingCart.GetCart(this.HttpContext);              ViewData["CartCount"] = cart.GetCount();              return PartialView("CartSummary");         } 

Если вы первый раз видите это код и не знаете откуда он взялся, то прочитайте предыдущую статью.

Естественно для каждого случая кеширования писать такой код очень неудобно. В инфраструктуре ASP.NET уже есть готовая инфраструктура, которая позволяет добиться того же результата гораздо меньшим количеством кода.

Зависимости кеша

В ASP.NET можно привязать ответы сервера к элементам в кеше (System.Web.Caching.Cache).

Делается это одной функцией:

Response.AddCacheItemDependency(cacheKey); 

Но сама по себе привязка не дает ничего. Для того чтобы обрабатывать Conditional-GET необходимо отдавать заголовок Last-Modified и\или E-Tag. Для этого так же есть функции:

Response.Cache.SetLastModifiedFromFileDependencies(); Response.Cache.SetETagFromFileDependencies(); 

Несмотря слово File в имени функций, анализируются любые зависимости ответа. Причем если ответу сервера много зависимостей, то Last-Modified выставляется в наибольшее значение, а E-Tag формируется из всех зависимостей.

Следующий шаг — разрешить кеширование ответа на сервере и на клиенте, ибо ASP.NET умеет обрабатывать Conditional-GET только для ответов, закешированных на сервере:

Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate); 

При выполнении этих четырех строчек кода ASP.NET отдает заголовки Last-Modified, E-Tag, Cache-Control: private и сохраняет ответ на сервере. Но появляется проблема — IE не запрашивает новую версию страницы, кешируя ответ по умолчанию на сутки или до перезапуска браузера. Вообще время кеширования ответа без указания max-age или заголовка Expires может сильно варьироваться между браузерами.

Чтобы победить эту проблему надо указать max-age=0. В ASP.NET это можно сделать следующей функцией:

Response.Cache.SetMaxAge(TimeSpan.FromSeconds(0)); 

Но эта функция также выставляет время жизни кеша ответа на сервере, и, фактически, ASP.NET перестает отдавать кешированые ответы сервера.

Правильный способ добиться результата:

Response.Cache.AppendCacheExtension("max-age=0") 

Тогда ответ кешируется на сервере, но клиенту отдается заголовок Cache-Control: private, max-age=0, который заставляет браузер каждый раз отправлять запрос. К сожалению этот способ не документирован нигде.

В итоге ASP.NET обрабатывает Conditional-GET и отдает ответы из кеша сервера пока в кеше ASP.NET хранится и не изменяется элемент с ключом cacheKey.

Полный код экшена:

[HttpGet] public ActionResult CartSummary() {     var cart = ShoppingCart.GetCart(this.HttpContext);     var cacheKey = "shopping-cart-" + cart.ShoppingCartId;     ViewData["CartCount"] = GetCachedCount(cart, cacheKey);                  this.Response.AddCacheItemDependency(cacheKey);     this.Response.Cache.SetLastModifiedFromFileDependencies();     this.Response.Cache.AppendCacheExtension("max-age=0");     this.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);      return PartialView("CartSummary"); }  private int GetCachedCount(ShoppingCart cart,string cacheKey) {     var value = this.HttpContext.Cache[cacheKey];     int result = 0;     if (value != null)     {         result = (int) value;     }     else     {         result = cart.GetCount();         this.HttpContext.Cache.Insert(cacheKey,result);     }     return result; } 

Согласитесь, это гораздо меньше кода, чем в предыдущей статье.

Варьирование кеша

По умолчанию ASP.NET сохраняет в кеше один ответ для любого пользователя по одному url (без учета querystring). Это приводит к тому, что в примере выше для всех пользователей будет отдаваться один и тот же ответ.

Кстати поведение ASP.NET отличается от заложенного в протокол HTTP, который кеширует ответ по полному url. Протокол HTTP предусматривает возможность варирования кеша с помощью заголовка ответа Vary. В ASP.NET можно также варировать ответ по параметрам в QueryString, по кодировке (заголовок Accept-Encoding), а также по кастомному параметру, привязанному к ответу.

Варьирование по кастомному параметру позволяет сохранять кеш для разных пользователей. Для того чтобы отдавать разные корзины разным пользователям надо:

1) Добавить в контроллер вызов

Response.Cache.SetVaryByCustom("sessionId"); 

2) В Global.asax переопределить метод GetVaryByCustomString

public override string GetVaryByCustomString(HttpContext context, string custom) {     if (custom == "sessionId")     {         var sessionCookie = context.Request.Cookies["ASP.NET_SessionId"];         if (sessionCookie != null)         {             return sessionCookie.Value;         }     }     return base.GetVaryByCustomString(context, custom); } 

Таким образом для разных сессий будут отдаваться разные экземпляры кеша.

При такой реализации надо помнить, что на сервере в кеше сохраняется каждый ответ сервера. Если сохранять большие страницы для каждого пользователя, то они будут часто вытесняться из кеша и это приведет к падению эффективности кеширования.

Зависимости между элементами кеша

Механизм зависимостей в ASP.NET позволяет привязывать не только ответ к элементу внутреннего кеша, но и привязать один элемент кеша к другому. За это отвечают класс CacheDependency и его наследники.

Например:

HttpContext.Cache.Insert("cacheItemKey",data, new CacheDependency(null, new[] { "anotherCacheItemKey" })); 

Если элемент с ключом anotherCacheItemKey будет изменен или удален из кеша, то элемент с ключом cacheItemKey автоматически будет удален из кеша.

Это позволяет строить системы с многоуровневыми синхронизированным кешем.

Дополнительные возможности

Механизм зависимостей кеша в ASP.NET расширяемый. По умолчанию можно создавать зависимости к элементам внутреннего кеша, зависимости к файлам и папкам, а также зависимости к таблицам в базе данных. Кроме того вы можете создать свои классы зависимостей кеша, например для Redis.

Но об этом всем в следующих статьях.

ссылка на оригинал статьи http://habrahabr.ru/post/227129/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *