Изучение кэширования в ASP.NET

ОГЛАВЛЕНИЕ

Большее число клиентов означает большее число запросов к веб-серверу и тяжелую нагрузку на сеть, вызывающую падение производительности. Для решения этой проблемы было применено кэширование в веб-приложении.

Введение

Большее число клиентов означает большее число запросов к веб-серверу и тяжелую нагрузку на сеть, вызывающую падение производительности. Для решения этой проблемы было применено кэширование в веб-приложении. Большинство описанных в статье вещей известно многим читателям, но статья была написана так, чтобы новички тоже поняли ее. В ходе написания статьи была обнаружена такая интересная особенность, как установка разных мест для кэширования. Также была приведена соответствующая диаграмма Visio.

Что такое кэширование?

К веб-приложениям обращается множество пользователей. На веб-сайт ложится тяжелая нагрузка, которая может расти экспоненциально и  тормозить сервер и доступ к сайту. Медленный доступ – самая распространенная проблема веб-сайтов при одновременном обращении к ним большого числа клиентов. Для решения этой проблемы можно использовать высокий уровень аппаратной конфигурации, балансировщик нагрузки, широкую полосу пропускания. Однако нагрузка – не единственная причина, тормозящая веб-сайт, поэтому надо создать какой-то механизм, обеспечивающий быстрый доступ к данным и прирост производительности. Кэширование дает такое решение.

Кэширование – это метод, позволяющий хранить часто используемые данные, и таким образом веб-страницы  могут временно храниться на локальном жестком диске для дальнейшего извлечения. Этот метод сокращает время доступа, когда много пользователей одновременно обращаются к веб-сайту, или когда один пользователь многократно обращается к веб-сайту. Кэширование для веб-приложений может происходить на клиенте (кэширование в браузере), на сервере между клиентом и веб-сервером (кэширование в прокси / кэширование в обратном прокси) и на самом веб-сервере (кэширование страницы или кэширование данных).

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

Разные места кэширования

Кэширование в веб-приложении можно делать на стороне клиента (браузер клиента), между клиентом и сервером (кэширование в прокси и в обратном прокси), или на стороне сервера (кэширование данных/кэширование вывода страницы). Места кэширования классифицируются следующим образом:
1.    Кэширование на клиенте
2.    Кэширование в прокси
3.    Кэширование в обратном прокси
4.    Кэширование на веб-сервере

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

 

Рис. 1 : Кэширование на клиенте

Плюсы
1.    Легкий доступ к данным, кэшированным на локальном клиенте
2.    Уменьшает сетевой трафик

Минусы
1. Кэшированные данные полностью зависят от браузера, поэтому они не разделяемы
2. Кэширование в прокси: Основной недостаток кэширования на клиенте заключается в том, что данные, хранящиеся в браузере клиента, являются специфичными для клиента. Кэширование в прокси использует выделенный сервер, хранящий кэшированную информацию между клиентом и веб-сервером в разделяемом месте, так что все клиенты могут использовать одни и те же разделяемые данные. Прокси-сервер (например, прокси-сервер Microsoft) исполняет все запросы к веб-странице без отправки запроса к фактическому веб-серверу через интернет, обеспечивая более быстрый доступ.

 

Рис. 2: Кэширование в прокси

Кэширующие прокси часто помещаются вблизи сетевых шлюзов для уменьшения трафика. Иногда несколько кэширующих прокси-серверов используются для большего числа клиентов. Это называется кэширующим массивом.

 

Рис. 3: Кэширующий массив

Плюсы
1. Легкий доступ к данным, кэшированным на прокси-сервере
2. Уменьшает сетевой трафик

Минусы
1. Требует развертывания и издержки инфраструктуры для обслуживания кэширующего прокси-сервера
2. Кэширование в обратном прокси: Можно поместить прокси-сервер перед веб-сервером, чтобы уменьшить количество получаемых им запросов. Это позволяет прокси-серверу отвечать на часто получаемые запросы и передавать веб-серверу только остальные запросы. Это называется обратным прокси.

 

Рис. 4: Кэширование в обратном прокси

Плюсы
1. Легкий доступ к данным, кэшированным на обратном прокси-сервере
2. Уменьшает количество запросов

Минусы
1. Так как сервер конфигурируется перед веб-сервером, может увеличиться сетевой трафик
2. Кэширование на веб-сервере: При кэшировании на веб-сервере кэшированные данные хранятся внутри веб-сервера. Кэширование данных и кэширование страницы использует механизм кэширования веб-сервера.

 

Рис. 5: Кэширование на веб-сервере

Плюсы
1. Повышает производительность сайтов путем уменьшения извлечения данных в обе стороны из базы данных или какого-то другого сервера

Минусы
1. Увеличивает нагрузку на сеть

Преимущества кэширования
1.    Уменьшает нагрузку на сервер
2.    Уменьшает потребление полосы пропускания


Возможность кэширования в ASP.NET

ASP.NET поддерживает кэширование страницы, неполной страницы (фрагмента) и кэширование данных. Кэширование динамически генерируемой страницы называется кэшированием вывода страницы. При кэшировании страницы, когда кэшируется динамически генерируемая страница, к ней обращаются только в первый раз. Любое последующее обращение к той же самой странице будет возвращаться из кэша. ASP.NET также позволяет кэшировать часть страницы, что называется кэшированием неполной страницы или кэшированием фрагмента. Кэшируются другие данные сервера (например, данные сервера SQL, данные XML), к которым можно легко обратиться без повторного извлечения данных с помощью кэширования данных. Кэширование уменьшает число запросов «туда-обратно» к базе данных и другим источникам данных. ASP.NET предоставляет полноценный механизм кэширования данных, снабженный поддержкой уборки мусора (на базе приоритета кэша), истечения, зависимостей от файла, ключа и времени. Есть два места, где кэширование может использоваться для повышения производительности приложений ASP.NET.

 

Рис 6: Возможность кэширования в ASP.NET

На рисунке выше (1) используется для возвратного кэширования страницы, то есть он используется в кэшировании вывода, и (2) избавляет от передачи данных туда-обратно путем хранения данных с помощью кэширования данных.

ASP.NET поддерживает два типа политик истечения, определяющих, когда объект истекает или удаляется из кэша.

Абсолютное истечение: Определяет, что истечения происходят в указанное время. Абсолютные истечения задаются в формате полного времени (чч:мм:сс). Объект будет удален из кэша в указанное время.

Разные типы кэширования

ASP.NET поддерживает три типа кэширования:
1.    Кэширование вывода страницы [кэширование вывода]
2.    Кэширование фрагмента [кэширование вывода]
3.    Кэширование данных

Кэширование вывода страницы

Чтобы  начать кэширование вывода страницы, надо знать процесс компиляции страницы, потому что исходя из генерации страницы можно понять, почему следует использовать кэширование. Процесс компиляции страницы ASPX двухэтапный. Сначала код компилируется в промежуточный язык Microsoft (MSIL). Далее MSIL компилируется в собственный код (оперативным компилятором) при выполнении. Весь код в странице ASP.NET компилируется в MSIL при построении сайтов, но в момент выполнения преобразуется в собственный код только часть MSIL, необходимая пользователю, и выполняются запросы пользователя, что повышает производительность.

 

Рис. 7: Процесс выполнения страницы ASP.NET

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

 

Рис. 8: Кэширование вывода страницы

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

Для кэширования вывода директива OutputCache добавляется на любую страницу ASP.NET с заданием длительности (в секундах) кэширования страницы.

Пример

<%@ Page Language="C#" %>
<%@ OutputCache Duration='300' VaryByParam='none' %>
<html>

  <script runat="server">
    protected void Page_Load(Object sender, EventArgs e) {
        lbl_msg.Text = DateTime.Now.ToString();
    }
  </script>

  <body>
    <h3>Output Cache example</h3>
    <p>Page generated on:
       <asp:label id="lbl_msg" runat="server"/></p>
  </body>
</html>

Также можно установить свойство кэширования из отделенного кода:

void Page_Load(Object sender, EventArgs e) {
      Response.Cache.SetExpires(DateTime.Now.AddSeconds(360));
      Response.Cache.SetCacheability(
                   HttpCacheability.Public);
      Response.Cache.SetSlidingExpiration(true);
      _msg.Text = DateTime.Now.ToString();
}

Надо указать длительность и атрибут VaryByParam. Длительность определяет, как долго кэш будет сохраняться. VaryByParam определяет, меняется ли кэш со значениями параметра.

 

Рис. 9: Кэширование нескольких страниц на базе параметров

Как показано на рисунке выше, если для страницы используется строка запроса, и надо кэшировать все страницы на базе строки запроса, то придется использовать атрибут VaryByParam кэширования вывода. На базе строки запроса данные должны кэшироваться, и когда пользователь запрашивает страницу со строкой запроса (ID на рисунке), страница извлекается из кэша. Следующий пример описывает использование атрибутов VaryByParam.

Пример:

<%@ OutputCache Duration="60" VaryByParam="*" %>
<! страница будет находиться в кэше 60 секунд, и создастся отдельная запись
   кэша для каждого варианта строки запроса -->

Следующая таблица показывает самые распространенные и самые важные атрибуты кэша вывода:

Атрибут

Значения

Описание

Duration

число

Определяет, как долго страница будет кэшированной (в секундах).

Location

'любое'

'клиент'

'ниже'

'сервер'

'нет'

Определяет местоположение кэша страницы. Далее рассматривается подробно.

VaryByCustom

'браузер'

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

VaryByParam

'нет' '*'

Обязательный атрибут, требуемый для параметра для страницы. Уже был рассмотрен.

Все атрибуты, указанные в директиве OutputCache, используются для заполнения экземпляра класса System.Web.HttpCachePolicy. Полная реализация политик кэширования, предоставляемых ASP.NET, инкапсулирована в классе HttpCachePolicy. Далее идет еще одна реализация кэширования из отделенного кода.

Место кэширования вывода

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

Следующая таблица показывает детали местоположения. Она показывает местоположение кэша и действия заголовков Cache-Control(управление кэшированием) и Expires(истекает).

Значение местоположения

Заголовок Cache-Control

Заголовок  Expires

Страница кэшируется на сервере

Описание

'любое'

public (открытый)

да

да

Страница кэшируется в браузере клиента, на последующем сервере, или на сервере.

'клиент'

private (закрытый)

да

нет

Страница кэшируется только в браузере клиента.

'ниже'

public

да

нет

Страница кэшируется на последующем сервере и клиенте.

'сервер'

no-cache

(не кэшировать)

нет

да

Страница кэшируется только на сервере.

'нет'

no-cache

нет

нет

Отключает кэширование вывода для этой страницы.

Например, если указать значение Client для атрибута Location директивы OutputCache на странице –  страница не будет сохраняться в кэше сервера, но ответ будет содержать заголовок Cache-Control (страницы могут указывать, должны ли они кэшироваться в прокси, с помощью заголовка Cache-Control) со значением private и заголовок Expires (ответ HTTP, указывающий дату и время, после которого страница должна быть снова извлечена с сервера) с временной меткой, установленной во время, указанное атрибутом Duration.

Пример

<%@ OutputCache Duration='120' Location='Client' VaryByParam='none' %>

Это сохранит кэш на 120 секунд, и кэшированные данные не должны сохраняться на сервере, они должны храниться только в браузере клиента.


Кэширование фрагмента страницы

ASP.NET предоставляет механизм для кэширования частей страниц, именуемый кэшированием фрагмента страницы. Для кэширования части страницы сначала необходимо инкапсулировать часть страницы, которую надо кэшировать, в пользовательский управляющий элемент. В исходный файл пользовательского управляющего элемента добавляется директива OutputCache, задающая атрибуты Duration и VaryByParam. Когда этот пользовательский управляющий элемент загружается в страницу при выполнении, он кэшируется, и все последующие страницы, ссылающиеся на тот же пользовательский управляющий элемент, будут извлекать его из кэша.

 

Рис. 10: Кэширование фрагмента

Следующий пример показывает подробности кэширования фрагмента:

Пример

<!— UserControl.ascx —>

<%@ OutputCache Duration='60'
                VaryByParam='none' %>
<%@ Control Language="'C#'" %>

<script runat="server">
  protected void Page_Load(Object src, EventArgs e)
  {
     _date.Text = "User control generated at " +
                   DateTime.Now.ToString();
  }
</script>
<asp:Label id='_date' runat="'server'" />

Здесь имеется кэширование пользователя на пользовательском управляющем элементе, поэтому при его использовании на странице часть страницы будет кэшироваться.

Кэширование данных

Кэширование данных может резко повысить производительность приложения путем уменьшения соперничества за базу данных и передачи данных туда-обратно. Кэширование данных хранит требуемые данные в кэше, так что веб-сервер не будет отправлять запросы серверу баз данных всегда для абсолютно всех запросов, что повышает производительность веб-сайта. Для кэширования данных необходимо кэшировать данные, которые доступны всем или очень распространены. Кэширование данных является полноценным механизмом кэширования, позволяющим хранить и извлекать данные между несколькими запросами HTTP и несколькими сессиями внутри одного и того же приложения.

 

Рис. 11: Кэширование данных

Изображение выше показывает, как обращаться к данным прямо из сервера баз данных и как данные извлекаются с помощью кэша. Кэширование данных относится не только к серверу SQL, можно хранить в других источниках данных, как показано на рис 1.4.

Далее показана реализация кэширования данных в веб-приложении. Данные или объекты добавляются в кэш тремя способами. Но, исходя из ситуации, придется обращаться к ним по-разному. Эти методы - Cache[],Cache.add(), cache.insert(). Следующая таблица показывает явное различие этих методов.

 

хранит данные в кэше

поддерживает зависимость

поддерживает истечение

поддерживает настройки приоритета

возвращает объект

cache[]

да

нет

нет

нет

нет

cache.insert()

да

да

да

да

нет

cache.add()

да

да

да

да

да

cache[] является простым в использовании свойством, но cache.insert() и cache.add() дают больше контроля над кэшированными данными.

Далее рассмотрены особенности методов Cache.Insert() и Cache.Add(). Cache.Insert() имеет четыре перегрузки, тогда как Cache.Add() не имеет перегруженных методов. Следующая таблица показывает чаще всего используемые свойства для этих методов.

Свойство

Тип

Описание

Key

String

Уникальный ключ, идентифицирующий эту запись в кэше.

Dependency

CacheDependency

Зависимость, которуюимеетэтазаписькэшанафайл, каталогилинадругуюзаписькэшапри ее изменении эта запись удаляется.

Expires

DateTime

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

Sliding Expiration

TimeSpan

Время между тем, когда к объекту последний раз обращались и когда объект должен быть удален из кэша.

Priority

CacheItemPriority

Наскольковажнодержатьэтотэлементвкэшепосравнениюсостальнымизаписямикэша(используется при решении,как удалять объекты кэшаприуборке мусора).

OnRemoveCallback

CacheItem RemovedCallback

Делегат, регистрируемый в записи кэша для вызова при удалении.

Первые два обязательны для методов Cache.Insert(), тогда как остальные меняются в зависимости от ситуации.


Зависимость кэша

Используя зависимость кэша, можно установить зависимость кэша от каких-либо данных или сущности, способных измениться. Можно установить зависимость кэша, по которой можно обновить/удалить кэш. ASP.NET поддерживает три типа зависимости:
•    Зависимость на базе файла
•    Зависимость на базе ключа
•    Зависимость на базе времени

Зависимость на базе файла: Зависимость на базе файла делает недействительным конкретный элемент кэша, когда меняется файл(ы) на диске.

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

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

Зависимость на базе ключа: Зависимость на базе ключа делает недействительным конкретный элемент кэша, когда меняется другой элемент кэша.

Применение: Полезна, когда в кэше есть несколько взаимосвязанных объектов, и если один из объектов меняется, надо обновить или удалить их все.

Зависимость на базе времени: Зависимость на базе времени заставляет элемент истечь в заданное время. Метод Cache.Insert() класса Cache используется для создания зависимости на базе времени. Доступны два типа зависимости на базе времени.
•    Абсолютная
•    Скользящая

Абсолютная: Устанавливает абсолютное время истечения элемента кэша. Абсолютные истечения задаются в формате полного времени (чч:мм:сс). Объект будет удален из кэша в заданное время.

Скользящая: Сбрасывает время истечения элемента в кэше при каждом запросе. Это полезно, когда надо сохранять объект в кэше, пока запросы на этот элемент приходят от разных клиентов.

Помимо перечисленных зависимостей, ASP.NET позволяет следующее:

Автоматическое истечение: Мало используемые элементы кэша без зависимостей автоматически истекают.

Поддержка обратного вызова: Объект кэша можно сконфигурировать на вызов конкретного фрагмента кода, который будет исполняться при удалении элемента из кэша. Это дает возможность обновить кэш. Можно использовать OnRemoveCallback().

Принципы кэширования

Принципы кэширования вывода
1.    Активировать кэширование вывода на страницу, к которой часто обращаются и которая возвращает абсолютно такое же содержимое для всех обращений.
2.    При активации кэширования вывода для страницы ни в коем случае не вводите неправильное поведение и/или отображение для любого конкретного клиента.
3.    Аккуратно определите длительность кэшированной страницы, чтобы уравновесить скорость доступа (пропускную способность) с потреблением памяти и правильностью согласованности кэша.
4.    Активируйте скользящее истечение на странице, если придется использовать VaryByParam='*'.

Принципы кэширования данных

1.    Кэш данных не является вместилищем для разделяемого корректируемого состояния.
2.    Кэшируйте данные, к которым часто обращаются, и получение которых относительно затратно.
3.    Если данные зависят от файла, каталога или другой записи кэша, используйте CacheDependency для обеспечения актуальности данных.

Рекомендуемые применения типов кэширования

Ситуация

Рекомендуемый тип кэширования

Генерируемаястраницавосновномнеменяется, нонесколькопоказываемыхввыводестраницрегулярно меняются.

Использовать кэширование фрагмента.

Генерируемаястраница постоянно меняется, но есть несколько объектов, меняющихся реже.

Использовать кэширование данных для объектов.

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

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

Ссылки

•    MSDN ссылка 1
•    MSDN ссылка 2
•    Книга: Основы ASP.NET с примерами на C# от Фрица Аньена