23 мая 2011 г.

Кэширование в ASP.NET Часть 3. Продвинутое создание зависимостей

В прошлых статьях мы познакомились с классом Cache, научились использовать его функции для помещения и удаления объектов в кэше. Сегодня мы поговорим о классе CacheDependency, напоминаю он связывает объект с определенными источниками данных и при выявлении изменений в последних, удаляет объект из кэша. В прошлой статье мы даже рассмотрели простой сценарий использования таких зависимостей, например связывание с файлом на диске. Однако класс CacheDependency является намного более гибким и расширяемым элементом инфраструктуры кэширования ASP.NET, поэтому пришло время познакомиться с ним подробнее.

Архитектура класса CacheDependency
В первых версиях ASP.NET данный класс представлял достаточно жалкое зрелище – функционал был ограничен, его нельзя было наследовать и, как следствие, расширять. Однако разработчики подсуетились и начиная с ASP.NET класс CacheDependency стал полноценным и расширяемым механизмом.
Стандартный класс CacheDependency позволяет настроить зависимости от файлов и папок на диске или от других объектов в кэше. Для более специализированных зависимостей существуют специальные расширения класса CacheDependency:
  1. AggregateCacheDependency – позволяет создать зависимость, которая комбинирует в себе произвольное количество других зависимостей;
  2. XmlDataCacheDependency – зависимость объекта в кэше от Xml данных в файле на диске;
  3. SqlCacheDependency – установка зависимостей от таблиц в базе данных;
  4. “Ваша собственная” – как говорилось выше класс CacheDependency является наследуемым объектом, поэтому разработчикам предоставляется возможность создать собственный специализированный класс зависимости.
Давайте рассмотрим примеры применения вышеперечисленных специализированных зависимостей.

Использование AggregateCacheDependency
Первой мы рассмотрим зависимость AggregateCacheDependency. Ее следует использовать в том случае, когда необходимо установить для объекта в кэше не единичную зависимость, а составную. Например для объекта в кэше можно установить зависимость от таблицы в базе данных и от файла на диске.
Рассмотрим следующий пример создания AggregateCacheDependency:
     public void PutObjectToCache(string key, MyClass value)
{
string filename1 = "info.txt"; string filename2 = "error.log"; CacheDependency dependency1 = new CacheDependency(filename1); CacheDependency dependency2 = new CacheDependency(filename2); CacheDependency[] depenMas = new CacheDependency[]  { dependency1, dependency2 }; AggregateCacheDependency dependencies = new AggregateCacheDependency(); dependencies.Add(depenMas); Cache.Insert(key, value, dependencies);
}
В данном случае мы устанавливаем зависимость помещаемого в кэш объекта от двух файлов на диске. Мы создали два объекта CacheDependency и передали их в массив. Создали экземпляр класса AggregateCacheDependency  и при помощи метода Add поместили туда созданный массив зависимостей. Массив должен содержать объекты типа CacheDependency, поэтому в него можно передать как объекты класса CacheDependency так и объекты его различных потомков(например SqlCacheDependency).

Создание SqlCacheDependency
Следующим мы рассмотрим процесс создания зависимостей для объектов в кэше от таблиц базы данных MS SQL Server. Стоит отметить, что данных механизм существенно эволюционировал по сравнению с первыми версиями ASP.NET, в которых для обеспечения таких зависимостей необходимо было использовать триггеры на уровне базы данных, что приводило к блокировкам на уровне базы данных в высоконагружаемых приложениях и, как следствие, наблюдалось существенная потеря производительности базы данных.
В версиях ASP.NET 2.0-4.0 разработчики постарались максимально упростить данный механизм. В результате мы получили более удобный программный интерфейс для работы с SqlCacheDependency. А также, начиная с SQL Server 2005, в ядро СУБД MS SQL внедрен эффективный механизм отслеживания изменений в таблицах баз данных, без применения триггеров и блокировок.
Итак, давайте посмотрим как можно создавать зависимости от таблиц SQL Server и применять эти зависимости для кэширования данных. Мы рассмотрим два способа кэширования:
  1. Кэширование данных, которые используются в SqlDataSource;
  2. Отслеживание изменений в результатах выполнения определенного запроса к базе данных.
Кэширование данных в SqlDataSource
Первым делом необходимо создать в файле конфигурации web.config строку подключения к необходимой базе данных. В примере ниже – строка соединения с базой данных Northwind:
<configuration>
   <connectionStrings>    
       <add name="NorthwindConnectionString" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=Northwind; User ID=temp_login; Password=temp_login" providerName="System.Data.SqlClient" />
   </connectionStrings>
</configuration>
Теперь необходимо создать объект SqlCacheDependency и соединить его со строкой подключения к базе данных Northwind, указанной выше. Это также можно седлать в файле конфигурации:
<system.web>
    <caching>
        <sqlCacheDependency enabled="true" pollTime="1000" >
           <databases>
             <add name="Northwind" connectionStringName="NorthwindConnetionString" pollTime="1000" />
           </databases>
        </sqlCacheDependency>
    </caching>
</system.web>
В примере создается объект SqlCacheDependency, в разделе <databases> находятся ссылки на базы данных, в которых будет проводится отслеживание изменений в рамках данной зависимости.
Свойство pollTime указывает частоту проверки базы данных на наличие изменений(в миллисекундах). Это свойство можно задавать как для всего объекта SqlCacheDependency, так и отдельно для каждой базы данных в разделе <databases>. Необходимо серьезно подумать каким будет оптимальный интервал проверки изменений для вашего проекта. В примере выше – это одна секунда. Однако данный показатель рассчитывается для каждого приложения индивидуально. Если разработчик установит слишком большой интервал – будет высокая вероятность того, что пользователи получат неактуальные данные, если установит слишком низкий интервал  – база данных будет нагружена дополнительными запросами.
Теперь осталось только соединить созданную конфигурацию SqlCacheDependency с источником данных в приложение – SqlDataSource:
    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
             EnableCaching="true"
             SelectCommand="SELECT * FROM Customers"
             ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
             SqlCacheDependency="Northwind:Customers">
    </asp:SqlDataSource>
Обязательно необходимо включить кэширование у компонента SqlDataSource, установив свойство EnableCaching в значение true.
Свойство SqlCacheDependency устанавливает объект зависимости. В примере указано Northwind:Customers. Northwind – это название записи в разделе <databases> конфигурации элемента SqlCacheDependency в файле web.config, Customers – это название таблицы в базе данных Northwind, в которой будут отслеживаться изменения. Если необходимо указать несколько таблиц – следует записать их через запятую.
В этом примере мы использовали компонент SqlDataSource для демонстрации кэширования данных из таблиц SQL Server. Однако также возможно настроить похожее кэширование для ObjectDataSource.
Кэширование результатов запросов к базе данных
Мы рассмотрели кэширование данных из баз данных для источников данных, теперь посмотрим как можно использовать объекты SqlCacheDependency для помещения данных в хранилище Cache. Рассмотрим следующий пример:
    protected void Page_Load(object sender, EventArgs e)
    {
SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings ["NorthwindConfigurationString"].ConnectionString); SqlCommand cmd = new SqlCommand( "SELECT * FROM Customers WHERE Country='USA'", conn); SqlDataAdapter sqladapter = new SqlDataAdapter(cmd); DataTable resultData = new DataTable(); sqladapter.Fill(resultData);
SqlCacheDependency sqlDependency = new SqlCacheDependency(cmd); Cache.Insert("NortwindCustomers", resultData, sqlDependency);
}
Здесь будет установлена зависимость от результатов конкретного запроса к таблице базы данных(объект cmd). Мы извлекаем результаты данного запроса в виде таблицы(resultData) и помещаем из в коллекцию Cache с ключом “NortwindCustomers”. Третьим параметром указывается объект SqlCacheDependency, который мы создали, передав в конструктор объект cmd.
Теперь таблица данных будет находится в кэше пока не изменятся результаты заданного запроса. Как только в таблицу Customers будут добавлены или удалены строки, у которых столбец Country равен “USA“, данные в кэше будут отмечены как устаревшие и будут удалены.
Допустим, мы хотим чтобы данные в кэше не удалялись, а обновлялись при изменении таблицы в базе данных. Чтобы это обеспечить, можно воспользоваться функцией обратного вызова. Напишем следующий код:
    public partial class WebForm : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e) { PushUSACustomersToCache(); } public void PushUSACustomersToCache() {
SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings ["NorthwindConfigurationString"].ConnectionString); SqlCommand cmd = new SqlCommand( "SELECT * FROM Customers WHERE Country='USA'", conn); SqlDataAdapter sqladapter = new SqlDataAdapter(cmd); DataTable resultData = new DataTable(); sqladapter.Fill(resultData); 
SqlCacheDependency sqlDependency  
= new SqlCacheDependency(cmd);
CacheItemRemovedCallback removeCallback = new CacheItemRemovedCallback(RemoveCustomersFromCache);
  Cache.Insert("NortwindCustomers", resultData, sqlDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, removeCallback); }   public void RemoveCustomersFromCache(string key, object value, CacheItemRemovedReason reason) {
if (reason == CacheItemRemovedReason.DependencyChanged) { PushUSACustomersToCache(); } }
}
В функции PushUSACustomersToCache извлекаются данные из таблицы базы данных(создание sql зависимости точно такое же как в прошлом примере) и объявляется объект делегата CacheItemRemovedCallback, который указывает на функцию RemoveCustomersFromCache. Теперь при каждом критическом изменении в базе данных, когда наши данные будут удаляться их кэша – будет вызываться функция RemoveCustomersFromCache. В ней проверяется причина удаления из кэша, которую передал механизм ASP.NET и, если это - изменение зависимостей, то мы снова помещаем последние данные из базы в кэш. Таким образом мы обеспечили постоянное нахождение в кэше актуальных данных из базы.

Кэширование данных при помощи XmlDataCacheDependency
Как понятно из названия данная создает зависимость данных в кэше от данных в xml документе. Мы конечно можем установить зависимость с самим файлов, содержащим данные xml, воспользовавшись стандартным объектом CacheDependency. Однако в этом случае данные в кэше будут удалятся при любом изменении в любом месте данного файла. Если же необходимо удалять данные из кэша только при изменениях в определенном узле документа xml – следует воспользоваться специализированным классом XmlDataCacheDependency.
Данный класс использует выражение Xpath для обнаружения поддерева в документе xml. После этого он с определенным интервалом извлекает данные из документа xml и сравнивает их со значениями, которые находятся в кэше. В случае несовпадения – данные к кэше отмечаются как устаревшие и удаляются.
Рассмотрим следующий пример. Допустим, у нас есть следующий xml документ:
  <Data>
     <Customers>
       ...
       <Customer>
          <id>150</id>
          <email>abc@hotmail.com</email>
          <firstname>Morgan</firstname>
          <lastname>Sparlok</lastname>
          <balance>200</balance>
       </Customer>
     </Customers>
  </Data>
Допустим, мы хотим чтобы данные оставались в кэше, пока у кастомера с id = 150 не изменяется баланс счета. Напишем Xpath выражение которое будет отображать узел <balance> объекта <customer> с <id> равным 150:
   string xpath = "/Data/Customers/Customer[id=150]/balance";
Теперь осталось только поместить данные в кэш:
      protected void Page_Load(object sender, EventArgs e)
      {
           XmlDataCacheDependency xmlDependency = 
                 new XmlDataCacheDependency("customers.xml", xpath, 1);
           Cache.Insert("my_key",xmlDependency.CurrentValue, xmlDependency);      
      }

Выводы
В этой статье мы подробно рассмотрели класс CacheDependency, изучили его потомков и научились создавать разнообразные зависимости для записей в кэше, такие как AggregateCacheDependency, XmlDataCacheDependency и SqlCacheDependency.

18 мая 2011 г.

Кэширование в ASP.NET Часть 2. Работа с объектом Cache

Теперь, когда мы познакомились с основами кэширования и разобрались с архитектурой и программным интерфейсом кэширования, которые предоставляет механизм ASP.NET, пришло время углубится в сам процесс работы с программным интерфейсом кэширования при разработке веб-проектов.


Задание приоритета для кэшируемого объекта
Напомню вам описание основного метода для помещения объекта в кэш:
void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);

Шестым параметром в этом методе является значение из перечисления CacheItemPriority. Это параметр имеет очень важное значение. Опираясь на него механизм кэширования ASP.NET определяет какие объекты можно в первую очередь удалять, в случае нехватки физической памяти машины. Данное поведение является одним из конкурентных преимуществ объекта Cache. Ведь именно разработка интеллектуальной системы управления рабочей памятью машины была одной из основных задач, которые стояли перед разработчиками механизм кэширования в ASP.NET.

Приоритет может быть установлен в следующие значения:

  1. Low – самый низкий приоритет, записи с таким приоритетом при освобождении памяти удаляются из кэша в первую очередь;

  2. BelowNormal – приоритет у объекта ниже среднего;
  3. Normal – среднее значение приоритета, автоматически устанавливается для всех объектов, добавляемых в кэш, при отсутствии явного указания приоритета;

  4. Default – значение по умолчанию, тоже самое что и Normal;
  5. AboveNormal – выше среднего;
  6. High – объект, помещаемый в кэш будет иметь высокий приоритет. При автоматическом освобождении системной памяти объекты с высоким приоритетом будут удаляться из кэша в последнюю очередь;

  7. NotRemovable – наивысший уровень приоритета. Объекты с таким приоритетом никогда не будут автоматически удалены из кэша. То есть время нахождения объекта в кэше контролирует только разработчик. В связи с этим разработчики должны соблюдать высочайшую осторожность при использовании данного приоритета, дабы предотвратить неконтролируемую утечку памяти.


Контроль срока хранения объекта в кэше
Конечно разработчики вправе самостоятельно контролировать время нахождения их объектов в кэше ASP.NET. Один из наиболее эффективных способов это сделать – применение временного интервала хранения объекта.
По умолчанию, объекты, помещаемые в кэш, не имеют фиксированного времени хранения. Однако разработчик может это изменить, установив время, о истечении которого механизм ASP.NET должен автоматически удалить объект из кеша. В этом случае разработчику предлагается на выбор две стратегии задания времени хранения:
  1. Абсолютное время хранения – в этом случае, при помещении объекта в кэш, разработчик передает в метод Insert или Add объект типа DateTime, который содержит определенную дату и время. При наступлении этой даты объект удаляется из кэша. Если разработчик не хочет устанавливать этот параметр – он может указать его как DateTime.MaxValue или передать константу Cache.NoAbsoluteExpiration. Тем самым устанавливается бесконечное время хранения объекта;

  2. Относительное время хранения – если в предыдущем режиме время удаления объекта было фиксированным, то в данном случае время жизни объекта в кэше может автоматически переустанавливаться. Переустановка происходит при каждом обращении какого-либо кода к данному объекту в кэше. Таким образом объект не будет удалятся из кэша, пока он востребован приложением разработчика. На мой взгляд, это один из наиболее эффективных сценариев использования кэша в ASP.NET.
Рассмотрим следующие примеры:
Cache.Insert("key_1", new MyClass(), null, DateTime.Now.AddMinutes(10),  Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
В данном случае в кэш помещается объект, который будет автоматически удален через 10 минут.
Cache.Insert("key_1", new MyClass(), null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5), CacheItemPriority.Normal, null);
Во втором случае время хранения для объекта устанавливается как текущее плюс 5 минут. И при каждом обращении к объекту в кэше данное время будет устанавливаться заново.


Установка зависимостей для объекта в кэше
Еще один способ контроля времени хранения объекта в кэше – установка зависимостей. Для помещаемого в кэш объекта устанавливается связь с определенным источником. В случае каким-либо изменений в связанном источнике, данный объект в кэше признается устаревшим и автоматически удаляется. В качестве источника может выступать одиночные файлы и каталоги, массивы файлов и каталогов, другие элементы в кэше, а также таблицы базы данных или внешние события.
Все вышеперечисленные зависимости устанавливаются при помощи класса CacheDependency или ряда его специализированных потомков. Данный класс имеет длинный список конструкторов, которые выполняют следующие функции:
  1. public CacheDependency(string filename);
    Указывается путь к файлу или каталогу, в котором должны отслеживаться изменения;

  2. public CacheDependency(string[] filenames);
    Задается массив путей к файлам или каталогам, в которых следует отслеживать изменения;

  3. public CacheDependency(string filename, DateTime start);
    Устанавливается путь к файлу или папке и время, начиная с которого нужно отслеживать изменения;

  4. public CacheDependency(string[] filenames, DateTime start);
    Задает массив путей к файлам и каталогам, а также время, начиная с которого необходимо отслеживать в них изменения;

  5. public CacheDependency(string[] filenames, string[] cachekeys);
    Указывает массив путей к файлам или каталогам, а также массив ключей кэша. Объект будет из кэша будет удалятся при изменениях в файлах, по заданным путям, или при изменениях в объектах кэша, с указанными ключами;

  6. public CacheDependency(string[] filenames, string[] cachekeys, CacheDependency dependency);
    Устанавливает массив путей к файлам или каталогам, массив ключей в кэше и дополнительный объект CacheDependency;

  7. public CacheDependency(string[] filenames, string[] cachekeys, DateTime start);
    Задает массив путей к файлам или каталогам, массив ключей кэша и время, начиная с которого данные зависимости начинают отслеживаться;

  8. public CacheDependency(string[] filenames, string[] cachekeys, CacheDependency dependency, DateTime start);
    Указывает массив путей к файлам или каталогам, массив ключей кэша, дополнительный объект CacheDependency и время, начиная с которого все эти зависимости начинают отслеживаться.
Некоторые конструкторы имеют одним из параметров объект DateTime, который устанавливает время, начиная с которого механизм кэширования ASP.NET должен следить за изменениями в связанной сущности. По умолчанию, мониторинг связанной сущности начинается немедленно после помещения объекта в кэш. Также, в несколько конструкторов можно передать дополнительный объект типа CacheDependency, это используется в случае необходимости комбинирования нескольких зависимостей.
После установки определенной зависимости механизм кэширования ASP.NET начинает мониторить связанную сущность(или сущности), проверяя наличие каких-либо изменений. Как только обнаружено изменение все связанные с данной сущностью объекты в кэше удаляются. Также, после удаления всех объектов из кэша, ASP.NET перестает мониторить изменения в связанной сущности, поскольку в кэше уже нету объектов, которые с ней связаны.
Рассмотрим несколько примеров:
CacheDependency depend = new CacheDependency("myfile.txt");
Cache.Insert("key_1", new MyClass(), depend);
Здесь показан самый простой случай – создается зависимость от файла myfile.txt, и заносится объект в кеш с указанием данной зависимости. При изменении файла myfile.txt – объект будет автоматически удален из кэша.
Cache.Insert("key_2", new MyClass());

CacheDependency depend = new CacheDependency(new string[]  {"myfile.txt"}, new string[] {"key_2"}, DateTime.Now.AddMinutes(10));
Cache.Insert("key_1", new MyClass(), depend);
В этом случае сначала кладется объект в кэш с ключом key_2, а затем устанавливается зависимость от файла myfile.txt и объекта в кэше с ключом key_2. Основной объект(с ключом key_1) будет удален из кэша при изменении файла myfile.txt или при изменении(удалении или обновлении) объекта в кэше с ключом key_2. Механизм кэширования начнет следить за связанными объектами через десять минут после помещения в кэш объекта с ключом key_1.


Определение функции обратного вызова
Мы рассмотрели выше множество параметров, в соответствии с которыми производится удаление объектов из кэша ASP.NET. Однако существует еще одна крайне полезная особенность, предоставляемая объектом Cache. Разработчик может установить функцию, которая будет выполнятся при удалении объекта из кэша.
Определенная функция привязывается к определенному объекту и срабатывает, начиная асинхронное выполнение, при удалении это объекта из кэша каким-либо способом. Для обеспечения максимального быстродействия рекомендуется объявлять функцию обратного вызова как статическую функцию. В против случае механизм ASP.NET будет вынужден поддерживать в памяти объект класса, содержащего данную функцию.
Рассмотрим пример занесения объекта в кэш с указанием функции обратного вызова:
    public class CacheDispatcher
    {
        public static List<string> Log = new List<string>();

        public static void NotifyFunction(string key, object value, CacheItemRemovedReason reason)
        {
            if (reason == CacheItemRemovedReason.Expired)
            {
                Log.Add(key);
            }
        }

        public void PutObjectToCache(string key, MyClass value)
        {
            CacheItemRemovedCallback onRemoveFunction;
            onRemoveFunction = new CacheItemRemovedCallback(NotifyFunction);

            Cache.Insert(key, value, null, DateTime.Now.AddMinutes(30), Cache.NoSlidingExpiration, CacheItemPriority.Normal, onRemoveFunction);
        }
    }
В этом примере функция NotifyFunction будет являться функцией обратного вызова. Она должна соответствовать строго определенному делегату CacheItemRemovedCallback:
       public delegate void CacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason);
В методе PutObjectToCache мы создали объект данного делегата, связали его с функцией NotifyFunction и указали в качестве последнего параметра метода Cache.Insert. Теперь эта функция связана с объектом в кэше и будет вызвана через 30 минут после выполнения метода PutObjectToCache(в соответствии с указанным сроком хранения объекта в кэше).
В эту функцию будет переданы ключ и значение удаленного объекта, а также причина, из-за которой он был удален из кэша. Причина является значением из перечисления CacheItemRemovedReason и может принимать следующие значения:
  1. Removed – объект был программно удален из кеша;
  2. Expired – объект был удален из кэша по истечению установленного времени хранения(абсолютного или относительного);
  3. Underused – объект был удален внутренним механизмом ASP.NET для освобождения системной памяти;
  4. DependencyChanged – объект был удален из кэша в связи с изменением одной из установленных для него зависимостей.
Стоит отметить, что функция обратного вызова вызывается после удаления объекта из кэша и повлиять на этот процесс внутри это функции разработчик не сможет. Впрочем, если необходимо, можно внутри этой функции еще раз помещать данный объект в кэш.


Очистка кэша
В программном интерфейсе объекта Cache не предусмотрен метод для полной очистки кэша. Однако при необходимости можно воспользоваться следующим способом:
        foreach (DictionaryEntry entry in Cache)
        {
             string key = entry.Key.ToString();
             Cache.Remove(key);
        }
Впрочем полностью очистить кэш разработчик все-таки не сможет. Я упоминал раньше, что в кэше, помимо объектов помещенных разработчиком, хранится ряд системных объектов, используемых механизмами ASP.NET, и доступ к ним надежно закрыт от разработчиков.
Выше был приведен пример перебора всех объектов, находящихся в кэше. Мы использовали это для их удаления, однако также это может быть полезно и необходимо для многих других задач.


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

16 мая 2011 г.

Кэширование в ASP.NET Часть 1. Введение

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

В этой статье мы рассмотрим возможности кэширования данных, предоставляемые механизмом ASP.NET.
Стоит отметить, что разработчики ASP.NET прекрасно понимают важность возможности кэширования, поэтому ASP.NET предоставляет прекрасно организованный, гибкий и расширяемый механизм кэширования данных. Однако ничего совершенного не существует, поэтому и у этого механизма есть свои недостатки. Одни из ключевых – это то, что кэширование является локальным и ограничивается одним AppDomain. Такой механизм абсолютно не подходит для использования в сценариях веб-фермы или веб-сада.
Примечание. Одним из наиболее распространённых и эффективных решений для кэширования данных, который применяется в сценариях веб-фермы и веб-сада является с++ решение MemCache. Также набирает популярности механизм кэширования данных в NoSQL хранилищах(таких как MongoDB), однако такие решения, впрочем как и сами NoSQL хранилища, являются достаточно “сырыми”.
Тем не менее механизм кэширования ASP.NET отлично работает для локального кэширования и сегодня мы рассмотрим насколько просто управлять этим механизмом при разработке собственных сайтов. В данной серии статей мы рассмотрим следующие аспекты, связанный с использованием кэширования в ASP.NET:
  1. Класс Cache – архитектура и свойства;
  2. Работа с объектом Cache;
  3. Настройка зависимостей в объекте Cache;
  4. Зависимости от данных XML;
  5. Зависимости от базы данных SQL Server.
Класс Cache Данный класс представляет собой механизм управления кэшированием в ASP.NET, он находится в пространстве имен System.Web.Caching и существует в пределах одного приложения(одного AppDomain). Собственно он и является хранилищем данных(коллекцией), которые мы помещаем в кэш. Текущий объект класса Cache можно получить обратившись к свойству Cache страницы(класса Page) или объекта HttpContext. Класс Cache наследует тип Object и реализует интерфейс IEnumerable. Он построен на основе словаря. Какой класс будет использоваться для кэширования среде ASP.NET зависит от выбранного разработчиком сценария. Если процесс приложения работает с одним ядром процессора – будет использоваться класс CacheSingle, в случае многоядерного процессора – будет использоваться класс CacheMultiple. И в том и в другом случае все объекты будут хранится в смешанной таблице, доступ к которой будет обеспечен из любой точки приложения. Ядро встроенного кэша ASP.NET также используется средствами механизмов самого ASP.NET для хранения собственных системных объектов. Такие объекты находятся в private секции ядра кэша и разработчики не могут получить к ним непосредственный доступ. Класс Cache является защищенным, потокобезопасным объектом. Все необходимые процессы синхронизации обеспечивают внутренние механизмы данного класса, поэтому при работе с ним нет необходимости явно использовать блокировки. Также класс предоставляет удобный рабочий интерфейс, основанный на работе со словарем. Данные в кэше хранятся не все время работы приложения – объект Cache предоставляет возможность установить для каждого объекта абсолютное и относительное время хранения в кэше, а также приоритет объектов. Время хранения задается в секундах – по его истечению объект удаляется из кэша. Приоритет позволяет механизму кэширования определить какие данные можно автоматически удалить из кэша, при нехватке оперативной памяти. Класс Cache имеет следующие публичные свойства:
  1. Count – определяет количество объектов, которые в данный момент находятся кэше;
  2. Item – свойство-индексатор, которое предоставляет доступ к объекту кэша по ключу, доступно для чтения и записи;
  3. EffectivePercentagePhysicalMemoryLimit – доступно только для чтения, возвращает процент физической памяти, который может использоваться кэш-хранилищем;
  4. EffectivePrivateBytesLimit – доступно только для чтения, определяет количество байт, которые может использовать кэш-хранилище.
А также две публичные константы, которое могут использоваться для задания срока хранения при помещении объекта в кэш:
  1. NoAbsoluteExpiration – указывает, что данный объект не будет иметь абсолютного срока хранения(данная константа имеет тип DateTime и установлена в значение DateTime.MaxValue);
  2. NoSlidingExpiration – указывает, что для данной записи не применяется политика относительного кэширования(имеет тип TimeSpan со значением TimeSpan.Zero).
При помощи свойства Item можно помещать объекты в кэш и извлекать их из него(это самый простой способ), например следующим образом:
Cache[“my_key”] = value;
Данные хранятся в кэше под типом object, то есть вы можете положить в кэш данные любого типа. Ключом обычно является строка(с учетом регистра). Если данные с таким ключом уже существуют в коллекции кэша – они перезаписываются, если нет – создается новая запись. При занесении объекта в кэш при помощи индексатора объекта Cache(как в примере выше) – создается запись в кэше с рядом параметров по умолчанию, в частности этому объекту будет присвоен средний приоритет, к нему не будет применяться политика определенного времени хранения и при его удалении не будут выполнятся дополнительные действия. Таким образом данный объект будет хранится в кэше неопределенное количество времени, пока он не будет удален программно из кода или пока приложение не прекратит свою работу. Чтобы задать дополнительные параметры хранения для объекта в кэше следует использовать метод Insert класса Cache. Класс Cache имеет следующие методы:
  1. object Add(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority  priority, CacheItemRemovedCallback onRemoveCallback);
    Add – добавляет объект в кэш. При этом разработчик может указать приоритет объекта, срок хранения объекта в кэше и метод, который будет вызываться при удалении данного объекта из кэша. Если запись с таким ключом уже существует, метод Add генерирует исключение. Данный метод возвращает объект типа object, представляющий новую запись в кэше;

  2. object Get(string key);
    
    Get – возвращает запись из кэша, хранящуюся с указанным ключом. Если такая запись не найдена метод возвращает null;

  3. IDictionaryEnumerator GetEnumerator();
    
    GetEnumerator – возвращает объект-перечислитель, позволяющий перемещаться по всем элементам коллекции;

  4. void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);
    Insert – вставляет объект в кэш с заданным ключом, при этом разработчик может указать приоритет объекта, срок хранения объекта в кэше и метод, который будет вызываться при удалении данного объекта из кэша. Метод имеет 5 перегрузок(выше приведен метод с наибольшим количеством параметров) и тип void. Если объект с таким ключом уже содержится в кэше – он будет перезаписан;

  5. object Remove(string key);
    Remove – метод удаляем запись из кэша по указанному ключу. Он возвращает объект, который был удален из коллекции кэша, а если запись с таким ключом отсутствует – возвращает null.

Помещение объекта в кэш Теперь, когда мы познакомились с классом Cache, его методами и свойствами, давайте посмотрим каким образом собственно может проходить процесс внесения объектов в кэш и извлечения их из него. Мы, конечно будет использовать самый продвинутый метод для внесения объектов в кэш – Insert. Как я упоминал выше, данный метод имеет 5 перегрузок:
1) public void Insert(string key, object value);
   2) public void Insert(string key, object value, CacheDependency dependencies);
   3) public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration);
   4) public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemUpdateCallback onUpdateCallback);
   5) public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);     

Давайте рассмотрим параметры этого метода подробнее:
  1. Ключ(key) – строковое значение(с учетом регистра), являющееся идентификатором записи в коллекции кэша. Значение не должно быть null. Если запись с таким ключом уже существует в коллекции, при использовании метода Insert, значение объекта будет обновлено;

  2. Значение(value) – объект типа object. который необходимо закэшировать. Не может быть null;

  3. Зависимости(dependencies) – объекты типа CacheDependency. которые определяют зависимость объекта, хранящегося в кэше, от определенных объектов(это могут быть файлы, таблицы в базе данных и другие объекты), при изменении последних, запись в кэше помечается как устаревшая и автоматически удаляется;

  4. Абсолютное время хранения объекта в кэше(absoluteExpiration) – объект типа DateTime, который определяет статическое время, по истечении которого данных объект будет автоматически удален из кэша. При обращении к объекту время не обновляется. Чтобы отключить это режим хранения необходимо задать этот параметр как DateTime.MaxValue или воспользоваться константой NoAbsoluteExpiration;

  5. Относительное время хранения объекта в кэше(slidingExpiration) – объект типа TimeSpan. Время хранения в таком случае устанавливается как текущее плюс переданное значение. При этом при каждом обращении к этой записи время хранения будет автоматически переустанавливаться. Такой механизм, на мой взгляд, позволяет всегда держать в кэше наиболее востребованные данные.

  6. Приоритет(priority) – константа из перечисления CacheItemPriority, которое устанавливает значимость данного элемента в кэше. Данное значение учитывается механизмом кэширования при автоматическом удалении записей из кэша, при нехватке физической памяти. Записи с низким приоритетом удаляются в первую очередь;

  7. Функция обратного вызова(onRemoveCallback) – указывается название функции, которая будет вызвана при удалении данного из кэша(программно или автоматически).
Рассмотри следующий пример:
public class MyClass { }
Cache.Insert("key_1", new MyClass(), null, DateTime.Now.AddMinutes(10), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
В данном примере в кэш помещается объект типа MyClass с ключом “key_1” и стандартным приоритетом, этот объект будет удален из кэша через 10 минут. Удаление объекта из кэша Объекты которые имеют зависимости или политику контроля времени хранения удаляются автоматически, при наступлении соответствующего события. Однако, также, программный интерфейс позволяет напрямую удалить объект из кэша. Операция производится следующим способом:
object Value = Cache.Remove(“my_key”);
В этом примере метод Remove удалит из кэша запись с ключом my_key, какой бы у нее не был приоритет и вернет удаленный из кэша объект. Если записи с таким ключом нет, метод вернет null. Если удаляемый объект содержит функцию обратного вызова, то при удалении она будет вызова и в нее будет передан ключ, значение удаляемого объекта и причина удаления. Причина удаления будет представлена перечислением CacheItemRemovedReason. Это перечисление содержит следующие значения:
  1. DependencyChanged – указывает, что объект удаляется из-за изменения объекта зависимости;

  2. Expired – объект удаляется из-за истечения абсолютного или относительного срока хранения;

  3. Removed – передается, при явном удалении посредством метода Cache.Remove. Также данная константа может передаваться в случае перезаписи объекта с использование метода Cache.Insert;

  4. Underused – объект удаляется механизмом кэширования для освобождения физической памяти.
Выводы Таким образом мы познакомились с общими принципами кэширования данных, которые предоставляет механизм ASP.NET и изучили архитектуру класса Cache. научившись проводить с ним элементарные операции. 
Следующая статья посвящена углубленному изучению процесса работы с объектом Cache.